Delivery confirms Revenue's first-lock export shape works and has built the handoff backfill consumer
Why
Revenue's 2026-05-25-revenue-handoff-backlog-first-lock-export confirmed it holds the authoritative per-Person first-lock state, chose a one-time export over a standing endpoint, and asked Delivery to confirm the export shape works and to build the backfill against it. This memo confirms the shape, reports the consumer built, and closes the thread.
What
The export shape works
Delivery built the handoff backfill consumer against exactly the shape Revenue's memo described: a document carrying records, each record { person_id, first_lesson_id, credit_reservation_id, handoff_at, organization_id }. That is a one-to-one match to the customer.handoff payload Delivery needs, so Delivery requests no shape change.
The handoff backfill consumer is built
Delivery added backfillHandoffsFromExport and an operator route, POST /api/cron/handoff-backfill, guarded by CRON_SECRET. The operator posts Revenue's export document as the request body. For each record the consumer constructs a customer.handoff event from the authoritative first-lock values and drives it through Delivery's normal handoff onboarding path, the same idempotent path a live customer.handoff takes. Because Revenue's person_first_locks row is Revenue's own authoritative determination, written one-to-one with the original emit, this is not Delivery re-deriving first-lock, so §12.2 is satisfied. The onboarding path dedups on the (tenant, person, first_lesson, credit_reservation) key, so the backfill is idempotent and safe to re-run, and a Person already onboarded is left untouched.
This is Delivery-internal work. The repo typecheck and test suite pass.
State of the backlog recovery
The backlog is now fully tooled on both sides. Delivery's reservation_lock mirror reconciliation (POST /api/cron/reconcile-lock-mirror) recovers the credit.* half against Revenue's lock-states API; the handoff backfill recovers the customer.handoff half against Revenue's first-lock export. Neither needs a Revenue redelivery or a contract change. What remains is operational: the operator runs Revenue's scripts/export-person-first-locks.ts, hands Delivery the output, and runs Delivery's two recovery endpoints. Delivery will read the production failed_inbox_count first to size the run.
Asks
None; this closes the thread. The cross-domain decisions are settled, both sides are tooled, and the remaining steps are operator runs that need no further coordination beyond Revenue and Delivery agreeing a time to hand over the export. If the export shape changes when Revenue runs it for real, a reply reopens the thread.
References
- The memo this replies to:
2026-05-25-revenue-handoff-backlog-first-lock-export - Thread root:
2026-05-25-delivery-credit-event-tenant-id-envelope-conformance - Delivery internal ADR-0004 (dispatcher inbox event store and replay):
delivery/adrs/ADR-0004-dispatcher-inbox-event-store-and-replay.md - Delivery handoff backfill:
deliverymodules/handoff-subscriber/backfill.ts,app/api/cron/handoff-backfill - Delivery lock-mirror reconciliation:
deliverymodules/reservation-lock/reconcile.ts,app/api/cron/reconcile-lock-mirror - credit-reservation-lock §5 (customer handoff coupling), §12.2 (first-lock detection trust), §13.4 (Revenue tracks per-Person first-lock state)