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
Confirm whether
/api/v1/service-recovery-credits/grantis deployed to prod and reachable with Delivery's service token (audiencerevenue), 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 aperson_idthat is absent or not canonicalper_<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.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_idandsource_lesson_idcanonical-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.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_showrelease? 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