Skip to content

ADR 021 Family-Sponsored Reservation Contract Acceptance

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.

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:

  1. resolves the active sponsorship grant;
  2. normalizes the reservation intent and computes its SHA-256 fingerprint;
  3. resolves the current service-contract version from durable backend data; and
  4. persists FamilySponsoredReservationDraft with 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.

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.

  • 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 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.

  • Service-contract template retention must preserve every accepted version for later rendering and audit.

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.

  • 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.