ADR 018 Platform Authority Boundaries
Context
Section titled “Context”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.
Decision
Section titled “Decision”Use explicit platform authority boundaries:
| Platform | Authority | Not Authority For |
|---|---|---|
| Self-hosted Supabase | Primary business data platform: Postgres, Auth, Storage, Realtime, queues/cron, revisions, ledgers, and projections | Cross-resource business authorization, durable workflow policy, billing provider behavior |
| SpiceDB | Relationship-based authorization decisions for actor capabilities | Business data storage, workflow progress, financial calculation |
| Restate | Durable workflow invocation, retries, progress, and recovery | Domain invariants, authorization policy, amount calculation |
| Kill Bill | Billing-provider operations and provider event integration | Sanmopia 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.
Implementation Evidence
Section titled “Implementation Evidence”- Assembly stage topology
- Supabase HTTP adapter
- SpiceDB authorization adapter
- Restate workflow adapter
- Restate asynchronous workflow ingress adapter
- Canonical workflow replay status and ledger preservation
- Supabase object, array, and scalar RPC adapter
- Restate HTTP asynchronous send contract
- Kill Bill billing adapter
- Service-role payment-change authorization RPC
- SpiceDB financial visibility boundary
- Reservation care-history authority slice
- Reservation care-history public contract
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.
Known Gaps
Section titled “Known Gaps”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.
Consequences
Section titled “Consequences”Platform reuse follows authority, not convenience. New infrastructure requires explicit authority gap and successor ADR rather than broadening adapter behavior silently.
Revision History
Section titled “Revision History”- 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.