frameworknodedough-context · v1claude-opus-5-cowork2026-08-12served from databaseAll documents

NodeDough — agent boot context

NodeDough — agent boot context

Purpose: everything an agent needs to be productive on NodeDough in one read, so nobody re-derives it. Reviewed 2026-08-12. Facts marked [v 2026-08-12] were measured live.

Identity

ThingValue
Domainnodedough.com, www.nodedough.com (port 8080)
Repojbnx/nodedough
Railway projectnodedough78dd8a83-0635-40d3-a673-4ac4bf340ba8
Railway servicesweb = f5397661-a0a4-486a-b4fa-f4c16ed7d4c8 · web-test = 21805b31-2a3d-4f17-bcf1-f8a3d55ca3d9
Environmentproduction = 92d6249b-13c6-473b-bd43-a59bd71d4072 (the ONLY environment)
Supabase (prod)hykjfezeywjvzslowjyf
Supabase (test)rvojqdskcxwcjpovwfev — exists, not seeded
BuilderRAILPACK, healthcheck /healthz, 1 replica, us-east4
StackSingle-page app, no framework. /static/app.js is the whole client.

⚠️ web production still builds from main, not production. [v 2026-08-12] Directive v36 says NodeDough is gated (test → chat approve → production), but the Railway source was never repointed. Until it is, a merge to main is a live customer deploy. Changing a service's source branch is dashboard-only — the Railway API explicitly refuses it.

Scale (so you calibrate effort correctly)

6 users · 6 households · 6 subscriptions · 68 nodes · 38 recurring items · 1,868 rows in ops_404_log. [v 2026-08-12] This is pre-launch. Optimise for correctness and for things that are cheap now and expensive at 6,000 users. Do not micro-optimise queries.

Billing is NOT live — do not assume it works

stripe_events = 0 · billing_customers = 0 · every subscriptions row has NULL stripe_customer_id AND NULL stripe_subscription_id. [v 2026-08-12]

Railway web has STRIPE_SECRET_KEY, STRIPE_PRICE_MONTHLY, STRIPE_PRICE_ANNUAL, STRIPE_PRICE_BOOKS_MONTHLY, STRIPE_PRICE_BOOKS_ANNUAL, STRIPE_CHECKOUT_SUCCESS_URL, STRIPE_CHECKOUT_CANCEL_URL, STRIPE_PORTAL_RETURN_URL — but no STRIPE_WEBHOOK_SECRET and no service/secret key. So checkout has no listener and nothing privileged enough to write the result back. All current paid accounts were hand-granted via admin_set_subscription. Plan codes in use: node_dough_monthly, node_dough_annual.

The admin RPC guard is ops_is_admin() — not what you'd guess

45+ admin_* SECURITY DEFINER functions open with if not public.ops_is_admin() then raise exception 'forbidden'; end if;.

Do not audit for require_platform_admin / is_platform_admin alone — you will generate false positives. Check for ops_is_admin too. Correct audit query:

select p.proname, p.prosecdef,
       (p.prosrc ilike '%ops_is_admin%' or p.prosrc ilike '%require_platform_admin%'
        or p.prosrc ilike '%is_platform_admin%') as has_guard
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.proname like 'admin\_%'
order by has_guard, p.proname;

Open hole [v 2026-08-12, proven]: public.admin_audit() has no guard. A non-admin authenticated user calling it raises no exception and writes 1 row to admin_audit_log. admin_comp_paid_user has no direct guard either — it is safe only transitively, because admin_set_subscription guards. Both should carry the guard explicitly.

Functions that MUST stay anon-callable

validate_invite_code (signup calls it pre-auth), ops_submit_ticket, ops_log_404. An audit will recommend revoking these. Revoking validate_invite_code breaks signup. Rate-limit and validate instead of revoking. Same rule for my_*_ids helpers — revoking EXECUTE on a function used inside an RLS policy makes every policy calling it fail closed.

Live front-end facts [v 2026-08-12]

identity-encoded. The only br asset is the third-party jsDelivr one.

being in place.

the auth path.

nosniff, frame-ancestors 'none', referrer-policy: same-origin. Use NodeDough as the reference when fixing other properties.

/healthz, /config.js. Legal pages are complete — the old "no ToS" blocker is resolved.

error handler. The tables exist; nothing writes to them.

manifest.webmanifest.

Migration drift — two lineages, do not replay blindly

Production was NOT built from the repo 0001–0023 series; a parallel ad-hoc series in supabase_migrations.schema_migrations built it. Migrations 0066–0076 were exported from live and committed with provenance on branch cursor/handoff-aug11-7196.

Still drifting: client calls 1-arg node_projection(p_node) which does not exist (only the 2-arg form); recurring_items has node_id (repo assumes account_id); profiles keys on user_id not id; plans.code vs plan_code. Repo file 0007_dated_balance_checkpoints.sql is broken — UNION with ORDER BY on aliased output columns, which Postgres rejects. Never replay 0065–0076 on production.

Schema landmarks

institutions (= households) · institution_members · institution_invites · people · nodes · node_edges · node_shares · recurring_items · account_adjustments · account_balance_checkpoints · cashflow_settings · subscriptions · billing_customers · stripe_events · support_tickets / ticket_events · feature_flags / feature_flag_tenants · analytics_events · app_errors · ops_404_log · audit_log / admin_audit_log · cms_documents / cms_document_revisions · email_campaigns / email_campaign_recipients · signup_allowlist · reserved_usernames.

Tenant isolation runs through my_visible_node_ids(), my_institution_ids(), my_writable_institution_ids(), my_owned_institution_ids(). A cross-household write flaw was fixed 2026-08-11 (items_write gated on membership while items_read gated on node visibility). Whenever you touch one policy, check its read/write twin agrees on scope.

Advisor baseline — so you can diff, not re-read

Security: 0 ERROR, 117 WARN, 19 INFO. 112 × authenticated_security_definer_function_executable (expected — that is how the whole app works, guards live inside the function bodies), 3 × anon_security_definer_function_executable (ops_log_404, ops_submit_ticket, validate_invite_code — all intentional), 2 × extension_in_public (citext, pg_trgm), 19 × rls_enabled_no_policy (locked, not leaking).

Performance: ~30 unused indexes, 1 unindexed FK (node_shares_granted_by_fkey), auth connection strategy is absolute (10) not percentage.

The security advisor payload is ~142 K characters — do not fetch it into your main context. Run it inside a subagent and ask for a deduplicated per-rule summary, or diff against the baseline above.