Skip to content

ADR 018 Platform Authority Boundaries

ADR 006 selected Supabase-first reuse but used “Supabase only” language. Current architecture has distinct authorization, durable workflow, and billing responsibilities that must not collapse into database tables or frontend policy.

Use explicit platform authority boundaries:

PlatformAuthorityNot Authority For
Self-hosted SupabasePrimary business data platform: Postgres, Auth, Storage, Realtime, queues/cron, revisions, ledgers, and projectionsCross-resource business authorization, durable workflow policy, billing provider behavior
SpiceDBRelationship-based authorization decisions for actor capabilitiesBusiness data storage, workflow progress, financial calculation
RestateDurable workflow invocation, retries, progress, and recoveryDomain invariants, authorization policy, amount calculation
Kill BillBilling-provider operations and provider event integrationSanmopia pricing, payout, refund eligibility, or settlement math

Backend domain/application feature slices remain business authority. Adapters translate each platform behind owned ports. CQRS remains optional and does not move authority into provider-specific read/write models.

Supabase RLS enforces row access and service-role boundaries. It does not replace SpiceDB capability checks for commands. Restate can retry command execution but cannot invent missing domain facts. Kill Bill events become trusted only after backend verification, idempotency, revision, and audit rules. Browser payloads cannot carry allowed, relationship, capability, or actor-role booleans as authorization evidence. Backend obtains SpiceDB decision, binds it to actor and resource, then invokes service-role-only Supabase mutation.

Frontend calls backend contracts. It must not call SpiceDB, Restate, or Kill Bill directly or treat raw Supabase rows as business authority.

Stage proof on 2026-07-11 applied the schema through pnpm authorization:bootstrap. Mother payment, authorized-payer receipt, branch refund, and HQ payment checks returned allowed. The assigned caregiver retained booking view but payment, refund, and receipt checks all returned denied. This proves relationship authorization separation; it does not yet prove the persisted payment-actor-to-authorized_payer outbox path.

Family payment-delegation follow-up on 2026-07-11 closes the persisted relationship lifecycle for reservation-scoped authority. Supabase RPCs atomically persist revisioned mother grants/revocations and enqueue PGMQ relationship mutations; the backend dispatcher applies expiring SpiceDB authorized_payer relationships with touch and removes them with delete. Authenticated stage HTTP proof passed grant, exact replay, permission convergence, revoke, exact replay, permission removal, and registered test-data reaping. Client retries may supply a later backend clock but must match actor, booking, scopes, expiration, evidence, reason, and expected revision.

Reservation care-history follow-up in backend 903e68d and contract e4cc9ed on 2026-07-12 applies the same authority boundary without broadening payment permission. Supabase owns revisioned, booking-scoped grant/revoke facts and atomically enqueues PGMQ relationship mutations. SpiceDB owns the delegated view_care_history decision. A separate submit_mother_daily_report_review permission plus a Supabase booking-authority reader guards review and rating mutation. Browser-supplied mother, actor, and actor_is_reservation_mother facts cannot authorize either path. Authenticated stage proof on booking 81af6692-7703-4da4-89cb-9300bfb5b769 and grant b7d413de-894e-4ada-894a-457823e79815 passed delegated read, cross-booking and forged-mother denial, revoke convergence, post-revoke denial, and deterministic DB/SpiceDB cleanup.

Reservation workflow stage proof on 2026-07-11 uses Restate asynchronous send ingress. Backend returns accepted invocation identity without waiting for workflow completion; progress remains a separate backend query. Live smoke returned invocation inv_198Joz6YUL3C5T51ASTErW3FWSSXtNW4qq, then reported workflow completed. Browser-to-settlement E2E passed on the same adapter.

Exact invocation replay proof on 2026-07-11 observed Restate status PreviouslyAccepted. Backend 48abf1c preserves that raw provider evidence, maps application/API/ledger status to accepted, and leaves an existing exact booking workflow row unchanged so terminal completed state cannot regress. A different persisted request or command payload fails closed. The same commit accepts valid scalar PostgREST RPC results under a stable value field, allowing the database-owned booking reaper preparation function to remain the dependency SSOT instead of duplicating foreign-key order in Python.

Backend ddd1559 keeps platform cleanup symmetric during stage proof: the registered Supabase batch derives DB deletion order, while reconstructed SpiceDB booking relationships are deleted before DB facts disappear. The continuous family-sponsored settlement run then verifies booking view is denied for mother, branch operator, and caregiver after cleanup.

Repository and stage topology prove configured boundaries, not complete runtime coverage. External Kill Bill webhook E2E is not established by this audit. Family authority now controls payment/refund/receipt visibility, but delegated payment execution still needs product/phase/amount-scoped grant consumption and a frozen historical payer snapshot. Care-history authorization is runtime wired, but the actual care-history content projection/query remains incomplete. Feature-level ADRs remain partial where SpiceDB, Restate, or provider paths lack linked runtime proof.

Platform reuse follows authority, not convenience. New infrastructure requires explicit authority gap and successor ADR rather than broadening adapter behavior silently.

  • 2026-07-10: accepted; supersedes ADR 006.
  • 2026-07-11: prohibited client-supplied authorization decisions and recorded service-role-only payment-change mutation evidence.
  • 2026-07-11: separated booking-care visibility from payment, refund, and receipt visibility and recorded stage SpiceDB denial evidence.
  • 2026-07-11: replaced obsolete Restate send routing with official asynchronous ingress and recorded live booking plus browser-to-settlement proof.
  • 2026-07-11: proved reservation-scoped family payer grant/revoke lifecycle across authenticated HTTP, Supabase RPC/PGMQ, and SpiceDB.
  • 2026-07-11: standardized Restate exact-replay status, protected terminal workflow ledgers, and added scalar PostgREST RPC support in backend 48abf1c.
  • 2026-07-11: proved one sponsored booking through settlement and verified symmetric Supabase plus SpiceDB cleanup in backend ddd1559.
  • 2026-07-12: proved reservation-scoped family care-history grant/revoke, delegated read-only access, mother-only review mutation, and deterministic cleanup across authenticated HTTP, Supabase/PGMQ, and SpiceDB.