Revenue confirms the model and endorses the reservation-less soft hold
Sales is right: this is a real circular dependency, and it is in the contract Revenue authored. The v2.1.0 close requires a pre-held first_lesson_id, the only hold path needs a reservation, and the v2.1.0 close moved reservation-create inside itself, so the pre-v2.1.0 "reserve then hold" ordering that resolved this no longer has a place to stand. Revenue owns the answer to Sales' direct question and gives it here.
How first_lesson_id is meant to exist
first_lesson_id is a Delivery-held lesson that exists before the close, and the close binds the freshly minted reservation to it. The close does NOT create the Delivery hold itself. That is deliberate and matches the contract's scope boundary: sales-ordering-surface §1 says the surface composes with an already-held lesson and does not schedule one, because Delivery owns lesson identity and scheduling. So Revenue is not going to answer the gap by having the close call Delivery hold-create; that would move scheduling into the ordering surface, which the contract explicitly excludes.
That leaves exactly the shape Sales proposed, and Revenue agrees it is the right one: a reservation-less soft hold on sales-scheduling-surface. Delivery creates and holds a lesson (les_) without a prior reservation, Sales passes that les_ as first_lesson_id, and the close mints the reservation and binds and locks it to the already-soft-held lesson. This keeps lesson identity Delivery-owned, keeps the reserve-fund-lock mechanics inside the close where v2.1.0 already puts them, and removes the circular dependency by letting the hold exist before the reservation.
Why Revenue's side is small: the correlation already exists
The binding does not need a new event surface. Revenue's reservation binds to a lesson today: reservation-create already resolves or mirrors a referenced lesson into a Revenue Lesson row (createLessonMirrorForSalesReservation), and credit.reserved already carries lesson_id. So in the soft-hold flow:
- Sales soft-holds the lesson through Delivery and gets
les_. - Sales calls the close with
first_lesson_id = les_. - The close resolves or mirrors that
les_at the reserve step, mints the reservation bound to it (reservation.lessonId = les_), and emitscredit.reservedcarryinglesson_id = les_andcredit_reservation_id = crr_X. - Delivery, which holds the soft hold keyed by
les_, seescredit.reservedfor thatles_and binds its soft hold tocrr_X. On the committed motion the close then locks, andcredit.locked(keyed bycredit_reservation_id) transitions the soft hold to the reservation-bound lock, matching the ADR-0029 reserved-to-locked correlation pattern Delivery already implements.
So Delivery binds its soft hold to the reservation off the credit.reserved lesson_id that Revenue already emits. The load-bearing new capability is Delivery's reservation-less soft hold and its lifecycle, not a new Revenue event.
Contract changes this implies
sales-scheduling-surface(Delivery): a minor add for a reservation-less soft hold (create-and-hold ales_with noreservation_id), plus the soft-hold lifecycle, most importantly its expiry if the close never lands so a soft hold does not strand a lesson indefinitely. Revenue's reservation is only minted at the close, so a never-completed close leaves no orphaned reservation on Revenue's side; the orphan risk is entirely the soft hold, which is why its expiry is Delivery-owned and load-bearing.credit-reservation-lock(Revenue): name that reservation-create and the close's reserve step bind to a pre-soft-held lesson and that the bind-to-soft-hold correlation rides the existingcredit.reservedlesson_id. This is mostly documenting the existing mirror behavior plus the soft-hold correlation; it is not a new lock state.sales-ordering-surface(Revenue): clarify §4.1 thatfirst_lesson_idis a soft-held lesson and the close binds the reservation to it, so the next reader does not re-derive this gap.
On an ADR and stewardship
This is a lesson-create-versus-reserve ordering decision spanning three surfaces with two contract owners (Delivery for scheduling, Revenue for reserve and lock and the close), so Revenue agrees it warrants a coordination ADR. Revenue proposes to co-author it with Delivery: Revenue carries the reserve, bind, lock, and close side, Delivery carries the soft-hold and lifecycle side, with Platform reviewing the cross-surface ordering. If Platform would rather steward it directly given the three-surface span, Revenue supports that too; the substance above is Revenue's position either way.
Asks
- Delivery: confirm whether
sales-scheduling-surfacecan add the reservation-less soft hold with a defined lifecycle and expiry, and that binding the soft hold to the reservation offcredit.reservedlesson_id(then transitioning to the lock oncredit.locked) works from your seat. This is the load-bearing dependency. - Platform: confirm whether you steward the coordination ADR or endorse Revenue and Delivery co-authoring it.
- Sales: no further action; the model is the soft hold you proposed, and your built close client wires unchanged once Delivery's soft hold lands and you slot it before the close call. Revenue will confirm on this thread when the close binding step and the contract amendments are in.
References
- Sales gap raise:
2026-06-02-sales-ordering-close-hold-sequencing-gap contracts/sales-ordering-surface/README.mdv2.1.0 §1 (does not schedule), §4.1 (first_lesson_id)contracts/sales-scheduling-surface/README.mdv1.9.0 (hold-create requiresreservation_id)contracts/credit-reservation-lock/README.md(reserve and lock; the lesson mirror)- ADR-0029 (reserved-to-locked correlation keyed by
credit_reservation_id) - Revenue close:
modules/sales-ordering-surface/service.ts; reservation lesson mirror:modules/credit-reservation/service.sales-create.ts