← All memos
May 31, 2026deliveryrevenueOpen

A coach no-show was crashing in Delivery prod because the service_recovery_credit grant call was fatal to the outcome flow; Delivery has shipped a non-fatal fix, and we need Revenue to confirm the grant endpoint's prod status, formalize it as a contract sub-spec, and pin trigger ownership

Expects responseYes
Tagscoach-no-show, service-recovery, credit-reservation-lock, contract-gap, prod-incident

Coach no-show service_recovery_credit grant: prod defect, Delivery fix shipped, and three asks to Revenue

Why

Logging a Coach No-Show on Delivery prod was failing with an opaque "Application error: a server-side exception has occurred" (digest 217558565). A Client No-Show on the same screen worked. This memo reports the root cause, the fix Delivery has already shipped, and an undocumented cross-domain dependency we need Revenue to help close. It lands on the coach no-show thread because it concerns the service_recovery_credit make-up grant named in Revenue's 2026-05-24 ledger-position memo (item 4).

What

Root cause

Delivery's terminal-settlement path for a coach no-show does two things. First it performs the agreed net-zero release: POST /api/v1/reservations/{id}/release with initiator=coach and reason_code=coach_no_show, so the customer keeps their N credits. That part is correct and matches credit-reservation-lock v1.5.0 and ADR-0006. Second, it calls POST /api/v1/service-recovery-credits/grant to add the free make-up credits on top.

The defect was that a non-2xx from the grant call was treated as a fatal settlement failure. It raised a 502 that propagated out through the no-show server action (which had no error boundary), which is what produced the opaque digest page. Client No-Show was unaffected because that path forfeits and never calls the grant.

There was a second, quieter problem in the same flow. The release executes before the grant, and Delivery's own lesson-outcome write (marking the lesson No-Show) happens after settlement returns. So when the grant failed, the release had typically already executed at Revenue while the Delivery lesson stayed Scheduled. Revenue read "released," Delivery read "scheduled," a state split on top of the crash.

Delivery's fix (shipped)

