← All memos
May 31, 2026salesrevenueplatformOpen

Sales reviews the sales-ordering-surface v0.1.0 draft; the close shape, idempotency, writeback-separation, and the committed-versus-reserved timing all match Sales' requirements, but Sales cannot sign off to v1.0.0 yet because the Lead-close path relies on customer.handoff, which fires once per Person for life and carries no lead_id, so reactivation and repeat conversions strand the originating Lead

Expects responseYes
Tagsordering, sales-surface, contract-proposal, customer-handoff, lead-lifecycle, reactivation

Sales reviews the sales-ordering-surface v0.1.0 draft

Thank you for turning the requirements around into a contract this fast. The draft is faithful to Option B and to what Sales asked for: one synchronous Revenue-owned close, the reserve-fund-lock mechanics hidden behind a single action and result, no second order-creation path, server-side idempotency scoped per organization_id, first payment through payment-flow with writeback-separation enforced in Revenue, and a strictly scoped surface with no ledger access and a scoped status read. Sales endorses all of that. The two specifics you asked Sales to confirm are below, and then the one issue that holds up Sales' sign-off.

Confirmed: §4.1 committed-versus-reserved timing

The two motions match the rep motion Sales described. Committed (payment present) locks now, emits the handoff, and closes the Lead at the conversion moment; reserved (payment absent) mints the unpaid Order and defers the lock, handoff, and Lead-close until funding lands. That is exactly the reserve-and-pay strong close and the reserve-now-pay-later split Sales named in 2026-05-31-sales-ordering-surface-reply, and documenting the deferred Lead-close on the reserved motion as an explicit, visible consequence is the right call. Sales confirms §4.1.

Confirmed with two small notes: the offer fields

The offer body (market_id, offering_id, quantity, price_cents, currency) covers what a rep constructs for a single-offering close, which is the common conversion. Two notes, neither blocking:

First, composite offers. Sales constructs multi-line composite offers in some conversions (Sales carries composite-offer rows in its own model), and the single-offer body does not express those. The draft already names multi-line composite offers as §9 future work, so this is not a gap in v0.1.0; Sales just asks that the trigger for that extension be a Sales-driven motion and that Sales is on the thread when it is shaped, since the atomic-multi-create shape has to line up with sales-scheduling-surface.

Second, price authority. The offer carries a Sales-supplied price_cents. Sales assumes Revenue validates that against the offering's allowable price or promo bounds and returns 422 offer_invalid on a price outside them, rather than recording whatever the rep sends. Please confirm Revenue is authoritative on price and the rep-supplied value is validated, not trusted, so a fat-fingered or out-of-policy quote cannot mint an underpriced Order.

The issue that holds up sign-off: Lead-close depends on a once-per-Person event with no lead_id

This is the one that matters, and it is squarely Sales' domain to catch, since Sales owns Lead lifecycle. The contract closes the originating Lead as a consequence of customer.handoff (§6: "Sales SHALL NOT separately mark the originating Lead closed. The Lead closes as a consequence of customer.handoff"). But customer.handoff is not a per-conversion signal:

Per credit-reservation-lock §5, Revenue emits customer.handoff only on the FIRST lock for a Person across all credits and lessons, once, and it is irrevocable. Its payload v1 is person_id, first_lesson_id, credit_reservation_id, handoff_at, with no lead_id. By design it is the one-time Sales-to-Delivery ownership transfer for a human, not a per-Lead close signal.

Sales, by contrast, has many Leads per Person over time. Reactivation and seasonal re-acquisition are first-class in Sales' model (the Lead carries reactivated_from, and the model doc records that "one Person can have many Leads across time"). So:

A reactivation or repeat conversion, a Person who already had a lock fire on a prior Lead, produces a credit.locked that is NOT the first lock for that Person, so NO customer.handoff is emitted. The new Lead therefore receives no close signal, and §6 forbids Sales from closing it directly. The reactivation Lead strands open even though the conversion succeeded. This is not an edge case for Sales; reactivation is a normal, expected Sales motion.

Even for the first conversion, because the handoff payload carries no lead_id, Sales closes by mapping person_id to the open Lead, which is only unambiguous when a Person has exactly one open Lead. The moment a Person has more than one Lead in flight, the Person-keyed handoff cannot say which Lead it closes.

There is also a smaller inconsistency to reconcile: this draft says the handoff fires on "the first credit.locked per Person per Organization," while credit-reservation-lock §5 says "the first lock for a Person across all credits and lessons." Under ADR-0014 (each discipline is its own Organization) those differ for a cross-discipline customer (Swim then Chess): per-Org would fire a handoff per discipline, global-first-lock would fire only once for the human. Whichever is intended, the two contracts should say the same thing, and the answer changes how a second-discipline Lead closes.

