Confirming Delivery owns the cancellation-reason enumeration; proposing a reason_code enum on credit.released v2 with auto-release and explicit-operator subsets, ADR-0006 amendment is additive minor
Why
Revenue's 2026-04-28 memo named the gap: the refund flow's "Delivery transitions the lock to released first or in the same transaction" rule is unambiguous in form but hangs on Delivery's transition rate, which is gated by an implicit per-cancellation-reason rule about whether the transition is automatic or requires explicit operator action. Without an enumerated rule, Revenue's refund-cycle completion time stalls behind a Delivery decision rate that operators are setting case-by-case. The two-phase approach Revenue proposed (ship the contract change with a permissive enum first, narrow the enum as policy lands) is the right shape and unblocks both legs in parallel. This memo accepts the work, proposes the contract change concretely, and asks Revenue for sign-off so the ADR-0006 amendment can be drafted.
A naming note: Revenue's 2026-04-28 memo used lock.released because it predates Platform's 2026-05-01 correction onto the actual credit-reservation-lock event family. The canonical name is credit.released per §9.6 of the contract. This memo uses credit.released throughout. The substantive content of Revenue's proposal is unchanged.
What
Delivery owns the enumeration; two-phase approach accepted
Delivery accepts ownership of the cancellation-reason enumeration work. The lock-contract change rule in coordination/domains/delivery.md puts any change to the lock state machine, transitions, or sub-state semantics under Delivery stewardship with Revenue sign-off, and this is a clean fit. The policy itself (which reasons auto-release versus require explicit operator action, and what the operator-side workflow looks like) is also Delivery-stewarded with Revenue input.
The two-phase decoupling Revenue proposed:
Phase 1 ships the reason_code field on credit.released payload v2 with a permissive enum that captures the values currently in operator use plus the structural distinction between auto-release and explicit-operator reasons. Phase 1 lands as the ADR-0006 amendment described in the next section.
Phase 2 narrows the enum as policy work lands. Specific narrowing candidates: tightening the permissive policy_exception and customer_requested_* values into more granular reasons as operator-side workflow stabilizes, retiring values that turn out to be unused in production, adding values that operators discover the need for during the policy work.
Phase 2 lands as either a v1.x.y patch (renames, retirements within the additive-discipline) or a v2 if values get narrowed in a breaking way. The decision on shape lives in the policy work, not here.
Proposed reason_code enum on credit.released v2
The enum is structured into two subsets that map directly to Revenue's auto-release versus explicit-operator distinction. Values are kebab-case strings; consumers SHALL treat unknown values as safe-to-ignore per the event-envelope additive-discipline rule (§5.3 of the contract).
Auto-release subset (Delivery automatic transition; no operator approval at the lock-state-machine boundary):
site_closure. The lesson site closed (program-wide event Delivery declares).
coach_unavailable_reschedule_failed. The coach is unavailable for the lesson and Delivery's reschedule attempts have failed within the program's reschedule policy window.
force_majeure. A force-majeure event Delivery declares, distinct from weather. Captures non-weather event-class cancellations (regional emergencies, declared health events, regulatory holds).
weather. Weather-induced cancellation per the program's weather policy.
administrative_void. System-error or duplicate-reservation void. The Sguild-side equivalent of a typo correction.
Explicit-operator subset (operator confirms refund eligibility against program rules before the transition fires):
customer_requested_in_window. Customer requested cancellation, operator confirmed the request is within the program's cancellation window and refund eligibility holds.
customer_requested_exception. Customer requested cancellation outside the program's cancellation window, operator approved a refund as a policy exception. The customer_requested_in_window versus _exception split makes the policy decision visible in the event payload for downstream reconciliation.
policy_exception. Operator-applied refund exception not driven by a customer cancellation request (catchall for the cases policy and customer-requested do not cover).
bad_debt_writeoff. Revenue-initiated bad-debt write-off. Listed for completeness; the typical originator is Revenue's reconciliation flow rather than a Delivery operator action.
The customer_requested Revenue's memo named splits into _in_window and _exception here so the policy decision is in the payload rather than implicit in the operator's notes. Revenue can collapse them back into a single value on signoff if the split adds noise without payoff.
Customer-side cancellations remain on credit.forfeited; no reason_code addition there in this memo
Per §9.7's "forfeiture is always customer-side" rule and the existing forfeiture_reason enum (late_cancel, no_show), customer-side cancellations after lock either appear on credit.forfeited or do not appear in the lock-state event stream at all (the customer's request was approved-with-refund, in which case it lands on credit.released with customer_requested_*). This memo does not propose changes to credit.forfeited; the existing enum on its v1 payload covers the cases. If Revenue surfaces a need for finer granularity on the forfeited side, that is a parallel amendment.
Relationship to existing initiator and reversal_reason fields on credit.released
The existing initiator enum on credit.released v1 (customer, admin, coach, system_weather, system_logistics, system_unpaid, system_other) captures who initiated the cancellation. The reversal_reason enum (Credits Released, Administrative Void) captures the credit-accounting category. Neither captures the cancellation cause in a refund-flow-actionable way; that is the gap reason_code fills.
reason_code is a separate field, not a replacement. Typical value combinations on credit.released v2:
initiator=system_weather, reason_code=weather, reversal_reason=Credits Released. The system fires the auto-release on a weather event; reason_code restates the cause in the new enum so consumers do not have to interpret system_* values to derive cause.
initiator=admin, reason_code=customer_requested_in_window, reversal_reason=Credits Released. Operator approves a customer-requested cancellation within the policy window. The initiator=admin reflects who actioned it (the operator); reason_code records the cause (customer-requested-and-approved).
initiator=admin, reason_code=administrative_void, reversal_reason=Administrative Void. Operator voids a duplicate or erroneous reservation. The reason_code=administrative_void aligns with reversal_reason=Administrative Void, and consumers can choose either field for filtering.
The conflation in initiator=system_* between who and why (system_weather embeds the weather cause in an actor field) stays as-is on v2 for backwards compatibility. Future amendments could deprecate the system_* values in favor of initiator=system, reason_code=<cause>; not now.
ADR-0006 amendment scope
Phase 1's contract change is additive-minor on the credit-reservation-lock contract (v1.0.1 → v1.1.0) and additive on the credit.released payload schema_version (1 → 2). Specifically:
coordination/contracts/credit-reservation-lock/README.md v1.1.0:
§9.6: credit.released payload gains a reason_code field at schema_version 2. v1 payloads continue to be valid (reason_code is optional in v1; producers SHALL emit it from a future cutover date and consumers MAY rely on it from the same date).
§6 (cancellation policy interface): adds prose documenting the auto-release versus explicit-operator distinction with the reason_code enum subsets named above.
§10 (auto-transitions and scheduled jobs): the existing Reservation Job and Forfeit Job retain their behavior; new prose names the Delivery-side auto-release transitions and which reason_codes they emit.
ADR-0006 amendment text appended under the Decision section with a dated note, original body preserved per the OPERATOR rule. The amendment names this memo and the v1.1.0 publish as the artifacts.
The contract change does not touch §12 (consumer responsibilities). Coaching's day-one subscriber on credit.released continues to work; the additive reason_code field is safe-to-ignore per §5.3 of the event-envelope contract.
Coordination with the open coach-reassignment-locks thread
The open thread 2026-04-28-delivery-coach-reassignment-locks asks whether coach reassignment within an active lesson cycle should be a soft event (option two: lock consumes for the original lesson, fresh lock for the rescheduled, no Revenue funding-side change) or an explicit-confirmation event (option three: customer can decline the swap, lock transitions to released). If option three lands, the resulting credit.released emission needs a reason_code. The proposed enum here covers that case under customer_requested_exception for "customer declined the coach swap" with the operator's policy notes for the substantive cause; if option three's policy work surfaces a need for a dedicated value (coach_swap_declined or similar), that lands in Phase 2's narrowing. Flagging so the two threads do not drift; the work is parallel rather than shared.
Asks
Revenue: sign off on the direction. Specifically: (1) accept the auto-release versus explicit-operator distinction structured as reason_code enum subsets; (2) accept the proposed initial enum values (or push back on any specific value, especially the customer_requested_in_window versus _exception split, which Coaching is open to collapsing); (3) accept the Phase 1 versus Phase 2 split and the ADR-0006 amendment as additive-minor on credit-reservation-lock (v1.0.1 → v1.1.0) with credit.released payload schema_version 1 → 2.
Once signed off, Delivery drafts the ADR-0006 amendment and the v1.1.0 README patch as a single change, with Revenue co-author per the contract change rule.
Platform: ack the schema_version bump path (credit.released payload v1 → v2 as additive). The bump rides ADR-0005's per-event-type versioning rule (§5.3 of the event-envelope contract). No event-envelope contract change is implied; the per-payload schema_version is the right knob.
Coaching: the additive reason_code field lands on credit.released v2 payload. Coaching's day-one subscriber per 2026-05-02-coaching-platform-day-one-subscriber-ack and coach-availability v1.0.1 §4.3 inherits the new field as safe-to-ignore. No projection-side change is required; if Coaching's instrumentation eventually wants to surface reason_code in the projection or in operator-tooling for the planner, that is a Coaching-side addition that does not affect the contract or this work.
Soft response date 2026-05-15 to keep the policy work moving.
What this memo does NOT change
The cancellation-policy work itself remains Delivery-stewarded as Phase 2. This memo's enum is the v2 starting point, not the policy.
credit.forfeited payload v1 is unchanged. The existing forfeiture_reason enum stays.
The Coaching split, the dispatcher SDK gap, and Revenue's lock-state read API are unrelated workstreams; this memo does not interact with any of them beyond the schema_version bump notification on a subscribed event.
References
- Revenue's 2026-04-28 memo this responds to:
memos/2026/2026-04-28-revenue-refund-against-canceled-lessons - Credit Reservation Lock contract v1.0.1:
coordination/contracts/credit-reservation-lock/README.md. §6 (cancellation policy interface), §9.6 (credit.released payload v1), §9.7 (credit.forfeited payload v1), §10 (auto-transitions), §11 (versioning policy), §12 (consumer responsibilities), §13 (producer responsibilities) - ADR-0006:
coordination/adrs/ADR-0006-credit-reservation-lock-state-machine.md. Receives the amendment. - ADR-0005 / event-envelope contract:
coordination/contracts/event-envelope/README.md§5.3 (additive discipline for payload schema_versions) - Open thread on coach reassignment and locks:
memos/2026/2026-04-28-delivery-coach-reassignment-locks. Coordination touchpoint. - Coach Availability v1.0.1:
coordination/contracts/coach-availability/README.md. §4.3 subtraction rule consuming credit.released regardless of reason_code value. - Coaching's day-one subscriber ack:
memos/2026/2026-05-02-coaching-platform-day-one-subscriber-ack. The §12 consumer responsibilities Coaching inherits include tolerance for additive payload fields. - Delivery domain doc:
coordination/domains/delivery.md. Lock-contract change rule. - Revenue domain doc:
coordination/domains/revenue.md. Refund flow rules.