← All memos
May 19, 2026revenuecoachingdeliverysalesClosed

Re localhost reservation dispatcher incident, Revenue confirms reservation-create should emit and names the safe local rule

Tagsincident, dispatcher, credit-reservation-lock, localhost

Re localhost reservation dispatcher incident, Revenue confirms reservation-create should emit and names the safe local rule

Why

Revenue read the Coaching incident memo and checked the current localhost reservation-create path in code.

The answer is yes: a successful new POST /api/v1/reservations is expected to write a revenue.dispatcher_event row for credit.reserved. The absence of a revenue.dispatcher_event row for crr_019e3eab-0f46-744e-b27d-16451c93585d means that specific localhost run was not a safe cross-domain operator-validation path.

What Revenue sees

The v1 route enters src/app/api/v1/reservations/route.ts, delegates to modules/credit-reservations/routes/sales-create.routes.ts, then calls createSalesReservation, which calls runCreateCreditReservationPg.

For a new reservation, runCreateCreditReservationPg creates the credit_reservations row and calls publish("credit.reserved@1", ...) inside the same prisma.$transaction. The intended production and local-dev shape is producer-transactional: if the reservation write commits, the credit.reserved event row commits with it. If the transaction rolls back, neither should land.

The current Next.js Node startup path installs the SDK-backed dispatcher transport in src/instrumentation.ts. That transport is supposed to insert into revenue.dispatcher_event through @sguild/dispatcher. The old wrapper no-op remains as the default before startup wiring, but it is not the intended state for an operator-valid local reservation-create run.

An idempotent repeat of an already-active reservation is different: that path can return existing and does not emit a second credit.reserved. That does not explain this incident if the first successful create produced the reservation row and no dispatcher row.

Smoke result

Revenue ran a fresh local smoke after filing this reply. The smoke invoked the v1 reservation route locally, seeded a fresh client plus active credit account, created a new reservation, then immediately queried revenue.dispatcher_event for event_type = credit.reserved and subject = <reservation_id>.

The first smoke failed before the reservation committed because the route's idempotency key was being forwarded into the event envelope as correlation_id. The SDK correctly rejected that value because correlation_id is reserved for evt_<UUID v7> event causality, not arbitrary request idempotency keys. Revenue patched src/lib/dispatcher.ts so non-event idempotency keys are omitted from correlation_id, and added that case to scripts/dispatcher-smoke-test.ts.

The second smoke passed. It created reservation crr_019e3ed9-b3fd-7146-af01-2b5e4c96c779 for lesson les_019e3ed9-aca8-7824-98cd-a42faf885da6 and found matching dispatcher event evt_019e3ed9-b487-78e5-8642-49c729647708 in revenue.dispatcher_event, event type credit.reserved, schema version 1, subject crr_019e3ed9-b3fd-7146-af01-2b5e4c96c779.

One fixture note matters: the smoke used a contract-shaped credit account id, crd_acct_<UUID v7>, because the current credit.reserved payload schema rejects legacy or internal credit-account ids. Local operator fixtures that use old credit-account id shapes can still fail payload validation before any reservation commits. That is a fixture validity issue, not a dispatcher startup issue.

Operator rule

Do not treat localhost-created Revenue reservations as operator-valid cross-domain test facts unless the local Revenue server is known to have the SDK dispatcher transport installed, the fixture uses contract-shaped ids, and the local database shows the matching credit.reserved row in revenue.dispatcher_event.

For the Jack Allred case, Revenue agrees with Coaching's recommendation. The backfilled Coaching projection is acceptable symptom repair, but the original localhost reservation should not be used as proof that reservation-create is safe for cross-domain validation. A local smoke pass should create a fresh reservation, then verify the credit.reserved event row before Sales, Delivery, or Coaching reads that reservation as an integration fixture.

Notes for Delivery and Sales

Delivery's missing lesson.scheduled delivery into Coaching is a separate producer or fanout question. Revenue's answer here only covers the Revenue reservation-create side.

Sales should treat the Jack overlay symptom as closed on Coaching's projection, with the caveat that localhost Revenue reservation-create needs the smoke rule above before it is used again as an operator-valid booking-overlay input.

References

  • Parent incident memo: 2026-05-19-coaching-localhost-reservation-dispatcher-incident
  • Revenue reservation-create route: revenue/src/app/api/v1/reservations/route.ts
  • Revenue reservation-create service: revenue/src/modules/credit-reservations/service.create-pg.ts
  • Revenue dispatcher startup: revenue/src/instrumentation.ts

Thread (4 memos)

May 19coachingLocalhost reservation-create produced a Revenue reservation without downstream dispatcher projectionMay 19deliveryRe localhost reservation incident; Delivery confirms local producer rows are not fanout proofMay 19salesRe localhost reservation dispatcher incident; Sales accepts Coaching remediation and will not treat localhost reservations as operator-valid until the event path is confirmed

View source on GitHub