ADR 017 Financial Decision Authority
Context
Section titled “Context”Legacy customer and operator screens posted totals, fee amounts, status ids, selected settlement rows, and manual adjustments as financial truth. That made browser state an authority for payment and settlement outcomes.
Narrow projection ADRs do not replace one cross-context invariant spanning reservation pricing, payment, caregiver payout, refunds, branch settlement, headquarters settlement, and financial reporting.
Decision
Section titled “Decision”Backend domain/application feature slices own all financial decisions.
Frontend and operator UI may submit:
- user intent and selected backend-issued references;
- expected revision and idempotency key;
- actor context resolved from authenticated session;
- uploaded evidence and memo/reason text; and
- provider references returned by approved billing flow.
Frontend and operator UI may not submit as authority:
- payable, fee, discount, refund, payout, or settlement totals;
- down-payment/balance splits;
- payment, lifecycle, or settlement status ids;
- capability, eligibility, readiness, or approval decisions; or
- raw manual adjustment math.
Command handlers resolve intent from frozen backend facts such as accepted quotes, finalized charge snapshots, service-occurrence ledgers, payment fact revisions, provider result evidence, and settlement statement revisions. Conflicting or stale revisions fail closed.
Query handlers return display-ready projections. Frontend may format returned money and labels but must not recompute them. CQRS is optional: command/query models separate only where invariants and projection needs differ.
Financial Control Rules
Section titled “Financial Control Rules”- Every financial mutation has idempotency and expected-revision semantics.
- Historical amounts reference immutable or revisioned evidence.
- Refund and provider-result handling preserves rejected/stale attempts as audit facts instead of silently rewriting payment state.
- Branch/HQ settlement lines derive from frozen financial facts, not UI rows.
- Caregiver payout instructions freeze destination revision and amount source.
- Repair and recalculation emit explicit invalidation/audit facts.
- SpiceDB authorizes finance-changing commands; Supabase RLS remains data access enforcement, not business capability authority.
Platform Boundaries
Section titled “Platform Boundaries”- Supabase is primary financial data, ledger, revision, and projection store.
- SpiceDB decides actor capability for financial commands.
- Restate coordinates durable multi-step workflow progress but does not decide amounts, eligibility, or settlement policy.
- Kill Bill executes billing/provider operations. Backend adapters verify and translate provider events; Kill Bill does not own Sanmopia settlement math.
- Kill Bill payment callbacks use official generic
ExtBusEventfields.PAYMENT_SUCCESS/PAYMENT_FAILEDplusmetaData.transactionTypeandmetaData.statusdecide purchase, capture, refund, void, and failed-operation handling. Invented event names and nested provider payload shapes are rejected. - Kill Bill push notifications provide no signature. Callback admission uses a restricted internal Traefik route and an edge-injected token; that token is transport admission, not provider-authenticity evidence.
- Frontend remains static Atomic Design shell with NanoStores limited to ephemeral interaction state.
- Frontend booking clients do not expose financial lifecycle mutation helpers. Reservation-to-settlement preview proof uses backend commands or the Playwright backend settlement driver, never browser-constructed service delivery, payout, branch-settlement, HQ-settlement, or completion payloads.
These boundaries align with ADR 018 and frontend constraints in ADR 012. Reservation payment handoff details are fixed by ADR 020.
Implementation Evidence
Section titled “Implementation Evidence”- Frontend
6ef99f95e981f83f4a76fd39aaf78a71980965b7binds the authenticated payment-change operator board query and bearer/idempotent completion command without deriving financial authority in the browser. The same revision keeps server-filtered rows intact, distinguishes visible versus returned counts, and reloads the SSR projection after a successful completion command. - Financial lifecycle application command
- Reservation finance transition audit domain model
- Supabase financial lifecycle adapter
- Kill Bill billing adapter
- Kill Bill ExtBusEvent verifier
- Kill Bill event ingestion
- Kill Bill ExtBusEvent persistence migration
- Atomic Kill Bill event inbox adapter
- Payment event conflict-quarantine migration
- Payment-change handoff authorization command
- Atomic payment-change handoff RPC
- SpiceDB authorization adapter
- Backend-owned reservation payment and immutable actor snapshot
pnpm supabase:smoke:stage:virtual-account-product-lifecycleproves rollback-scoped virtual-account issue, deposit, payment-fact closeout, and notification-route readiness for pre-reservation, customer-share deposit, customer-share balance, gift-coupon, and payment-change products on stage Supabase. Pre-reservation additionally proves the backend closeout plan.pnpm supabase:smoke:stage:gift-coupon-closeout-side-effectsreuses the same stage proof and additionally binds a paid gift-coupon checkout to issued promotional entitlement, fulfillment request, receiver fulfillment workflow route, and frozen gift-coupon settlement template without frontend calculation authority.pnpm supabase:smoke:stage:killbill-callback-to-settlement-completionproves a stage reservation can move through synthetic Kill BillPAYMENT_SUCCESS, service delivery, caregiver payout instruction, branch payout deposit acknowledgement, caregiver payout receipt acknowledgement, branch settlement preparation, HQ settlement preparation, approved-payment settlement-continuation handoff, settlement completion, operator board refresh, and registered test-data reaping. The smoke now uses backend HTTP commands for branch/HQ settlement preparation and rejects missing closeout references instead of calling direct helper shortcuts.- The same proof exposed and fixed two backend contract gaps: HQ settlement
preparation now preserves add-option selection revision references, and
caregiver payout receipt acknowledgement now has an HTTP/API command that
writes the guard-read
CaregiverPayoutAcknowledgementfact instead of only updating the receipt-state projection. - Follow-up stage proof now records caregiver payout receipt through the
assigned caregiver actor. Authorization resolves payout target profile id to
the frozen
assigned_caregiver_user_id, then requires SpiceDB booking view permission before writing the guard-visible acknowledgement. - The caregiver receipt command also advances the receipt-state projection to
acknowledged, so caregiver/operator UX reads and settlement-continuation guards no longer diverge after one accepted caregiver action. Stage reaper registration now deletes receipt-state events/projections before payout instructions, preserving cleanup proof for the fuller lifecycle.
Evidence proves code foundations at pinned revision. It does not prove every financial path uses them or that external provider integration is live.
Known Gaps
Section titled “Known Gaps”- HQ actor stage GET and branch-actor denial are proven at frontend
6ef99f95e981f83f4a76fd39aaf78a71980965b7, with redaction-reviewed desktop/mobile animated WebP. The board returned no test-owned paid row, so completion mutation, same-key replay, cleanup, and a deployed frontend artifact digest remain unproven. - Synthetic ExtBusEvent callback-to-queue-to-worker-to-Supabase stage proof
passes. Canonical JSON replay returns
duplicate; changed payload under the same event identity returnsquarantined; both produce no second queue message. Live Kill Bill-originated callback and trusted Traefik admission route remain unproven. - Webhook inbox idempotency is proven, and gift-coupon closeout side effects now have rollback-scoped stage proof. Live Kill Bill-originated provider events, durable closeout workflow/API execution, receipts, and settlement invalidation remain incomplete.
- Payment-change handoff authorization has service-role-only atomic persistence, revision CAS, structured audit, replay proof, and atomic unpaid supersession. Paid or provider-uncertain replacement routes to reconciliation instead of destructive replacement.
- Provider attempts represent timeout and ambiguous responses as
provider_unknown. Append-onlyPaymentProviderOperationReconciliationfacts freeze provider evidence, payment/refund ledger revisions, settlement blocking, and one safe next action. Only confirmed providernot_foundpermits provider-operation retry. Backend now assembles a Supabase-backed, newest-first reconciliation history query. Supabase Auth identifies actor and SpiceDBclose_reservation_financedecides access; metadata roles never grant access. OpenAPI and strict JSON Schema expose no mutable financial fields. Stage runtime proof still awaits deployment of the new backend revision. - Reconciliation mutation accepts only attempt identity, expected revisions,
reason, and idempotency metadata. Provider, payment, booking, operation kind,
request key, local closeout state, and source evidence come from persisted
attempt/ledger context. The authorized path booking must match the attempt
booking before any provider lookup. Kill Bill transaction evidence is matched
by
transactionExternalKeyandPURCHASE/REFUND;SUCCESSis accepted,PAYMENT_FAILURE/PLUGIN_FAILUREare rejected, andUNKNOWN/PENDING/PAYMENT_SYSTEM_OFFremain unknown. This follows the official Kill Bill PaymentTransaction model. A service-role-only Supabase RPC locks attempt/payment/refund rows, rechecks both ledger revisions, and appends one immutable reconciliation fact. - Payment product plans now own
PaymentProductCashReceiptPolicy: merchant of record, collection channel, receipt issuer, eligibility, supported purposes, and stable denial reason. Company-collected products use provider issuance on behalf of Sanmopia. Direct caregiver service balance freezes caregiver as merchant, direct collection, no issuer, and denialdirect_caregiver_receivable_not_company_tax_evidence. Authenticated clients may request a typed purpose and vault identity reference, but cannot submit merchant, issuer, eligibility, or raw registration identity. Supabase catalog constraints and stage smoke prove policy backfill. Provider handoff, refund reversal, receipt artifact selection, and tax-export consumption remain open. - Full sales-dashboard refund recomputation/read APIs remain incomplete.
- Persisted artifact reissue lineage and complete operator repair projections remain incomplete.
- ADR 012 records remaining frontend semantic authority leak candidate.
- Production-scale settlement and replay behavior remains outside repository structure proof.
- Live provider-originated virtual-account deposit, and product-specific downstream side-effect proof beyond the pre-reservation closeout plan, remain incomplete.
- Caregiver payout receipt self-acknowledgement is now separated into target caregiver profile id and authenticated caregiver user id. Remaining work is broader caregiver portal UX conflict handling and repair copy, not the settlement guard or receipt-state synchronization split.
- Frontend booking client no longer exports financial lifecycle mutation methods or request-path builders for service delivery, caregiver payout, branch settlement, HQ settlement, settlement completion, or lifecycle workflow start. Remaining preview gap is browser-visible projection handoff after backend settlement proof.
Implementation stays partial until these paths have linked runtime evidence
and no active frontend path supplies financial authority.
Consequences
Section titled “Consequences”Missing backend financial facts block UI completion. Browser convenience cannot override revision, authorization, provider verification, or ledger invariants.
Revision History
Section titled “Revision History”- 2026-07-09: accepted backend financial decision authority.
- 2026-07-10: consolidated implementation diary into durable rules, pinned evidence, and explicit gaps.
- 2026-07-10: replaced fictional callback semantics with official Kill Bill ExtBusEvent operation mapping and recorded synthetic stage runtime evidence.
- 2026-07-10: made event inbox insert plus PGMQ send atomic, added canonical replay detection and append-only conflict quarantine, and proved no duplicate queue effect in stage.
- 2026-07-11: added payer/booking-bound payment-change handoff authorization, service-role-only atomic persistence, revision CAS, and exact-replay stage proof.
- 2026-07-11: separated scheduled booking from waiting payment, removed browser-owned pre-registration facts, froze delegated payer authority and amount ceiling, and passed the bounded stage reservation-to-settlement flow.
- 2026-07-13: added rollback-scoped stage proof for virtual-account issue, deposit, payment-fact closeout, and notification-route readiness across all modeled virtual-account product purposes.
- 2026-07-13: extended callback-to-settlement stage proof through caregiver payout receipt acknowledgement, branch/HQ settlement API preparation, payment workflow settlement-continuation handoff, and settlement completion.
- 2026-07-13: replaced branch-operator-only caregiver receipt proof with direct assigned-caregiver acknowledgement using payout instruction assignee resolution plus SpiceDB booking view permission.
- 2026-07-13: made caregiver receipt acknowledgement update both
CaregiverPayoutAcknowledgementandCaregiverPayoutReceiptStateProjection, then re-proved stage cleanup after receipt-state event registration. - 2026-07-13: removed frontend booking-client financial lifecycle mutation surface; frontend can no longer drive settlement commands directly.
- 2026-07-13: bound the HQ payment-change read projection and idempotent
completion controller, then recorded HQ
200, branch403, and redacted desktop/mobile read-only WebP evidence without claiming completion mutation.