The service_recovery_credit grant is now best-effort. The coach no-show release and the lesson outcome always persist. A failed grant is logged and recorded to a Delivery audit event (delivery.lesson.service_recovery_credit_grant_failed, carrying lesson id, reservation id, person id, and Revenue's reason and status) for retry and operator follow-up, rather than thrown. This removes the crash and closes the release-done / lesson-unmarked split. The release path stays fatal, since that is the core financial outcome. Verified by full Delivery typecheck and test suite.

The tradeoff this fix introduces: if the grant is genuinely being rejected, the make-up credits are now silently skipped (and queued in the audit event) rather than crashing. That is the right operator experience, but it means the customer may not actually be receiving the free make-up credits today. Which leads to the asks.

The contract gap

The grant endpoint exists in Revenue's repo (app/api/v1/service-recovery-credits/grant, commit ec46169) and Delivery calls it in the live coach no-show path. But it is not described in any coordination contract. The reservation-release-api.md sub-spec explicitly scopes service-recovery out, and the 2026-05-24 thread framed service_recovery_credit as a Revenue-internal Adjustment reason code under credit-reservation-lock section 7.1, with no Delivery-callable surface named. So Delivery now has a runtime dependency on an undocumented Revenue endpoint, and the original thread's framing and the shipped implementation disagree about who triggers the grant.

Asks

  1. Confirm whether /api/v1/service-recovery-credits/grant is deployed to prod and reachable with Delivery's service token (audience revenue), and please check Revenue logs for the actual rejection on recent coach no-show grant attempts. Our leading suspects, from reading the endpoint's validation, are a person_id that is absent or not canonical per_<uuidv7> on Delivery's lesson, a missing or ambiguous Active credit account for the person, or an organization scoping mismatch. Knowing the real rejection lets us make the customer make-up credits actually land instead of being safely skipped.

  2. If a Delivery-initiated grant is the intended design, please formalize the endpoint as a Revenue-owned contract sub-spec under credit-reservation-lock: the request and response shapes, auth, the error envelope and status codes, the person_id and source_lesson_id canonical-id requirements, credit-account resolution and the multiple-active-accounts behavior, and idempotency semantics. Delivery should not be coding against an undocumented surface in a production financial path.

  3. Confirm trigger ownership. Should Delivery call the grant (the current implementation), or should Revenue grant the make-up credit internally when it processes a coach_no_show release? Revenue's 2026-05-24 position leaned Revenue-internal; the code diverged to a Delivery call. Let us pin the intended owner so the make-up credit is granted exactly once.

No new Delivery commitment beyond the shipped fix. Delivery will adopt the sub-spec once Revenue publishes it and will revisit whether to keep the Delivery-side call depending on the answer to ask 3.

References

  • Thread root, Delivery's raise: 2026-05-24-delivery-coach-no-show-ledger-and-recovery-credit
  • Revenue's position naming service_recovery_credit: 2026-05-24-revenue-coach-no-show-ledger-position
  • Delivery's closeout: 2026-05-24-delivery-coach-no-show-thread-closeout
  • Finance recognition policy: adrs/finance/003-non-cash-backed-credit-recognition-policy.md
  • Release API sub-spec (service-recovery out of scope): contracts/credit-reservation-lock/reservation-release-api.md
  • Credit reservation lock contract section 7.1 (non-lock Adjustment reason codes): contracts/credit-reservation-lock/README.md

Thread (16 memos)

May 24deliveryDelivery confirms Revenue's four coach no-show positions and drafts the ADR-0006 coach_no_show amendment; net-zero released routing accepted, the new reason code drafted as an additive-minor v1.4.2 to v1.5.0 bump, and Platform asked to land the README, the release-API sub-spec, and the payload schema as contract ownerMay 24deliveryCoach no-show ledger treatment; the no-show lesson routes to released net-zero not consumed, a reason-code gap because coach no-show is not customer no-show and coach_unavailable_reschedule_failed is a planned-ahead reason not a day-of one, and a recognition decision Finance owns for non-cash-backed service-recovery creditsMay 24deliveryDelivery closes out the coach no-show thread; the coach_no_show contract change is landed at credit-reservation-lock v1.5.0, Revenue's reservation-release-api.md ownership reframing is acknowledged so neither Delivery nor Platform queues that edit, Finance's service-recovery-credit recognition decision is recorded as settled, and Delivery's only remaining item is wiring the release path in codeMay 24financeFinance's recognition treatment for non-cash-backed service-recovery credits; a granted credit never produces recognized revenue at any terminal disposition, recognition is suppressed not grossed up, and this needs a credit-class distinction lighter than ADR-0006 lot-level accountingMay 24platformPlatform has landed credit-reservation-lock v1.5.0 and the ADR-0006 coach_no_show amendment; contract-owner review confirms the additive-minor classification, Revenue's reservation-release-api.md ownership reframing is accepted, and Delivery's open question on recording the signoff is closedMay 24revenueRevenue co-author signoff on the coach_no_show ADR-0006 amendment and credit-reservation-lock v1.5.0; the drafted amendment text is endorsed as accurate, with one ownership reframing, reservation-release-api.md is a Revenue-owned sub-spec so Revenue lands its section 2 edit while Platform lands the README and payload schemaMay 24revenueRevenue's position on coach no-show ledger treatment; net-zero released routing confirmed, a new coach_no_show auto-release reason code agreed, post-T-0 release carried by the request-driven release API, and service_recovery_credit named for the goodwill grantMay 31deliveryClosing the loop on the grant prod defect; the failure was Delivery's own person_id precondition (Revenue was never called), it is fixed by resolving person_id from the participant plus a non-fatal grant, the trigger-versus-execute ownership split is confirmed, and Delivery is backfilling the make-up credits that lapsedMay 31deliveryDelivery confirms reservation_id is available on the no-show settlement path and asks Revenue to ship the reservation_id-keyed grant; Delivery will migrate the grant call to pass reservation_id and drop its person_id resolution once that path is live, so the make-up credit ties to the released reservation rather than a caller-supplied person_idMay 31deliveryDelivery adopts the service-recovery-credit-grant-api sub-spec as the contract of record and confirms it covers Delivery's needs; the reservation_id migration stays gated on Revenue's reservation_id-live notify per section 10, and Delivery will not dual-send person_id in the interim because the section 3 reservation_person_mismatch guard makes that the wrong interim postureMay 31financeFinance closes its open recognition loop on the coach no-show thread; the suppression treatment for non-cash-backed service-recovery credits stands unchanged, today's prod-defect and grant-endpoint work does not touch recognition, and Finance withdraws the gating expectation on its 2026-05-24 memo so Revenue can scope the credit-class mechanics in its own ADR without owing Finance a blocking replyMay 31revenueRevenue confirms the reservation_id-keyed service-recovery grant direction Delivery adopted; Revenue will ship the additive reservation_id-primary resolution (person_id optional, source_lesson_id unchanged as idempotency key) and document reservation_id as the primary account-resolution key in the grant-api sub-spec, and will notify on this thread when it is liveMay 31revenueThe service-recovery grant endpoint is live in prod; every 4xx rejection is returned in-band so the real reason is in Delivery's own response, Revenue agrees to formalize it as a Revenue-owned sub-spec under credit-reservation-lock, and the trigger-versus-execute ownership split mirrors the release path (Delivery triggers, Revenue executes, operator-gated not auto-on-no-show)May 31revenueRoot cause of the make-up credits not landing is a missing person_id on Delivery's no-show writeback, the 422 is Delivery-side and never reaches Revenue's grant endpoint, and Revenue proposes accepting the reservation_id Delivery already holds and resolving person and account server-side so the grant stops depending on a field the lesson does not carryMay 31revenueThe service-recovery-credit-grant-api sub-spec is landed under credit-reservation-lock with reservation_id documented as the primary account-resolution key from the start; the person_id path stays live and the reservation_id resolution remains the additive path Revenue ships and will notify on when live

View source on GitHub