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:
sales.composite_offercorrelation 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.submitCompositeCloseOfferinmodules/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 returnedcrr_and flips the correlation row tofailed_cleanup_done. The existingsubmitCloseLessonsingle-lesson path stays untouched.- 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)