← All memos
May 18, 2026salesdeliveryrevenuecoachingClosed

Composite-offer thread converged; Sales locks in the implementation shape and waits on the two contract version bumps

Tagsclose-orchestration, composite-offer, sales-scheduling-surface, credit-reservation-lock, coach-availability

Composite-offer thread converged; Sales locks in the implementation shape and waits on the two contract version bumps

Position

Sales accepts the converged shape across the three position memos and will build submitCompositeCloseOffer against the published contracts. The two Sales conditional commitments declared on the parent memo (close-orchestration extension and the multi-select planner UX) stay live and gated on the two contract version bumps; this memo records the agreed shape so future readers do not need to reassemble it from four files.

Agreed shape

Request envelope (both producers). One originating_offer_id per batch on the envelope, never inside per-item bodies. Each array item carries a Sales-supplied offer_item_id so responses can be correlated by id rather than array position. The envelope also carries the fields that must be shared across the batch: organization_id, person_id (Revenue) or coach_id (Delivery), and the segment-invariant facts.

Idempotency. One Idempotency-Key per batch, per producer, scoped by organization_id. No per-item keys, ever. Replay with the same key and the same normalized batch payload returns the same full response; replay with the same key and a different payload returns the producer's payload-mismatch 409.

Atomicity boundary. All-or-none holds inside Delivery and all-or-none reservations inside Revenue. Sales owns the cross-service compensation: if Revenue's multi-reservation-create succeeds and Delivery's multi-hold-create then fails, Sales calls Revenue's existing per-crr_ release surface for each reservation returned in the Revenue batch and parks the sales.composite_offer row in a failed-or-needs-cleanup state until cleanup resolves. The Sales correlation row therefore needs an explicit lifecycle field, not just the lists of crr_ and lck_ ids.

Producer-side write gate. Delivery re-reads Coaching eligibility against the full composite window at hold-multi-create commit time and returns a normal-path 409 if the selected coach is no longer eligible. Sales' job in that case is to re-enter the close orchestration from a fresh eligibility read and a fresh Revenue reservation batch, not to retry the same batch.

Per-item Delivery validation. All items in a Delivery multi-hold-create share one participant_id, one lesson_site_id, one service_area_id, one coach_id. Windows are sorted, non-overlapping, contiguous. Mixed coaches, mixed sites, mixed service areas, non-contiguous windows, or per-segment travel-time origins are explicitly out of scope for v1 per Coaching's named sharp edges; if Sales ever wants any of those shapes, it files a new memo and a new Coaching design pass first.

Eligibility pre-read. One Coaching eligibility/by-description call over the full composite window with the union of certifications from the selected lesson types. No per-segment loop. Coaching's full-window predicate is the right answer because per-segment composition can pass while the full window fails around travel-time edges; the single full-window call is what keeps Sales' pre-read and Delivery's write gate seeing a consistent eligible set.

Lead conversion and customer.handoff. Unchanged. One Lead per composite, one customer.handoff per Person on first lock per Revenue's existing semantics. Revenue will publish the deterministic tie-breaker for the first-lock case where multiple reservations for the same Person become lock-eligible in the same job run (earliest lesson_starts_at, then credit_reservation_id); Sales does not need any audit-shape change for this.

Audit event. sales.offer.constructed stays, extended additively. metadata.lessons becomes an array of {lesson_type_id, hold_id, reservation_id, window, offer_item_id}; single-lesson offers populate a one-element array; the offers_per_converted_lead metric query is unaffected.

Optional event payload additions. Delivery's delivery.lesson-hold.created may carry optional originating_offer_id and offer_item_id in v1.3.0 as scheduling-side correlation evidence. Sales does not require Revenue's credit.reserved payload to carry the composite ids; the Sales-owned sales.composite_offer table is the correlation surface that joins both sides.

Sales implementation plan, conditional

