@sguild/dispatcher 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +4 -1
  2. package/contracts/README.md +30 -0
  3. package/contracts/coach-availability/README.md +355 -0
  4. package/contracts/coach-availability/README.v2.md +263 -0
  5. package/contracts/coach-availability/schema/payloads/coach.assigned-v1.json +91 -0
  6. package/contracts/coach-availability/validation/delivery-assignment.md +89 -0
  7. package/contracts/coach-availability/validation/sales-offer-construction.md +76 -0
  8. package/contracts/coaching-confirmation/README.md +96 -0
  9. package/contracts/coaching-confirmation/schema/payloads/coaching.lesson.confirmation_decided-v1.json +142 -0
  10. package/contracts/coaching-confirmation/schema/payloads/lead.coach.confirmation.requested-v1.json +124 -0
  11. package/contracts/credit-reservation-funding-state/README.md +147 -0
  12. package/contracts/credit-reservation-lock/README.md +433 -0
  13. package/contracts/credit-reservation-lock/delivery-state-vocabulary.md +73 -0
  14. package/contracts/credit-reservation-lock/reservation-create-api.md +191 -0
  15. package/contracts/credit-reservation-lock/reservation-release-api.md +171 -0
  16. package/contracts/credit-reservation-lock/schema/payloads/credit.locked-v1.json +1 -1
  17. package/contracts/credit-reservation-lock/schema/payloads/credit.reserved-v1.json +2 -3
  18. package/contracts/credit-reservation-lock/validation/lesson-lifecycle.md +318 -0
  19. package/contracts/event-envelope/README.md +205 -0
  20. package/contracts/event-envelope/schema/envelope-v1.json +2 -2
  21. package/contracts/event-envelope/validation/event-vocabulary.md +270 -0
  22. package/contracts/event-types-registry.json +337 -24
  23. package/contracts/external-actions/README.md +338 -0
  24. package/contracts/finance-mart/README.md +238 -0
  25. package/contracts/finance-mart/cac-payback.md +113 -0
  26. package/contracts/finance-mart/cash-position.md +98 -0
  27. package/contracts/finance-mart/cohort-summary.md +72 -0
  28. package/contracts/finance-mart/customer-journey-audit.md +92 -0
  29. package/contracts/finance-mart/ltv.md +92 -0
  30. package/contracts/finance-mart/margin.md +87 -0
  31. package/contracts/finance-mart/pnl.md +83 -0
  32. package/contracts/finance-mart/reconciliation.md +98 -0
  33. package/contracts/finance-mart/revenue-recognition-rollup.md +87 -0
  34. package/contracts/finance-mart/unit-economics.md +94 -0
  35. package/contracts/growth-warehouse-api/README.md +162 -0
  36. package/contracts/identity/README.md +184 -0
  37. package/contracts/identity/person-canonical-fields.md +120 -0
  38. package/contracts/identity/person-externals.md +267 -0
  39. package/contracts/identity/person-resolution-semantics.md +144 -0
  40. package/contracts/identity/person-role-taxonomy.md +120 -0
  41. package/contracts/identity/schema/payloads/intake.captured-v2.json +60 -0
  42. package/contracts/identity/schema/payloads/intake.matched-v2.json +123 -0
  43. package/contracts/identity/schema/payloads/person.updated-v1.json +8 -2
  44. package/contracts/identity/schema/payloads/role.assigned-v1.json +50 -0
  45. package/contracts/identity/schema/payloads/role.retired-v1.json +54 -0
  46. package/contracts/identity/validation/client-table.md +131 -0
  47. package/contracts/identity/validation/coach-handling.md +100 -0
  48. package/contracts/identity/validation/person-graph.md +140 -0
  49. package/contracts/lead-lifecycle/README.md +187 -0
  50. package/contracts/lead-lifecycle/schema/payloads/lead.handoff.context.recorded-v1.json +108 -0
  51. package/contracts/lead-lifecycle/schema/payloads/lead.qualified-v1.json +54 -0
  52. package/contracts/lead-lifecycle/schema/payloads/sales.lead.onboarded-v1.json +120 -0
  53. package/contracts/lesson-lifecycle/README.md +118 -0
  54. package/contracts/lesson-lifecycle/schema/payloads/lesson.cancelled-v1.json +30 -0
  55. package/contracts/lesson-lifecycle/schema/payloads/lesson.delivered-v1.json +29 -0
  56. package/contracts/lesson-lifecycle/schema/payloads/lesson.rescheduled-v1.json +157 -0
  57. package/contracts/lesson-lifecycle/schema/payloads/lesson.scheduled-v1.json +107 -0
  58. package/contracts/lesson-lifecycle/validation/README.md +5 -0
  59. package/contracts/mart-consumer-api/README.md +108 -0
  60. package/contracts/order-flow/README.md +106 -0
  61. package/contracts/order-flow/schema/payloads/order.created-v1.json +58 -0
  62. package/contracts/order-flow/schema/payloads/order.updated-v1.json +63 -0
  63. package/contracts/payment-flow/README.md +157 -0
  64. package/contracts/platform-comms/README.md +84 -0
  65. package/contracts/platform-comms/schema/payloads/platform.comms.inbound-v1.json +83 -0
  66. package/contracts/platform-geography-snapshot/README.md +205 -0
  67. package/contracts/platform-geography-snapshot/schema/payloads/geography.market.archived-v1.json +36 -0
  68. package/contracts/platform-geography-snapshot/schema/payloads/geography.market.upserted-v1.json +59 -0
  69. package/contracts/platform-geography-snapshot/schema/payloads/geography.service-area.archived-v1.json +36 -0
  70. package/contracts/platform-geography-snapshot/schema/payloads/geography.service-area.upserted-v1.json +65 -0
  71. package/contracts/portfolio-mart/README.md +133 -0
  72. package/contracts/portfolio-mart/cohort-funnel-panel.md +76 -0
  73. package/contracts/portfolio-mart/cross-discipline-performance.md +91 -0
  74. package/contracts/portfolio-mart/cross-market-performance.md +84 -0
  75. package/contracts/portfolio-mart/health-composites.md +88 -0
  76. package/contracts/portfolio-mart/org-topology.md +70 -0
  77. package/contracts/portfolio-mart/portfolio-level-funnel-health.md +92 -0
  78. package/contracts/portfolio-mart/validation/consumer-isolation.md +33 -0
  79. package/contracts/portfolio-mart/validation/decoupling-discipline.md +34 -0
  80. package/contracts/refund-flow/README.md +136 -0
  81. package/contracts/refund-flow/sales-callable-refund-initiation-api.md +218 -0
  82. package/contracts/sales-scheduling-surface/README.md +532 -0
  83. package/contracts/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.cancelled-v1.json +42 -0
  84. package/contracts/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.created-v1.json +115 -0
  85. package/contracts/sales-scheduling-surface/validation/composite-hold-create.md +97 -0
  86. package/contracts/sales-scheduling-surface/validation/lock-state-machine-conformance.md +84 -0
  87. package/contracts/sales-scheduling-surface/validation/sales-close-orchestration.md +77 -0
  88. package/contracts/warehouse-silver/README.md +118 -0
  89. package/contracts/warehouse-silver/coaching-utilization-columns.md +105 -0
  90. package/dist/events.d.ts +63 -0
  91. package/dist/events.js +293 -0
  92. package/dist/index.d.ts +2 -0
  93. package/dist/index.js +7 -1
  94. package/dist/postgres-consumer.js +2 -1
  95. package/dist/validator.js +1 -0
  96. package/package.json +1 -1
