Reservation Control Collaboration Gap Mining
Reservation Control Collaboration Gap Mining
Section titled “Reservation Control Collaboration Gap Mining”Scope: branch/HQ reservation control UX that currently loses operator context because writes are broad, non-versioned, non-realtime, and browser-history driven. UI polish cannot fix this without a backend collaboration contract.
2026-07-08 Reservation Scenario Smoke Loop
Section titled “2026-07-08 Reservation Scenario Smoke Loop”The reservation scenario is now runnable before the Supabase/ReState production stack is fully composed. The backend owns the workflow, state transition, and mother-visible charge summary; frontend smoke runs only render and submit the contract.
Backend command:
cd backend-repouv run uvicorn sanmopia_modernization.dev_smoke_app:app \ --app-dir src \ --host 127.0.0.1 \ --port 8001Frontend command:
cd frontend-repoPUBLIC_SANMOPIA_API_BASE_URL='http://127.0.0.1:8001' \SANMOPIA_MOTHER_BOOKING_OPERATIONAL_CONTEXT_JSON='<qualified fixture json>' \pnpm exec astro dev --background --host 127.0.0.1 --port 4326 --forceIf port 4326 is occupied, use another local Astro port in the 4300-4399
range. The smoke backend accepts those localhost origins by regex. For remote
or nonstandard preview origins, set:
SANMOPIA_RESERVATION_SMOKE_CORS_ORIGINS='https://reservation-smoke.example.test'2026-07-08 proof artifact:
frontend-repo/output/playwright/reservation-scenario-20260708-full-stack-qualified/.
The run submitted /mother/booking, received 202 from
POST /reservation-booking-workflow-starts, read a completed workflow through
GET /reservation-booking-workflow-requests/{bookingRequestId}, and read the
frozen mother-visible charge summary through
GET /reservation-financial-lifecycles/{reservationBookingId}/mother-visible-charge-summary.
The UI showed workflow status completed / 확정, reservation id
366cbba6-eb71-4aee-b311-c9abd607aeab, and backend-owned approved charge
989,881원.
2026-07-08 repeat proof artifact after frontend payment-method derivation and
smoke CORS fix:
frontend-repo/output/playwright/reservation-scenario-20260708-cors-repeat/.
The run used Astro 4327, payment method stayed family_bank, the browser POST
completed, and the page showed completed reservation state without the old
context-mismatch message or CORS failure.
2026-07-08 backend follow-up proof after payment purpose preflight:
RBQ-SCENARIO-20260708-1 submitted through the smoke backend with canonical
card, returned 202, completed the workflow, produced reservation id
e32cb903-89cd-45a1-babc-da6c1ea275b5, and returned mother-visible charge
summary 200 with customer payable 150019. The older family_bank browser
fixture value is now treated as an unsupported hidden legacy value by backend
preflight; booking-start payment now also checks the
reservation.customer_share.deposit product plan before workflow acceptance.
2026-07-09 backend smoke proof extends the same local app through settlement:
POST /reservation-smoke-scenarios/{reservationBookingId}/settlement-completions
advances the in-memory financial lifecycle from charge_finalized to settled
after the booking workflow persists its service occurrence ledger. The response
returns backend-owned caregiver payout, branch settlement, HQ settlement, ledger
line codes, and policy version. The smoke test verifies the mother-visible
charge summary before and after settlement is byte-for-byte identical, so a
frontend card flow can demonstrate reservation-to-settlement without doing UI
pricing, payout, or settlement arithmetic.
2026-07-10 backend 597b56d tightened that local smoke path: settlement
completion now runs through AdvanceReservationFinancialLifecycleHandler with
RecordReservationServiceDeliveryCommand, caregiver payout, branch settlement,
HQ settlement, and final settled commands. The demo ledger split is carried by a
versioned _SmokeReservationSettlementPolicy, so the preview route no longer
mutates the aggregate directly or hides basis-point arithmetic in route logic.
This is still smoke policy, not production settlement policy; production proof
must continue to use catalog-backed payout, branch, HQ, and refund/repair facts.
Observed blockers converted into work items:
- Frontend fixture defaults must derive hidden payment method values from the
operational context. A stale hardcoded
cardvalue broke contexts that only allowedfamily_bank; the frontend lane fixed this by deriving the initial payment method frompaymentMethodOptions[0].value. - Non-headquarters branch fixtures must carry a headquarters reference before reservation, branch settlement, or HQ settlement scenarios run.
- Caregiver assignment fixtures must include normalized qualification facts, including approved license titles, before workflow execution.
- Backend
ReservationBookingContextQualityGatenow runs before direct booking or durable workflow start. Branch topology and caregiver profile/qualification gaps fail with named preflight invariant codes instead of being accepted into a workflow that later fails during assignment. - Payment method preflight now rejects hidden browser-only or unsupported method
values with
payment_method_catalog_eligibility_invalidbefore workflow start. Booking-start purpose eligibility now rejects canonical but purpose-invalid methods withpayment_purpose_method_eligibility_invalid; provider-route workflow verification now requires a frozen pre-registration provider-route fee snapshot before ReState starts from either the public API or Kill Bill paid webhook ingestion. Virtual-account lifecycle, card-replacement conversion, and provider-success/local-write-failure compensation remain tracked inSFC-20260708-CUR-086. - Production proof still needs the same path against the Supabase/ReState stack, including projection invalidation queue enqueue, failure, repair, requeue, and completion.
2026-07-13 Branch Reservation Worklist Delta
Section titled “2026-07-13 Branch Reservation Worklist Delta”Legacy reservation history initializes a branch operator’s branch from the
session, but the browser then posts branchIndex with every DataTables GET.
The controller copies that query value into options['branch'], and the model
uses it directly as the SQL branch predicate. Evidence:
source-refs/sanmopia-admin/application/controllers/Reservation.php:165-205resolves the first branch value from the authenticated session;source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:224-260sends browser-owned branch, date, quick-filter, status, and pagination values;source-refs/sanmopia-admin/application/controllers/Reservation.php:208-315accepts thatbranchIndex, serializes the complete query state into a cookie, and delegates it to the model; andsource-refs/sanmopia-admin/application/models/Reservation_model.php:4483-4685joins the broad reservation/customer/address/payment/coupon projection and appliesoptions['branch']as the branch SQL filter.
The modern P0 replacement is implemented in Layer-first form:
application/reservation_operations/features/reservation_operation_worklist/queries/adapters/reservation_operations/features/reservation_operation_worklist/interfaces/reservation_operations/features/reservation_operation_worklist/GET /branch-reservation-operation-worklist accepts search, semantic repeated
status, opaque cursor, and limit only. It accepts no branch or actor id. Supabase
Auth resolves the principal, the membership adapter requires exactly one
branch_operator membership, SpiceDB checks branch:view, and the booking read
itself is filtered by that server-owned branch profile. Public rows expose the
business reservation id, revision, display-only names, schedule, canonical
status, updated time, and policy decisions. They do not expose booking/context
UUIDs or raw user ids.
Contract 18955f7, backend c9b06d7, and frontend 1f95287 add this boundary,
SSR states, status/search controls, 15 semantic status labels, and an explicit
executionReady=false gate. Stage-data proof observed branch operator 200,
mother 403, visibleItemCount=501, first page 50, cursor present, and no
operation-context decision rows. Desktop/mobile WebP evidence is registered at
branch-reservation-worklist-stage-20260713T023958Z.
This is Partial actual, not reservation-operation completion. Remaining parity:
- date-basis, expected/actual birth, unplanned schedule, unassigned caregiver, service-balance, export, and HQ cross-branch query projections;
- server-resolved command actor and branch, expected revision, idempotency, conflict/replay, audit, and cleanup proof before mutation is enabled; and
- a source-state catalog that preserves legacy extension request
10, pre-reservation waiting16, waiting-deposit cancellation17, and pre-reservation cancellation18instead of merging their meanings.
2026-07-20 P05 Command Backend Readiness
Section titled “2026-07-20 P05 Command Backend Readiness”Backend f77db221 closes the command mutation gap without changing P05 from
blocked. POST /branch-reservation-operation-commands/{reservationId} accepts
expectedRevision, Idempotency-Key, command, reason, and note only. Supabase
Auth resolves the user; booking, member profile, exact branch membership, and
branch profile resolve branch/HQ scope on the server. SpiceDB still owns
operation permission.
One PostgreSQL RPC locks booking/context rows and atomically updates booking status/revision, operation context, and immutable plan receipt. It rejects foreign branch scope, stale revision/status, and same-key changed facts. Exact replay returns the stored receipt and does not restart the follow-up workflow.
Disposable pinned Supabase CLI proof passed fresh migration, public-schema lint,
happy-path smoke, same-key concurrent commit/replay, different-key concurrent
commit/revision-conflict, forced insert rollback, grants, cleanup, and container
teardown. Focused Python passed 33, HTTP passed 12, API/follow-up passed 5;
Tach dependency/interface/external checks passed and Vulture confidence 100
reported 0 candidates. Platform follow-up d33be2b1 replaced the obsolete
global SECURITY DEFINER count with per-function search-path, PUBLIC-revoke,
and Data API grant checks, and restored the settlement acknowledgement table
inventory. The focused access-control corpus passed 92; changed-impact pytest
passed 6310 with 244 unaffected tests deselected. The clean backend policy
gate is therefore GREEN.
Frontend cbc993f binds allowed worklist decisions to the strict public command
route without client branch/actor authority. It sends revisioned commands with
deterministic replay keys, preserves current URL filters through success reload,
and maps 400/401/403/404/409/503 to actor-safe guidance. Focused Vitest passed
38, full Vitest passed 642, ESLint passed, Astro check reported 0 errors,
and the production server build passed.
P05 remains blocked until P04 receives production legal approval. No P05 actual branch actor, browser, primary-state manifest, screenshot, animated WebP, or four-repository acceptance run exists yet.
2026-07-06 Reservation Lifecycle Backend Task Delta
Section titled “2026-07-06 Reservation Lifecycle Backend Task Delta”These tasks are backend bounded-context work. They must not be implemented as frontend state checks or caregiver-matching changes.
| Priority | Bounded context / feature slice | Source evidence | Concrete backend task |
|---|---|---|---|
| P0 | reservation_operations / reservation_state_version_catalog | source-refs/sanmopia-admin/application/config/constants.php:115-160, source-refs/sanmopia_web/application/config/constants.php:47-92, source-refs/sanmopia-admin/application/views/reservation_status/reservation_detail.php:86-120, source-refs/sanmopia-admin/static/js/reservation_status/reservation_add.js:1350-1352 | Create ReservationStateVersionCatalog and LegacyReservationStateSnapshot. Seed every old booking with source status code, label, source system, effective catalog version, pre-reservation flags, virtual-account flags, offline marker, and closeout timestamp. Domain commands use stable state names only; raw codes -2/-1/0..19 stay adapter evidence. Add migration checks for old bookings whose status meaning changes by PRERESERVATION_FL, PRE_CONVERT_FL, or offline status 19. |
| P0 | service_calendar / service_calendar_policy_profile | source-refs/sanmopia-admin/application/models/Manage_system_model.php:9-24, source-refs/sanmopia-admin/application/models/Manage_system_model.php:51-181, source-refs/sanmopia-admin/application/models/Reservation_model.php:4183-4193, source-refs/sanmopia-admin/application/models/Reservation_model.php:4204-4216 | Split global holiday rows from service-calendar policy. Import source HOLIDAY_TB and free-text LIST_HOLIDAY_TYPE_TB as versioned holiday publications with source evidence, but add missing modern fields: national/substitute/company/branch-closure kind, substitute-of relation, announcement date, supersession, branch scope, and audit actor. BranchServiceClosurePolicy must model local closures explicitly because source only has global date rows and cannot represent branch-specific closed service days. |
| P0 | reservation_operations / reservation_service_calendar_plan | source-refs/sanmopia-admin/application/models/Reservation_model.php:4009-4047, source-refs/sanmopia-admin/application/controllers/Reservation.php:902-917, source-refs/sanmopia-admin/application/models/Reservation_model.php:4089-4139, source-refs/sanmopia-admin/application/models/Reservation_model.php:4153-4256 | Build ReservationServiceCalendarPlanRebuildCommand. Inputs: start date, duration unit, policy profile, selected Saturday dates, selected holiday dates, Wanju/local benefit extension, source booking state version. Output immutable service occurrences and ServiceCalendarImpactPlan facts for report obligations, pricing, refund, coupon entitlement, settlement, and document eligibility. Preserve source warnings for selected-count mismatch, but reject or flag SAT/HOLI bucket collisions and substitute-holiday/local-closure impact through backend validation. |
| P0 | reservation_operations / service_suspension_lifecycle | source-refs/sanmopia_web/application/controllers/api/Reservation.php:86-239, source-refs/sanmopia_web/application/models/Reservation_model.php:1091-1111, source-refs/sanmopia-admin/application/models/Reservation_model.php:1768-1792, source-refs/sanmopia-admin/application/models/Reservation_model.php:1888-1892, source-refs/sanmopia_web/application/models/Report_model.php:267-391 | Implement ServiceSuspensionCommand set: request pause, approve pause, reject pause, resume service. Persist ServiceSuspension interval, effective stop date, resume boundary, reason, actor, expected revision, and downstream impact plan. Legacy has stop request 12, stopped 13, coupon reset on actual transition to 13, and report readers inferring resume from change logs; modern code must stop deriving pause/resume from raw status logs. |
| P0 | reservation_operations / reservation_extension_chain | source-refs/sanmopia_web/application/models/Reservation_model.php:2039-2145, source-refs/sanmopia_web/static/js/step3.js:563-576, source-refs/sanmopia_web/application/models/Reservation_model.php:2473-2501 | Implement ReservationExtensionChainCommand. Store immutable original/new booking link, sequence number, previous service-end snapshot, requested extension start, copied-fact audit, and voucher-continuation compatibility decision. Backend must enforce start-after-previous-end and ancestor-chain voucher rules; customer pages must not be the authority for extension start validation or voucher eligibility. |
| P0 | promotion_entitlements / product_coupon_service_day_policy | source-refs/sanmopia-admin/application/config/constants.php:276-277, source-refs/sanmopia-admin/application/models/Coupon_model.php:33-155, source-refs/sanmopia-admin/application/models/Coupon_model.php:211-324, source-refs/sanmopia-admin/application/models/Coupon_model.php:500-552, source-refs/sanmopia-admin/application/models/Reservation_model.php:1299-1328, source-refs/sanmopia-admin/application/models/Reservation_model.php:3888-3916, source-refs/sanmopia-admin/application/models/Reservation_model.php:4537-4556 | Create ProductCouponEntitlementByServiceDayPolicy. Freeze eligibility from service type, extension-chain source service type, service term, working type, service-calendar occurrences, actual attendance count, suspension interval, cancellation/stop state, and source state catalog version. Resolve source conflicts: insert logic uses 5 days -> +3, >=10 days live-in -> +3, >=10 days commute -> +8; list/status logic also uses global PRODUCT_COUPON_PUBLISH_INTERVAL_DAY = 15 or service-start-only gates; reset logic downgrades stopped/cancelled normal reservations by actual attendance count 3..7 or >=8. Coupon issuance becomes event/outbox state, not view-time date math. |
Source Failure Shape
Section titled “Source Failure Shape”-
Full-form stale overwrite. Status:
Contracted and backend-backed. Evidence:source-refs/sanmopia-admin/application/controllers/Reservation.php:331-358,source-refs/sanmopia-admin/application/views/reservation_status/reservation_detail.php:1406-1448,source-refs/sanmopia-admin/static/js/reservation_status/reservation_detail.js:270-333,source-refs/sanmopia-admin/application/controllers/Reservation.php:650-704,source-refs/sanmopia-admin/application/controllers/Reservation.php:834-1018,source-refs/sanmopia-admin/application/models/Reservation_model.php:1682,source-refs/sanmopia-admin/application/models/Reservation_model.php:1769, andsource-refs/sanmopia-admin/application/models/Reservation_model.php:2166. Source behavior: detail opens one snapshot, posts broadFormData, and saves many field groups together. The endpoint logs before/after values around the save, but it does not reject another actor’s newer values before applying the form. Service-day, status, and caregiver updates write current rows without a row version predicate, so two branch/HQ users can silently last-write-win. Modern replacement:ReservationCollaborationCommandrequiresexpected_revision,idempotency_key, actor, operation command, mutation kinds, and a lower-snakechange_set.ReservationCollaborationPolicyreturns a conflict diff when the current revision differs. Contract repo now exposesReservationCollaborationCommitRequest,ReservationCollaborationCommitResponse, andReservationCollaborationConflictResponse, and OpenAPI documents409conflict responses for stale saves. If the Supabase RPC detects a revision race after the pre-write snapshot read, the application re-reads the latest snapshot and returns the same409conflict contract instead of a false infrastructure503. -
Destructive child-list rewrite without version guard. Status:
Backend-backed for service addresses. Evidence:source-refs/sanmopia-admin/application/models/Reservation_model.php:1349-1456. Source behavior: service addresses are deleted and recreated from the posted form. Modern replacement:address.service_addressesis now a command-specific field group under the sameexpectedRevisiongate and emits one journal entry that lists the before and after address payloads. The Supabase collaboration RPC replacessanmopia_reservation_service_addressesrows only after the revision bump, journal write, and realtime event succeed in the same transaction. -
Browser-history and filter-state loss. Status:
Backend-backed. Evidence:source-refs/sanmopia-admin/static/js/reservation_status/reservation_detail.js:331-342,source-refs/sanmopia-admin/static/js/reservation_status/pre_reservation_detail.js:241,source-refs/sanmopia-admin/static/js/reservation_status/pre_reservation_detail.js:322,source-refs/sanmopia-admin/static/js/reservation_status/pre_reservation_detail.js:332,source-refs/sanmopia-admin/static/js/reservation_status/pre_reservation_detail.js:276-332,source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:5-13,source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:46-63,source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:130-143, andsource-refs/sanmopia-admin/application/controllers/Reservation.php:299-314. Source behavior: successful saves usehistory.back()and list screens reconstruct search state from hashes/cookies. Modern replacement: commands carrynavigation_intentandreturn_state_token; the backend response tells the portal whether to stay, return to a saved list state, open the next work item, or close a modal.POST /reservation-collaboration-workspaces/{reservationId}now returns the current revision, realtime topic, latest audit event id, and last return-state token before the operator starts editing. The same workspace response now returnscoordinationPolicy.dirtyNavigationPolicy = require_save_discard_or_draft, so route changes and browser back must be driven by explicit save, discard, or draft persistence. Frontend state must store the returneddraftSessionin Nanostores and recover it after route change or browser history navigation instead of relying onhistory.back(). The backend now persistsReservationWorkspaceDraftSessionrows insanmopia_reservation_workspace_sessions, withbaseRevision,latestKnownRevision,status,openedAt,lastSeenAt, anddirtySinceso stale browser sessions can be resumed or discarded explicitly. -
Branch transfer bypasses the main journal. Status:
Implemented for branch handoff. Evidence:source-refs/sanmopia-admin/application/views/reservation_status/reservation_detail.php:213-232,source-refs/sanmopia-admin/application/views/reservation_status/reservation_detail.php:1463-1478,source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:22-28, andsource-refs/sanmopia-admin/application/models/Branch_model.php:23-73. Source behavior: branch transfer posts only reservation id and branch id, then writes a narrow branch log. Modern replacement: branch handoff isReservationMutationKind.BRANCH_HANDOFFwith actor, reason, expected revision, idempotency key, reservation id, field diff, and realtime invalidation.transfer_reservation_branchis now a HQ-only reservation operation command requiring target branch profile, actor branch audit context, and before/after diff. Thesanmopia_commit_reservation_collaborationRPC now appliesbranch_handoff.branch_profile_idtosanmopia_reservation_bookingsinside the same expected-revision update that writes the journal and private Broadcast event, so branch ownership cannot be overwritten by a stale page. -
Fragmented change logging. Status:
Contracted and backend-backed. Evidence:source-refs/sanmopia-admin/application/controllers/Reservation.php:1023-1092,source-refs/sanmopia-admin/application/controllers/Reservation.php:1799-1814,source-refs/sanmopia-admin/application/models/Reservation_model.php:3730-3740, andsource-refs/sanmopia-admin/application/models/Reservation_model.php:5252-5258. Source behavior: normal save, pre-reservation save, memo update, and branch transfer use inconsistent or narrow logs. Normal save creates a post-write diff log, but pre-reservation save has no matchingRESERVATION_CHANGE_LOGcoverage, and source logs are not part of the transactional concurrency decision. Modern replacement:ReservationChangeJournalEntryis append-only and covers every accepted mutation path.POST /reservation-collaboration-journal/{reservationId}now exposes the recent operator-facing timeline through the service-role adapter. Browser-level events that are not domain mutations, such asnavigation_blocked,draft_marked_dirty,draft_recovered, anddraft_discarded, now go throughPOST /reservation-collaboration-workspace-events/{reservationId}and are stored insanmopia_reservation_workspace_eventsbeside the durable session.pgAuditcan remain a database compliance and troubleshooting layer, but operator-visible history must come from this domain journal. -
Realtime projection missing. Status:
Contracted and backend-backed. Evidence:source-refs/sanmopia-admin/static/js/reservation_status/reservation_history_new.js:224-230, andsource-refs/sanmopia-admin/application/controllers/Reservation.php:1014-1020. Source behavior: list/detail screens reload Ajax data or navigate away after save. Modern replacement: every accepted command emitsReservationRealtimeProjectionEventwith a reservation-specific Supabase Broadcast topic, revision, changed fields, and audit event id. Stale open workspaces receive invalidation and can show a conflict/refresh state before the operator saves over newer data. The workspace API returnsrealtimeEventName = reservation_projection_invalidatedandcoordinationPolicy.realtimeChannelMode = private_broadcast_and_presenceso the portal can subscribe and publish operator presence without hardcoding channel behavior in UI code. Supabase Realtime Presence is only for slow-changing editor state such as active workspace and actor profile; cursor-level or high-frequency UI details must use Broadcast or stay local. -
Stop/resume collaboration command shape. Status:
Partial. Evidence:source-refs/sanmopia_web/static/js/mypage/reservation.js:336-421,source-refs/sanmopia_web/application/controllers/api/Reservation.php:86-239,source-refs/sanmopia_web/application/models/Reservation_model.php:1091-1111,source-refs/sanmopia-admin/application/models/Reservation_model.php:1768-1792,source-refs/sanmopia-admin/application/models/Reservation_model.php:1888-1892, andsource-refs/sanmopia_web/application/models/Report_model.php:267-391. Source behavior: customer stop is only a request row plus status12; branch/admin stop is a raw status13transition, with product coupon reset only when the status actually changes to13. No source pause/resume command was found. Report code infers a resume boundary from a later change log whoseBEFORE_DATAcontains status13. Modern gap: stop confirmation and resume must be first-class reservation collaboration commands withexpectedRevision, idempotency key, effective service date, reason, actor, impacted interval id, and stale-conflict response. The command result should include one impact plan for calendar dates, reports, caregiver assignment, coupons, refund/payment quotes, customer communication, and stopped-voucher settlement eligibility. -
Realtime conflict event payload and support log. Status:
Partial. Evidence:source-refs/sanmopia-admin/application/controllers/Reservation.php:650-704,source-refs/sanmopia-admin/application/controllers/Reservation.php:834-1020,source-refs/sanmopia-admin/application/models/Reservation_model.php:3730-3740, andsource-refs/sanmopia-admin/static/js/reservation_status/reservation_detail.js:270-342. Source behavior: stale edits are not rejected before mutation, and post-write change logs cannot explain which open workspace overwrote which field. Modern gap:ReservationCollaborationConflictLogshould be recorded whenever a Broadcast invalidation or commit-time revision check produces a stale-save conflict. Log fields: conflict event id, reservation id, workspace id, actor kind/profile, expected revision, current revision, conflicting field paths, latest audit event id, realtime topic/event name, refresh-required flag, attempted mutation kinds, idempotency key, user-facing resolution, and recorded time.commit_conflictedworkspace events should reference this log so support can trace realtime delivery, stale page save, and operator resolution without reading raw DB audit. -
Caregiver-assignment audit UX is too thin for branch-manager conflict review. Status:
Partial. Priority:P1. Evidence:source-refs/sanmopia-admin/application/views/reservation_status/reservation_detail.php:1148-1178,source-refs/sanmopia-admin/static/js/reservation_status/reservation_detail.js:1105-1155,source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:180-208,source-refs/sanmopia-admin/application/models/Reservation_model.php:2165-2204, andsource-refs/sanmopia-admin/application/models/Reservation_model.php:2270-2285. Source behavior: reservation detail exposes “change history” buttons for primary and assistant caregivers, then opens an Ajax modal that lists old caregiver, new caregiver, and created date from source manager-log tables. The save path inserts main or sub caregiver log rows only when ids change. It does not capture actor, branch/head-office role, reason, expected revision, selected candidate snapshot, realtime conflict id, or whether a branch manager overwrote another open workspace. Modern gap:ReservationCaregiverAssignmentAuditTrailshould be an operator-facing timeline backed byReservationChangeJournalEntry, not a separate popup over source log tables. EachCaregiverAssignmentChangedevent should link assignment key, primary/assistant role, handoff order, before/after caregiver identity, selected candidate snapshot, actor branch profile, reason, expected/current revision, conflict log id, and idempotency key so branch manager conflict review can explain both assignment history and stale-save resolution.
Enterprise UX Contract
Section titled “Enterprise UX Contract”The branch/HQ reservation screen must behave like a collaborative operations workspace, not a PHP form.
sequenceDiagram
autonumber
actor BranchOperator as Branch Operator A
actor BranchManager as Branch Operator B
participant PortalA as Operator Portal A
participant PortalB as Operator Portal B
participant API as Backend API
participant Realtime as Supabase Realtime
participant DB as Supabase Postgres
BranchOperator->>PortalA: Open reservation
PortalA->>API: POST /reservation-collaboration-workspaces/{reservationId}
API->>DB: Read revision, fields, latest journal
API->>DB: Upsert workspace session, append workspace_opened event
API-->>PortalA: currentRevision=7, realtimeTopic, draftSession
PortalA->>Realtime: Subscribe private Broadcast and Presence
PortalA->>API: POST /reservation-collaboration-workspace-events navigation_blocked
BranchManager->>PortalB: Open same reservation
PortalB->>API: POST /reservation-collaboration-workspaces/{reservationId}
PortalB->>Realtime: Subscribe private Broadcast and Presence
PortalB->>API: POST /reservation-collaboration-commits/{reservationId} expectedRevision=7
API->>DB: Bump revision to 8, append journal, store realtime event
API->>Realtime: Broadcast reservation_projection_invalidated
Realtime-->>PortalA: revision 8 invalidation
PortalA->>API: POST /reservation-collaboration-commits/{reservationId} expectedRevision=7
API-->>PortalA: 409 conflict with currentRevision=8 and conflictingFields
PortalA->>API: POST /reservation-collaboration-workspace-events commit_conflicted
PortalA->>API: POST /reservation-collaboration-journal/{reservationId}
API-->>PortalA: Operator audit timeline
UX requirements:
- Save button submits only changed lower_snake field paths with
expectedRevision; stale save shows conflict diff and refresh action. - If another operator commits between workspace read and RPC commit, API still
returns
409with current revision and conflict fields; UI must not blindly retry or overwrite. - Route change, modal close, browser back, and list navigation all consult
coordinationPolicy.dirtyNavigationPolicy. Dirty workspace offers save, discard, or draft recovery, and records the chosen UX event through the workspace-event endpoint instead of hiding it in browser state. - Presence shows other active reservation editors but never replaces authorization or optimistic concurrency.
- Journal timeline is append-only domain history. Database audit or OTel logs support compliance/operations, not operator UX.
- Workspace event timeline is append-only operational UX history. It records page/session events that do not change reservation facts but matter for support, stale-save debugging, and branch manager accountability.
- Conflict timeline entries include expected/current revision, conflicting field paths, idempotency key, workspace id, realtime event id, and selected resolution so stale-save support cases are searchable without replaying raw PHP logs.
- Commit responses carry
navigationIntentandreturnStateToken; UI never infers destination fromhistory.back().
OSS And Platform Decision
Section titled “OSS And Platform Decision”- Supabase Realtime Broadcast is the operator invalidation channel. The backend
writes the domain journal and calls
realtime.send(..., private := true), whilerealtime.messagesRLS limits reservation topics to HQ or the reservation branch. Supabase documentsrealtime.send()as the flexible database broadcast function for custom payloads, whilerealtime.broadcast_changes()is better for trigger-shaped row changes. The reservation contract therefore keeps custom domain invalidation payloads onrealtime.send()and avoids raw table streaming as the operator contract. - Supabase Realtime Presence is the operator co-editing signal. Presence is a warning layer showing who else has the reservation workspace open; the expected-revision commit remains the overwrite guard.
pgAuditis useful for database compliance/security/troubleshooting logs, not as the operator-facing change history.- OpenTelemetry logs are the operations export format for backend audit observability because the OTel log data model is stable and can carry correlation ids from the domain journal.
supa_auditcan be evaluated for generic row-version auditing on self-hosted Supabase, but the migration should not depend on it for business replay until extension availability and retention cost are verified.pgMementois an OSS PostgreSQL row-history option for self-hosted forensic reconstruction and schema-versioned audit trails. It is not the default operator UX source because it requires extension/script installation and produces database-centric history, not domain command language.- Native PostgreSQL temporal-table support can become a future storage option only after the self-hosted Supabase Postgres version supports it. Today the business contract uses explicit revision columns and append-only journals so it is portable across PostgREST, generated Supabase clients, and Python service-role adapters.
Reference:
- Supabase Realtime database changes
- Supabase Realtime broadcast
- Supabase Realtime authorization
- Supabase pgaudit extension
- OpenTelemetry log data model
- supabase/supa_audit
- pgMemento
- PostgreSQL temporal tables
Implemented Backend Foundation
Section titled “Implemented Backend Foundation”- Domain:
backend-repo/src/sanmopia_modernization/domain/reservation_operations/features/reservation_collaboration/reservation_collaboration.py - Application contract:
backend-repo/src/sanmopia_modernization/application/reservation_operations/features/reservation_collaboration/reservation_collaboration_contract.py - Application port:
backend-repo/src/sanmopia_modernization/application/reservation_operations/features/reservation_collaboration/commit_reservation_collaboration.py - Supabase adapter:
backend-repo/src/sanmopia_modernization/adapters/reservation_operations/features/reservation_collaboration/supabase_reservation_collaboration.py - Supabase migration:
backend-repo/supabase/migrations/20260702184000_reservation_collaboration_journal.sqlplus replay fixbackend-repo/supabase/migrations/20260702190000_fix_reservation_collaboration_idempotent_audit_return.sqland branch handoff applicationbackend-repo/supabase/migrations/20260702193000_apply_branch_handoff_collaboration.sqland service schedule applicationbackend-repo/supabase/migrations/20260702194000_apply_service_schedule_collaboration.sqland extra service-day applicationbackend-repo/supabase/migrations/20260702195000_apply_extra_service_days_collaboration.sqland Presence RLSbackend-repo/supabase/migrations/20260702200000_reservation_collaboration_presence_policy.sqland caregiver assignment roster applicationbackend-repo/supabase/migrations/20260702201000_reservation_caregiver_assignment_collaboration.sqland service term, payment fact, address, and memo applicationbackend-repo/supabase/migrations/20260703193000_reservation_collaboration_admin_fact_bindings.sql - Generated Supabase contract:
backend-repo/supabase/generated/database.types.ts
Current boundary:
ReservationCollaborationCommandReservationCollaborationPolicyReservationCollaborationConflictReservationCollaborationConflictLogReservationChangeJournalEntryReservationRealtimeProjectionEventReservationCollaborationCommitPlanReservationWorkspaceCoordinationPolicyReservationCollaborationSnapshotCommitReservationCollaborationHandlerOpenReservationCollaborationWorkspaceHandlerListReservationCollaborationJournalHandlerReservationCollaborationCommitStoreReservationCollaborationJournalReaderSupabaseReservationCollaborationSnapshotReaderSupabaseReservationCollaborationCommitStoreSupabaseReservationCollaborationJournalReaderPOST /reservation-collaboration-workspaces/{reservationId}POST /reservation-collaboration-journal/{reservationId}POST /reservation-collaboration-commits/{reservationId}sanmopia_commit_reservation_collaborationtransfer_reservation_branchbranch_handoff.branch_profile_idservice_schedule.start_onservice_schedule.end_onservice_schedule.extra_service_daysservice_term.codepayment_fact.customer_share_deposit_paid_atpayment_fact.customer_share_balance_paid_atpayment_fact.service_balance_paid_ataddress.service_addressesmemo.operator_notecaregiver_assignment.rostercoordinationPolicy.realtimeChannelModecoordinationPolicy.dirtyNavigationPolicy
Remaining work:
- Bind every branch/HQ reservation mutation path to
CommitReservationCollaborationHandlerbefore the owning persistence command writes a legacy save field. Branch handoff, booking service date fields, service term, payment date facts, service-address replacement, memo, extra service-day child rows, and caregiver assignment roster rows are now concrete mutation paths applied by the Supabase collaboration RPC. Service date changes updateservice_start_atandservice_end_atwith timestamptz parsing and range validation inside the expected-revision commit. Extra service days replacesanmopia_reservation_extra_service_daysrows only after expected-revision commit succeeds, and the same transaction rolls back the journal if child-row validation fails. Caregiver assignment roster replacessanmopia_reservation_caregiver_assignmentsrows bycontract_assignment_key, preserves primary/assistant roles, grade, planned service days, term dates, and assigned Supabase user ids, then updates the booking primaryassigned_caregiver_user_idfor current access compatibility. Service term writes updateservice_term_code; payment date facts update or create purpose-keyedsanmopia_payment_records; address replacement rewritessanmopia_reservation_service_addresses; memo writes updateoperator_memo. Commit-time RPC revision races now raiseReservationCollaborationPersistenceConflict; the application handler re-reads the latest snapshot and returns the normal stale-save409conflict response. Remaining conflict-log hardening: persist a durableReservationCollaborationConflictLogrow or equivalent workspace event metadata that links the returned409, Broadcast invalidation, attempted mutation kinds, idempotency key, and operator resolution. - Stop confirmation/resume must become explicit mutation kinds, not free status
edits. Legacy source infers stop intervals from status
13entries inRESERVATION_CHANGE_LOG; modern collaboration commits should writeServiceSuspensionrequest/approval/resume facts and let downstream reports, assignment, pricing, refund, and coupon workflows consumeServiceSuspensionImpactPlan. - Frontend Nanostores collaboration foundation is implemented in
frontend-repo/src/features/reservation-collaboration-workspace/. It opensPOST /reservation-collaboration-workspaces/{reservationId}before edit, commits throughPOST /reservation-collaboration-commits/{reservationId}, handles top-level409conflict payloads withexpectedRevision,currentRevision,conflictingFields, andrefreshRequired, subscribes to the returned privaterealtimeTopic, publishes Presence, and lists operator history throughPOST /reservation-collaboration-journal/{reservationId}. It now protects browser back, pagehide, beforeunload, link navigation, explicit reload/return, dirty discard, and stale save states, and recordsdraft_marked_dirty,navigation_blocked,draft_discarded, andcommit_conflictedthroughPOST /reservation-collaboration-workspace-events/{reservationId}. Remaining frontend work is generated client import replacement for the temporary local contract types, branch-manager screen wiring, and a two-workspace stale-save smoke test against the running Supabase Realtime stack. - Contract repo now exposes OpenAPI paths, JSON Schema, and TypeScript client types for collaboration workspace, journal, commit, conflict, realtime event, coordination policy, and route/header helpers. Frontend must import those contracts instead of handwritten fetch shapes.
- Backfill strategy from existing reservation rows into initial revision and journal seeds.
- Stage smoke: open one reservation in two workspaces, commit one branch/HQ edit, prove the second workspace receives Broadcast invalidation, then reject the stale save with a conflict diff.
2026-07-03 Branch Manager Control Delta
Section titled “2026-07-03 Branch Manager Control Delta”-
Assignment history panel and decision evidence. Status:
Pending. Evidence:source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:180-208,source-refs/sanmopia-admin/application/models/Reservation_model.php:57-113, andsource-refs/sanmopia-admin/application/models/Reservation_model.php:2165-2303. Source behavior: branch/HQ screens can fetch old/new manager names from manager-change logs, while assignment changes still mutate reservation rows and secondary caregiver slots. Modern gap:CaregiverAssignmentDecisionshould be exposed beside collaboration journal with decision source, candidate evidence id, old/new caregiver, role/slot, replacement reason, and assignment conflict result. -
Narrow reservation corrections outside full-form save. Status:
Pending. Evidence:source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:212-221,source-refs/sanmopia-admin/application/controllers/ajax/Reservation.php:68-178, andsource-refs/sanmopia-admin/application/models/Reservation_model.php:606-643. Source behavior: baby due, payment-change status/list edits, and service-balance corrections are narrow ajax routes outside the main detail form. Modern gap: every narrow branch-manager correction needs its ownReservationCollaborationCommandmutation kind, expected revision, idempotency key, field diff, and return-state token. -
Branch manager history/read models. Status:
Pending. Evidence:source-refs/sanmopia-admin/application/models/Reservation_model.php:985-1043,source-refs/sanmopia-admin/application/controllers/Reservation.php:198-205, andsource-refs/sanmopia-admin/application/controllers/Calculate.php:460-498. Source behavior: list/history screens derive branch filters, reservation status groupings, Danbee completion flags, and settlement filters from current query state. Modern gap: branch manager control needs stable read models for reservation work queue, assignment history, payment-change board, settlement status, and return-state tokens so operator screens do not reconstruct work context from GET filters and source status ids.