Delivery confirms the reservation-less soft hold, and proposes the shape
Sales identified the contradiction correctly: the v2.1.0 close needs a held first_lesson_id before a reservation exists, but today's only hold path (sales-scheduling-surface hold-create) requires a reservation_id first. Delivery can close this, and the good news is half of it already exists in Delivery's model.
Why it is feasible: lockless lessons already exist
A Lesson without a reservation lock is not new for Delivery. Lesson.reservation_lock_id is nullable in Delivery's schema, and sales-scheduling-surface §4.3 already defines the read shape for it: a wildcard list item with no reservation lock returns lock_state: "scheduled", reservation_lock_id: null, reservation_id: null. So Delivery already schedules and reads lessons that carry no reservation (the workbench schedules them today). What today's hold-create couples to a reservation is the lock and the slot-hold, not the lesson identity itself.
So the missing capability is not "a lesson without a reservation." It is two things: a soft hold that genuinely holds the coach slot without a credit lock, with a lifecycle and expiry; and a way to bind a reservation to that pre-held lesson afterward. Both are Delivery-owned and land as a sales-scheduling-surface minor add (v1.9.0 to v1.10.0).
The proposed shape
Two operations plus a lifecycle.
Soft-hold-create (reservation-less). Sales calls it with the same booking inputs hold-create takes today (participant_id, coach_id, lesson_site_id, window, lesson_type_id, organization) but no reservation_id. In one transaction Delivery creates the Lesson (les_), re-checks coach eligibility at write time, and takes a soft slot hold so the window genuinely blocks another concurrent hold on the same coach (a CoachLessonBooking in reserved, not locked, per ADR-0018, plus the same overlap guard hold-create runs). It returns first_lesson_id. The hold state is a new soft_held value (lockless: reservation_lock_id stays null until bind), and it carries a TTL.
Bind-reservation. The inverse of today's reservation-coupled create. The close mints the Person-keyed reservation, then calls Delivery to bind that crr_ to the existing soft-held lesson: Delivery creates the lck_ reservation lock bound to the reservation, advances soft_held to held, flips the CoachLessonBooking from reserved to locked, and emits delivery.lesson-hold.created, in one transaction per ADR-0009. It re-checks at bind time that the soft hold still holds the slot and that eligibility has not lapsed (409 with a conflict_reason if either has), exactly the write-time guards §4.1 already specifies, just starting from an existing lesson rather than creating one.
Soft-hold lifecycle and expiry. soft_held advances to held on bind, or to released on expiry or explicit abandon. Delivery owns the expiry sweep (a cron) so a close that never lands releases the slot: the soft CoachLessonBooking is released and the Lesson is cancelled, with no orphaned hold. The TTL is short and configurable; the close is expected to bind within it, and a soft hold that expires mid-close fails the bind cleanly (the consumer re-soft-holds). Reschedule of a soft hold stays out of scope, consistent with the §9.1 reschedule deferral.
Ownership: keep lesson identity and the hold state machine in Delivery
The bind is a Delivery operation, not a close-internal write into Delivery storage. Delivery owns the Lesson identity, the soft-hold lifecycle, and the lock state machine; the close mints the reservation (v2.1.0 already puts the reserve-fund-lock mechanics there) and then calls Delivery's bind to attach it. That keeps the §4.1 invariant intact (Sales and Revenue never write Delivery's lesson-hold state directly; they drive it through this surface) while inverting only the order: soft-hold first, reserve-and-bind second, instead of reserve-first, hold second. credit-reservation-lock continues to govern the lock states unchanged; the only new fact there is that a reservation may be bound to a pre-existing soft-held lesson.
Asks
Revenue: confirm the v2.1.0 close binds the reservation it mints to the pre-held (soft-held) lesson through Delivery's bind operation, rather than the close creating the Delivery hold itself. That is the model that keeps lesson identity Delivery-owned and matches what the §4.1 first_lesson_id text already implies (the lesson is pre-held by the consumer, the close binds to it). If Revenue intends the close to drive the hold directly, say so, because that crosses the surface ownership boundary and Delivery would push back.
Platform: Delivery reads this as warranting a coordination ADR, because the reserve-versus-hold ordering across sales-scheduling-surface, sales-ordering-surface, and credit-reservation-lock is load-bearing cross-domain and just flipped. Delivery supports Platform stewarding it. The sales-scheduling-surface soft-hold add and the credit-reservation-lock bind note are Delivery-and-Revenue to author; the ordering decision is the ADR.
Sales: this unblocks the close build. Once the shape is settled, Sales soft-holds to get first_lesson_id, calls the v2.1.0 close, and the close mints and binds the reservation. No first_lesson_id that Sales cannot produce.
Commitment
Delivery has declared a conditional commitment on this memo to ship the soft-hold-create, the bind-reservation operation, and the soft-hold expiry lifecycle as the sales-scheduling-surface minor add, gated on Revenue confirming the close-binds-the-pre-held-lesson model and Platform settling any ordering ADR. Delivery is not building against an unsettled cross-surface ordering, but the Delivery-side shape above is ready to spec the moment those two land.
References
- Gap raise:
2026-06-02-sales-ordering-close-hold-sequencing-gap contracts/sales-scheduling-surface/README.mdv1.9.0 (§4.1 hold-create requiresreservation_id; §4.3 already returnslock_state: "scheduled"for a lockless Lesson)contracts/sales-ordering-surface/README.mdv2.1.0 §4.1 (first_lesson_idREQUIRED, pre-held)contracts/credit-reservation-lock/README.md(reservation-create and the lock state machine; ADR-0006)- ADR-0018 (CoachLessonBooking states
reserved | locked | consumed | released), ADR-0009 (producer-transactional guarantee) - Delivery code:
prisma/schema.prisma(Lesson.reservation_lock_idnullable),modules/lesson-hold(today's reservation-coupled create)