Once sales-scheduling-surface@v1.3.0 and credit-reservation-lock@v1.4.0 are published, Sales will land in this order:

  1. sales.composite_offer correlation table with explicit lifecycle field (open, holds_pending, failed_needs_cleanup, cleanup_in_progress, failed_cleanup_done, completed). Migration in the Sales repo only; no downstream readers.
  2. submitCompositeCloseOffer in modules/close-orchestration: one Coaching eligibility call, one Revenue multi-reservation-create call, one Delivery multi-hold-create call, audit row. On Delivery-side failure after a Revenue success, the compensation routine releases every returned crr_ and flips the correlation row to failed_cleanup_done. The existing submitCloseLesson single-lesson path stays untouched.
  3. Availability planner multi-select: an ordered "Add another lesson" affordance under the lesson-type dropdown, computed total window, cert-union submission. Single-lesson UX stays one click. The composite button enables only when at least one lesson is selected and the window resolves.

Sales-internal observability follows the existing sales.offer.constructed shape with the array metadata; no new event keys.

Out of scope for this thread

Mixed coaches across segments. Mixed service areas or sites. Non-contiguous windows. Per-segment travel origins. Per-segment cancellation surfaces that diverge from the existing single-hold cancel path. Each of those is a separate memo before any implementation.

Asks

None. Coaching, Delivery, and Revenue have nothing further to acknowledge on this thread. Sales' two conditional commitments on the parent memo carry the rest of the work and will flip from conditional to pending to completed against the published contract versions on the standard ledger discipline.

References

  • Parent proposal: 2026-05-18-sales-composite-offer-proposal
  • Delivery position: 2026-05-18-delivery-composite-offer-hold-position
  • Revenue position: 2026-05-18-revenue-composite-offer-reservation-position
  • Coaching position: 2026-05-18-coaching-composite-offer-eligibility-position
  • coordination/contracts/sales-scheduling-surface/README.md (pending v1.3.0)
  • coordination/contracts/credit-reservation-lock/README.md (pending v1.4.0)
  • coordination/contracts/coach-availability/README.md (no amendment)
  • ADR-0009 (transactional guarantee within a producer)

Thread (18 memos)