@@ -0,0 +1,532 @@
1
+ # Sales Scheduling Surface Contract
2
+
3
+ **Status:** v1.6.0
4
+ **Date:** 2026-05-20
5
+ **Owner:** Delivery (scheduling and reservation-lock service)
6
+ **Consumers:** Sales (close orchestration, lesson hold creation, hold cancellation), Revenue (Workbench confirmed lesson cancellation caller and secondary subscriber on `delivery.lesson-hold.*` events for ledger reconciliation observability), Coaching (Coach Day Planner lesson rendering)
7
+ **Related ADRs:** ADR-0001 (tenant ID shape), ADR-0003 (Person canonical with role records, amended 2026-05-01 to relocate Coach to Coaching), ADR-0005 (event envelope), ADR-0006 (credit reservation lock state machine; this contract is a Sales-facing Delivery surface over linked Revenue reservations), ADR-0008 (Coaching as sixth domain; eligibility reads flow through Coaching's coach-availability contract before any call into this surface), ADR-0009 (dispatcher producer-transactional-guarantee for the `delivery.lesson-hold.*` events this surface produces)
8
+ **Sub-specs (authoritative):** n/a for surface endpoints; see `validation/composite-hold-create.md` for the atomic-multi-create batch semantics
9
+ **Validations:** `validation/sales-close-orchestration.md`, `validation/lock-state-machine-conformance.md`, `validation/composite-hold-create.md`
10
+
11
+ ## 1. Purpose and scope
12
+
13
+ Sales' close orchestration needs a Delivery-owned, transactional surface for turning a customer commitment into a scheduled lesson. Before this contract, Sales reached into Delivery's lesson surface through ad-hoc paths that varied by environment and bypassed the lock state machine in some flows. This contract specifies the canonical surface so every Sales-originated booking travels through the same path: eligibility-read against Coaching, reservation-request against Revenue, then a single transactional hold-create call into Delivery that schedules the lesson, assigns the coach, attaches the lesson site, and advances Delivery's lesson-hold state from `requested` to `held` in one transaction.
14
+
15
+ This contract is the Sales-facing surface for managing lesson holds, with the confirmed-cancellation command also callable by Revenue Workbench when a reservation-scoped refund must be composed through Delivery. It is read-and-write: Sales creates holds, inspects holds, and cancels holds. Revenue inspects holds for Workbench display and may call hold-cancel for confirmed lesson cancellations that must compose Delivery cancellation with Revenue refund initiation. The Revenue credit reservation lifecycle remains governed by ADR-0006, the credit-reservation-lock contract, and the refund-flow contract; this surface does not redefine Revenue lifecycle semantics, transitions, funding sub-states, or refund truth. The operations exposed here drive Delivery's lesson-hold state through the subset of transitions consumers are permitted to originate (`requested` to `held`, `held` to `released`, and `confirmed` to `released` when paired with Revenue's refund initiation path); transitions consumers do not originate (`held` to `confirmed`, `confirmed` to `consumed`) are out of scope and travel through their existing surfaces.
16
+
17
+ Out of scope: lesson rescheduling (a held lesson moving to a different coach, time, or site without releasing and re-reserving credit; deferred to a future minor per §9.1), lesson-day attendance and roll-forward semantics (Delivery-internal, not Sales-facing), payment and funding state except for the confirmed-cancel refund initiation handoff in §4.4.1 (Revenue owns; Sales reads funding state through Revenue's surfaces, not this one), Person identity and Guardian-aware comms-routing (Platform owns the Identity contract; Sales already calls those surfaces directly), pricing and program rules (out of Delivery's scope per the Delivery domain memo), pre-reservation eligibility reads (Coaching's coach-availability contract owns those; this surface assumes the eligibility check has already happened), Sales' lead pipeline state (Sales-owned per the Sales domain memo, untouched by this surface).
18
+
19
+ This document is the contract. Two validation files document Sales' close-orchestration call shape and the lock-state-machine conformance gate so the producer can verify the surface drives the state machine correctly under every path.
20
+
21
+ ## 2. Normative language
22
+
23
+ The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted per RFC 2119.
24
+
25
+ ## 3. Terminology
26
+
27
+ - **Lesson.** A scheduled instance of a service delivered to a Participant by a Coach at a Lesson Site within a time window. Identified by a `les_` prefix per the entity-id convention. Owned by Delivery.
28
+ - **Lesson Hold.** The state of a Lesson while its associated reservation lock is in `held` or `confirmed` and the Lesson has not yet been attended or cancelled. The Lesson row and the lock are linked by `lesson.reservation_lock_id`. A Lesson Hold is not a separate entity; it is a Lesson observed in the held-or-confirmed lock state.
29
+ - **Reservation Lock.** The Delivery-owned record that runs the lesson-hold state machine linked to Revenue's credit reservation lifecycle. Identified by an `lck_` prefix. Five Delivery states: `requested`, `held`, `confirmed`, `consumed`, `released`. Funding sub-states on `held` and `confirmed` mirror Revenue's ledger reservation.
30
+ - **Reservation.** Revenue's `crr_` row that pairs with the Delivery lock. Created when Sales submits a reservation request through Revenue's credit-reservation-lock surface. The Lesson Hold is created against an existing Revenue Reservation; this contract does not create Revenue Reservations.
31
+ - **Eligibility.** The boolean predicate "may this Coach take this lesson," computed by Coaching against capacity, service area, certification, and conflict-window inputs. Sales reads eligibility through Coaching's `coach-availability` contract before calling this surface. The Coach passed to hold-create MUST be eligible at the time of the call; this surface re-checks eligibility at write time and rejects the create with HTTP 409 if eligibility has lapsed.
32
+ - **Lesson Site.** The physical or virtual site at which a Lesson is delivered. Identified by an `lsi_` prefix. Owned by Delivery.
33
+ - **Service Area.** A geographic boundary owned by Delivery. Lesson Sites belong to Service Areas; Coaches cover Service Areas through Coaching's coach-availability projection. The triple `(organization_id, service_area_id, time window)` scopes every read and write on this surface.
34
+ - **Idempotency Key.** A consumer-supplied opaque string accompanying every write request. The producer treats two writes with the same `(organization_id, idempotency_key)` pair as the same write and returns the original result, regardless of payload variance. Idempotency keys are scoped per organization.
35
+ - **Optimistic-concurrency token.** The `lock_version` field returned on every Lesson Hold read. Cancellation requests MUST echo the token; the producer rejects cancellations with HTTP 409 if the token has advanced.
36
+
37
+ ## 4. Surface
38
+
39
+ The contract defines four endpoints, the lock-state transitions each endpoint drives, the event surface this contract produces, and the consumer-side discipline for each operation.
40
+
41
+ ### 4.1 Hold create
42
+
43
+ `POST /delivery/v1/lesson-holds`
44
+
45
+ Headers:
46
+
47
+ - `Idempotency-Key` (required, string up to 128 chars). Scoped per organization. Two writes with the same key on the same organization return the same result.
48
+
49
+ Body:
50
+
51
+ ```
52
+ {
53
+ "organization_id": "org_...",
54
+ "reservation_id": "crr_...",
55
+ "participant_id": "par_...",
56
+ "person_id": "per_..." | null,
57
+ "coach_id": "coa_...",
58
+ "lesson_site_id": "lsi_...",
59
+ "service_area_id": "sva_...",
60
+ "window": { "start": "2026-05-09T17:00:00-10:00", "end": "2026-05-09T17:30:00-10:00" },
61
+ "lesson_type_id": "lty_...",
62
+ "notes": "optional free text up to 500 chars"
63
+ }
64
+ ```
65
+
66
+ The producer SHALL validate every field on receipt: `organization_id` and `reservation_id` MUST resolve to a live Revenue `crr_` in lifecycle state `reserved` for that organization, and Delivery MUST NOT already have advanced that reservation into a `held` or later lesson-hold state; `participant_id` MUST resolve to a Participant tied to the same Person referenced on the reservation; `coach_id` MUST be eligible per Coaching's projection at the read instant; `lesson_site_id` MUST be active and cover the Service Area named in the body; `window` MUST not overlap any other held or confirmed Lesson on the same Coach in the same organization; `lesson_type_id` MUST be active for the organization.
67
+
68
+ Response (HTTP 201) on success:
69
+
70
+ ```
71
+ {
72
+ "lesson_id": "les_...",
73
+ "reservation_lock_id": "lck_...",
74
+ "lock_state": "held",
75
+ "lock_version": 1,
76
+ "organization_id": "org_...",
77
+ "reservation_id": "crr_...",
78
+ "participant_id": "par_...",
79
+ "coach_id": "coa_...",
80
+ "lesson_site_id": "lsi_...",
81
+ "window": { "start": "...", "end": "..." },
82
+ "lesson_type_id": "lty_...",
83
+ "as_of": "2026-05-07T18:42:11.123Z"
84
+ }
85
+ ```
86
+
87
+ The transaction that creates the Lesson row MUST also advance Delivery's lesson-hold state from `requested` to `held` and emit `delivery.lesson-hold.created` per §4.5. The three writes (Lesson row, Delivery lock state, event) ride together inside a single `prisma.$transaction` per ADR-0009's producer-transactional-guarantee. A failure in any of the three rolls all three back; consumers do not see partial state.
88
+
89
+ Error responses:
90
+
91
+ - `400 Bad Request`: malformed payload, invalid identifiers, window in the past, window length exceeds the per-organization maximum.
92
+ - `404 Not Found`: `reservation_id` does not resolve, `participant_id` does not resolve, `coach_id` does not resolve, `lesson_site_id` does not resolve.
93
+ - `409 Conflict`: Revenue reservation is not in `reserved` lifecycle state, Delivery's lesson-hold state is not `requested`, coach no longer eligible at write time, slot taken by a concurrent write between the consumer's eligibility read and this call. The response body includes a `conflict_reason` enum and a `current_state` snapshot the consumer can use to refresh and retry.
94
+ - `422 Unprocessable Entity`: body well-formed but business rules failed (lesson site does not cover the service area, lesson type inactive for the organization, participant ineligible for the lesson type).
95
+ - `429 Too Many Requests`: per-organization rate limit exceeded; the consumer SHOULD retry with exponential backoff.
96
+
97
+ The endpoint is the only canonical path for advancing Delivery's lesson-hold state from `requested` to `held` for Sales-originated traffic in v1.0.0. Sales MUST NOT write directly into Delivery's storage layer or into Revenue's reservation lifecycle surface to drive this transition.
98
+
99
+ ### 4.2 Hold inspect
100
+
101
+ `GET /delivery/v1/lesson-holds/{lesson_id}`
102
+
103
+ Path parameter:
104
+
105
+ - `lesson_id` (required, string).
106
+
107
+ Query parameter:
108
+
109
+ - `organization_id` (required, string). The producer rejects reads without one with HTTP 400 and SHALL NOT return a Lesson scoped to a different organization than the one in the query.
110
+
111
+ Response (HTTP 200):
112
+
113
+ ```
114
+ {
115
+ "lesson_id": "les_...",
116
+ "reservation_lock_id": "lck_...",
117
+ "lock_state": "requested" | "held" | "confirmed" | "consumed" | "released",
118
+ "lock_version": 1..N,
119
+ "lock_funding_substate": "pending" | "partial" | "cleared" | null,
120
+ "organization_id": "org_...",
121
+ "reservation_id": "crr_...",
122
+ "participant_id": "par_...",
123
+ "coach_id": "coa_...",
124
+ "lesson_site_id": "lsi_...",
125
+ "service_area_id": "sva_...",
126
+ "window": { "start": "...", "end": "..." },
127
+ "lesson_type_id": "lty_...",
128
+ "created_at": "...",
129
+ "updated_at": "...",
130
+ "as_of": "..."
131
+ }
132
+ ```
133
+
134
+ `lock_funding_substate` is non-null only when `lock_state` is `held` or `confirmed`, mirroring Revenue's ledger reservation funding state per ADR-0006. Consumers MUST treat it as a read-only signal; this surface does not drive funding transitions.
135
+
136
+ `lock_version` is the optimistic-concurrency token. Cancellation requests in §4.4 MUST echo the token and the producer rejects cancellations with HTTP 409 if the token has advanced since the read.
137
+
138
+ ### 4.3 Hold list
139
+
140
+ `GET /delivery/v1/lesson-holds`
141
+
142
+ Query parameters:
143
+
144
+ - `organization_id` (required, string). Tenancy scope.
145
+ - `coach_id` (optional, string). If present, filter to a single coach.
146
+ - `participant_id` (optional, string). If present, filter to a single participant.
147
+ - `lock_state` (optional, repeated). Filter to one or more lock states; default is "all states except `released` and `consumed`." `lock_state=*` or `lock_state[]=*` is the v1.2 expanded calendar read.
148
+ - `window_start` (optional, ISO 8601). Lower bound on lesson window start; defaults to "now."
149
+ - `window_end` (optional, ISO 8601). Upper bound on lesson window start; maximum span 90 days from `window_start`.
150
+ - `cursor` (optional, opaque string). Pagination cursor returned by a previous response.
151
+ - `limit` (optional, integer, 1..200). Page size; default 50.
152
+
153
+ Response (HTTP 200):
154
+
155
+ ```
156
+ {
157
+ "as_of": "...",
158
+ "organization_id": "org_...",
159
+ "lesson_holds": [ /* same shape as §4.2 response, one entry per Lesson, with the v1.2 wildcard additions below */ ],
160
+ "next_cursor": "..." | null
161
+ }
162
+ ```
163
+
164
+ The endpoint is read-only. List responses are eventually-consistent; consumers MUST NOT treat list freshness as a substitute for a fresh §4.2 read before a write.
165
+
166
+ When a consumer passes `lock_state=*` or `lock_state[]=*`, the producer SHALL return:
167
+
168
+ - Lessons associated with reservation locks in any Delivery lock state: `requested`, `held`, `confirmed`, `consumed`, or `released`.
169
+ - Scheduled Lessons without a reservation lock, including legacy migration rows that predate this surface.
170
+
171
+ For a wildcard result item that has no reservation lock, the producer SHALL return `lock_state: "scheduled"`, `reservation_lock_id: null`, `reservation_id: null`, `lock_version: null`, and `lock_funding_substate: null`. All other Lesson fields retain the §4.2 names and meanings. The `scheduled` value is only valid on §4.3 wildcard list responses; §4.2 inspect remains lock-backed and does not return lockless Lessons.
172
+
173
+ For §4.2 and §4.3 responses, `person_id` is the canonical Platform Person identifier resolved from Delivery's Participant row when available. It is nullable for legacy or malformed rows where Delivery cannot resolve a Participant to a canonical Person. Consumers that need display names, contact routing, or other Person facts SHALL use `person_id` with Platform's identity surface rather than treating `participant_id` as identity.
174
+
175
+ Consumers that need Coach Day Planner or calendar-mode rendering SHOULD use wildcard mode with `coach_id` plus a bounded `window_start` and `window_end`. Existing list calls without wildcard keep the v1.0 default and do not return lockless Lessons.
176
+
177
+ ### 4.4 Hold cancel
178
+
179
+ `POST /delivery/v1/lesson-holds/{lesson_id}:cancel`
180
+
181
+ Headers:
182
+
183
+ - `Idempotency-Key` (required, string up to 128 chars). Same scoping rules as §4.1.
184
+ - `X-Sguild-Caller-Domain` (optional, enum `sales` or `revenue`). Defaults to `sales` for backwards compatibility. Revenue Workbench SHALL send `revenue` when it calls this command for a confirmed lesson cancellation.
185
+
186
+ Path parameter:
187
+
188
+ - `lesson_id` (required, string).
189
+
190
+ Body:
191
+
192
+ ```
193
+ {
194
+ "organization_id": "org_...",
195
+ "lock_version": 1..N,
196
+ "reason_code": "customer_cancellation" | "operator_correction" | "no_show" | "other",
197
+ "reason_notes": "optional free text up to 500 chars",
198
+ "refund": {
199
+ "person_id": "per_...",
200
+ "order_id": "ord_...",
201
+ "initiator": "admin",
202
+ "reason_code": "customer_requested_in_window",
203
+ "amount_cents": null,
204
+ "currency": "USD"
205
+ }
206
+ }
207
+ ```
208
+
209
+ `lock_version` MUST match the version returned on the most recent §4.2 read; the producer rejects writes with HTTP 409 when the token has advanced.
210
+
211
+ `reason_code` is a closed Delivery cancellation enum. New values MAY be appended in a minor version per §5.3; consumers MUST treat unknown values as safe-to-ignore for branching logic and surface them as opaque strings to operators.
212
+
213
+ `refund` is required when cancelling a `confirmed` Lesson through v1.1. It carries the Revenue refund-flow fields Delivery forwards to `refund-flow/sales-callable-refund-initiation-api.md`. `refund.person_id`, `refund.initiator`, and `refund.reason_code` are required. `refund.order_id` is optional and acts as a defensive match check. `refund.amount_cents` is optional; when present, `refund.currency` is required. For `requested` and `held` cancellations, `refund` is ignored if supplied and SHOULD be omitted.
214
+
215
+ Revenue Workbench MAY call hold-cancel for a confirmed Lesson when its reservation drawer has loaded the current Delivery hold inspect values (`reservation_lock_id`, `lock_state: "confirmed"`, and `lock_version`). Revenue's request follows the same body shape as Sales' confirmed-cancel path and MUST set `X-Sguild-Caller-Domain: revenue` so the resulting `delivery.lesson-hold.cancelled` event carries `originator: "revenue"`. This caller marker is scheduling-side provenance only; Revenue remains the source of truth for the refund and reservation-release facts.
216
+
217
+ Response (HTTP 200) on success:
218
+
219
+ ```
220
+ {
221
+ "lesson_id": "les_...",
222
+ "reservation_lock_id": "lck_...",
223
+ "lock_state": "released",
224
+ "lock_version": N+1,
225
+ "released_at": "...",
226
+ "reason_code": "...",
227
+ "refund": {
228
+ "refund_id": "ref_...",
229
+ "refund_state": "initiated",
230
+ "lock_release_state": "release_recorded",
231
+ "as_of": "..."
232
+ },
233
+ "as_of": "..."
234
+ }
235
+ ```
236
+
237
+ The cancellation transaction MUST advance the reservation lock from its current state (`requested`, `held`, or `confirmed`) to `released`, mark the Lesson as cancelled in Delivery's storage, and emit `delivery.lesson-hold.cancelled` per §4.5. The three Delivery writes ride together in one transaction per ADR-0009. For `confirmed` cancellations, the producer MUST first receive a successful Revenue refund-initiation response whose `lock_release_state` is `release_recorded` or another explicitly non-lock-tied success state. Delivery SHALL NOT locally release a `confirmed` Lesson if Revenue returns `lock_release_required`, `confirmed_state_requires_joint_release`, or any unverifiable branch.
238
+
239
+ Error responses:
240
+
241
+ - `400 Bad Request`: malformed payload, missing `lock_version`, unknown `reason_code` outside the closed set.
242
+ - `404 Not Found`: `lesson_id` does not resolve to a Lesson in the named organization.
243
+ - `409 Conflict`: `lock_version` mismatch, lock state is already terminal (`consumed` or `released`), lock state is `confirmed` without the required `refund` object.
244
+ - `422 Unprocessable Entity`: business rules failed (Lesson is past its window and cannot be cancelled retroactively through this surface; reason_notes exceeded length).
245
+ - `503 Service Unavailable`: Revenue refund initiation is unavailable, unverifiable, or returns a lock-release state Delivery cannot compose.
246
+
247
+ #### 4.4.1 Confirmed-to-released cancellations
248
+
249
+ Cancellation of a Lesson whose lock is in `confirmed` (funding has cleared, payment posted) requires Revenue's refund-flow contract because money has moved. In v1.1.0, Sales MAY call this surface to cancel a `confirmed` Lesson only when the request includes the `refund` object. Delivery forwards the refund fields to Revenue's Sales-callable refund initiation API before recording the local scheduling-side release. If Revenue cannot initiate or verify the refund, Delivery returns HTTP 503 and leaves the Lesson and Reservation Lock unchanged.
250
+
251
+ This path is jointly owned by Delivery and Revenue. Revenue remains the source of truth for Refund, provider writeback, `refund.initiated`, and `refund.completed`. Delivery remains the source of truth for Lesson cancellation, the Delivery Reservation Lock state, and `delivery.lesson-hold.cancelled`.
252
+
253
+ ### 4.5 Atomic multi-create
254
+
255
+ `POST /delivery/v1/lesson-holds/atomic-multi-create`
256
+
257
+ Creates multiple lesson holds in a single all-or-none transaction. This endpoint is the canonical surface for Sales' composite-offer close path, where a single operator action commits several back-to-back sessions for the same participant with the same coach.
258
+
259
+ Headers:
260
+
261
+ - `Idempotency-Key` (required, string up to 128 chars). One key per batch, scoped per organization. Two batch writes with the same key on the same organization return the original array response. No per-item idempotency keys at v1.3.0; per-item keys imply partial-replay semantics, which is the inverse of the all-or-none guarantee this endpoint provides.
262
+
263
+ Body:
264
+
265
+ ```
266
+ {
267
+ "organization_id": "org_...",
268
+ "coach_id": "coa_...",
269
+ "lesson_site_id": "lsi_...",
270
+ "service_area_id": "sva_...",
271
+ "participant_id": "par_...",
272
+ "originating_offer_id": "optional string, Sales' composite-offer correlation id",
273
+ "items": [
274
+ {
275
+ "reservation_id": "crr_...",
276
+ "window": { "start": "2026-05-09T17:00:00-10:00", "end": "2026-05-09T17:30:00-10:00" },
277
+ "lesson_type_id": "lty_...",
278
+ "offer_item_id": "optional string, per-item Sales correlation id",
279
+ "notes": "optional free text up to 500 chars"
280
+ }
281
+ // ... one or more items
282
+ ]
283
+ }
284
+ ```
285
+
286
+ Batch-share constraint: all items in the request MUST share the top-level `coach_id`, `lesson_site_id`, `service_area_id`, and `participant_id`. Mixed coaches, mixed sites, mixed service areas, and mixed participants are out of scope for v1.3.0; the producer rejects such a request with HTTP 422 and a `batch_constraint_violation` error.
287
+
288
+ Window constraints: item windows MUST be non-overlapping, sorted ascending by start, and each individually valid (end after start, not in the past). The producer does not require windows to be contiguous, but contiguity is the expected shape for composite-session offers. The producer validates the full set of windows before opening the transaction.
289
+
290
+ The producer SHALL re-check coach eligibility at write time against the composite window, defined as `[earliest item window start, latest item window end]`. If the selected coach is not eligible over the full composite window at the moment of the write, the producer returns HTTP 409 with `conflict_reason: coach_not_eligible_at_write_time`. Sales' canonical retry path is to re-enter close orchestration from a fresh eligibility read and fresh reservation requests; not to retry the same batch.
291
+
292
+ Transaction semantics: all item writes ride a single `prisma.$transaction`. On first validation or write failure for any item inside the transaction, all prior item writes in the batch are rolled back. No `delivery.lesson-hold.created` events emit for a batch that rolls back. The consumer sees either the full success array or a single error.
293
+
294
+ Response (HTTP 201) on success:
295
+
296
+ ```
297
+ {
298
+ "organization_id": "org_...",
299
+ "originating_offer_id": "...",
300
+ "items": [
301
+ {
302
+ "offer_item_id": "...",
303
+ "lesson_id": "les_...",
304
+ "reservation_lock_id": "lck_...",
305
+ "lock_state": "held",
306
+ "lock_version": 1,
307
+ "reservation_id": "crr_...",
308
+ "window": { "start": "...", "end": "..." },
309
+ "lesson_type_id": "lty_..."
310
+ }
311
+ // ... one entry per request item, in request order
312
+ ],
313
+ "as_of": "..."
314
+ }
315
+ ```
316
+
317
+ Response items are in request order. Consumers MAY correlate items by `offer_item_id` or by position; both are stable within a successful response.
318
+
319
+ Error responses:
320
+
321
+ - `400 Bad Request`: malformed payload, invalid identifiers, empty `items` array, any individual item window in the past or malformed.
322
+ - `404 Not Found`: any `reservation_id` in `items` does not resolve, `participant_id` does not resolve, `coach_id` does not resolve, `lesson_site_id` does not resolve.
323
+ - `409 Conflict`: any `reservation_id` not in Revenue lifecycle `reserved` state, coach not eligible at composite window write time, slot taken by a concurrent write on any item. Response body includes `conflict_reason` and `item_index` (zero-based) identifying the first item that failed when the failure is item-specific.
324
+ - `422 Unprocessable Entity`: batch-share constraint violated (mixed coaches, sites, service areas, or participants), item windows overlap, lesson site does not cover the service area, any lesson type inactive for the organization.
325
+ - `429 Too Many Requests`: per-organization rate limit exceeded; consumers SHOULD retry with exponential backoff.
326
+
327
+ ### 4.6 Event surface
328
+
329
+ The producer emits two events on the dispatcher per ADR-0009. Both events ride the standard event envelope per ADR-0005 and the event-envelope contract.
330
+
331
+ `delivery.lesson-hold.created` is emitted in the same transaction as a successful `POST /delivery/v1/lesson-holds` or for each item in a successful `POST /delivery/v1/lesson-holds/atomic-multi-create`. Payload:
332
+
333
+ ```
334
+ {
335
+ "lesson_id": "les_...",
336
+ "reservation_lock_id": "lck_...",
337
+ "reservation_id": "crr_...",
338
+ "organization_id": "org_...",
339
+ "participant_id": "par_...",
340
+ "coach_id": "coa_...",
341
+ "lesson_site_id": "lsi_...",
342
+ "service_area_id": "sva_...",
343
+ "lesson_zip": "75208",
344
+ "window": { "start": "...", "end": "..." },
345
+ "lesson_type_id": "lty_...",
346
+ "originator": "sales",
347
+ "idempotency_key": "...",
348
+ "originating_offer_id": "optional, present when emitted from atomic-multi-create",
349
+ "offer_item_id": "optional, per-item correlation id, present when emitted from atomic-multi-create"
350
+ }
351
+ ```
352
+
353
+ `delivery.lesson-hold.cancelled` is emitted in the same transaction as a successful cancellation. Payload:
354
+
355
+ ```
356
+ {
357
+ "lesson_id": "les_...",
358
+ "reservation_lock_id": "lck_...",
359
+ "organization_id": "org_...",
360
+ "reason_code": "...",
361
+ "originator": "sales",
362
+ "idempotency_key": "..."
363
+ }
364
+ ```
365
+
366
+ `lesson_zip` is optional and nullable. It carries the normalized ZIP from the Delivery lesson site when Delivery has one at hold-create time; it is routing context for consumers that compute travel buffers, not canonical identity. `originator` is `sales` for Sales-originated traffic, `revenue` for Revenue Workbench confirmed-cancellation traffic, and `delivery-internal` for Delivery's own internal flows such as operator corrections and no-show reconciliation. Consumers can filter on `originator` to scope projections to the caller that actually initiated the scheduling-side write.
367
+
368
+ The lock state transitions that fire as part of these writes also produce `credit.*` events on Revenue's credit-reservation-lock surface per the credit-reservation-lock contract §9. This contract does not duplicate or replace those producers; consumers that already subscribe to `credit.*` events MUST NOT subscribe to `delivery.lesson-hold.*` to learn the same facts. The split is: `delivery.lesson-hold.*` carries scheduling-side facts (coach, site, window, lesson type, originator); `credit.*` carries lock-state and funding facts. Subscribe to the events that match the consumer's read profile, not both.
369
+
370
+ ## 5. Versioning policy
371
+
372
+ ### 5.1 Semantic versioning
373
+
374
+ - **Patch** (v1.0.0 → v1.0.1): editorial clarifications, typo fixes, validation file additions. No surface change.
375
+ - **Minor** (v1.x.y → v1.x+1.0): additive changes. New optional request fields, new optional response fields, new endpoints, appended enum values (including new `reason_code` values), additional event types prefixed `delivery.lesson-hold.`. Consumers on older minors continue to work.
376
+ - **Major** (v1.x.y → v2.0.0): breaking changes. Removed endpoints, changed request or response shapes, narrowed enums, required parameters added, retired event types.
377
+
378
+ ### 5.2 Deprecation policy
379
+
380
+ On major-version publication, the previous major enters a two-week deprecation window per the org standard. Consumers running against deprecated versions after the window contribute to the contract-currency drift metric. The deprecation window is written in from v1.0.0 even though there is no v0.x to deprecate; the discipline applies forward.
381
+
382
+ ### 5.3 Additive discipline within a major
383
+
384
+ New request and response fields MUST default to null or a backwards-compatible value. New enum values for `lock_state`, `lock_funding_substate`, `reason_code`, and `originator` MUST be appended; consumers MUST treat unknown enum values as safe-to-ignore for branching logic and surface them as opaque strings to operators.
385
+
386
+ New optional query parameters or body fields MAY be added in a minor version. Required-parameter additions are major-version changes. New event types under the `delivery.lesson-hold.` namespace are minor changes; existing event payloads MUST NOT have fields removed or retyped without a major bump.
387
+
388
+ ## 6. Consumer responsibilities
389
+
390
+ ### 6.1 Tenancy scoping
391
+
392
+ Every read and write SHALL include `organization_id`. The producer rejects requests without one with HTTP 400. A response for `organization_id=A` SHALL NOT contain Lessons, lock state, or events scoped to `organization_id=B`, even if the calling consumer has access to multiple organizations on the producer side.
393
+
394
+ ### 6.2 Idempotency
395
+
396
+ Every write (§4.1, §4.4) MUST include an `Idempotency-Key` header. Keys are opaque strings up to 128 characters, scoped per organization. The producer treats two writes with the same `(organization_id, idempotency_key)` pair as the same write and returns the original result, regardless of payload variance. Consumers SHALL NOT reuse keys across writes that are intended to be distinct (a hold-create and a separate retry of that hold-create share a key; two unrelated hold-creates do not).
397
+
398
+ ### 6.3 Optimistic concurrency
399
+
400
+ Cancellation (§4.4) MUST echo the `lock_version` returned on the most recent §4.2 read. The producer rejects writes with HTTP 409 when the token has advanced. Consumers SHALL re-read the Lesson and decide whether to proceed (typically yes) or abort (typically no, since lock state may have moved through `confirmed` and require the refund-flow path per §4.4.1).
401
+
402
+ ### 6.4 Pre-write eligibility
403
+
404
+ Consumers MUST call Coaching's coach-availability contract eligibility surface (§4.2.2 of the coach-availability contract) before calling §4.1 of this surface. The producer re-checks eligibility at write time and rejects the create with HTTP 409 if eligibility has lapsed. The consumer-side read is for offer construction; the producer-side re-check is the gate.
405
+
406
+ ### 6.5 No direct storage access
407
+
408
+ Consumers SHALL NOT reach into Delivery's storage layer for Lesson rows, reservation_lock rows, or any related state. All access goes through the contract surface. Lesson reads needed for operator-tooling display SHALL go through this contract for scheduling-side fields, through Coaching's coach-availability contract for eligibility-side fields, and through Platform's identity contract for Person fields.
409
+
410
+ ### 6.6 Sales-specific (close orchestration)
411
+
412
+ Sales' close orchestration follows this canonical path: (1) operator selects coach and window from Coaching's eligibility-by-description response (§4.2.2 of the coach-availability contract), (2) operator commits to the offer; Sales submits a reservation request to Revenue's credit-reservation-lock surface and receives a `crr_` in Revenue lifecycle state `reserved`, (3) Sales calls §4.1 of this surface with the reservation_id and offer details, receiving a `les_` and a Delivery lesson-hold advance to `held`, (4) Sales surfaces the booked lesson to the operator and to the customer.
413
+
414
+ If the projection is stale at step 1 and the slot has been taken by step 3, the producer returns HTTP 409 on §4.1; Sales SHALL retry against a fresh eligibility read and a new reservation request. Sales SHOULD NOT retry the same reservation_id against §4.1 because the reservation may have aged out; the canonical retry path is to release the reservation and reserve fresh.
415
+
416
+ See `validation/sales-close-orchestration.md` for the consumer-shape verification.
417
+
418
+ ### 6.7 Event subscription discipline
419
+
420
+ Consumers that subscribe to `delivery.lesson-hold.*` MUST be idempotent over `event_id` per the event-envelope contract. Redelivery is expected; consumer-side projections SHALL converge to the same state regardless of replay or out-of-order delivery within a per-Lesson stream.
421
+
422
+ A consumer that already subscribes to `credit.*` events on Revenue's surface MUST NOT subscribe to `delivery.lesson-hold.*` to learn the same lock-state facts. The split is: `delivery.lesson-hold.*` carries scheduling-side facts; `credit.*` carries lock-state and funding facts. Subscribing to both for overlapping concerns introduces double-counting bugs in consumer projections.
423
+
424
+ ## 7. Producer responsibilities
425
+
426
+ ### 7.1 Transactional guarantee
427
+
428
+ Every write SHALL ride a single Prisma transaction that composes the storage write, the lock-state advance, and the event emit per ADR-0009's producer-transactional-guarantee. A failure in any of the three rolls all three back; consumers do not see partial state.
429
+
430
+ ### 7.2 Lock-state-machine conformance
431
+
432
+ The producer SHALL drive Delivery's lesson-hold state through the transitions this contract specifies while preserving the Revenue lifecycle mapping in `../credit-reservation-lock/delivery-state-vocabulary.md`. Hold-create advances Delivery `requested` to `held`. Hold-cancel advances Delivery `requested` to `released` or `held` to `released`. The producer SHALL NOT introduce new states, sub-states, or transitions through this surface; any change to the linked Revenue lifecycle is an ADR-0006 amendment with Revenue sign-off and the standard two-week deprecation window for downstream lock-state consumers per the Delivery domain memo's lock-contract change rule.
433
+
434
+ The producer SHALL run `/scripts/audit-locks.mjs` on a continuous schedule (operationally, not contract-defined) and treat any nonzero lock-to-ledger reconciliation drift as a P1 incident jointly with Revenue per the Delivery quality bar.
435
+
436
+ See `validation/lock-state-machine-conformance.md` for the per-transition test surface the producer maintains.
437
+
438
+ ### 7.3 No emission of `credit.*` events
439
+
440
+ Delivery SHALL NOT emit any `credit.*` event under any circumstance. The lock-state transitions driven by this surface fire `credit.*` events through Revenue's credit-reservation-lock surface per §9 of the credit-reservation-lock contract; this surface does not duplicate or pre-empt that producer.
441
+
442
+ ### 7.4 Org-isolation invariant
443
+
444
+ A response for `organization_id=A` SHALL NOT contain Lessons, lock state, or events scoped to `organization_id=B`. The producer SHALL enforce this at the storage-access layer, not solely at the response-construction layer. Test coverage for org isolation is part of v1.0.0's CI gate.
445
+
446
+ ### 7.5 Idempotency-key retention
447
+
448
+ The producer SHALL retain `(organization_id, idempotency_key)` keys for at least 24 hours from the original write. Writes with the same key after the retention window MAY be treated as fresh writes; consumers SHOULD NOT depend on idempotency beyond 24 hours.
449
+
450
+ ### 7.6 Comms-routing rule
451
+
452
+ Outbound communications about a Lesson Hold on behalf of a minor Participant SHALL flow through Platform's Guardian-aware comms endpoint per the Delivery domain memo's comms-routing rule. The producer SHALL NOT direct-message minors; lesson-day reminders, schedule changes, and coach communications all route through the Guardian-aware path.
453
+
454
+ ### 7.7 Eligibility re-check at write time
455
+
456
+ The producer SHALL re-check coach eligibility against Coaching's coach-availability contract at the moment of the write (§4.1). Eligibility lapse between the consumer's read and the producer's write is a known race; the producer's re-check is the gate. The producer SHALL NOT skip the re-check on the assumption that the consumer's eligibility read was recent.
457
+
458
+ ## 8. Security and privacy
459
+
460
+ ### 8.1 PII footprint
461
+
462
+ The contract surface returns `participant_id`, `person_id`, `coach_id`, `organization_id`, scheduling fields, and lock-state fields. No Person-level PII (name, contact information, demographics) flows through this contract. Consumers that need Person fields SHALL resolve via Platform's Person facts API per the identity contract.
463
+
464
+ The `notes` and `reason_notes` free-text fields MAY contain operator-entered context. Producer logs SHALL redact or hash these fields by default; full-text retrieval for operator-tooling display goes through the contract surface, not the logs.
465
+
466
+ ### 8.2 Org isolation as a security invariant
467
+
468
+ Cross-organization data leakage through this contract surface is treated as a P1 security event. The org-isolation rule in §7.4 is enforced at storage-access; producer logs SHALL include `organization_id` on every read and every write so leakage detection is auditable.
469
+
470
+ ### 8.3 Log hygiene
471
+
472
+ Producer logs SHALL NOT include linkages between `participant_id`, `person_id`, or `coach_id` and Person-level PII. The `idempotency_key` is consumer-supplied opaque text and SHALL NOT be assumed to be PII-free; producers SHOULD log a hash rather than the raw value when the key is included in operational logs.
473
+
474
+ ### 8.4 Rate limiting
475
+
476
+ The producer SHALL rate-limit every endpoint per organization. Limits are operational, not contract-defined; consumers SHOULD handle HTTP 429 with exponential backoff. The numeric limits are tuned in production and are not part of the contract surface.
477
+
478
+ ### 8.5 Authentication and tenancy
479
+
480
+ Every request SHALL carry an authenticated session bound to a Platform tenant per ADR-0001's tenant-ID shape and the Auth bootstrap semantics. The producer SHALL reject requests without a tenant binding with HTTP 401, and SHALL reject requests where the bound tenant does not have access to the named `organization_id` with HTTP 403.
481
+
482
+ ## 9. Future work
483
+
484
+ ### 9.1 Lesson rescheduling
485
+
486
+ A held Lesson moving to a different coach, time, or site without releasing and re-reserving credit is a common operator-side ask but is deliberately deferred from v1.0.0 because it interacts with the lock state machine in a non-trivial way (the lock stays in `held` while the underlying scheduling fields change; the coach-availability projection needs to subtract from one slot and unsubtract from the other in a single transaction). Trigger to add: Sales operator-tooling and Delivery operator-tooling both report reschedule volume that is being worked around through cancel-and-rebook, and Coaching is comfortable with the projection-edit pattern. Likely shape: `POST /delivery/v1/lesson-holds/{lesson_id}:reschedule` with the same idempotency and concurrency semantics as cancel.
487
+
488
+ ### 9.2 Multi-Participant lessons
489
+
490
+ The contract assumes one Participant per Lesson. Group lessons (multiple Participants on a single Coach in a single window) are out of scope in v1.0.0; the surface returns one Lesson per call and one Participant per Lesson. Trigger to add: a class of bookings that requires group scheduling appears on the operator side. Likely shape: a list-typed `participant_ids` field on §4.1, an event payload extension with the same list, no change to the lock state machine.
491
+
492
+ ### 9.3 Read-side projection
493
+
494
+ A consumer-friendly projection of "all my held lessons by week" or "all coach-day slots booked through this surface" is read-shaped enough to live in a separate read model. v1.0.0 keeps the surface query-driven through §4.3 with a simple cursor; trigger to extract a projection: operator-tooling reports list-call latency at p99 over 1 second on representative loads.
495
+
496
+ ### 9.4 Sub-spec extraction
497
+
498
+ The lock-state-machine conformance test surface (§7.2) and the close-orchestration consumer shape (§6.6) are candidates for sub-spec extraction if they grow. Trigger to add: either section reaches a length where the README's index role is muddied, or a non-coordination consumer needs to reference the section without referencing the rest of the README.
499
+
500
+ ## 10. Change log
501
+
502
+ ### v1.6.0 (2026-05-20)
503
+
504
+ Additive minor. Names Revenue Workbench as a first-class caller of §4.4 hold-cancel for reservation-scoped confirmed lesson cancellations, using the same command-first composition path Delivery answered on `2026-05-20-delivery-reservation-scoped-refund-composition`. Adds optional `X-Sguild-Caller-Domain` with `sales` default and `revenue` as the Workbench marker. Appends `originator: "revenue"` semantics to `delivery.lesson-hold.cancelled`; existing Sales traffic and event payload fields remain unchanged.
505
+
506
+ ### v1.5.0 (2026-05-19)
507
+
508
+ Additive minor. Adds nullable `person_id` to §4.2 hold-inspect and §4.3 hold-list responses, resolved by Delivery from the Participant row to the canonical Platform Person identifier. Consumers that need first names or other Person facts use this identifier with Platform identity instead of treating `participant_id` as a Person surrogate. No Person PII fields are added to the Delivery surface.
509
+
510
+ ### v1.3.0 (2026-05-19)
511
+
512
+ Additive minor. Adds §4.5 `POST /delivery/v1/lesson-holds/atomic-multi-create` for composite-offer close paths. The new endpoint accepts a batch of hold-create items sharing one coach, site, service area, and participant; all items ride a single all-or-none Prisma transaction; eligibility is re-checked at the composite window at write time. A single `Idempotency-Key` covers the batch; no per-item idempotency keys at v1.3.0. Existing §4.5 Event surface renumbered to §4.6; `delivery.lesson-hold.created` payload extended with optional `originating_offer_id` and `offer_item_id` fields (null on single-hold-create traffic; populated by atomic-multi-create traffic for Sales correlation). Adds `validation/composite-hold-create.md` covering batch-share constraints, window rules, eligibility re-read semantics, and the all-or-none rollback behavior.
513
+
514
+ ### v1.4.0 (2026-05-19)
515
+
516
+ Additive minor. Extends `delivery.lesson-hold.created` with optional nullable `lesson_zip`, sourced from Delivery's lesson site when known, so Coaching can seed booked-lesson travel-buffer context without a follow-up address read. Full lesson address remains behind Delivery's lesson-facts read surface.
517
+
518
+ ### v1.2.0 (2026-05-16)
519
+
520
+ Additive minor. Adds §4.3 wildcard list mode through `lock_state=*` or `lock_state[]=*` for Coach Day Planner and calendar-mode consumers. Wildcard mode returns all reservation-lock states plus scheduled lockless Lessons, with lock fields set to null and `lock_state: "scheduled"` for lockless rows. Existing default list behavior and explicit lock-state filters are unchanged.
521
+
522
+ ### v1.1.0 (2026-05-16)
523
+
524
+ Additive minor. Enables Sales-originated confirmed-to-released cancellations when paired with Revenue's Sales-callable refund initiation API from refund-flow v1.1.0. Adds an optional `refund` object to hold-cancel requests, adds an optional `refund` object to successful cancellation responses, and changes confirmed-state handling from a hard v1.0.x 409 to the v1.1 composition path when refund context is supplied. `requested` and `held` cancellation behavior is unchanged. `delivery.lesson-hold.cancelled` remains the scheduling-side event; Revenue continues to own refund events.
525
+
526
+ ### v1.0.1 (2026-05-16)
527
+
528
+ Editorial clarification. Reconciles this contract's Delivery lesson-hold vocabulary with Revenue's credit reservation lifecycle vocabulary. Replaces phrasing that described a Revenue `crr_` as being in `requested` state with the precise requirement: hold-create needs a live Revenue `crr_` in lifecycle state `reserved`, plus Delivery-side preconditions that permit a `requested → held` lesson-hold transition. No endpoint path, request body, response body, event payload, or runtime transition changes.
529
+
530
+ ### v1.0.0 (2026-05-07)
531
+
532
+ Initial publication. Drafted as part of the Delivery migration and Sales scheduling surface integration initiative (`memos/2026/2026-05-07-delivery-migration-initiative-scope`). Producer Delivery, primary consumer Sales, secondary subscriber Revenue. Surface covers hold-create (§4.1), hold-inspect (§4.2), hold-list (§4.3), and hold-cancel (§4.4), with two events under `delivery.lesson-hold.` (§4.5). The lock state machine is unchanged from ADR-0006; this surface is an additional Sales-facing entry point that drives the existing transitions. Validation files document Sales' close-orchestration shape and the lock-state-machine conformance gate.
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.sguild/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.cancelled-v1.json",
4
+ "title": "delivery.lesson-hold.cancelled payload v1",
5
+ "description": "Payload for the delivery.lesson-hold.cancelled event_type per contracts/sales-scheduling-surface/README.md section 4.6. Emitted by Delivery in the same transaction that cancels the Lesson and advances the Delivery reservation lock to released.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "lesson_id",
10
+ "reservation_lock_id",
11
+ "organization_id",
12
+ "reason_code",
13
+ "originator",
14
+ "idempotency_key"
15
+ ],
16
+ "properties": {
17
+ "lesson_id": {
18
+ "type": "string",
19
+ "minLength": 1
20
+ },
21
+ "reservation_lock_id": {
22
+ "type": "string",
23
+ "minLength": 1
24
+ },
25
+ "organization_id": {
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "reason_code": {
30
+ "type": "string",
31
+ "minLength": 1
32
+ },
33
+ "originator": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ },
37
+ "idempotency_key": {
38
+ "type": "string",
39
+ "minLength": 1
40
+ }
41
+ }
42
+ }