feat(billing): per-service-DB boundary hardening — billing_svc role + backend projection - #77
feat(billing): per-service-DB boundary hardening — billing_svc role + backend projection#77izzywdev wants to merge 3 commits into
Conversation
…ervice handlers [skip ci]
…te projection, renumber migration 010->011 [skip ci]
|
Superseded by #92: billing_svc least-privilege role + schema + bootstrap Job landed there. |
|
Reopening — verification showed 3 files NOT on master: backend/src/migrations/011_add_billing_to_entities.ts, backend/src/services/billingProjection.ts, backend/tests/billing-projection.test.ts (the backend billing-projection work). Most of #77 landed via #92, but that part didn't. My 'superseded' close was on assumption, not verification — apologies. |
|
@claude complete this branch and get it in. |
|
On it (via the Claude Code web session — the Status:
The Generated by Claude Code |
|
Claude finished @izzywdev's task in 4m 46s —— View job PR #77 — billing boundary hardening: review & merge readiness
Review verdict — the code is sound ✅I read every changed file. The per-service-DB boundary is enforced correctly and the design is clean:
Minor observations (non-blocking)
|
Prior commits on this branch carried [skip ci], so tsc+jest never ran. This empty commit lets the pipeline produce the green signal the merge gate needs. Co-authored-by: Israel Weinberg <izzywdev@users.noreply.github.com>
…ith feature/billing-payments Delivers all content from PR #77 (billing-boundary) with conflicts against feature/billing-payments resolved: - Least-privilege billing_svc Postgres role (db-bootstrap.ts + Helm) - Removed cross-schema public.* writes from billing-service handlers - Backend plan-state projection (billingProjection.ts + unit tests) - Migration renumbered 010 → 011 to avoid collision with identity track - claude.yml: checkout PR branch before running @claude (fixes 0s issue) Closes #77
|
@claude if you recommended to merge this PR, why was it closed? |
|
Claude finished @izzywdev's task in 1m 14s —— View job Why #77 was closed even though I recommended merging itShort version: the recommendation and the closure don't actually conflict — the code got in through a different PR, and #77 was then closed as redundant. The close means "delivered elsewhere," not "rejected." Here's the full chain from this PR's own timeline:
So #77 was closed exactly per that plan: its content was merged into If you want to confirm the content actually landedI couldn't run git fetch origin feature/billing-payments
git ls-tree origin/feature/billing-payments -- \
backend/src/services/billingProjection.ts \
backend/src/migrations/011_add_billing_to_entities.ts \
backend/tests/billing-projection.test.ts
gh pr view 206 --json state,merged,mergedAt,baseRefNameIf all three files are present on |
Billing boundary hardening (per-service-DB direction)
billing-service must own ONLY the
billingschema and never write the platform's public tables. This PR enforces that.1. Least-privilege
billing_svcPostgres rolebackend/src/scripts/db-bootstrap.ts: idempotently creates/syncs abilling_svcLOGIN role (NOSUPERUSER/NOCREATEDB/NOCREATEROLE) and provisions abillingschema owned by it (CREATE SCHEMA ... AUTHORIZATION billing_svc) with USAGE/CREATE + DML + default privileges on that schema only. ExplicitlyREVOKE ALL ON SCHEMA public FROM billing_svc. Gated onBILLING_DB_PASSWORDso existing installs are a no-op.db-bootstrap-job.yamlpassesBILLING_DB_USER/BILLING_DB_PASSWORD;secret.yamladdsBILLING_DB_PASSWORD;values.yamladdssecret.billingDbPassword, setsbillingService.dbUser: billing_svc, and the billing-serviceDATABASE_URLnow connects asbilling_svc(falls back to the shared role when the password is unset).2. Removed cross-schema writes from billing-service
writePlanCachefromHandlerContext(handlers/types.ts), its definition inindex.ts, and every call (subscription-updated,invoice-paid,invoice-failed).invoice-failednow also emitsbilling.subscription.changed(statuspast_due) so the projection still gets the status change. Handlers continue to mirror the billing schema, sync Permit, and emit events.public.users/public.organizations/writePlanCache/UPDATE publicinservices/billing-service/src(only doc comments).3. Backend plan-state projection
backend/src/services/billingProjection.tsconsumesbilling.subscription.changed(sharedTypedConsumer+billingSubscriptionChangedSchemaV1) and updatesbilling_plan_tier/billing_plan_statusonusers/organizations, keyed by(entityType, entityId). Degrades to a no-op when Kafka is disabled. Wired intostartServer+ graceful shutdown.backend/tests/billing-projection.test.ts(mocked knex, no DB/broker).4. Migration 010 collision resolved
010_add_billing_to_entities->011_add_billing_to_entitiesin bothbackend/src/migrations/andbackend/security/src/migrations/(the identity track owns010_create_api_tokens_table). Columns unchanged; the WRITER moved from billing-service to the backend projection. Comments updated.Verification (Linux/CI for tsc+jest; locally for Helm)
helm template ... --set billingService.enabled=true→ renders rc=0;DATABASE_URL=postgresql://billing_svc:$(BILLING_DB_PASSWORD)@...; withsecret.billingDbPassword=""it falls back tofuzefront_user:$(DB_PASSWORD).os=linuxnpmrc pin that breaks native installs).Base:
feature/billing-payments(PR #66).