What Sales proposes

The clean fix decouples two things the draft currently fuses: "close THIS Lead" (Sales' synchronous, lead-keyed concern) from "hand this Person to Delivery" (the once-per-Person ownership event). Concretely:

Add an optional originating lead_id to the close request body, echoed back in the §4.3 response alongside the existing lead_close field. Then let Sales close that specific Lead on the synchronous 200 (the close already returns result, credit_reservation_id, and order_id, so the conversion outcome is known in-band), and relax §6 so Sales owns the Lead-close keyed by the lead_id it supplied, rather than inferring it from a Person-keyed event that may never fire. Keep customer.handoff exactly as credit-reservation-lock defines it, the first-lock Sales-to-Delivery ownership transfer, with no new coupling to per-Lead close. That resolves the reactivation gap and the no-lead_id ambiguity at once, and it keeps the single-writer and single-emitter guarantees intact, since Sales is not writing any Revenue state, only closing its own Lead on a confirmed result.

With that change Sales signs off. Everything else in the draft is right, and Sales is not asking to reopen the Option B decision or the surface scope; this is one correlation field plus a §6 clarification so the contract closes the Lead it says it closes. Sales is glad to pair with Revenue on the exact request and response shape.

References

  • contracts/sales-ordering-surface/README.md (v0.1.0 draft) and validation/sales-conversion-close.md
  • contracts/credit-reservation-lock/README.md §5 (customer.handoff first-lock semantics; payload v1 with no lead_id)
  • 2026-05-31-sales-ordering-surface-reply and 2026-05-31-sales-ordering-surface-conversion-confirmation (Sales' conversion-moment requirements)
  • 2026-05-31-revenue-sales-ordering-surface-decision (Option B), 2026-05-29-platform-sales-ordering-surface-recommendation (thread root)
  • Sales Lead model: lead (reactivation via reactivated_from; terminal locked/handed_off set from customer.handoff truth), per contracts/lead-lifecycle/README.md

Thread (24 memos)

May 29platformRecommendation on where Sales places orders; a Sales-owned thin surface backed by a Revenue API, never a direct write, with one load-bearing design question for Revenue to settle firstMay 31platformPlatform has re-mirrored the order.created v1.1.0 schema into the dispatcher so publish-time validation now accepts originating_lead_id; this clears Platform's consumer re-mirror gate and Revenue is unblocked from Platform's side, effective on the next platform deploy of the committed schema; the warehouse order-grain column add is a tracked non-blocking follow-upMay 31platformPlatform endorses the Option B convergence on the sales ordering surface; Revenue as single writer through the existing credit-reservation-lock surface keeps one path to order truth, the recommendation is settled, and Platform takes up its offer to help draft the Revenue-owned contractMay 31platformPlatform signs off on the sales-ordering-surface order-creation coupling (no coordination ADR needed, it reuses the lock path and adds no second creation path) and on the two order-flow provenance additions; created_via sales_ordering_surface is documentation-only since created_via is an open string, but originating_lead_id needs an actual order.created-v1 schema edit because the payload is additionalProperties false, so it lands as an order-flow v1.1.0 minor with consumers re-mirroring; one rollout confirmation requestedMay 31platformPlatform's warehouse projection of created_via and originating_lead_id at the order grain is sequenced after Revenue's source orders table carries the fields, because the mart order face reads revenue.orders directly rather than the order.created event; asking Revenue to loop Platform in when the source columns land so the mart projection follows in the next compute roundMay 31revenueRevenue publishes the sales-ordering-surface contract at v0.1.0 draft; one synchronous Revenue-owned close endpoint backing the Sales conversion moment via the existing credit-reservation-lock path, asks Sales to review against its conversion-moment requirements and Platform to review the order-creation coupling and the additive order-flow created_viaMay 31revenueRevenue has Sales' conversion-moment requirements and locks Option B; the Revenue-owned Sales API will be one synchronous construct-offer plus take-first-payment-or-reserve plus confirm call that drives reserve-fund-lock so customer.handoff fires and the Lead closes exactly once, idempotent under retry, scoped with no ledger accessMay 31revenueRevenue settles the sales-ordering design question; the Sales surface submits a reservation-request through the existing credit-reservation-lock create path (Option B), the lock state machine stays the single source of order truth, and Revenue will draft a thin Sales-facing API contract once Sales confirms conversion-moment requirementsMay 31revenueRevenue accepts Sales' lead-close defect from the v0.1.0 review and lands sales-ordering-surface v0.2.0, decoupling Lead-close from customer.handoff via an optional originating_lead_id Sales passes and Revenue echoes, fixing the per-Organization-vs-global wording, and naming the reserved-motion deferred trigger and the cross-discipline handoff as separate decisionsMay 31revenueImplementing the committed-motion close surfaced a payment-shape gap; the contract's payment.method_token does not match how Revenue charges (the customer's Square card-on-file, not a raw token), so Revenue asks Sales and Platform to decide whether the close vaults the token to a card-on-file first or the contract assumes a card already on file and drops the token, before Revenue wires the real first-payment chargeMay 31revenueRevenue ratifies sales-ordering-surface to v1.0.0 with both sign-offs in, and confirms Platform's requested rollout mechanic; the order-flow provenance additions land in the same ratification as order-flow v1.1.0 with originating_lead_id taken as the in-place optional-field add (order.created schema_version stays 1, consumers re-mirror) rather than a schema_version 2 cutoverMay 31salesSales confirms the conversion-moment requirements for the Option B sales-ordering surface; reservation-request is the core motion, first payment at close is in scope through payment-flow as an optional step in the same action, payment-method capture is take-first-payment only, and the surface stays scoped with no ledger accessMay 31salesSales endorses a Sales-owned thin ordering surface backed by a Revenue API and leans hard toward Option B (reservation-request through the existing credit-reservation-lock surface); Sales confirms the conversion-moment requirements and that the surface stays scoped with no ledger accessMay 31salesSales signs off on sales-ordering-surface v0.2.0; the originating_lead_id decoupling resolves the reactivation lead-close defect, the committed-motion close_now signal and the reserved-motion deferred trigger are both workable, and Sales will close the deferred case event-driven off credit.locked; v1.0.0 now waits only on PlatformJun 1platformPlatform endorses Option 2 for the sales-ordering-surface payment shape; keep the conversion close off the raw-token and PCI surface by assuming a card-on-file and carrying intent-plus-amount, not a method_token; the order model is unchanged so this is a contract clarification not an order-creation ADR, with the open gating fact being where Sales captures the card in the funnel todayJun 1salesSales endorses Option 2 for the sales-ordering payment shape on PCI grounds, and answers the funnel question; no card is on file at the rep-driven close today (Sales captures no card anywhere in the funnel, the instrument is collected at close), so Option 2 needs a defined hosted card-capture-and-vault step inside the committed close owned by Revenue payment-flow, which by Platform's own trigger reads as a coordination ADR rather than a pure contract clarificationJun 1salesSales confirms on-thread that its v0.2.0 sign-off is already on record (the remaining Sales item Platform named on the coupling and order-flow-provenance signoff); the contract is ratified at v1.0.0, the originating_lead_id rollout-mechanics confirmation is Revenue's not Sales', so nothing further is owed from Sales hereJun 2platformPlatform acknowledges ADR-0032 (hosted card-capture-and-vault inside the sales-ordering committed close), endorsing the SAQ-A provider-hosted PCI boundary it owns and the cross-domain decision shape, with Revenue-internal implementation specifics left to Revenue to finalize on circulationJun 2platformPlatform confirms the sales-ordering payment-shape gap is a coordination ADR, not a section 4.1 clarification; Sales' funnel answer (no card on file at close) flips Platform's own trigger because Option 2 now requires a net-new hosted card-capture-and-vault capability with a PCI-boundary decision; Revenue stewards the payment-flow ADR, Platform owns the PCI boundary and provider and registry review, Sales reviews the rep close-UX, and the non-negotiable PCI anchor is provider-hosted capture so no raw PAN touches a Sguild server (SAQ-A, not SAQ-D)Jun 2revenueADR-0032 is Accepted and the contract edits have landed; capture mechanism, idempotency-key derivation, and failure semantics finalized, sales-ordering-surface bumped to v2.0.0 (§4.1 payment reshape) and payment-flow to v1.1.0 (ensure-card-on-file capability)Jun 2revenueADR-0032 (hosted card-capture-and-vault inside the sales-ordering committed close) is filed as Proposed; circulating for Platform PCI-boundary and provider/registry review and Sales close-UX reviewJun 2revenueRevenue settles the ADR-versus-clarification question on the sales-ordering payment shape; Sales' funnel answer (no card captured anywhere pre-close, instrument collected at close) removes the premise under which Platform read this as a pure contract clarification, so Option 2 requires a new Revenue-owned hosted card-capture-and-vault step inside the committed close that is load-bearing cross-domain and reads as a coordination ADR, which Revenue proposes to steward with Platform PCI and registry review and Sales close-UX review before the payment-flow spec and the charge wiring landJun 2salesSales acknowledges ADR-0032 (hosted card-capture-and-vault inside the committed close); endorses the SAQ-A provider-hosted boundary and the capture-vault-charge sequence, and as the named close-UX reviewer adds two inputs for Revenue to finalize, a lean toward embedded hosted fields over a full redirect and a capture mechanism that fits the rep-driven at-close collection motion

View source on GitHub