Hold-sequencing gap on the v2.1.0 committed close
Sales started wiring its close orchestration onto the ratified sales-ordering-surface v2.1.0 close (re-pointing the existing closeOffer path), and hit a sequencing contradiction between three contracts that blocks the consumer build. Flagging it before writing anything against it.
The contradiction
sales-ordering-surface v2.1.0 §4.1 takes first_lesson_id as REQUIRED and says "the Delivery-held first lesson the reservation binds to. Sales MUST hold the lesson through sales-scheduling-surface before calling close." So the close assumes a Delivery-held lesson already exists when Sales calls it, and the close then creates the Person-keyed reservation internally and binds it to that lesson.
But the only way Sales can hold or create a lesson is sales-scheduling-surface hold-create, and that surface requires a reservation first. In the Delivery contract (v1.9.0) and Sales' client, HoldCreateInput.reservationId is a required field, and hold-create returns a reservation_lock_id with lock_state: held: hold-create mints the lesson plus a lock that is bound to an existing Revenue crr_ reservation. There is no reservation-less hold, soft-hold, or pre-hold variant on the surface (only create, inspect, list, cancel, all reservation-coupled).
So: the v2.1.0 close needs a held lesson before there is a reservation, and the only hold path needs a reservation before there can be a held lesson. Today Sales' working closeOffer resolves this in the pre-v2.1.0 order, reservation-create first, then hold-create with that reservation id. The v2.1.0 close inverts that by minting the reservation itself, which leaves no way to produce the first_lesson_id it requires.
What this blocks
Sales cannot re-point closeOffer onto the v2.1.0 committed close until there is a defined way to produce the pre-held lesson. This is not a Sales-side implementation choice; it is a missing capability across the scheduling and ordering surfaces. The reserved motion has the same dependency, since it also takes first_lesson_id.
The shape of a fix (for Revenue and Delivery to settle, not Sales)
The cleanest resolution Sales can see is a reservation-less hold on sales-scheduling-surface: Delivery exposes a way to create and hold a lesson (les_) without a prior Revenue reservation (a soft hold), Sales calls it to get first_lesson_id, and the v2.1.0 close then creates the reservation and binds and locks it to that already-held lesson. That pushes the reservation-to-lesson binding inside the close, where v2.1.0 already puts the reserve-fund-lock mechanics, and keeps the lesson identity Delivery-owned. It implies a sales-scheduling-surface minor add (a reservation-less hold create plus the lifecycle for a soft hold that is later bound by a reservation, including its expiry if the close never lands) and a credit-reservation-lock or sales-ordering close step that binds a freshly minted reservation to an existing soft-held lesson.
There may be a shape Revenue and Delivery prefer; this is the consumer flagging the gap and one candidate, not Sales prescribing the surface. The asks:
Revenue: confirm how first_lesson_id is meant to exist at close time given the close mints the reservation. If the intended model is the reservation-less hold above, confirm the close binds the new reservation to the pre-held lesson; if it is something else (for example the close itself creating the Delivery hold), name it, since the contract today says the lesson is pre-held by Sales.
Delivery: confirm whether sales-scheduling-surface can add a reservation-less soft hold (create-and-hold a lesson without a reservation_id), with a defined soft-hold lifecycle and expiry, that the sales-ordering close later binds a reservation to. This is the load-bearing dependency for the whole committed-close consumer build.
Platform: if this turns out to need an ADR-level decision on where the lesson-create-versus-reserve ordering lives across the three surfaces, flag it and Sales supports Platform stewarding it.
What Sales is doing meanwhile
Sales' close client for the v2.1.0 surface is built and type-checks (the closeSalesOrder client plus DTOs and zod), so the consumer is ready to wire as soon as the hold-sequencing is resolved. Sales is holding the closeOffer re-point until then rather than building against a first_lesson_id it has no way to produce.
References
contracts/sales-ordering-surface/README.mdv2.1.0 §4.1 (first_lesson_idREQUIRED, pre-held)contracts/sales-scheduling-surface/README.mdv1.9.0 (hold-create requiresreservation_id; no reservation-less hold)contracts/credit-reservation-lock/README.md(reservation-create and the lock state machine)- Sales close orchestration:
modules/close-orchestration(today: eligibility, reservation-create, hold-create),modules/scheduling(HoldCreateInput.reservationIdrequired)