@sguild/dispatcher 2.0.0

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 (66) hide show
  1. package/README.md +354 -0
  2. package/contracts/credit-reservation-funding-state/schema/payloads/reservation.funded-v1.json +59 -0
  3. package/contracts/credit-reservation-funding-state/schema/payloads/reservation.refunded-v1.json +80 -0
  4. package/contracts/credit-reservation-funding-state/schema/payloads/reservation.refunding-v1.json +74 -0
  5. package/contracts/credit-reservation-lock/schema/payloads/credit.consumed-v1.json +33 -0
  6. package/contracts/credit-reservation-lock/schema/payloads/credit.forfeited-v1.json +41 -0
  7. package/contracts/credit-reservation-lock/schema/payloads/credit.funded-v1.json +31 -0
  8. package/contracts/credit-reservation-lock/schema/payloads/credit.locked-v1.json +42 -0
  9. package/contracts/credit-reservation-lock/schema/payloads/credit.purchased-v1.json +39 -0
  10. package/contracts/credit-reservation-lock/schema/payloads/credit.released-v1.json +61 -0
  11. package/contracts/credit-reservation-lock/schema/payloads/credit.released-v2.json +77 -0
  12. package/contracts/credit-reservation-lock/schema/payloads/credit.reserved-v1.json +60 -0
  13. package/contracts/credit-reservation-lock/schema/payloads/customer.handoff-v1.json +35 -0
  14. package/contracts/event-envelope/schema/envelope-v1.json +79 -0
  15. package/contracts/event-types-registry.json +541 -0
  16. package/contracts/identity/schema/payloads/intake.amended-v1.json +124 -0
  17. package/contracts/identity/schema/payloads/intake.captured-v2.json +114 -0
  18. package/contracts/identity/schema/payloads/person.updated-v1.json +99 -0
  19. package/contracts/lead-lifecycle/schema/payloads/lead.attempt.exhausted-v1.json +36 -0
  20. package/contracts/lead-lifecycle/schema/payloads/lead.callback.scheduled-v1.json +39 -0
  21. package/contracts/lead-lifecycle/schema/payloads/lead.created-v1.json +50 -0
  22. package/contracts/lead-lifecycle/schema/payloads/lead.reached-v1.json +39 -0
  23. package/contracts/lead-lifecycle/schema/payloads/lead.stage.changed-v1.json +44 -0
  24. package/contracts/payment-flow/schema/payloads/payment.failed-v1.json +88 -0
  25. package/contracts/payment-flow/schema/payloads/payment.received-v1.json +69 -0
  26. package/contracts/refund-flow/schema/payloads/refund.completed-v1.json +75 -0
  27. package/contracts/refund-flow/schema/payloads/refund.initiated-v1.json +69 -0
  28. package/dist/config.d.ts +67 -0
  29. package/dist/config.js +81 -0
  30. package/dist/dispatcher-errors.d.ts +20 -0
  31. package/dist/dispatcher-errors.js +42 -0
  32. package/dist/dispatcher.d.ts +123 -0
  33. package/dist/dispatcher.js +171 -0
  34. package/dist/dlq.d.ts +173 -0
  35. package/dist/dlq.js +391 -0
  36. package/dist/fanout-drain.d.ts +11 -0
  37. package/dist/fanout-drain.js +31 -0
  38. package/dist/fanout.d.ts +144 -0
  39. package/dist/fanout.js +321 -0
  40. package/dist/inbox.d.ts +125 -0
  41. package/dist/inbox.js +120 -0
  42. package/dist/index.d.ts +36 -0
  43. package/dist/index.js +70 -0
  44. package/dist/internal/id.d.ts +38 -0
  45. package/dist/internal/id.js +78 -0
  46. package/dist/internal/pg-search-path.d.ts +34 -0
  47. package/dist/internal/pg-search-path.js +55 -0
  48. package/dist/internal/resolve-contract-path.d.ts +41 -0
  49. package/dist/internal/resolve-contract-path.js +65 -0
  50. package/dist/observability.d.ts +24 -0
  51. package/dist/observability.js +37 -0
  52. package/dist/postgres-consumer.d.ts +175 -0
  53. package/dist/postgres-consumer.js +561 -0
  54. package/dist/postgres-transport.d.ts +70 -0
  55. package/dist/postgres-transport.js +144 -0
  56. package/dist/producer-db.d.ts +80 -0
  57. package/dist/producer-db.js +115 -0
  58. package/dist/registry.d.ts +94 -0
  59. package/dist/registry.js +99 -0
  60. package/dist/signature.d.ts +44 -0
  61. package/dist/signature.js +79 -0
  62. package/dist/types.d.ts +107 -0
  63. package/dist/types.js +13 -0
  64. package/dist/validator.d.ts +60 -0
  65. package/dist/validator.js +171 -0
  66. package/package.json +48 -0
