@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
package/contracts/coaching-confirmation/schema/payloads/coaching.lesson.confirmation_decided-v1.json
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://contracts.sguild/coaching-confirmation/schema/payloads/coaching.lesson.confirmation_decided-v1.json",
|
|
4
|
+
"title": "coaching.lesson.confirmation_decided payload v1",
|
|
5
|
+
"description": "Payload for the coaching.lesson.confirmation_decided event_type at schema_version 1. Coaching emits this after deciding a Sales lead.coach.confirmation.requested event. The envelope subject is confirmation_request_id. The payload intentionally excludes customer contact details, full Lead notes, Guardian details, and coach private contact details.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"organization_id",
|
|
10
|
+
"lead_id",
|
|
11
|
+
"confirmation_request_id",
|
|
12
|
+
"coach_id",
|
|
13
|
+
"decision",
|
|
14
|
+
"denial_reason",
|
|
15
|
+
"lesson_window",
|
|
16
|
+
"service_area_id",
|
|
17
|
+
"lesson_type_id",
|
|
18
|
+
"decided_at",
|
|
19
|
+
"decided_by",
|
|
20
|
+
"notes"
|
|
21
|
+
],
|
|
22
|
+
"properties": {
|
|
23
|
+
"organization_id": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Organization scope copied from the Sales confirmation request.",
|
|
26
|
+
"minLength": 1,
|
|
27
|
+
"maxLength": 128
|
|
28
|
+
},
|
|
29
|
+
"lead_id": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Sales Lead identifier copied from the request.",
|
|
32
|
+
"pattern": "^lead_[A-Za-z0-9._:-]+$",
|
|
33
|
+
"maxLength": 160
|
|
34
|
+
},
|
|
35
|
+
"confirmation_request_id": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Sales-minted idempotency and correlation key. Mirrors the event envelope subject.",
|
|
38
|
+
"minLength": 1,
|
|
39
|
+
"maxLength": 160
|
|
40
|
+
},
|
|
41
|
+
"coach_id": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Coach identifier Coaching evaluated.",
|
|
44
|
+
"pattern": "^coa_[A-Za-z0-9._:-]+$",
|
|
45
|
+
"maxLength": 160
|
|
46
|
+
},
|
|
47
|
+
"decision": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": ["confirmed", "denied"],
|
|
50
|
+
"description": "Coaching's decision for the requested coach and lesson window."
|
|
51
|
+
},
|
|
52
|
+
"denial_reason": {
|
|
53
|
+
"type": ["string", "null"],
|
|
54
|
+
"enum": [
|
|
55
|
+
"coach_unavailable",
|
|
56
|
+
"capacity_changed",
|
|
57
|
+
"coverage_mismatch",
|
|
58
|
+
"certification_mismatch",
|
|
59
|
+
"operator_declined",
|
|
60
|
+
"other",
|
|
61
|
+
null
|
|
62
|
+
],
|
|
63
|
+
"description": "Required when decision=denied. Null when decision=confirmed."
|
|
64
|
+
},
|
|
65
|
+
"lesson_window": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"additionalProperties": false,
|
|
68
|
+
"required": ["start", "end"],
|
|
69
|
+
"properties": {
|
|
70
|
+
"start": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"format": "date-time",
|
|
73
|
+
"description": "Requested lesson start, UTC ISO 8601."
|
|
74
|
+
},
|
|
75
|
+
"end": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"format": "date-time",
|
|
78
|
+
"description": "Requested lesson end, UTC ISO 8601."
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"service_area_id": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Service area copied from the request.",
|
|
85
|
+
"minLength": 1,
|
|
86
|
+
"maxLength": 128
|
|
87
|
+
},
|
|
88
|
+
"lesson_type_id": {
|
|
89
|
+
"type": ["string", "null"],
|
|
90
|
+
"description": "Lesson type copied from the request when Sales supplied one.",
|
|
91
|
+
"maxLength": 128
|
|
92
|
+
},
|
|
93
|
+
"decided_at": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"format": "date-time",
|
|
96
|
+
"description": "When Coaching made the decision, UTC ISO 8601."
|
|
97
|
+
},
|
|
98
|
+
"decided_by": {
|
|
99
|
+
"type": ["string", "null"],
|
|
100
|
+
"description": "Operator id when human-decided. Null only for a future system path.",
|
|
101
|
+
"maxLength": 160
|
|
102
|
+
},
|
|
103
|
+
"notes": {
|
|
104
|
+
"type": ["string", "null"],
|
|
105
|
+
"description": "Optional operator context. Not for PII or state-machine logic.",
|
|
106
|
+
"maxLength": 500
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"allOf": [
|
|
110
|
+
{
|
|
111
|
+
"if": {
|
|
112
|
+
"properties": { "decision": { "const": "confirmed" } },
|
|
113
|
+
"required": ["decision"]
|
|
114
|
+
},
|
|
115
|
+
"then": {
|
|
116
|
+
"properties": { "denial_reason": { "type": "null" } }
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"if": {
|
|
121
|
+
"properties": { "decision": { "const": "denied" } },
|
|
122
|
+
"required": ["decision"]
|
|
123
|
+
},
|
|
124
|
+
"then": {
|
|
125
|
+
"properties": {
|
|
126
|
+
"denial_reason": {
|
|
127
|
+
"type": "string",
|
|
128
|
+
"enum": [
|
|
129
|
+
"coach_unavailable",
|
|
130
|
+
"capacity_changed",
|
|
131
|
+
"coverage_mismatch",
|
|
132
|
+
"certification_mismatch",
|
|
133
|
+
"operator_declined",
|
|
134
|
+
"other"
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"required": ["denial_reason"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
package/contracts/coaching-confirmation/schema/payloads/lead.coach.confirmation.requested-v1.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://contracts.sguild/coaching-confirmation/schema/payloads/lead.coach.confirmation.requested-v1.json",
|
|
4
|
+
"title": "lead.coach.confirmation.requested payload v1",
|
|
5
|
+
"description": "Payload for the lead.coach.confirmation.requested event_type at schema_version 1. Sales emits this when a Lead enters the coach-confirmation workflow and asks Coaching to confirm or deny a proposed coach and lesson window. The envelope subject is confirmation_request_id. The payload intentionally excludes customer contact details, full Lead notes, Guardian details, and coach private contact details.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"organization_id",
|
|
10
|
+
"lead_id",
|
|
11
|
+
"confirmation_request_id",
|
|
12
|
+
"coach_id",
|
|
13
|
+
"service_area_id",
|
|
14
|
+
"lesson_type_id",
|
|
15
|
+
"lesson_window",
|
|
16
|
+
"required_certifications",
|
|
17
|
+
"requested_at",
|
|
18
|
+
"source_pointer"
|
|
19
|
+
],
|
|
20
|
+
"properties": {
|
|
21
|
+
"organization_id": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Organization scope for the confirmation request.",
|
|
24
|
+
"minLength": 1,
|
|
25
|
+
"maxLength": 128
|
|
26
|
+
},
|
|
27
|
+
"lead_id": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Sales Lead identifier for the Lead entering coach confirmation.",
|
|
30
|
+
"pattern": "^lead_[A-Za-z0-9._:-]+$",
|
|
31
|
+
"maxLength": 160
|
|
32
|
+
},
|
|
33
|
+
"confirmation_request_id": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Sales-minted idempotency and correlation key. Mirrors the event envelope subject and is echoed by coaching.lesson.confirmation_decided.",
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"maxLength": 160
|
|
38
|
+
},
|
|
39
|
+
"coach_id": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Coach identifier Sales proposes for the lesson.",
|
|
42
|
+
"pattern": "^coa_[A-Za-z0-9._:-]+$",
|
|
43
|
+
"maxLength": 160
|
|
44
|
+
},
|
|
45
|
+
"service_area_id": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Service area for the proposed lesson.",
|
|
48
|
+
"minLength": 1,
|
|
49
|
+
"maxLength": 128
|
|
50
|
+
},
|
|
51
|
+
"lesson_type_id": {
|
|
52
|
+
"type": [
|
|
53
|
+
"string",
|
|
54
|
+
"null"
|
|
55
|
+
],
|
|
56
|
+
"description": "Lesson type for the proposed lesson when Sales has one.",
|
|
57
|
+
"maxLength": 128
|
|
58
|
+
},
|
|
59
|
+
"lesson_window": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"additionalProperties": false,
|
|
62
|
+
"required": [
|
|
63
|
+
"start",
|
|
64
|
+
"end"
|
|
65
|
+
],
|
|
66
|
+
"properties": {
|
|
67
|
+
"start": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"format": "date-time",
|
|
70
|
+
"description": "Requested lesson start, UTC ISO 8601."
|
|
71
|
+
},
|
|
72
|
+
"end": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"format": "date-time",
|
|
75
|
+
"description": "Requested lesson end, UTC ISO 8601."
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"required_certifications": {
|
|
80
|
+
"type": [
|
|
81
|
+
"array",
|
|
82
|
+
"null"
|
|
83
|
+
],
|
|
84
|
+
"description": "Certification keys Sales believes are required for this lesson. Null when none are required or Sales has no source-backed certification requirement.",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"minLength": 1,
|
|
88
|
+
"maxLength": 128
|
|
89
|
+
},
|
|
90
|
+
"uniqueItems": true
|
|
91
|
+
},
|
|
92
|
+
"requested_at": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"format": "date-time",
|
|
95
|
+
"description": "When Sales created the confirmation request, UTC ISO 8601."
|
|
96
|
+
},
|
|
97
|
+
"source_pointer": {
|
|
98
|
+
"type": [
|
|
99
|
+
"object",
|
|
100
|
+
"null"
|
|
101
|
+
],
|
|
102
|
+
"description": "Optional Sales-side source pointer for audit. This points to the local action or workflow source, not customer-visible content.",
|
|
103
|
+
"additionalProperties": false,
|
|
104
|
+
"required": [
|
|
105
|
+
"source_type",
|
|
106
|
+
"source_id"
|
|
107
|
+
],
|
|
108
|
+
"properties": {
|
|
109
|
+
"source_type": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "Sales source kind, for example manual_action, stage_transition, or cadence_runtime.",
|
|
112
|
+
"minLength": 1,
|
|
113
|
+
"maxLength": 80
|
|
114
|
+
},
|
|
115
|
+
"source_id": {
|
|
116
|
+
"type": "string",
|
|
117
|
+
"description": "Sales-local source row or workflow id.",
|
|
118
|
+
"minLength": 1,
|
|
119
|
+
"maxLength": 160
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Credit Reservation Funding State
|
|
2
|
+
|
|
3
|
+
**Status:** v1.0.0
|
|
4
|
+
**Date:** 2026-05-05
|
|
5
|
+
**Owner:** revenue (credit-reservations module, credit-ledger-entries module, payment-adapters module)
|
|
6
|
+
**Consumers:** revenue (own funding-state reconciliation runbook reads these events for cross-checking provider-side state against canonical commercial state), platform-warehouse (analytics ingestion). No other domain subscribes at v1: Sales gates Lead-close on `customer.handoff` not on funding sub-state per `coordination/contracts/credit-reservation-lock/README.md` §5; Delivery's lock-state subscriber consumes `credit.funded` for the funded-by-credit-availability invariant per Delivery's reply on `2026-05-02-delivery-funding-event-shape-and-payment-refund-acks`; Coaching's lock-aware availability projection keys on lock state per `coach-availability` v1.0.1 §4.3 not on funding sub-state; Growth subscribes only to `payment.received` per its parent-thread reply.
|
|
7
|
+
**Related ADRs:** ADR-0001 (tenant_id), ADR-0002 (canonical entity ID template; `crr_` and `per_` prefixes), ADR-0003 (Person canonical, Revenue does not own Person), ADR-0005 (event envelope), ADR-0006 (credit-reservation-lock state machine; this contract is a sibling that extends Revenue's producer surface without amending the lock state machine), ADR-0009 (dispatcher transport, producer transactional guarantee), ADR-0010 (provider externals at Platform; Square is the v1 provider for the payment-processor reference)
|
|
8
|
+
**Related contracts:** Event Envelope Contract (`../event-envelope/README.md`), Credit Reservation Lock Contract (`../credit-reservation-lock/README.md`), Payment Flow Contract (`../payment-flow/README.md`), Refund Flow Contract (`../refund-flow/README.md`)
|
|
9
|
+
**Sub-specs (authoritative):** `schema/payloads/reservation.funded-v1.json`, `schema/payloads/reservation.refunding-v1.json`, `schema/payloads/reservation.refunded-v1.json`
|
|
10
|
+
**Validations:** none yet
|
|
11
|
+
|
|
12
|
+
## 1. Purpose and scope
|
|
13
|
+
|
|
14
|
+
This contract specifies the producer responsibilities and consumer-visible payload shape for Revenue's three funding-sub-state events: `reservation.funded`, `reservation.refunding`, `reservation.refunded`. The events are produced by Revenue at the writeback transaction commit for funding-sub-state transitions on a credit reservation, with the payment-processor reference carried on every payload per the funding-state external-reference rule in `coordination/domains/revenue.md`. All three fire under the producer-transactional-guarantee shape from ADR-0009 (dispatcher.publish inside the same Prisma transaction as the funding-state transition's row write).
|
|
15
|
+
|
|
16
|
+
This contract is the Shape A landing per `2026-05-05-revenue-funding-event-shape-decided-shape-a`, converging the `reservation.*` family decision unanimously endorsed by Delivery, Sales, Growth, Coaching, and Platform on the parent thread (`2026-05-02-revenue-emit-wiring-registry-additions`). The architectural shape: lock-state semantics stay on the credit-reservation-lock contract, the funding-sub-state surface lives here as a sibling, and the cross-domain consumer surface stays clean (lock-state events to broad consumers, funding sub-state to Revenue + warehouse only).
|
|
17
|
+
|
|
18
|
+
In scope: the three event names, the payload shapes at v1, the producer's transactional-guarantee discipline, the consumer-visible enum partitions on `funding_source` and `refund_reason`, the payment-processor reference fields, idempotency expectations, and the versioning policy under additive discipline.
|
|
19
|
+
|
|
20
|
+
Out of scope: the lock state machine and its `reserved -> locked` transition (covered by `credit-reservation-lock` §3-§5; lock-state semantics live there); the credit-purchase flow that creates funded balances on credit accounts (covered by `credit-reservation-lock` §9.1 with `credit.purchased`); payment provider adapter wire-format and webhook handling (lives in `revenue/src/lib/providers/square/` and the upstream external-actions module per ADR-0011); the payment-flow and refund-flow events that fire on the order side (covered by sibling contracts `payment-flow` and `refund-flow`); revenue recognition (Revenue-internal, see `domains/revenue.md`).
|
|
21
|
+
|
|
22
|
+
## 2. Normative language
|
|
23
|
+
|
|
24
|
+
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted per RFC 2119.
|
|
25
|
+
|
|
26
|
+
## 3. Terminology
|
|
27
|
+
|
|
28
|
+
**Funding sub-state.** A property of a credit reservation in the `reserved` lifecycle state, separate from the lifecycle state itself. Values: `pending_funding`, `funded`, `refunding`, `refunded`. The funding sub-state is what the lock-state machine's T-24h lock check reads to decide `reserved -> locked` vs `reserved -> released (system_unpaid)` per credit-reservation-lock §4.2. This contract emits events on the three transitions out of `pending_funding`: `pending_funding -> funded` (`reservation.funded`), `funded -> refunding` (`reservation.refunding`), and `refunding -> refunded` (`reservation.refunded`).
|
|
29
|
+
|
|
30
|
+
**Funding-state writeback.** The Revenue-internal transaction that records a funding-sub-state transition: the credit-reservation funding-status update, optional credit-ledger-entry adjustments, the payment-processor reference snapshot, and the dispatcher.publish for the corresponding event. Distinct from the **provider call** (the payment-flow or refund-flow operation that motivates the funding-state transition).
|
|
31
|
+
|
|
32
|
+
**Payment-processor reference.** The provider-side identifier that links a funding-state transition back to the payment provider's record. Required on every payload per the funding-state external-reference rule in `domains/revenue.md`. Format: `<provider>:<provider_ref>` where provider is one of `square`, `stripe`, `manual` (for cash/check/credit_balance internal reconciliations) and provider_ref is the provider's own id. The shape mirrors the payment-flow `provider_ref` field but adds the explicit provider prefix because the funding-state surface is multi-provider over time and the audit trail benefits from explicit provider attribution.
|
|
33
|
+
|
|
34
|
+
**Funding source.** The path through which the reservation became funded. Enum partition per §4.4. Used by Revenue's reconciliation runbook to route by source-specific verification rules; warehouse consumes it as a dimensional join on the funding-event grain.
|
|
35
|
+
|
|
36
|
+
**Refund reason.** The cancellation or operator action that triggered the refund-flow against the reservation's funded balance. Enum partition per §4.5. Mirrors the `reason_code` enum on `credit.released` v2 per ADR-0006 amended (the `_in_window` vs `_exception` partition lands here too) so reconciliation can join the credit.released event and the reservation.refunding event on the same logical operator decision.
|
|
37
|
+
|
|
38
|
+
## 4. Event types
|
|
39
|
+
|
|
40
|
+
### 4.1 reservation.funded
|
|
41
|
+
|
|
42
|
+
Emitted by Revenue at the writeback transaction commit when a credit reservation's funding sub-state transitions from `pending_funding` to `funded`. The corresponding upstream event is the payment-flow's `payment.received` (the customer paid, and the order's owning credit account now has the funded credits available); this contract's `reservation.funded` fires when the per-reservation funding sub-state on a specific reservation flips, which happens at credit-purchase commit time when the reservation is the next-up claim against the newly-purchased credits.
|
|
43
|
+
|
|
44
|
+
The publish lives inside the same `prisma.$transaction` as the credit-reservation funding-status update per ADR-0009. Either the writeback commits and the event fires, or the writeback rolls back and no event fires.
|
|
45
|
+
|
|
46
|
+
Payload v1: `credit_reservation_id`, `person_id`, `funding_source`, `payment_processor_provider`, `payment_processor_ref`, `funded_at`. See `schema/payloads/reservation.funded-v1.json` for the authoritative shape and field constraints.
|
|
47
|
+
|
|
48
|
+
The `funding_source` field is partitioned per §4.4 and disambiguates whether the funding came from a Square invoice payment, an active charge, a cash/check operator-recorded payment, an internal credit-balance application, or a refund recovery (the rare path where a previously-refunded reservation gets re-funded by a follow-up purchase).
|
|
49
|
+
|
|
50
|
+
### 4.2 reservation.refunding
|
|
51
|
+
|
|
52
|
+
Emitted by Revenue at the writeback transaction commit when a credit reservation's funding sub-state transitions from `funded` to `refunding`. The transition typically follows the lock-state machine's `released` event (with `credit.released` v2 carrying the `reason_code`); this contract's `reservation.refunding` is the funding-side mirror that captures the refund-flow's intent before the refund completes against the provider.
|
|
53
|
+
|
|
54
|
+
The pairing: a `credit.released` event on the credit-reservation-lock contract (lock-state side) plus a `reservation.refunding` event on this contract (funding-state side) together describe the same operator decision viewed from two angles. Subscribers that care about lock state (Delivery, Coaching) read the lock event; subscribers that care about funding state (Revenue's reconciliation, warehouse) read this event. The two events are emitted in the same Prisma transaction when both apply.
|
|
55
|
+
|
|
56
|
+
Not all `credit.released` transitions emit `reservation.refunding`. A reservation released for `reason_code = customer_requested_in_window` with the credits going back to the credit-account balance (no provider-side refund) does not emit `reservation.refunding`; the funding sub-state stays at `funded` until the credits are re-claimed by another reservation. A reservation released for `reason_code = customer_requested_exception` with the credits being refunded to the customer's payment method DOES emit `reservation.refunding` because the funding-state machine transitions to `refunding`.
|
|
57
|
+
|
|
58
|
+
Payload v1: `credit_reservation_id`, `person_id`, `refund_reason`, `payment_processor_provider`, `payment_processor_ref`, `refund_amount_cents`, `currency`, `refunding_at`. See `schema/payloads/reservation.refunding-v1.json` for the authoritative shape.
|
|
59
|
+
|
|
60
|
+
### 4.3 reservation.refunded
|
|
61
|
+
|
|
62
|
+
Emitted by Revenue at the writeback transaction commit when a credit reservation's funding sub-state transitions from `refunding` to `refunded`. The corresponding upstream event is the refund-flow's `refund.completed` (the provider has settled the refund); this contract's `reservation.refunded` fires when the per-reservation funding sub-state on a specific reservation reflects the completed refund, which happens at the refund-completion writeback.
|
|
63
|
+
|
|
64
|
+
Payload v1: `credit_reservation_id`, `person_id`, `refund_reason`, `payment_processor_provider`, `payment_processor_ref`, `refund_amount_cents`, `currency`, `refunding_at`, `refunded_at`. The `refunding_at` field on this payload SHALL match the corresponding `reservation.refunding` event's `refunding_at` so consumers correlating the two via the funding-state machine have a stable join key. See `schema/payloads/reservation.refunded-v1.json` for the authoritative shape.
|
|
65
|
+
|
|
66
|
+
### 4.4 funding_source partition
|
|
67
|
+
|
|
68
|
+
The `funding_source` enum at v1 covers the paths through which a reservation becomes funded. Consumers SHALL treat unknown values as safe-to-ignore per the additive-discipline in §8:
|
|
69
|
+
|
|
70
|
+
- `invoice_paid`. Customer paid against a Square invoice; the payment-flow's `payment.received` event fired with `payment_path = invoice_paid` upstream.
|
|
71
|
+
- `active_charge`. Customer's payment-method-on-file was charged via a Revenue-initiated Square call; the payment-flow's `payment.received` event fired with `payment_path = active_charge` upstream.
|
|
72
|
+
- `cash`. Operator recorded a cash payment in person at a Sguild facility; the external-actions row records the operator who handled it.
|
|
73
|
+
- `check`. Operator recorded a check payment; the external-actions row records the check number and the operator.
|
|
74
|
+
- `credit_balance`. The reservation was funded from existing credit-account balance without a provider call (the customer had previously-purchased credits; this reservation drew from that pool).
|
|
75
|
+
- `refund_recovery`. Rare: a previously-refunded reservation got re-funded because the customer made a follow-up purchase that the reconciliation runbook routed back to the original reservation. Mostly an audit-trail value; the warehouse model dimensionally distinguishes recovered fundings from primary fundings.
|
|
76
|
+
|
|
77
|
+
### 4.5 refund_reason partition
|
|
78
|
+
|
|
79
|
+
The `refund_reason` enum at v1 mirrors the `reason_code` enum on `credit.released` v2 per ADR-0006 amended. The two enums are co-versioned: a value that lands on `credit.released` v2 SHALL also land here at the same version, and vice versa. This co-versioning is the seam that lets reconciliation join the lock-state event and the funding-state event on the same operator decision.
|
|
80
|
+
|
|
81
|
+
Values at v1: `site_closure`, `coach_unavailable_reschedule_failed`, `force_majeure`, `weather`, `administrative_void`, `customer_requested_in_window`, `customer_requested_exception`, `policy_exception`, `bad_debt_writeoff`. See `credit-reservation-lock` §6 and §9.6 for the per-value semantics; this contract does not duplicate those definitions. Consumers SHALL treat unknown values as safe-to-ignore per the additive-discipline.
|
|
82
|
+
|
|
83
|
+
Note: not every `refund_reason` value triggers a `reservation.refunding` event. The auto-release subset (`site_closure`, `coach_unavailable_reschedule_failed`, `force_majeure`, `weather`, `administrative_void`, `customer_requested_in_window`) routes credits back to the credit-account balance without a provider-side refund, in which case the funding sub-state stays at `funded` and no `reservation.refunding` event fires. The explicit-operator subset (`customer_requested_exception`, `policy_exception`, `bad_debt_writeoff`) typically routes through a provider-side refund and DOES emit `reservation.refunding`. The `funding_source` enum on the prior `reservation.funded` event determines whether a provider-side refund is even possible (a `credit_balance`-funded reservation cannot be refunded to a payment method; it routes back to the credit-account balance regardless of `refund_reason`).
|
|
84
|
+
|
|
85
|
+
## 5. Producer responsibilities
|
|
86
|
+
|
|
87
|
+
Revenue SHALL emit `reservation.funded` exactly once per reservation when its funding sub-state transitions from `pending_funding` to `funded`. Idempotency is enforced at the Prisma transaction level: the writeback transaction is keyed by the credit-reservation id, and a duplicate writeback against the same `credit_reservation_id` with funding sub-state already at `funded` no-ops at the upstream funding-state-machine validation rather than re-emitting.
|
|
88
|
+
|
|
89
|
+
Revenue SHALL emit `reservation.refunding` and `reservation.refunded` per the funding-state machine transitions described in §4.2 and §4.3. The two events fire in separate Prisma transactions: `reservation.refunding` at refund-flow initiation (paired with `refund.initiated` on the refund-flow contract), `reservation.refunded` at refund-flow completion (paired with `refund.completed` on the refund-flow contract).
|
|
90
|
+
|
|
91
|
+
Revenue SHALL emit all three events inside the same Prisma transaction as the credit-reservation funding-status row update per ADR-0009's producer-transactional-guarantee shape. The publish call is the last statement in the transaction by convention, so any earlier rollback prevents the event from firing.
|
|
92
|
+
|
|
93
|
+
Revenue SHALL populate `payment_processor_provider` and `payment_processor_ref` on every emit. The funding-state external-reference rule in `domains/revenue.md` requires both fields. For `funding_source` values that have no provider call (`cash`, `check`, `credit_balance`), `payment_processor_provider` SHALL be `manual` and `payment_processor_ref` SHALL be the external-actions row id, prefixed with `ext_` to distinguish from provider-issued ids. The consistent shape across providers is what makes the warehouse's dimensional join on (provider, provider_ref) work.
|
|
94
|
+
|
|
95
|
+
Revenue SHALL NOT emit any of these events if the upstream payment-flow or refund-flow event did not fire. The funding-state events are downstream of payment/refund settlement; an emit without the corresponding upstream fact is a producer-side bug.
|
|
96
|
+
|
|
97
|
+
Revenue SHALL preserve the `refunding_at` value across `reservation.refunding` and `reservation.refunded` for the same refund-flow instance, so consumers correlating the two events have a stable join key.
|
|
98
|
+
|
|
99
|
+
## 6. Consumer responsibilities
|
|
100
|
+
|
|
101
|
+
Consumers SHALL handle each of the three events independently; the funding-state machine's transitions are emitted at separate transaction commits and consumers SHALL NOT presume a temporal ordering beyond the natural funded -> refunding -> refunded sequence within a single reservation.
|
|
102
|
+
|
|
103
|
+
Consumers SHALL treat unknown values for the `funding_source` and `refund_reason` enums as safe-to-ignore per the additive-discipline in §8. The closed-set guarantee is at v1 publish time; future v1.x versions may add values without a consumer cutover.
|
|
104
|
+
|
|
105
|
+
Consumers SHALL treat the `reservation.refunding` and `reservation.refunded` event pair as a refund-flow-instance-correlated pair, joining on `(credit_reservation_id, refunding_at)`. A `reservation.refunded` without a prior `reservation.refunding` for the same `(credit_reservation_id, refunding_at)` pair is a reconciliation case that the consumer SHOULD surface but SHALL NOT block on.
|
|
106
|
+
|
|
107
|
+
### 6.1 Revenue's own consumption (reconciliation)
|
|
108
|
+
|
|
109
|
+
Revenue's funding-state reconciliation runbook reads all three events to cross-check provider-side state against canonical commercial state. The flow:
|
|
110
|
+
|
|
111
|
+
For each `reservation.funded` event, the runbook verifies the corresponding `payment.received` (or `payment.failed` followed by a recovery `payment.received`) exists on the payment-flow event stream with matching `(payment_processor_provider, payment_processor_ref)`. Drift surfaces as a runbook-flagged case the operator resolves manually.
|
|
112
|
+
|
|
113
|
+
For each `reservation.refunding` event, the runbook verifies the corresponding `refund.initiated` exists on the refund-flow event stream with matching reference. Drift surfaces as above.
|
|
114
|
+
|
|
115
|
+
For each `reservation.refunded` event, the runbook verifies the corresponding `refund.completed` exists on the refund-flow event stream. Drift surfaces as above.
|
|
116
|
+
|
|
117
|
+
The reconciliation is observational; Revenue does not gate any state transition on the consumer-side processing of these events from its own seat. The producer-transactional-guarantee on the producer side is the load-bearing correctness primitive; this consumer-side reconciliation is the audit-trail surface that catches the rare cases where producer-side discipline got bypassed (e.g., a manual SQL edit on a reservation row that didn't fire through the state machine).
|
|
118
|
+
|
|
119
|
+
### 6.2 platform-warehouse
|
|
120
|
+
|
|
121
|
+
The platform-warehouse semantic-layer model picks up all three events as facts on a funding-event grain. Dimensional joins on `payment_processor_provider`, `payment_processor_ref`, `funding_source`, `refund_reason`, and the time fields. The model correlates with the payment-flow and refund-flow facts via the shared `payment_processor_ref`, and with the credit-reservation-lock facts via `credit_reservation_id`. Warehouse usage is observability-only; Platform does not gate any state transition on these events per Platform's parent-thread reply on `2026-05-02-platform-revenue-emit-wiring-acks-and-shape-a`.
|
|
122
|
+
|
|
123
|
+
## 7. Idempotency and dedup
|
|
124
|
+
|
|
125
|
+
The producer-transactional-guarantee plus the funding-state-machine's idempotency keys (each transition is keyed by the credit-reservation id and the from-state) prevent double-emits at the producer side. At the consumer side, the dispatcher SDK's per-(consumer, event_id) dedup per the Phase 2 ship memo (`2026-05-09-platform-dispatcher-sdk-phase-2-shipped` Slice 3) handles redelivery semantics; consumers do not need application-level dedup against these events at v1.
|
|
126
|
+
|
|
127
|
+
Future cutover windows where the funding-state-machine emits both v1 and v2 of any event_type would introduce a dual-emit-window dedup case. Per Revenue's pattern-positions reply (`2026-05-05-revenue-engineering-method-patterns-positions` §4), consumers SHOULD be prepared to add payload-composite dedup at that point. No such window is in flight today.
|
|
128
|
+
|
|
129
|
+
## 8. Versioning policy
|
|
130
|
+
|
|
131
|
+
This contract follows the same versioning policy as `credit-reservation-lock` and the sibling payment-flow / refund-flow contracts. Per-event-type `schema_version` is independent across the three event types. Additive discipline holds: a new optional field, a new enum value on `funding_source` or `refund_reason`, or a new event type can be added at v1.x without a cutover; a breaking change increments to v2 with a deprecation window per the operator standard "no consumer running against a deprecated contract version for more than two weeks."
|
|
132
|
+
|
|
133
|
+
The `refund_reason` enum is co-versioned with `credit.released`'s `reason_code` enum on the credit-reservation-lock contract per §4.5. A v2 of either enum forces a coordinated v2 of the other; the joint-review pattern lands the amendment per `2026-05-08-delivery-reason-codes-signoff-accepted`'s precedent.
|
|
134
|
+
|
|
135
|
+
## 9. Producer's emit-site map
|
|
136
|
+
|
|
137
|
+
The Revenue-side call sites for the three events are documented in the Revenue repo's emit-wiring inventory at `revenue/docs/dispatcher-emit-wiring.md`. Summary for cross-domain readers:
|
|
138
|
+
|
|
139
|
+
`reservation.funded` fires from the credit-ledger-entries module's funding-state transition handler, inside the same Prisma transaction as the credit-reservation funding-status update from `pending_funding` to `funded`. The transition is triggered upstream by either a `payment.received` arrival (for invoice_paid / active_charge sources) or a credit-purchase commit (for credit_balance source) or an operator action (for cash / check sources).
|
|
140
|
+
|
|
141
|
+
`reservation.refunding` fires from the refunds module's refund-initiation handler, inside the same Prisma transaction as the credit-reservation funding-status update from `funded` to `refunding`. Paired with the refund-flow's `refund.initiated` event in the same transaction.
|
|
142
|
+
|
|
143
|
+
`reservation.refunded` fires from the refunds module's refund-completion handler, inside the same Prisma transaction as the credit-reservation funding-status update from `refunding` to `refunded`. Paired with the refund-flow's `refund.completed` event in the same transaction.
|
|
144
|
+
|
|
145
|
+
## 10. Changelog
|
|
146
|
+
|
|
147
|
+
- **v1.0.0** (2026-05-05) — Initial release. Three event types (`reservation.funded`, `reservation.refunding`, `reservation.refunded`) with the funding_source and refund_reason enum partitions, the payment-processor reference fields per the funding-state external-reference rule, the producer-transactional-guarantee discipline mirroring credit-reservation-lock and payment-flow / refund-flow, and the additive-discipline versioning policy. Lands per `2026-05-05-revenue-funding-event-shape-decided-shape-a` after unanimous Shape A endorsement on the parent thread.
|