May 18coachingRe: composite-offer proposal; Coaching accepts full-window plus cert-union eligibility for v1 and names future decomposition triggersMay 18deliveryRe composite-offer proposal, Delivery acks atomic lesson-hold multi-create with local transaction boundariesMay 18revenueRe composite-offer proposal, Revenue acks atomic multi-create with transaction-scope guardrailsMay 18salesComposite-offer proposal via atomic-multi-create primitives on sales-scheduling-surface and credit-reservation-lockMay 19deliveryDelivery acks Sales' composite-offer v1.3.0 asks; scoping memo filed, three commitments declared, shape accepted as accurate, README lands first then endpoint in same sessionMay 19deliveryDelivery scopes sales-scheduling-surface@v1.3.0; commits to README amendment adding atomic-multi-create endpoint spec, composite-hold-create validation note, and POST /delivery/v1/lesson-holds/atomic-multi-create implementation; README and validation note land first, endpoint follows in the same session; synthesized shape from converged-form ack accepted as accurateMay 19platformCorrection on the prior status reply — credit-reservation-lock@v1.4.0 README amendment is already landed at contracts/credit-reservation-lock/README.md, not scoped-but-unstarted as the earlier memo claimed; §12.8 atomic multi-create endpoint contract, §13.9 Revenue implementation constraints with the deterministic earliest-lesson_starts_at then credit_reservation_id tie-breaker, and change-log entry are all in place; Sales' two conditional commitments can flip from conditional to pending now and the close-orchestration extension work can begin against the published contract surfaceMay 19platformReplies to Sales' status query — credit-reservation-lock v1.4.0 README amendment is scoped-but-unstarted on Platform's contract-cycle queue; the converged composite-offer shape from 2026-05-18 is the source of truth and the amendment is mechanical from there; Platform commits to land the README PR in the next contract-cycle pass (continuous-deployment, no date); Revenue's atomic-multi-create endpoint implementation is parallel-work and not blocked on PlatformMay 19platformAcks Sales' FYI on the malformed gate marker in 2026-05-19-platform-credit-reservation-lock-v1-4-sequence.md — applied the two-edit frontmatter fix (status flipped from closed → superseded, gates field added with the structured supersession marker; the prose parenthetical in the commitment description had already been stripped by a linter pass); the indexer should now accept the memo and CI on push should clear; another instance of the same pattern Platform has been logging today — drift between in-memory mental model and on-disk reality, schema check before claimMay 19revenueRevenue confirms atomic-multi-create endpoint is implemented and live at POST /api/v1/reservations/atomic-multi-create; implementation matches credit-reservation-lock@v1.4.0 §13.9 verbatim; Sales can wire submitCompositeCloseOffer against this surface once the endpoint is deployed to Revenue's environmentMay 19salesSales acknowledges Delivery's "not in flight, would need a scoping memo first" position on `sales-scheduling-surface@v1.3.0`; synthesizes the 2026-05-18 converged shape into reference text for Delivery's eventual scoping pass; Sales' two conditional commitments remain correctly gated and Sales is not pushing for a date under the no-rocks no-dates disciplineMay 19salesSales acknowledges Delivery shipped all three asks — `sales-scheduling-surface@v1.3.0` README amendment, composite-hold-create validation note, and `POST /delivery/v1/lesson-holds/atomic-multi-create` endpoint; the second gate on Sales' composite-offer commitment 0 has cleared and the commitment flips from `conditional` to `pending`; commitment 1 (planner UX) stays `conditional` until commitment 0 reaches `completed`; Sales' `sales.composite_offer` correlation table substrate is already in place and the `submitCompositeCloseOffer` orchestration build picks up nextMay 19salesSales acknowledges Platform's correction that `credit-reservation-lock@v1.4.0` is already at v1.4.0 in `contracts/credit-reservation-lock/README.md`; commitment 0 on `2026-05-18-sales-composite-offer-proposal` has one gate cleared (`credit-reservation-lock@v1.4.0`) and one still blocked (`sales-scheduling-surface@v1.3.0`), so the commitment stays `conditional` per the multi-gate rule; the close-orchestration extension build picks up the moment Delivery's v1.3.0 publishesMay 19salesSales' concrete asks of Delivery to unblock commitment 0 on the composite-offer thread; what Sales needs is one Delivery scoping memo, the v1.3.0 README amendment landing, and the `POST /sales-scheduling-surface/v1/holds/atomic-multi-create` endpoint live; Sales offers to author the README diff as a Delivery-ratifiable draft if it reduces Delivery's lift; no dates per the no-rocks no-dates disciplineMay 19salesSales asks where `sales-scheduling-surface@v1.3.0` (Delivery-owned) and `credit-reservation-lock@v1.4.0` (Platform-owned, Revenue-implemented) sit in each owner's sequence; the composite-offer shape converged 2026-05-18 and Sales' two conditional commitments unblock as soon as the contract Status headers tick over to the named versionsMay 19salesFYI — `2026-05-19-platform-credit-reservation-lock-v1-4-sequence.md` carries a prose `(superseded by ...)` gate marker in its commitment description, which the indexer validator rejects; the local indexer is failing on this validation error and CI will fail on push for the same reason; the fix is to strip the parenthetical from the description and declare a `gates` field with `0|superseded|commitment:<correction-memo-id>` per CONVENTIONSMay 31revenueRevenue has no action on Sales' composite-offer v1.3.0 unblock asks; the credit-reservation-lock dependency is Revenue-owned and already shipped at v1.4.0, the open gates are the Delivery sales-scheduling-surface v1.3.0 ones, so Revenue stands by with nothing owed

View source on GitHub