@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.
- package/README.md +4 -1
- package/contracts/README.md +30 -0
- package/contracts/coach-availability/README.md +355 -0
- package/contracts/coach-availability/README.v2.md +263 -0
- package/contracts/coach-availability/schema/payloads/coach.assigned-v1.json +91 -0
- package/contracts/coach-availability/validation/delivery-assignment.md +89 -0
- package/contracts/coach-availability/validation/sales-offer-construction.md +76 -0
- package/contracts/coaching-confirmation/README.md +96 -0
- package/contracts/coaching-confirmation/schema/payloads/coaching.lesson.confirmation_decided-v1.json +142 -0
- package/contracts/coaching-confirmation/schema/payloads/lead.coach.confirmation.requested-v1.json +124 -0
- package/contracts/credit-reservation-funding-state/README.md +147 -0
- package/contracts/credit-reservation-lock/README.md +433 -0
- package/contracts/credit-reservation-lock/delivery-state-vocabulary.md +73 -0
- package/contracts/credit-reservation-lock/reservation-create-api.md +191 -0
- package/contracts/credit-reservation-lock/reservation-release-api.md +171 -0
- package/contracts/credit-reservation-lock/schema/payloads/credit.locked-v1.json +1 -1
- package/contracts/credit-reservation-lock/schema/payloads/credit.reserved-v1.json +2 -3
- package/contracts/credit-reservation-lock/validation/lesson-lifecycle.md +318 -0
- package/contracts/event-envelope/README.md +205 -0
- package/contracts/event-envelope/schema/envelope-v1.json +2 -2
- package/contracts/event-envelope/validation/event-vocabulary.md +270 -0
- package/contracts/event-types-registry.json +337 -24
- package/contracts/external-actions/README.md +338 -0
- package/contracts/finance-mart/README.md +238 -0
- package/contracts/finance-mart/cac-payback.md +113 -0
- package/contracts/finance-mart/cash-position.md +98 -0
- package/contracts/finance-mart/cohort-summary.md +72 -0
- package/contracts/finance-mart/customer-journey-audit.md +92 -0
- package/contracts/finance-mart/ltv.md +92 -0
- package/contracts/finance-mart/margin.md +87 -0
- package/contracts/finance-mart/pnl.md +83 -0
- package/contracts/finance-mart/reconciliation.md +98 -0
- package/contracts/finance-mart/revenue-recognition-rollup.md +87 -0
- package/contracts/finance-mart/unit-economics.md +94 -0
- package/contracts/growth-warehouse-api/README.md +162 -0
- package/contracts/identity/README.md +184 -0
- package/contracts/identity/person-canonical-fields.md +120 -0
- package/contracts/identity/person-externals.md +267 -0
- package/contracts/identity/person-resolution-semantics.md +144 -0
- package/contracts/identity/person-role-taxonomy.md +120 -0
- package/contracts/identity/schema/payloads/intake.captured-v2.json +60 -0
- package/contracts/identity/schema/payloads/intake.matched-v2.json +123 -0
- package/contracts/identity/schema/payloads/person.updated-v1.json +8 -2
- package/contracts/identity/schema/payloads/role.assigned-v1.json +50 -0
- package/contracts/identity/schema/payloads/role.retired-v1.json +54 -0
- package/contracts/identity/validation/client-table.md +131 -0
- package/contracts/identity/validation/coach-handling.md +100 -0
- package/contracts/identity/validation/person-graph.md +140 -0
- package/contracts/lead-lifecycle/README.md +187 -0
- package/contracts/lead-lifecycle/schema/payloads/lead.handoff.context.recorded-v1.json +108 -0
- package/contracts/lead-lifecycle/schema/payloads/lead.qualified-v1.json +54 -0
- package/contracts/lead-lifecycle/schema/payloads/sales.lead.onboarded-v1.json +120 -0
- package/contracts/lesson-lifecycle/README.md +118 -0
- package/contracts/lesson-lifecycle/schema/payloads/lesson.cancelled-v1.json +30 -0
- package/contracts/lesson-lifecycle/schema/payloads/lesson.delivered-v1.json +29 -0
- package/contracts/lesson-lifecycle/schema/payloads/lesson.rescheduled-v1.json +157 -0
- package/contracts/lesson-lifecycle/schema/payloads/lesson.scheduled-v1.json +107 -0
- package/contracts/lesson-lifecycle/validation/README.md +5 -0
- package/contracts/mart-consumer-api/README.md +108 -0
- package/contracts/order-flow/README.md +106 -0
- package/contracts/order-flow/schema/payloads/order.created-v1.json +58 -0
- package/contracts/order-flow/schema/payloads/order.updated-v1.json +63 -0
- package/contracts/payment-flow/README.md +157 -0
- package/contracts/platform-comms/README.md +84 -0
- package/contracts/platform-comms/schema/payloads/platform.comms.inbound-v1.json +83 -0
- package/contracts/platform-geography-snapshot/README.md +205 -0
- package/contracts/platform-geography-snapshot/schema/payloads/geography.market.archived-v1.json +36 -0
- package/contracts/platform-geography-snapshot/schema/payloads/geography.market.upserted-v1.json +59 -0
- package/contracts/platform-geography-snapshot/schema/payloads/geography.service-area.archived-v1.json +36 -0
- package/contracts/platform-geography-snapshot/schema/payloads/geography.service-area.upserted-v1.json +65 -0
- package/contracts/portfolio-mart/README.md +133 -0
- package/contracts/portfolio-mart/cohort-funnel-panel.md +76 -0
- package/contracts/portfolio-mart/cross-discipline-performance.md +91 -0
- package/contracts/portfolio-mart/cross-market-performance.md +84 -0
- package/contracts/portfolio-mart/health-composites.md +88 -0
- package/contracts/portfolio-mart/org-topology.md +70 -0
- package/contracts/portfolio-mart/portfolio-level-funnel-health.md +92 -0
- package/contracts/portfolio-mart/validation/consumer-isolation.md +33 -0
- package/contracts/portfolio-mart/validation/decoupling-discipline.md +34 -0
- package/contracts/refund-flow/README.md +136 -0
- package/contracts/refund-flow/sales-callable-refund-initiation-api.md +218 -0
- package/contracts/sales-scheduling-surface/README.md +532 -0
- package/contracts/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.cancelled-v1.json +42 -0
- package/contracts/sales-scheduling-surface/schema/payloads/delivery.lesson-hold.created-v1.json +115 -0
- package/contracts/sales-scheduling-surface/validation/composite-hold-create.md +97 -0
- package/contracts/sales-scheduling-surface/validation/lock-state-machine-conformance.md +84 -0
- package/contracts/sales-scheduling-surface/validation/sales-close-orchestration.md +77 -0
- package/contracts/warehouse-silver/README.md +118 -0
- package/contracts/warehouse-silver/coaching-utilization-columns.md +105 -0
- package/dist/events.d.ts +63 -0
- package/dist/events.js +293 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -1
- package/dist/postgres-consumer.js +2 -1
- package/dist/validator.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Credit Reservation Lock, Reservation Create API
|
|
2
|
+
|
|
3
|
+
**Status:** v1.1.0
|
|
4
|
+
**Date:** 2026-05-16
|
|
5
|
+
**Owner:** Revenue
|
|
6
|
+
**Consumers:** Sales close orchestration; Delivery hold-create reads the resulting `credit_reservation_id`
|
|
7
|
+
**Parent contract:** Credit Reservation Lock Contract v1.2.0
|
|
8
|
+
|
|
9
|
+
## 1. Purpose and scope
|
|
10
|
+
|
|
11
|
+
This sub-spec defines the Sales-callable API for creating a Revenue credit reservation before Delivery creates a lesson hold. It is an operational API surface owned by Revenue and governed by the Credit Reservation Lock Contract.
|
|
12
|
+
|
|
13
|
+
The endpoint creates or returns a Revenue `crr_` credit reservation in lifecycle state `reserved`. It does not create a Delivery lesson hold, assign a coach, confirm funding, or post a lock debit. Delivery hold-create consumes the returned `credit_reservation_id` and advances the Delivery lesson-hold state separately.
|
|
14
|
+
|
|
15
|
+
For Sales-originated close orchestration, `lesson_id` is absent until Delivery hold-create mints the Delivery-owned `les_` identifier. Revenue MUST NOT require Sales to fabricate or preallocate a `les_` value.
|
|
16
|
+
|
|
17
|
+
Out of scope: payment collection, refund initiation, Delivery hold-create, Coaching eligibility reads, lesson rescheduling, and cancellation or release submission.
|
|
18
|
+
|
|
19
|
+
## 2. Endpoint
|
|
20
|
+
|
|
21
|
+
`POST /api/v1/reservations`
|
|
22
|
+
|
|
23
|
+
Headers:
|
|
24
|
+
|
|
25
|
+
- `Authorization`: bearer service token or authenticated cross-domain caller credential accepted by Revenue.
|
|
26
|
+
- `Idempotency-Key`: required, string up to 128 characters. Scoped by `organization_id`.
|
|
27
|
+
|
|
28
|
+
Body:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"organization_id": "org_...",
|
|
33
|
+
"person_id": "per_...",
|
|
34
|
+
"participant_id": "par_...",
|
|
35
|
+
"lesson_type_id": "lty_...",
|
|
36
|
+
"service_area_id": "sva_...",
|
|
37
|
+
"window": {
|
|
38
|
+
"start": "2026-05-09T17:00:00-10:00",
|
|
39
|
+
"end": "2026-05-09T17:30:00-10:00"
|
|
40
|
+
},
|
|
41
|
+
"offering_id": "off_...",
|
|
42
|
+
"notes": "optional free text up to 500 chars"
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Required fields:
|
|
47
|
+
|
|
48
|
+
- `organization_id`
|
|
49
|
+
- `person_id`
|
|
50
|
+
- `participant_id`
|
|
51
|
+
- `lesson_type_id`
|
|
52
|
+
- `service_area_id`
|
|
53
|
+
- `window.start`
|
|
54
|
+
- `window.end`
|
|
55
|
+
|
|
56
|
+
Optional fields:
|
|
57
|
+
|
|
58
|
+
- `offering_id`, when Sales already knows the sold offering that determines trial or regular credit price.
|
|
59
|
+
- `lesson_id`, only when Delivery has already minted a canonical `les_` in a non-standard or replay path. Sales close orchestration normally omits it.
|
|
60
|
+
- `notes`, operator context. Producers SHOULD redact this field from logs.
|
|
61
|
+
|
|
62
|
+
Revenue MAY derive `reserved_credits` from the lesson duration, lesson type, and offering. Consumers SHALL NOT send `reserved_credits` as an authority field unless a later minor version adds it as an explicit override with Revenue validation.
|
|
63
|
+
|
|
64
|
+
## 3. Success response
|
|
65
|
+
|
|
66
|
+
HTTP 201 on a new reservation:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"reservation_id": "crr_...",
|
|
71
|
+
"credit_reservation_id": "crr_...",
|
|
72
|
+
"organization_id": "org_...",
|
|
73
|
+
"person_id": "per_...",
|
|
74
|
+
"participant_id": "par_...",
|
|
75
|
+
"lesson_id": null,
|
|
76
|
+
"lesson_type_id": "lty_...",
|
|
77
|
+
"service_area_id": "sva_...",
|
|
78
|
+
"window": {
|
|
79
|
+
"start": "2026-05-09T17:00:00-10:00",
|
|
80
|
+
"end": "2026-05-09T17:30:00-10:00"
|
|
81
|
+
},
|
|
82
|
+
"lifecycle_state": "reserved",
|
|
83
|
+
"funding_state": "pending",
|
|
84
|
+
"reserved_credits": 6,
|
|
85
|
+
"result": "created",
|
|
86
|
+
"as_of": "2026-05-16T06:30:00.000Z"
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
HTTP 200 on an idempotent replay or equivalent existing active reservation:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"reservation_id": "crr_...",
|
|
95
|
+
"credit_reservation_id": "crr_...",
|
|
96
|
+
"organization_id": "org_...",
|
|
97
|
+
"person_id": "per_...",
|
|
98
|
+
"participant_id": "par_...",
|
|
99
|
+
"lesson_id": null,
|
|
100
|
+
"lesson_type_id": "lty_...",
|
|
101
|
+
"service_area_id": "sva_...",
|
|
102
|
+
"window": {
|
|
103
|
+
"start": "2026-05-09T17:00:00-10:00",
|
|
104
|
+
"end": "2026-05-09T17:30:00-10:00"
|
|
105
|
+
},
|
|
106
|
+
"lifecycle_state": "reserved",
|
|
107
|
+
"funding_state": "pending",
|
|
108
|
+
"reserved_credits": 6,
|
|
109
|
+
"result": "existing",
|
|
110
|
+
"as_of": "2026-05-16T06:30:00.000Z"
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`reservation_id` and `credit_reservation_id` are aliases in v1.0.0. Producers SHALL include both. Consumers SHOULD store `credit_reservation_id` as the explicit Revenue field and MAY pass `reservation_id` to older surfaces that use the shorter name.
|
|
115
|
+
|
|
116
|
+
## 4. Error response
|
|
117
|
+
|
|
118
|
+
Every error response uses this envelope:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"error": {
|
|
123
|
+
"code": "validation_failed",
|
|
124
|
+
"message": "Human-readable summary.",
|
|
125
|
+
"conflict_reason": "existing_active_reservation",
|
|
126
|
+
"current_state": {
|
|
127
|
+
"reservation_id": "crr_...",
|
|
128
|
+
"lifecycle_state": "reserved",
|
|
129
|
+
"funding_state": "pending"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"as_of": "2026-05-16T06:30:00.000Z"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`conflict_reason` and `current_state` are present only when useful for HTTP 409.
|
|
137
|
+
|
|
138
|
+
Status codes:
|
|
139
|
+
|
|
140
|
+
- `400 Bad Request`: malformed JSON, missing required field, invalid identifier prefix, invalid window shape.
|
|
141
|
+
- `401 Unauthorized`: missing or invalid caller credential.
|
|
142
|
+
- `403 Forbidden`: caller is authenticated but not allowed to act for the named `organization_id`.
|
|
143
|
+
- `404 Not Found`: `person_id`, `participant_id`, supplied `lesson_id`, `lesson_type_id`, `service_area_id`, or `offering_id` does not resolve through the appropriate authoritative surface.
|
|
144
|
+
- `409 Conflict`: the request is well-formed but cannot create the requested reservation because live state changed or conflicts with an existing reservation.
|
|
145
|
+
- `422 Unprocessable Entity`: business rule failure on a well-formed request.
|
|
146
|
+
- `429 Too Many Requests`: per-organization or caller rate limit exceeded.
|
|
147
|
+
- `500` or `503`: Revenue cannot verify or persist the reservation. Consumers SHALL treat this branch as unverifiable and SHOULD avoid creating a Delivery hold until a retry or operator review succeeds.
|
|
148
|
+
|
|
149
|
+
HTTP 409 `conflict_reason` values:
|
|
150
|
+
|
|
151
|
+
- `existing_active_reservation`: the lesson already has an active Revenue reservation.
|
|
152
|
+
- `reservation_already_terminal`: the matching reservation is terminal and cannot be reused.
|
|
153
|
+
- `person_mismatch`: the participant or lesson context does not map to the request's `person_id`.
|
|
154
|
+
- `organization_mismatch`: the request names state across more than one Organization.
|
|
155
|
+
- `credit_account_missing`: Revenue cannot find an active Credit Account for `person_id`.
|
|
156
|
+
- `credit_account_ambiguous`: Revenue finds multiple active Credit Accounts for `person_id`.
|
|
157
|
+
- `lesson_not_reservable`: the lesson is not in a state that can receive a reservation.
|
|
158
|
+
- `window_conflict`: the requested lesson window conflicts with known reservation state.
|
|
159
|
+
- `idempotency_payload_mismatch`: the `Idempotency-Key` was already used with a different payload in the same `organization_id`.
|
|
160
|
+
|
|
161
|
+
Consumers SHALL treat unknown `conflict_reason` values as safe to surface to an operator and unsafe for automated retry branching.
|
|
162
|
+
|
|
163
|
+
## 5. Producer responsibilities
|
|
164
|
+
|
|
165
|
+
Revenue SHALL create the reservation and emit `credit.reserved` in the same transaction per ADR-0009. A failure to publish the event rolls back the reservation create. A failure to create the reservation emits no event.
|
|
166
|
+
|
|
167
|
+
Revenue SHALL scope idempotency to `(organization_id, idempotency_key)` and retain keys for at least 24 hours. Replays with the same key and same normalized payload return the original result. Replays with the same key and a different normalized payload return HTTP 409 with `conflict_reason: idempotency_payload_mismatch`.
|
|
168
|
+
|
|
169
|
+
Revenue SHALL return only a `reserved` lifecycle state from this endpoint. Funding may already be sufficient, in which case `funding_state` MAY be `funded`, but the lifecycle state remains `reserved` until a separate lock transition occurs.
|
|
170
|
+
|
|
171
|
+
Revenue SHALL accept absent `lesson_id` on the Sales close path. If Revenue stores a lesson reference on the `crr_`, the attachment point is Delivery's successful hold-create result or `delivery.lesson-hold.created`, not the initial reservation-create request.
|
|
172
|
+
|
|
173
|
+
## 6. Consumer responsibilities
|
|
174
|
+
|
|
175
|
+
Sales SHALL call Coaching eligibility before calling this endpoint when the reservation is part of close orchestration. Sales SHALL pass the returned `credit_reservation_id` to Delivery hold-create and SHALL NOT infer Delivery's lesson-hold state from Revenue's lifecycle state.
|
|
176
|
+
|
|
177
|
+
Sales SHALL NOT call Delivery hold-create when this endpoint returns an error or an unverifiable 5xx branch. The safe path is retry with the same idempotency key if the request may not have reached Revenue, or operator review if Revenue may have persisted state but the client did not receive the response.
|
|
178
|
+
|
|
179
|
+
## 7. Versioning
|
|
180
|
+
|
|
181
|
+
Patch versions may clarify prose and add non-normative examples. Minor versions may add optional request fields, optional response fields, relax required fields, or add new `conflict_reason` values. Major versions are required to remove or rename fields, add new required fields, change idempotency semantics, or change the path.
|
|
182
|
+
|
|
183
|
+
## 8. Change log
|
|
184
|
+
|
|
185
|
+
### v1.1.0 (2026-05-16)
|
|
186
|
+
|
|
187
|
+
Makes `lesson_id` optional and nullable on the Sales close-orchestration create path. Revenue reservation-create remains before Delivery hold-create; Delivery mints `les_` during hold-create and can attach it back to Revenue through `delivery.lesson-hold.created` or an explicit reconciliation write. No path, idempotency, lifecycle-state, or Delivery hold-create ordering change.
|
|
188
|
+
|
|
189
|
+
### v1.0.0 (2026-05-16)
|
|
190
|
+
|
|
191
|
+
Initial Sales-callable reservation-create sub-spec.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Credit Reservation Lock, Reservation Release API
|
|
2
|
+
|
|
3
|
+
**Status:** v1.0.0
|
|
4
|
+
**Date:** 2026-05-16
|
|
5
|
+
**Owner:** Revenue
|
|
6
|
+
**Consumers:** Sales close orchestration; Delivery cancellation and reconciliation flows
|
|
7
|
+
**Parent contract:** Credit Reservation Lock Contract v1.2.0
|
|
8
|
+
|
|
9
|
+
## 1. Purpose and scope
|
|
10
|
+
|
|
11
|
+
This sub-spec defines the Sales-callable API for submitting a cancellation or release request to Revenue for an existing credit reservation. Revenue owns the policy decision and the resulting lifecycle transition.
|
|
12
|
+
|
|
13
|
+
The endpoint can result in `released` or `forfeited` depending on the reservation state, initiator, reason code, timing, funding state, and Revenue policy. Consumers submit intent and context; they do not directly choose the terminal state.
|
|
14
|
+
|
|
15
|
+
Out of scope: refund initiation for confirmed payment flows, Delivery lesson-hold cancellation storage, no-show recording, and creating replacement reservations.
|
|
16
|
+
|
|
17
|
+
## 2. Endpoint
|
|
18
|
+
|
|
19
|
+
`POST /api/v1/reservations/{reservation_id}/release`
|
|
20
|
+
|
|
21
|
+
Path parameter:
|
|
22
|
+
|
|
23
|
+
- `reservation_id`: required, a Revenue `crr_` credit reservation id.
|
|
24
|
+
|
|
25
|
+
Headers:
|
|
26
|
+
|
|
27
|
+
- `Authorization`: bearer service token or authenticated cross-domain caller credential accepted by Revenue.
|
|
28
|
+
- `Idempotency-Key`: required, string up to 128 characters. Scoped by `organization_id`.
|
|
29
|
+
|
|
30
|
+
Body:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"organization_id": "org_...",
|
|
35
|
+
"initiator": "admin",
|
|
36
|
+
"reason_code": "customer_requested_in_window",
|
|
37
|
+
"reason_notes": "optional free text up to 500 chars",
|
|
38
|
+
"lesson_id": "les_..."
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Required fields:
|
|
43
|
+
|
|
44
|
+
- `organization_id`
|
|
45
|
+
- `initiator`
|
|
46
|
+
- `reason_code`
|
|
47
|
+
|
|
48
|
+
Optional fields:
|
|
49
|
+
|
|
50
|
+
- `reason_notes`, operator context. Producers SHOULD redact this field from logs.
|
|
51
|
+
- `lesson_id`, accepted as a defensive match check when the caller is releasing a reservation as part of a Delivery hold conflict or retry path.
|
|
52
|
+
|
|
53
|
+
`initiator` values:
|
|
54
|
+
|
|
55
|
+
- `customer`
|
|
56
|
+
- `admin`
|
|
57
|
+
- `coach`
|
|
58
|
+
- `system_weather`
|
|
59
|
+
- `system_logistics`
|
|
60
|
+
- `system_unpaid`
|
|
61
|
+
- `system_other`
|
|
62
|
+
|
|
63
|
+
`reason_code` values are the `credit.released` v2 values in the parent contract §6.1:
|
|
64
|
+
|
|
65
|
+
- `site_closure`
|
|
66
|
+
- `coach_unavailable_reschedule_failed`
|
|
67
|
+
- `force_majeure`
|
|
68
|
+
- `weather`
|
|
69
|
+
- `administrative_void`
|
|
70
|
+
- `customer_requested_in_window`
|
|
71
|
+
- `customer_requested_exception`
|
|
72
|
+
- `policy_exception`
|
|
73
|
+
- `bad_debt_writeoff`
|
|
74
|
+
|
|
75
|
+
## 3. Success response
|
|
76
|
+
|
|
77
|
+
HTTP 200:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"reservation_id": "crr_...",
|
|
82
|
+
"credit_reservation_id": "crr_...",
|
|
83
|
+
"organization_id": "org_...",
|
|
84
|
+
"lesson_id": "les_...",
|
|
85
|
+
"prior_lifecycle_state": "reserved",
|
|
86
|
+
"lifecycle_state": "released",
|
|
87
|
+
"funding_state": null,
|
|
88
|
+
"initiator": "admin",
|
|
89
|
+
"reason_code": "customer_requested_in_window",
|
|
90
|
+
"reversal_reason": "Credits Released",
|
|
91
|
+
"released_at": "2026-05-16T06:30:00.000Z",
|
|
92
|
+
"ledger_reversal_created": false,
|
|
93
|
+
"result": "released",
|
|
94
|
+
"as_of": "2026-05-16T06:30:00.000Z"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`result` values:
|
|
99
|
+
|
|
100
|
+
- `released`: the request produced or idempotently returned a `released` terminal state.
|
|
101
|
+
- `forfeited`: the request produced or idempotently returned a `forfeited` terminal state.
|
|
102
|
+
- `already_terminal`: the reservation was already in the same terminal state produced by the same idempotency key.
|
|
103
|
+
|
|
104
|
+
When `result` is `forfeited`, `lifecycle_state` is `forfeited`, `released_at` is omitted, and `forfeited_at` is present. Consumers SHALL surface this branch to an operator and SHALL NOT treat it as a release.
|
|
105
|
+
|
|
106
|
+
`reservation_id` and `credit_reservation_id` are aliases in v1.0.0. Producers SHALL include both.
|
|
107
|
+
|
|
108
|
+
## 4. Error response
|
|
109
|
+
|
|
110
|
+
Every error response uses this envelope:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"error": {
|
|
115
|
+
"code": "conflict",
|
|
116
|
+
"message": "Human-readable summary.",
|
|
117
|
+
"conflict_reason": "confirmed_state_requires_refund_flow",
|
|
118
|
+
"current_state": {
|
|
119
|
+
"reservation_id": "crr_...",
|
|
120
|
+
"lifecycle_state": "locked",
|
|
121
|
+
"funding_state": "funded"
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"as_of": "2026-05-16T06:30:00.000Z"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Status codes:
|
|
129
|
+
|
|
130
|
+
- `400 Bad Request`: malformed JSON, missing required field, invalid identifier prefix, invalid `initiator`, invalid `reason_code`.
|
|
131
|
+
- `401 Unauthorized`: missing or invalid caller credential.
|
|
132
|
+
- `403 Forbidden`: caller is authenticated but not allowed to act for the named `organization_id`.
|
|
133
|
+
- `404 Not Found`: `reservation_id` does not resolve in Revenue.
|
|
134
|
+
- `409 Conflict`: live reservation state prevents the requested operation.
|
|
135
|
+
- `422 Unprocessable Entity`: business rule failure on a well-formed request.
|
|
136
|
+
- `429 Too Many Requests`: per-organization or caller rate limit exceeded.
|
|
137
|
+
- `500` or `503`: Revenue cannot verify or persist the release. Consumers SHALL treat this branch as unverifiable and SHOULD avoid assuming the reservation was released until a retry or operator review succeeds.
|
|
138
|
+
|
|
139
|
+
HTTP 409 `conflict_reason` values:
|
|
140
|
+
|
|
141
|
+
- `reservation_already_consumed`: the reservation is already consumed.
|
|
142
|
+
- `reservation_already_released`: the reservation is already released by a different idempotency key or different reason.
|
|
143
|
+
- `reservation_already_forfeited`: the reservation is already forfeited by a different idempotency key or different reason.
|
|
144
|
+
- `confirmed_state_requires_refund_flow`: money has moved and the request must go through the refund-flow handoff before release can complete.
|
|
145
|
+
- `locked_state_requires_policy_decision`: the request could produce forfeiture and needs operator or policy confirmation not present in the request.
|
|
146
|
+
- `organization_mismatch`: the reservation belongs to a different Organization.
|
|
147
|
+
- `lesson_mismatch`: the optional `lesson_id` does not match the reservation.
|
|
148
|
+
- `idempotency_payload_mismatch`: the `Idempotency-Key` was already used with a different payload in the same `organization_id`.
|
|
149
|
+
- `concurrent_state_change`: the reservation changed while Revenue was evaluating the request.
|
|
150
|
+
|
|
151
|
+
Consumers SHALL treat unknown `conflict_reason` values as safe to surface to an operator and unsafe for automated retry branching.
|
|
152
|
+
|
|
153
|
+
## 5. Producer responsibilities
|
|
154
|
+
|
|
155
|
+
Revenue SHALL run the cancellation policy from the parent contract §6 and perform the resulting lifecycle transition. Revenue SHALL emit `credit.released` or `credit.forfeited` in the same transaction as the state change and any required ledger write per ADR-0009.
|
|
156
|
+
|
|
157
|
+
Revenue SHALL NOT emit `credit.released` for a request that policy resolves to forfeiture. Revenue SHALL NOT silently release a confirmed or locked reservation when refund-flow or forfeiture policy is required.
|
|
158
|
+
|
|
159
|
+
Revenue SHALL scope idempotency to `(organization_id, idempotency_key)` and retain keys for at least 24 hours. Replays with the same key and same normalized payload return the original result. Replays with the same key and a different normalized payload return HTTP 409 with `conflict_reason: idempotency_payload_mismatch`.
|
|
160
|
+
|
|
161
|
+
## 6. Consumer responsibilities
|
|
162
|
+
|
|
163
|
+
Sales SHALL call this endpoint before retrying close orchestration with a fresh reservation when a Delivery hold-create conflict leaves an unused Revenue reservation. Sales SHALL wait for a success response before treating the prior reservation as released.
|
|
164
|
+
|
|
165
|
+
Sales SHALL NOT call this endpoint to cancel a confirmed lesson for refund unless the refund-flow contract has provided the required initiation path. Until that path exists, confirmed-to-released cancellations remain out of scope for Sales-originated v1 close orchestration.
|
|
166
|
+
|
|
167
|
+
Delivery MAY call this endpoint for cancellation submissions that must flow through Revenue policy, but Delivery remains responsible for its own lesson-hold storage transition and `delivery.lesson-hold.cancelled` event per the sales-scheduling-surface contract.
|
|
168
|
+
|
|
169
|
+
## 7. Versioning
|
|
170
|
+
|
|
171
|
+
Patch versions may clarify prose and add non-normative examples. Minor versions may add optional request fields, optional response fields, or new `conflict_reason` values. Major versions are required to remove or rename fields, change required fields, change idempotency semantics, change the path, or make `released` the only possible successful terminal outcome.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://contracts.sguild/credit-reservation-lock/schema/payloads/credit.locked-v1.json",
|
|
4
4
|
"title": "credit.locked payload v1",
|
|
5
|
-
"description": "Payload for the credit.locked event_type per contracts/credit-reservation-lock/README.md §9.4. Emitted by Revenue when the reservation enters 'locked' state at T-
|
|
5
|
+
"description": "Payload for the credit.locked event_type per contracts/credit-reservation-lock/README.md §9.4. Emitted by Revenue when the reservation enters 'locked' state at T-24h. Subscribers: Delivery (day-of preparation), Sales (close Lead via the customer.handoff sidecar), Coaching (state-transition only; the slot stays subtracted in the availability projection per coach-availability §4.3.2). The customer.handoff sidecar fires on the first credit.locked per Person.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://contracts.sguild/credit-reservation-lock/schema/payloads/credit.reserved-v1.json",
|
|
4
4
|
"title": "credit.reserved payload v1",
|
|
5
|
-
"description": "Payload for the credit.reserved event_type per contracts/credit-reservation-lock/README.md §9.2. Emitted by Revenue
|
|
5
|
+
"description": "Payload for the credit.reserved event_type per contracts/credit-reservation-lock/README.md §9.2. Emitted by Revenue when a reservation claim is recorded. Sales-originated reservations can omit lesson_id until Delivery hold-create mints the Delivery-owned les_ identifier. Subscribers: Delivery, Coaching, Platform warehouse.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
9
9
|
"credit_reservation_id",
|
|
10
10
|
"credit_account_id",
|
|
11
11
|
"person_id",
|
|
12
|
-
"lesson_id",
|
|
13
12
|
"organization_id",
|
|
14
13
|
"reserved_credits",
|
|
15
14
|
"reserved_at",
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
},
|
|
34
33
|
"lesson_id": {
|
|
35
34
|
"type": "string",
|
|
36
|
-
"description": "The Delivery-owned lesson. les_<UUID v7> per ADR-0002.",
|
|
35
|
+
"description": "The Delivery-owned lesson. les_<UUID v7> per ADR-0002. Optional on Sales-originated reservations until Delivery hold-create succeeds.",
|
|
37
36
|
"pattern": "^les_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
|
38
37
|
},
|
|
39
38
|
"organization_id": {
|