ADR 021 Family-Sponsored Reservation Contract Acceptance
Context
Section titled “Context”Legacy reservation intake stated that only the mother could enter the service application contract, while spouse and family data existed only as emergency contact fields. A spouse could not safely prepare a reservation for the mother. The browser also carried booking actor facts, so a caller could attempt to submit branch authority rather than authenticated intent.
The target family-account capability must support a husband, partner, guardian, or other approved family member preparing the reservation without transferring the mother’s contracting authority. Later grant revocation, contract-version change, request mutation, and idempotent retries must remain explainable.
Decision
Section titled “Decision”ReservationSponsorshipGrant is revisioned, effective-dated authority granted
and revoked under FamilySponsorshipGrantorPolicy. The first policy version
requires the active mother to grant or revoke sponsorship. Relationship alone
never grants reservation authority.
An authenticated sponsor submits the complete reservation intent. The backend:
- resolves the active sponsorship grant;
- normalizes the reservation intent and computes its SHA-256 fingerprint;
- resolves the current service-contract version from durable backend data; and
- persists
FamilySponsoredReservationDraftwith grant id and revision.
The browser cannot submit a request fingerprint, contract version, grant
revision, sponsor identity, or acceptance facts. The authenticated mother may
accept or decline one expected draft revision. Acceptance creates immutable
SponsoredReservationContractAcceptance, binding the booking request,
fingerprint, contract version, sponsor, mother, grant revision, draft revision,
actor, and acceptance time.
Booking start rechecks the current grant and current contract version, compares
the normalized request fingerprint, and atomically creates one
SponsoredReservationContractAcceptanceConsumption. Revoked or stale grants,
declined drafts, changed requests, changed contracts, replay conflicts, and
reused acceptance ids fail before financially binding progression.
Public booking start accepts a Supabase bearer session and intent only. Backend profile data derives the branch actor. SpiceDB must authorize coarse booking submission capability; domain policy and Supabase transaction functions enforce exact family authority, revision, state, fingerprint, and one-time consumption.
CQRS Boundary
Section titled “CQRS Boundary”Grant, revoke, submit, accept, decline, and consume are commands with expected revision and idempotency. Acceptance and draft reads are projections. Queries cannot mutate grant or booking state. Frontend code renders backend states and sends intent; it cannot reconstruct eligibility or approval.
Authority Boundaries
Section titled “Authority Boundaries”- Supabase Auth authenticates the user and maps the user to member or branch profile identity.
- SpiceDB answers coarse actor capability and relationship questions. It does not decide request fingerprint, contract version, grant revision, or draft state.
- Member Management owns family sponsorship, mother review, and immutable acceptance evidence.
- Reservation Orchestration consumes accepted authority before booking starts.
- Supabase Postgres owns durable grants, drafts, acceptances, consumptions, idempotency, immutable evidence, and relationship outbox writes.
- Contract repository owns public request/response shapes. Assembly owns the integrated scenario and evidence pins.
Implementation Evidence
Section titled “Implementation Evidence”Implementation remains partial. Backend commit 109a717 adds the revisioned
domain rules, Pydantic CQRS commands, HTTP/runtime composition, Supabase
transaction functions and RLS, generated database types, SpiceDB relationship
schema and outbox writes, and authenticated booking actor derivation. The
changed slice passed 233 colocated/integration tests, Ruff, and exact Tach
dependency, interface, cycle, and external-package gates.
Contract commit 15b0261 publishes all six sponsorship/draft/review/read routes,
shared reservation-intent schemas, generated TypeScript types and validators,
and the optional booking-start acceptance id. Contract layout, ESLint,
TypeScript build, and 74 affected Jest tests pass. Live stage OpenAPI exposes the
same six route and field boundaries after Mutagen flush and backend-only restart.
Backend commit 48abf1c adds the bounded family-sponsored stage runner and fixes
the runtime defects it exposed: branch coverage now correlates a booking request
key separately from the later booking UUID; Restate PreviouslyAccepted is
treated as exact replay without regressing a completed workflow ledger; and the
shared actor-free booking fixture derives address and price references while
Supabase Auth supplies the branch operator. Exact replay preserves the existing
request only when both request identity and command payload match.
The common reservation smoke preserves the asynchronous financial boundary: booking pre-registers a waiting payment obligation, the payment workflow verifies it, finalizes the charge, synchronizes the approved projection, and starts settlement continuation. This is bounded application proof, not a substitute for one continuous sponsored-booking-to-settlement scenario.
The schema through backend b0bbd21 is applied to stage. A fresh bounded stage
run created booking 3ce972db-996b-4c8b-a3f2-b21fc04a9b5a, exercised family
payment delegation, continued service delivery, and completed settlement. That
proves the common reservation and financial path remains operable after the
sponsorship schema landed; it did not submit, accept, decline, or consume a
family-sponsored reservation draft.
A live family run created family account
family-account-sponsored-stage-20260711160911378835, sponsorship grant
aa2fc2ce-962f-4b07-a9ed-e9179985bcb0, and completed booking
1ad6710c-1108-49bc-84b7-9b2a9cae3f33. It proved mother grant and grant replay,
sponsor draft, changed-payload idempotency rejection, mother acceptance and
immutable acceptance query, stale draft revision rejection, decline,
cross-intent acceptance reuse denial, one-time acceptance consumption, exact
booking-start replay, grant revocation, revoked-grant denial, and verified
registered-data reaping.
Backend ddd1559 reuses the same settlement orchestrator with a temporary
preserve mode instead of copying financial steps. One continuous live run
created sponsorship grant 08027679-7462-4b1c-ad40-276c088693a2, booking
faa26ffd-0a3b-4e51-b87f-132b2d27d035, delegated-payer snapshot
c2dafb28-f3bd-50a2-a901-eb0a9ac14280, and closeout event
e59c748a-f4a8-4262-a709-a1e88b3a54a7. The same booking continued through
payment, service delivery, and settlement, returned one branch settlement line,
then removed its SpiceDB participant permissions and reaped registered DB rows.
Backend 5414fa4 closes the two remaining authority-invalidity stage gaps. A
bounded run created replacement sponsorship grant
fba6d1a5-6056-4780-8400-be271e8f34f6 and completed booking
21fb9787-416e-49d6-ab0f-ca5e8e3d5311 after proving that an acceptance bound
to a revoked-and-replaced grant cannot start a booking and that an acceptance
bound to the previous service-contract definition version cannot survive a
catalog rollover. Catalog mutation uses optimistic version and timestamp
filters, with restore-or-reap cleanup.
The continuous command then repeated that authority matrix on replacement grant
55784662-1e77-4ceb-92b6-4d56511c2a59 and booking
3130d737-d45f-41ef-a4c6-d85cdcd42783, froze delegated-payer snapshot
73f321bf-7914-5cab-9a2e-aba9640c3771, and completed closeout event
bd63b47d-f2a2-49dd-9a79-e5ae6809fdc7. It returned one settlement line, then
verified SpiceDB permission removal and registered Supabase data reaping.
Backend bb4d6ee adds FamilyAuthorityDirectoryView and
GetFamilyAuthorityDirectoryQuery as the first backend-owned directory
boundary for family grants. own view returns only the authenticated member’s
sponsorship, payment-delegation, and care-history grant rows. administration
view requires both mother domain authority and SpiceDB administer on the
family account. The HTTP response excludes authority evidence and does not
accept browser-supplied actor/member ids. Focused API and HTTP tests prove
own-view filtering, SpiceDB denial for administration, non-mother rejection, and
no authority-evidence leakage. Later evidence in this ADR closes the raw
Supabase read narrowing and stage HTTP proof.
Backend 4477372 closes the raw Supabase read side of that same boundary. It
drops family authority grant read policies for linked or involved members and
revokes direct authenticated select on sponsorship, payment-delegation,
legacy care-history, and reservation care-history grant tables. Service-role
backend reads remain. Lexical migration tests prove legacy policies are removed,
authenticated browser reads are not re-granted, and table comments point readers
to the backend family authority directory API.
Backend 13dc531 adds the missing inactive-actor proof at the API and HTTP
boundaries. Withdrawn Supabase member sessions are rejected before directory
query handling, and revoked family members fail the domain active-member check
before any own authority directory projection can be returned.
Backend dde8177 extends the authenticated stage care-history smoke to prove
the directory over a real family account. The run completed booking
f175bd00-441f-4801-b21d-3081bfa75682, granted care-history authority
aecf7aa6-d034-407d-9148-74deb5f87d1c, proved the delegate own directory
shows that active grant without authorityEvidence, proved non-admin
administration access is denied, revoked the grant, and proved the own
directory no longer exposes the revoked grant. The registered booking batch was
reaped after the smoke.
Source and parity lineage remains in
Source Feature Checklist,
rows SFC-20260711-FAM-001 and SFC-20260711-FAM-007.
Known Gaps
Section titled “Known Gaps”- Service-contract template retention must preserve every accepted version for later rendering and audit.
Consequences
Section titled “Consequences”Family members can prepare reservations without impersonating the mother. Mother acceptance remains explicit and reviewable. Any change to financially relevant intent or contract version requires a new acceptance. Additional family relationships can be added through profile and policy data without creating spouse-specific booking code.
Revision History
Section titled “Revision History”- 2026-07-11: accepted revisioned family sponsorship, backend-derived intent fingerprint, mother contract acceptance, and one-time booking consumption.
- 2026-07-11: recorded applied stage schema and fresh common reservation-to-settlement proof without overstating sponsor-acceptance E2E.
- 2026-07-11: published and verified contract schemas, OpenAPI routes, generated
TypeScript types, validators, and affected tests in contract
15b0261. - 2026-07-11: proved family sponsorship through completed booking plus exact
replay and negative cases, then re-proved authenticated booking through
settlement and automatic registered-data cleanup in backend
48abf1c. - 2026-07-11: carried one frozen family acceptance through booking, delegated
payment, service delivery, settlement, SpiceDB cleanup, and DB reaping in
backend
ddd1559. - 2026-07-12: proved superseded-grant and contract-version-rollover rejection,
then repeated the full family-sponsored booking-to-settlement path with
symmetric SpiceDB and Supabase cleanup in backend
5414fa4. - 2026-07-13: added backend-owned family authority directory projection for
own versus mother-administration views in backend
bb4d6ee. - 2026-07-13: removed authenticated raw Supabase grant-table reads for family
authority directory data in backend
4477372. - 2026-07-13: proved withdrawn session and revoked family member denial for
family authority directory reads in backend
13dc531. - 2026-07-13: proved family authority directory visibility in authenticated
stage HTTP smoke in backend
dde8177.