@@ -0,0 +1,541 @@
1
+ {
2
+ "$schema-self": "Each entry under 'events' maps an event_type to its producer, consumers, and per-schema-version payload schema location. Producers SHALL register every new event_type here before first use per §10.4 of the event-envelope contract. Consumers SHALL handle unknown values gracefully per §9.5. Adding a new event_type or a new schema_version is a minor change on the owning contract per §8.1; this registry is a discovery surface, not a versioning gate.",
3
+ "$schema-format": {
4
+ "events": {
5
+ "<event_type>": {
6
+ "producer": "<closed-set domain>",
7
+ "consumers": [
8
+ "<closed-set domains, or 'platform-warehouse' for the warehouse loader>"
9
+ ],
10
+ "owning_contract": "<contracts/<contract>/README.md path>",
11
+ "schema_versions": [
12
+ {
13
+ "version": "<integer, starts at 1>",
14
+ "payload_schema": "<contracts/<contract>/schema/payloads/<event_type>-v<n>.json path; null if not yet authored>",
15
+ "status": "<active | deprecated | sunset>",
16
+ "registered_at": "<YYYY-MM-DD>",
17
+ "notes": "<optional, human-readable>"
18
+ }
19
+ ]
20
+ }
21
+ }
22
+ },
23
+ "registry_version": "0.1.0-phase-0",
24
+ "registry_date": "2026-05-02",
25
+ "registry_owner": "platform",
26
+ "registry_notes": "Phase 0 of the dispatcher SDK build per memos/2026/2026-05-01-platform-dispatcher-sdk-build-plan. Stood up before the SDK runtime exists; payload_schema paths are populated as Phase 0 authors them and may be null for events that have not yet had a JSON Schema authored. The registry is the single source of truth for which (event_type, schema_version) pairs are valid; the dispatcher SDK reads this manifest at startup to drive validation. CI validation that every emit matches a registered (event_type, schema_version) is a Phase 0 deliverable that lands when the SDK runtime ships.",
27
+ "events": {
28
+ "intake.captured": {
29
+ "producer": "growth",
30
+ "consumers": [
31
+ "platform",
32
+ "sales",
33
+ "platform-warehouse"
34
+ ],
35
+ "owning_contract": "contracts/identity/README.md",
36
+ "schema_versions": [
37
+ {
38
+ "version": 1,
39
+ "payload_schema": null,
40
+ "status": "active",
41
+ "registered_at": "2026-05-02",
42
+ "notes": "Form submission with intake metadata (source, campaign, UTM, submission). Phone-bearing or email-bearing. Platform mints/matches; Sales opens a Lead. v2 bundles visitor_id (per cross-device-attribution thread) and source enum extensions for subscriber_promotion/partner_upload/manual_capture/derived_match (per subscriber-promotion thread); both expected before 2026-06-15 Identity Contract v1 ship."
43
+ },
44
+ {
45
+ "version": 2,
46
+ "payload_schema": "contracts/identity/schema/payloads/intake.captured-v2.json",
47
+ "status": "active",
48
+ "registered_at": "2026-05-07",
49
+ "active_from": "2026-05-07",
50
+ "notes": "Growth producer emits schema_version=2. v2 carries visitor_id plus source/campaign/submission metadata used by downstream stitching and lead-opening consumers. Payload schema authored 2026-05-07 per 2026-05-01-platform-growth-subscriber-promotion-confirmed."
51
+ }
52
+ ]
53
+ },
54
+ "intake.amended": {
55
+ "producer": "growth",
56
+ "consumers": [
57
+ "sales",
58
+ "platform-warehouse"
59
+ ],
60
+ "owning_contract": "contracts/identity/README.md",
61
+ "schema_versions": [
62
+ {
63
+ "version": 1,
64
+ "payload_schema": "contracts/identity/schema/payloads/intake.amended-v1.json",
65
+ "status": "active",
66
+ "registered_at": "2026-05-12",
67
+ "notes": "Operator-correction event for previously-captured intakes. Emitted by Growth when fields on a lead_intake row change (typo fix, missing zip filled in, refined student_age_group, lesson_setting reclassification, new_qualified upgrade/downgrade). Producer-transactional with the lead_intake update per ADR-0009. Sales updates lead_intake_snapshot in place; phone/email amendments are out of scope at v1 (identity-bearing, require Person merge through Platform). The cross-domain motivation: gives operators a clean fix path for wrong captured data without needing manual cross-DB row editing or breaking event immutability via dispatcher_event payload edits."
68
+ }
69
+ ]
70
+ },
71
+ "intake.matched": {
72
+ "producer": "platform",
73
+ "consumers": [
74
+ "growth",
75
+ "sales",
76
+ "platform-warehouse"
77
+ ],
78
+ "owning_contract": "contracts/identity/README.md",
79
+ "schema_versions": [
80
+ {
81
+ "version": 1,
82
+ "payload_schema": null,
83
+ "status": "active",
84
+ "registered_at": "2026-05-02",
85
+ "notes": "Emitted after Person mint or match on intake.captured. Carries person_id and the originating event_id (correlation)."
86
+ }
87
+ ]
88
+ },
89
+ "person.updated": {
90
+ "producer": "platform",
91
+ "consumers": [
92
+ "sales",
93
+ "growth",
94
+ "platform-warehouse"
95
+ ],
96
+ "owning_contract": "contracts/identity/README.md",
97
+ "schema_versions": [
98
+ {
99
+ "version": 1,
100
+ "payload_schema": "contracts/identity/schema/payloads/person.updated-v1.json",
101
+ "status": "active",
102
+ "registered_at": "2026-05-11",
103
+ "notes": "Emitted by Platform's identity service when any canonical Person field changes (status transition, name edits, is_minor flip from the daily invariant job). Carries the full post-update canonical Person, the list of changed_fields, and prior_status when status was among the changes. Subscribers filter on changed_fields to skip noise. Delivery, revenue, and coaching may join the consumer set additively (additive consumer registration is a minor change per envelope contract §8.1); the initial registration covers sales and growth, who need archive-aware behavior on Lead nurture and acquisition messaging respectively, plus the warehouse loader."
104
+ }
105
+ ]
106
+ },
107
+ "customer.handoff": {
108
+ "producer": "revenue",
109
+ "consumers": [
110
+ "delivery",
111
+ "sales",
112
+ "platform-warehouse"
113
+ ],
114
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
115
+ "schema_versions": [
116
+ {
117
+ "version": 1,
118
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/customer.handoff-v1.json",
119
+ "status": "active",
120
+ "registered_at": "2026-05-02",
121
+ "notes": "Sidecar emission on first credit.locked per Person, per credit-reservation-lock §5. Payload: person_id, first_lesson_id, credit_reservation_id, handoff_at."
122
+ }
123
+ ]
124
+ },
125
+ "credit.purchased": {
126
+ "producer": "revenue",
127
+ "consumers": [
128
+ "sales",
129
+ "growth",
130
+ "delivery",
131
+ "platform-warehouse"
132
+ ],
133
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
134
+ "schema_versions": [
135
+ {
136
+ "version": 1,
137
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.purchased-v1.json",
138
+ "status": "active",
139
+ "registered_at": "2026-05-02",
140
+ "notes": "Emitted on Purchase Credit ledger entry per §9.1. Payload: credit_account_id, person_id, purchased_credits, order_id, purchased_at."
141
+ }
142
+ ]
143
+ },
144
+ "credit.reserved": {
145
+ "producer": "revenue",
146
+ "consumers": [
147
+ "delivery",
148
+ "coaching",
149
+ "platform-warehouse"
150
+ ],
151
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
152
+ "schema_versions": [
153
+ {
154
+ "version": 1,
155
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.reserved-v1.json",
156
+ "status": "active",
157
+ "registered_at": "2026-05-02",
158
+ "notes": "Emitted at scheduling per §9.2. Coaching subscribes per coach-availability §4.3 and ADR-0008."
159
+ }
160
+ ]
161
+ },
162
+ "credit.funded": {
163
+ "producer": "revenue",
164
+ "consumers": [
165
+ "sales",
166
+ "delivery"
167
+ ],
168
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
169
+ "schema_versions": [
170
+ {
171
+ "version": 1,
172
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.funded-v1.json",
173
+ "status": "active",
174
+ "registered_at": "2026-05-02",
175
+ "notes": "Emitted on funding sub-state flip per §9.3. Coaching does NOT subscribe (§4.3.1 of coach-availability)."
176
+ }
177
+ ]
178
+ },
179
+ "credit.locked": {
180
+ "producer": "revenue",
181
+ "consumers": [
182
+ "delivery",
183
+ "sales",
184
+ "coaching",
185
+ "platform-warehouse"
186
+ ],
187
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
188
+ "schema_versions": [
189
+ {
190
+ "version": 1,
191
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.locked-v1.json",
192
+ "status": "active",
193
+ "registered_at": "2026-05-02",
194
+ "notes": "Emitted on T-48h lock per §9.4. customer.handoff sidecar fires on first per Person."
195
+ }
196
+ ]
197
+ },
198
+ "credit.consumed": {
199
+ "producer": "revenue",
200
+ "consumers": [
201
+ "delivery",
202
+ "revenue",
203
+ "platform-warehouse"
204
+ ],
205
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
206
+ "schema_versions": [
207
+ {
208
+ "version": 1,
209
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.consumed-v1.json",
210
+ "status": "active",
211
+ "registered_at": "2026-05-02",
212
+ "notes": "Emitted at lesson delivery per §9.5. Coaching does NOT subscribe (terminal-past, ages out on a clock)."
213
+ }
214
+ ]
215
+ },
216
+ "credit.released": {
217
+ "producer": "revenue",
218
+ "consumers": [
219
+ "delivery",
220
+ "sales",
221
+ "coaching",
222
+ "platform-warehouse"
223
+ ],
224
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
225
+ "schema_versions": [
226
+ {
227
+ "version": 1,
228
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.released-v1.json",
229
+ "status": "active",
230
+ "registered_at": "2026-05-02",
231
+ "deprecated_at": "2026-05-22",
232
+ "notes": "Initial release. Active until the v1.1.0 cutover date 2026-05-22 per the credit-reservation-lock contract §16 changelog. Status flips to deprecated at cutover; v1 consumers continue to work indefinitely per §11 additive-discipline (the new reason_code field on v2 is safe-to-ignore on the v1 surface)."
233
+ },
234
+ {
235
+ "version": 2,
236
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.released-v2.json",
237
+ "status": "active",
238
+ "registered_at": "2026-05-02",
239
+ "active_from": "2026-05-22",
240
+ "notes": "Adds the required reason_code field per the v1.1.0 amendment landed 2026-05-08 (memos/2026-05-08-revenue-refund-reason-codes-signoff and 2026-05-08-delivery-reason-codes-signoff-accepted). Producers SHALL emit at v2 from 2026-05-22 (v1.0.2 envelope publish date plus 14 days). reason_code is partitioned per §6.1 into auto-release and explicit-operator subsets."
241
+ }
242
+ ]
243
+ },
244
+ "credit.forfeited": {
245
+ "producer": "revenue",
246
+ "consumers": [
247
+ "delivery",
248
+ "revenue",
249
+ "coaching",
250
+ "platform-warehouse"
251
+ ],
252
+ "owning_contract": "contracts/credit-reservation-lock/README.md",
253
+ "schema_versions": [
254
+ {
255
+ "version": 1,
256
+ "payload_schema": "contracts/credit-reservation-lock/schema/payloads/credit.forfeited-v1.json",
257
+ "status": "active",
258
+ "registered_at": "2026-05-02",
259
+ "notes": "Emitted on locked → forfeited per §9.7. Customer-side cancellations only (forfeiture_reason: late_cancel | no_show)."
260
+ }
261
+ ]
262
+ },
263
+ "lesson.scheduled": {
264
+ "producer": "delivery",
265
+ "consumers": [],
266
+ "owning_contract": null,
267
+ "schema_versions": [
268
+ {
269
+ "version": 1,
270
+ "payload_schema": null,
271
+ "status": "active",
272
+ "registered_at": "2026-05-02",
273
+ "notes": "Emitted by Delivery when a lesson is scheduled. No cross-domain consumer registered yet; Coaching MAY subscribe in future for assigned-vs-eligible projection enrichment per coach-availability §4.2.1 producer-internal note."
274
+ }
275
+ ]
276
+ },
277
+ "lesson.attended": {
278
+ "producer": "delivery",
279
+ "consumers": [],
280
+ "owning_contract": null,
281
+ "schema_versions": [
282
+ {
283
+ "version": 1,
284
+ "payload_schema": null,
285
+ "status": "active",
286
+ "registered_at": "2026-05-02",
287
+ "notes": "Emitted on attendance reconciliation. No cross-domain consumer registered yet."
288
+ }
289
+ ]
290
+ },
291
+ "lesson.cancelled": {
292
+ "producer": "delivery",
293
+ "consumers": [],
294
+ "owning_contract": null,
295
+ "schema_versions": [
296
+ {
297
+ "version": 1,
298
+ "payload_schema": null,
299
+ "status": "active",
300
+ "registered_at": "2026-05-02",
301
+ "notes": "Emitted on lesson cancellation by Delivery. The cancellation request to Revenue (which runs the lock state machine) is separate; that submission produces credit.released or credit.forfeited per §6 and §12.4 of the credit-reservation-lock contract, not this event."
302
+ }
303
+ ]
304
+ },
305
+ "lesson.delivered": {
306
+ "producer": "delivery",
307
+ "consumers": [
308
+ "revenue"
309
+ ],
310
+ "owning_contract": null,
311
+ "schema_versions": [
312
+ {
313
+ "version": 1,
314
+ "payload_schema": null,
315
+ "status": "active",
316
+ "registered_at": "2026-05-02",
317
+ "notes": "Emitted post-delivery, triggers Revenue's Lesson Completion Job per §10 of credit-reservation-lock."
318
+ }
319
+ ]
320
+ },
321
+ "coach.assigned": {
322
+ "producer": "delivery",
323
+ "consumers": [
324
+ "coaching"
325
+ ],
326
+ "owning_contract": "contracts/coach-availability/README.md",
327
+ "schema_versions": [
328
+ {
329
+ "version": 1,
330
+ "payload_schema": null,
331
+ "status": "active",
332
+ "registered_at": "2026-05-02",
333
+ "notes": "Single-writer per ADR-0008. Coaching consumes for the assigned-vs-eligible projection enrichment per coach-availability §4.3 prose."
334
+ }
335
+ ]
336
+ },
337
+ "lead.created": {
338
+ "producer": "sales",
339
+ "consumers": [
340
+ "platform-warehouse"
341
+ ],
342
+ "owning_contract": "contracts/lead-lifecycle/README.md",
343
+ "schema_versions": [
344
+ {
345
+ "version": 1,
346
+ "payload_schema": "contracts/lead-lifecycle/schema/payloads/lead.created-v1.json",
347
+ "status": "active",
348
+ "registered_at": "2026-05-14",
349
+ "notes": "Emitted by Sales when a new Lead opens per lead-lifecycle §4.1. Two opening cases (from intake.captured with null person_id, or from intake.matched with person_id populated) plus reactivations carrying reactivated_from. Producer-transactional emit per ADR-0009; same Prisma transaction as the Lead row creation."
350
+ }
351
+ ]
352
+ },
353
+ "lead.stage.changed": {
354
+ "producer": "sales",
355
+ "consumers": [
356
+ "platform-warehouse"
357
+ ],
358
+ "owning_contract": "contracts/lead-lifecycle/README.md",
359
+ "schema_versions": [
360
+ {
361
+ "version": 1,
362
+ "payload_schema": "contracts/lead-lifecycle/schema/payloads/lead.stage.changed-v1.json",
363
+ "status": "active",
364
+ "registered_at": "2026-05-14",
365
+ "notes": "Broad state-transition event per lead-lifecycle §4.2. transition_reason enum (operator_action | cadence_runtime | inbound_event) identifies what drove the move. May co-fire with lead.reached, lead.callback.scheduled, or lead.attempt.exhausted when both apply. Stage values flexible string at v1.0.0; v1.1 candidate to tighten to a closed enum."
366
+ }
367
+ ]
368
+ },
369
+ "lead.reached": {
370
+ "producer": "sales",
371
+ "consumers": [
372
+ "platform-warehouse"
373
+ ],
374
+ "owning_contract": "contracts/lead-lifecycle/README.md",
375
+ "schema_versions": [
376
+ {
377
+ "version": 1,
378
+ "payload_schema": "contracts/lead-lifecycle/schema/payloads/lead.reached-v1.json",
379
+ "status": "active",
380
+ "registered_at": "2026-05-14",
381
+ "notes": "Successful reach event per lead-lifecycle §4.3. Voicemail, busy, no-answer, disconnected do NOT fire this event; only real conversations. attempt_number 1-4 per the cadence cap. outcome flexible string at v1.0.0; v1.1 candidate to tighten."
382
+ }
383
+ ]
384
+ },
385
+ "lead.callback.scheduled": {
386
+ "producer": "sales",
387
+ "consumers": [
388
+ "platform-warehouse"
389
+ ],
390
+ "owning_contract": "contracts/lead-lifecycle/README.md",
391
+ "schema_versions": [
392
+ {
393
+ "version": 1,
394
+ "payload_schema": "contracts/lead-lifecycle/schema/payloads/lead.callback.scheduled-v1.json",
395
+ "status": "active",
396
+ "registered_at": "2026-05-14",
397
+ "notes": "Operator-driven callback scheduling per lead-lifecycle §4.4. scheduled_by required (no auto-callback queueing per domains/sales.md). May co-fire with lead.stage.changed when the scheduling moves the Lead's stage."
398
+ }
399
+ ]
400
+ },
401
+ "lead.attempt.exhausted": {
402
+ "producer": "sales",
403
+ "consumers": [
404
+ "platform-warehouse"
405
+ ],
406
+ "owning_contract": "contracts/lead-lifecycle/README.md",
407
+ "schema_versions": [
408
+ {
409
+ "version": 1,
410
+ "payload_schema": "contracts/lead-lifecycle/schema/payloads/lead.attempt.exhausted-v1.json",
411
+ "status": "active",
412
+ "registered_at": "2026-05-14",
413
+ "notes": "Cadence-cap-hit terminal-transition event per lead-lifecycle §4.5. Co-fires with lead.stage.changed (transition_reason: cadence_runtime, to_stage: attempt_exhausted). The explicit-decision artifact required by the domains/sales.md quality bar."
414
+ }
415
+ ]
416
+ },
417
+ "payment.received": {
418
+ "producer": "revenue",
419
+ "consumers": [
420
+ "sales",
421
+ "growth",
422
+ "delivery",
423
+ "platform-warehouse"
424
+ ],
425
+ "owning_contract": "contracts/payment-flow/README.md",
426
+ "schema_versions": [
427
+ {
428
+ "version": 1,
429
+ "payload_schema": "contracts/payment-flow/schema/payloads/payment.received-v1.json",
430
+ "status": "active",
431
+ "registered_at": "2026-05-02",
432
+ "notes": "Emitted on writeback transaction commit for a successful payment per payment-flow §4.1. Producer-transactional-guarantee shape from ADR-0009. payment_path field disambiguates invoice_paid vs active_charge."
433
+ }
434
+ ]
435
+ },
436
+ "payment.failed": {
437
+ "producer": "revenue",
438
+ "consumers": [
439
+ "sales",
440
+ "growth",
441
+ "platform-warehouse"
442
+ ],
443
+ "owning_contract": "contracts/payment-flow/README.md",
444
+ "schema_versions": [
445
+ {
446
+ "version": 1,
447
+ "payload_schema": "contracts/payment-flow/schema/payloads/payment.failed-v1.json",
448
+ "status": "active",
449
+ "registered_at": "2026-05-02",
450
+ "notes": "Emitted on writeback transaction commit when the provider reports failure per payment-flow §4.2. Order is NOT flipped to PAID. Consumers route on failure_code per §4.4."
451
+ }
452
+ ]
453
+ },
454
+ "refund.initiated": {
455
+ "producer": "revenue",
456
+ "consumers": [
457
+ "sales",
458
+ "platform-warehouse"
459
+ ],
460
+ "owning_contract": "contracts/refund-flow/README.md",
461
+ "schema_versions": [
462
+ {
463
+ "version": 1,
464
+ "payload_schema": "contracts/refund-flow/schema/payloads/refund.initiated-v1.json",
465
+ "status": "active",
466
+ "registered_at": "2026-05-02",
467
+ "notes": "Emitted at the writeback transaction commit when the Refund record is minted at PENDING per refund-flow §4.1. Producer-transactional-guarantee shape from ADR-0009. Optional cancellation_reason mirrors credit.released v2 reason_code for lock-tied refunds."
468
+ }
469
+ ]
470
+ },
471
+ "refund.completed": {
472
+ "producer": "revenue",
473
+ "consumers": [
474
+ "sales",
475
+ "delivery",
476
+ "platform-warehouse"
477
+ ],
478
+ "owning_contract": "contracts/refund-flow/README.md",
479
+ "schema_versions": [
480
+ {
481
+ "version": 1,
482
+ "payload_schema": "contracts/refund-flow/schema/payloads/refund.completed-v1.json",
483
+ "status": "active",
484
+ "registered_at": "2026-05-02",
485
+ "notes": "Emitted at the writeback transaction commit when the Square webhook confirms settlement and the Refund Debit ledger entries post per refund-flow §4.2. Delivery subscribes for lock-released invariant verification per the refund-flow rule."
486
+ }
487
+ ]
488
+ },
489
+ "reservation.funded": {
490
+ "producer": "revenue",
491
+ "consumers": [
492
+ "revenue",
493
+ "platform-warehouse"
494
+ ],
495
+ "owning_contract": "contracts/credit-reservation-funding-state/README.md",
496
+ "schema_versions": [
497
+ {
498
+ "version": 1,
499
+ "payload_schema": "contracts/credit-reservation-funding-state/schema/payloads/reservation.funded-v1.json",
500
+ "status": "active",
501
+ "registered_at": "2026-05-05",
502
+ "notes": "Emitted at the writeback transaction commit when a credit reservation's funding sub-state transitions from pending_funding to funded per credit-reservation-funding-state §4.1. Revenue consumes for reconciliation; platform-warehouse ingests on the funding-event grain."
503
+ }
504
+ ]
505
+ },
506
+ "reservation.refunding": {
507
+ "producer": "revenue",
508
+ "consumers": [
509
+ "revenue",
510
+ "platform-warehouse"
511
+ ],
512
+ "owning_contract": "contracts/credit-reservation-funding-state/README.md",
513
+ "schema_versions": [
514
+ {
515
+ "version": 1,
516
+ "payload_schema": "contracts/credit-reservation-funding-state/schema/payloads/reservation.refunding-v1.json",
517
+ "status": "active",
518
+ "registered_at": "2026-05-05",
519
+ "notes": "Emitted at the writeback transaction commit when a credit reservation's funding sub-state transitions from funded to refunding per credit-reservation-funding-state §4.2. Paired with refund.initiated when a provider-side refund starts."
520
+ }
521
+ ]
522
+ },
523
+ "reservation.refunded": {
524
+ "producer": "revenue",
525
+ "consumers": [
526
+ "revenue",
527
+ "platform-warehouse"
528
+ ],
529
+ "owning_contract": "contracts/credit-reservation-funding-state/README.md",
530
+ "schema_versions": [
531
+ {
532
+ "version": 1,
533
+ "payload_schema": "contracts/credit-reservation-funding-state/schema/payloads/reservation.refunded-v1.json",
534
+ "status": "active",
535
+ "registered_at": "2026-05-05",
536
+ "notes": "Emitted at the writeback transaction commit when a credit reservation's funding sub-state transitions from refunding to refunded per credit-reservation-funding-state §4.3. Paired with refund.completed when the provider-side refund settles."
537
+ }
538
+ ]
539
+ }
540
+ }
541
+ }
@@ -0,0 +1,124 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.sguild/identity/schema/payloads/intake.amended-v1.json",
4
+ "title": "intake.amended payload v1",
5
+ "description": "Payload for the intake.amended event_type at schema_version 1. Emitted by Growth when an operator corrects fields on a previously-captured intake (typo fixes, missing zip, refined student_age_group, lesson_setting reclassification, etc.). Producer-transactional with the lead_intake row update per ADR-0009. Consumers update their snapshot in place: Sales updates lead_intake_snapshot; platform-warehouse projects to the latest-snapshot model. The envelope subject carries the fsm_<UUID v7> form_submission_id and tenant_id mirrors the envelope tenant_id for producer-local validation and downstream warehouse convenience.\n\nIdentity-bearing field amendments (phone, email) are deliberately out of scope at v1. Phone is the identity key per the Identity Contract; changing it via a snapshot-only event would orphan the existing Person attachment. Operators correcting phone numbers SHALL initiate a Person merge through Platform's identity API instead. Email is also restricted at v1 for symmetry — Platform's matcher uses it as a secondary signal and a change here could create a mismatched view between producer snapshot and Platform Person. The changed_fields enum reflects this constraint by omitting phone and email.\n\nProducers SHALL emit intake.amended only when at least one of the enumerated fields actually changed value; the changed_fields array is minItems: 1 and consumers SHALL NOT receive no-op amendments. The submission block carries the FULL post-amendment snapshot (not a diff) so consumers can apply the new shape without joining back to the producer.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "tenant_id",
10
+ "form_submission_id",
11
+ "person_id",
12
+ "amended_at",
13
+ "changed_fields",
14
+ "submission"
15
+ ],
16
+ "properties": {
17
+ "tenant_id": {
18
+ "type": "string",
19
+ "description": "Tenant discriminator. Mirrors the event envelope tenant_id. Per ADR-0001 this is tenancy, not organization_id."
20
+ },
21
+ "form_submission_id": {
22
+ "type": "string",
23
+ "pattern": "^fsm_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
24
+ "description": "The intake row being amended. Same id space as the originating intake.captured event's subject and Growth's lead_intake.id."
25
+ },
26
+ "person_id": {
27
+ "type": ["string", "null"],
28
+ "pattern": "^per_[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
29
+ "description": "Current Person attachment at the time of amendment. Null when the intake is still unmatched. Carried for convenience so consumers can decide whether to also touch identity-derived state without a separate lookup."
30
+ },
31
+ "amended_at": {
32
+ "type": "string",
33
+ "format": "date-time",
34
+ "description": "Wall-clock UTC at which the amendment was committed on Growth's lead_intake row."
35
+ },
36
+ "amended_by": {
37
+ "type": ["string", "null"],
38
+ "description": "Operator identity (Growth-internal id at v1.0.0, flexible string). Null for system-driven amendments (e.g., a deduplication job correcting a normalized field across many rows). SHOULD be non-null for operator-initiated UI actions so the audit trail records the human."
39
+ },
40
+ "reason": {
41
+ "type": ["string", "null"],
42
+ "description": "Free-form operator note explaining the amendment. Carried verbatim into consumers' snapshots for audit. Null when no reason was captured (e.g., system-driven or trivial corrections)."
43
+ },
44
+ "changed_fields": {
45
+ "type": "array",
46
+ "minItems": 1,
47
+ "uniqueItems": true,
48
+ "items": {
49
+ "type": "string",
50
+ "enum": [
51
+ "first_name",
52
+ "last_name",
53
+ "zip",
54
+ "dob",
55
+ "student_age_group",
56
+ "timeline",
57
+ "lesson_setting",
58
+ "new_qualified"
59
+ ]
60
+ },
61
+ "description": "Which fields actually changed value. Closed enum: source/campaign_code/landing_url are immutable provenance and not amendable; phone/email are identity-bearing and out of scope at v1 (see header description). Producers SHALL NOT emit intake.amended when no enumerated field changed."
62
+ },
63
+ "submission": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": [
67
+ "first_name",
68
+ "last_name",
69
+ "phone",
70
+ "zip"
71
+ ],
72
+ "description": "Full post-amendment submission snapshot. Same shape as intake.captured-v2.submission. Carries the new value for every field including those that did not change, so consumers can apply the whole block without joining back to producer state. Phone is required and present (because it was on the original capture) but is not amendable through this event.",
73
+ "properties": {
74
+ "first_name": {
75
+ "type": "string",
76
+ "minLength": 1,
77
+ "description": "Given name after amendment."
78
+ },
79
+ "last_name": {
80
+ "type": "string",
81
+ "minLength": 1,
82
+ "description": "Family name after amendment."
83
+ },
84
+ "phone": {
85
+ "type": "string",
86
+ "minLength": 1,
87
+ "description": "Phone number in Growth's normalized display format. Carried for parity with intake.captured-v2 but unchanged from the original capture (phone amendments are out of scope at v1)."
88
+ },
89
+ "zip": {
90
+ "type": "string",
91
+ "pattern": "^[0-9]{5}$",
92
+ "description": "Five-digit ZIP code after amendment. The pattern matches intake.captured-v2.submission.zip; producers SHALL coerce / validate before emit so consumers can rely on the constraint."
93
+ },
94
+ "email": {
95
+ "type": ["string", "null"],
96
+ "format": "email",
97
+ "description": "Optional email. Carried for snapshot parity; not amendable through this event."
98
+ },
99
+ "dob": {
100
+ "type": ["string", "null"],
101
+ "format": "date",
102
+ "description": "Optional date of birth after amendment."
103
+ },
104
+ "student_age_group": {
105
+ "type": ["string", "null"],
106
+ "description": "Submitted age bracket of the prospective student after amendment. Same flexible-string shape as intake.captured-v2.submission.student_age_group."
107
+ },
108
+ "timeline": {
109
+ "type": ["string", "null"],
110
+ "description": "When the prospect wants to start lessons, after amendment. Same shape as intake.captured-v2.submission.timeline."
111
+ },
112
+ "lesson_setting": {
113
+ "type": ["string", "null"],
114
+ "description": "Preferred lesson setting / format after amendment. Same shape as intake.captured-v2.submission.lesson_setting."
115
+ },
116
+ "new_qualified": {
117
+ "type": ["string", "null"],
118
+ "enum": ["new", "qualified", null],
119
+ "description": "Stage hint after amendment. Same closed enum as intake.captured-v2.submission.new_qualified."
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }