Skip to content

Reservation Lifecycle Side Effect Execution

Reservation Lifecycle Side Effect Execution

Section titled “Reservation Lifecycle Side Effect Execution”

ReservationLifecycleSideEffectExecution is the orchestration boundary for running side effects derived from an accepted reservation lifecycle transition. It consumes ReservationLifecycleSideEffectPlan work items, enriches them with execution metadata, persists them first, then dispatches idempotent work through the neutral workflow engine port. Restate is the current workflow engine adapter. Supabase remains the business ledger, read model, and repair source of record.

Provider names, source-era table names, and source status labels must stay in sourceEvidenceRef, evidence payloads, trace labels, or migration docs. They must not name target components, target features, command kinds, workflow kinds, or generated client fields.

ReservationLifecycleSideEffectPlan belongs to reservation_operations and persists intent only. Its current work item fields are:

  • kind
  • target_context
  • dispatch_mode
  • idempotency_key
  • required_revision
  • reason_code

The plan answers which durable work is required after a lifecycle transition. It does not call payment, notification, settlement, benefit, audit, or workflow providers. Execution must keep that split.

Each plan work item becomes one immutable execution record before dispatch. Required execution fields:

  • executionId: stable id for one plan item execution.
  • reservationLifecycleSideEffectPlanId: accepted plan snapshot id.
  • workItemKind: copied from the plan item kind.
  • targetContext: copied from the plan item target_context.
  • targetFeature: provider-neutral owning feature, such as reservation_operations.reservation_audit_entry, customer_engagement.communication_dispatch_request, pricing_settlement.payment_refund_closeout, pricing_settlement.payment_refund_completion_barrier, pricing_settlement.reservation_promotion_side_effect, pricing_settlement.branch_settlement_opening, or pricing_settlement.branch_settlement_reversal, or pricing_settlement.financial_lifecycle_closeout.
  • commandKind: provider-neutral command to run inside the owning feature, such as append_lifecycle_audit, enqueue_lifecycle_notification, request_payment_refund_closeout, record_refund_closeout_completion, plan_reservation_promotion_side_effect, open_branch_settlement, request_branch_settlement_reversal, or close_financial_lifecycle.
  • sourceEvidenceRef: source-only reference list proving why the work item exists.
  • idempotencyKey: copied from the plan item and reused for target command replay checks.
  • requiredRevision: copied from the plan item and checked before execution.
  • commandPayload: frozen provider-neutral command input. Retry and repair re-use this payload; they do not rebuild payment, settlement, benefit, or notification input from current mutable rows.
  • dependsOnWorkItemKinds: explicit ordering dependency between side effects. Refund completion barriers now gate benefit release and branch settlement reversal.
  • providerEventOrderingStatus: event-ordering checkpoint for provider-backed commands, such as awaiting_provider_command, awaiting_provider_event, provider_event_matched, provider_event_out_of_order, provider_event_conflict, or provider_event_timeout.
  • providerEventRefs: provider event ledger refs matched to this execution.
  • targetResultRefs: target feature result refs emitted after command handling.
  • preconditions: persisted checks required before dispatch.
  • executionStatus: public execution state.
  • dispatchMode: ledger, outbox, or workflow.
  • workflowRequestId: workflow request id when Restate dispatch is used.
  • attemptCount, maxAttempts, lastAttemptAt, nextRetryAt.
  • deadLetterReason: terminal failure reason when automatic retry stops.
  • lastErrorCode, lastErrorMessage: latest retry or repair diagnostic.
  • repairCommandKind: backend repair command available to operators.
  • compensation: explicit compensation policy for partial or successful side effects that later need rollback or correction.

Execution rows must be service-role-only Supabase rows. Portal users read filtered backend projections, not execution tables.

refund_paid_payments expands into two execution records: the refund closeout workflow and a refund completion barrier. The closeout starts in awaiting_provider_command; the barrier depends on the closeout and starts in awaiting_provider_event. Benefit release and branch settlement reversal depend on the barrier. The barrier requires a terminal provider refund event or a zero-refund fact before benefit release, coupon restore, settlement reversal, or customer-facing receipt state can be marked complete. This prevents source-era races where KCP/Kill Bill refund outcomes, coupon restoration, and settlement rows were mutated from separate controller branches.

Use one status vocabulary across ledger, outbox, and workflow-backed side effects:

StatusMeaning
acceptedExecution row persisted from a plan item, not dispatched yet.
blocked_preconditionRequired reservation, policy, actor, or target fact is missing.
dispatch_pendingPreconditions passed and dispatch is waiting.
runningWorker or workflow invocation accepted the execution snapshot.
succeededTarget feature command completed idempotently.
retry_pendingRetryable failure recorded with next attempt time.
dead_letteredAutomatic retry limit reached or payload is not executable.
repair_requiredOperator or target-feature repair command must resolve it.
compensation_pendingSuccessful or partial side effect needs compensating command.
compensatedCompensation command completed and original evidence remains linked.
cancelledBackend repair command intentionally stopped future dispatch.

executionStatus is business-visible progress. Internal Restate invocation status, queue visibility timeout, provider result code, and source status id are evidence only. The backend can map those internals into this status, but UI and contracts must not expose them as control flow.

Persist preconditions as named checks with pass/fail status, observed revision, and evidence. Required checks:

  • Reservation transition was accepted and plan snapshot exists.
  • Current reservation revision still satisfies requiredRevision.
  • Actor capability snapshot still allows the originating operation.
  • Target feature has required facts, such as refund ledger, benefit reservation, settlement participant, communication route, or financial lifecycle request.
  • idempotencyKey has no conflicting successful execution.
  • Target command can replay same snapshot without recomputing from current rows.

Failed preconditions set blocked_precondition; they do not silently skip work. Manual repair can refresh facts, suppress work with reason, or requeue the same execution snapshot.

Retry uses the persisted execution snapshot. It must not rebuild command input from current reservation, payment, settlement, benefit, or communication rows.

  • Retryable failures set retry_pending, increment attemptCount, and store lastErrorCode, lastErrorMessage, and nextRetryAt.
  • Non-retryable failures or exhausted attempts set dead_lettered with deadLetterReason.
  • Repair commands must be backend commands with actor, reason, expected execution revision, and idempotency key.
  • Repair actions may retry, suppress, mark_external_result, require_compensation, or cancel.
  • Every repair writes an append-only audit event and keeps the original sourceEvidenceRef.

Dead-letter handling belongs to the execution ledger, not to browser state or provider callbacks.

Each target feature declares its own compensation policy before execution:

  • Audit entries default to none; corrections append new audit facts.
  • Notifications use suppress or corrected follow-up dispatch, not deletion.
  • Payment/refund closeout uses target-feature reversal or manual ledger repair.
  • Benefit release uses target-feature restore when policy allows it.
  • Settlement and financial lifecycle work uses repair or compensating lifecycle command, never direct table mutation.

Compensation commands must link to original executionId, target command result, actor, reason, and evidence refs. Compensation never erases completed attempt rows.

Persistence order:

  1. Persist accepted reservation operation plan and lifecycle side-effect plan.
  2. Insert one ReservationLifecycleSideEffectExecution row per work item.
  3. Commit execution rows before any workflow, queue, provider, or target-feature dispatch.
  4. Dispatch through Restate or a queue using the persisted executionId and idempotencyKey.
  5. Handler re-reads the Supabase execution snapshot and applies the owning feature command.
  6. Handler writes attempt, result, retry, dead-letter, repair, or compensation events back to Supabase.
  7. Backend projections expose public status through WorkflowStatusProjection or a reservation-operation read model.

Restate owns replay and handler progress. Supabase owns durable facts, operator repair, read-model filtering, and RLS boundaries.

Workflow dispatch must use the neutral WorkflowRequest, WorkflowInvocation, and WorkflowInvocationPort contract. Target feature commands receive a provider-neutral snapshot:

  • executionId
  • reservationLifecycleSideEffectPlanId
  • reservationId
  • targetFeature
  • commandKind
  • commandPayload
  • dependsOnWorkItemKinds
  • idempotencyKey
  • requiredRevision
  • sourceEvidenceRef
  • providerEventOrderingStatus
  • providerEventRefs
  • targetResultRefs
  • precondition results

The Restate handler may call target feature application ports, but must not call source adapters or provider-specific clients directly unless the owning feature has already selected that provider behind a port. Restate workflow names and invocation ids are operational evidence only.

Primary evidence anchors:

  • source-refs/sanmopia-admin/application/models/Reservation_model.php:1769-1916
  • source-refs/sanmopia-admin/application/models/Reservation_model.php:1980-2140
  • source-refs/sanmopia_web/application/controllers/api/Reservation.php:115-239
  • source-refs/sanmopia_web/application/models/Payment_model.php:101-441

Provider/source names found in those files may appear in evidence refs and evidence payloads only. Target vocabulary stays bounded-context and provider-neutral.