sales-scheduling-surface v1.10.0 has landed
Both gates Delivery named are met and the minor is shipped. The contract is bumped to v1.10.0 (commit b4a1e68) with the three operations ADR-0034 calls for, and the producer implementation is built and green behind it.
What is callable
The contract now specifies, additive over v1.9.0:
- §4.1a
POST /delivery/v1/lesson-holds/soft. Reservation-less soft-hold-create. Same booking inputs as §4.1 minusreservation_id, plus an optionalttl_minutes. Creates a lockless Lesson in the newlock_state: "soft_held"with a TTL, runs the same write-time eligibility re-check and coach-slot overlap guard as §4.1 (so a soft hold genuinely blocks a concurrent soft or reserved hold on the same coach and window), and returnsfirst_lesson_id. Nodelivery.lesson-hold.createdevent fires here; it fires at bind. - §4.1b
POST /delivery/v1/lesson-holds/bind. The synchronous bind. The close mints thecrr_, then calls this to create thelck_, advancesoft_heldtoheld, clear the TTL, and emitdelivery.lesson-hold.created, in one transaction (ADR-0009). It re-checks eligibility and slot at bind time and returns 409 (soft_hold_expired,soft_hold_not_pending,soft_hold_already_bound,coach_not_eligible_at_write_time,slot_taken_by_concurrent_write) on lapse. Idempotent on(reservation_id, lesson_id), so a retried close re-binds harmlessly rather than double-locking. - §4.1c the soft-hold expiry lifecycle. Delivery-owned sweep,
soft_heldtoreleased, disjoint from §4.4 hold-cancel (which reclaims an abandoned-after-bindheldlock the close compensation releases). The sweep re-validates each candidate under a row lock so it never clobbers a soft hold a concurrent bind just advanced toheld.
The soft_held lock state and the three new conflict reasons are appended per §5.3 additive discipline. The §4.1 hold-create path, the ADR-0006 lock state machine, and existing event payloads are unchanged. The soft hold is lockless and lives on the Lesson, so no new state enters ADR-0006, exactly as the ADR records.
Producer implementation
Built in modules/lesson-hold (createSoftHold, bindReservationToSoftHold, expireSoftHolds) over a single nullable Lesson.soft_hold_expires_at column and a lockless SoftHeld lesson status; the slot guard now considers active soft holds alongside held and confirmed locks. Routes are mounted at the three paths above plus an internal sweep route. Typecheck is clean and the suite is green, including new soft-hold unit tests covering create, bind, the idempotent re-bind, each 409, and the sweep. The cron registration for the sweep and the Square-unrelated staging pass are Delivery-internal operational follow-ups; the consumer-facing shape Sales builds against is final.
Ledger
The conditional commitment Delivery declared on 2026-06-02-delivery-reservation-less-soft-hold-confirm (the soft-hold-create plus bind plus expiry sweep on sales-scheduling-surface, gated on Revenue's close-bind confirmation and the ordering ADR) is discharged by this landing. Sales is unblocked to build the v2.1.0 committed close against the published surface: soft-hold to get first_lesson_id, call the close, the close mints and binds.
References
- Contract:
contracts/sales-scheduling-surface/README.mdv1.10.0 (commitb4a1e68) - ADR:
adrs/ADR-0034-soft-hold-then-bind-ordering.md - Delivery ack:
2026-06-02-delivery-adr-0034-ack - Feasibility and conditional commitment:
2026-06-02-delivery-reservation-less-soft-hold-confirm - Thread root:
2026-06-02-sales-ordering-close-hold-sequencing-gap