The story's verification_recipe, automated: a Playwright spec proving that with the fast lane routed to the Postgres engine, one status transition still reaches all four of its consumers — and that a repeated invite still sends one email.
Verified on origin/main@b944dab5, because a test card that names a harness it has not opened is asserting one:
tests/e2e/_helpers/job-routing.ts — moves a job between lanes MID-SPEC through the file channel MOTIR_POSTGRES_JOB_IDS_FILE, cleared in afterEach. An env var could not do this: it is fixed at server boot, and jobs-flow.spec.ts proves email.send on Inngest against the same server.tests/e2e/_helpers/job-worker-process.ts — globalSetup starts the engine's worker as a third process from the SHIPPED bundle (.worker/worker.mjs), and resolves on the worker's own startup line rather than a sleep. playwright.config.ts's webServer command already runs pnpm run build:worker for it.tests/e2e/_helpers/email-capture.ts (waitForEmail) and _helpers/email-fault.ts — the outbox and the deterministic fault injector.The lane runs workers: 1, so the routing file is not raced by a parallel spec.
routeJobsToEngine the four work-item/transitioned consumers plus email.send, for this spec only./settings/workspace/jobs — itself a real user surface — each of the four job_run rows appears and reaches succeeded. And the three consumers with user-visible effects are asserted where a person would see them: the parent's rolled-up status on the board (status-derivation), the bell entry (notification-fan-in), and the watcher email in the outbox (watcher-notify → email.send). Four ledger rows prove the fan-out; three visible effects prove the fan-out did something.Every wait is on an authoritative signal — a row visible in the dashboard, an outbox entry, a DOM state the app renders. No waitForTimeout. The engine's backoff is ours (1s / 2s / 4s with jitter, per MOTIR-3427's own note that this is why its journey finishes in seconds where the Inngest equivalent needs ~150s), so there is no vendor scheduler to wait out and no excuse for a sleep.
A NEW spec alongside tests/e2e/jobs-postgres-engine.spec.ts rather than an edit to it. That one proves the pilot job's DLQ and replay journey on the engine (MOTIR-3427); this one proves fan-out and idempotency. Do not re-derive its DLQ coverage — the retry / dead-letter / replay path is already asserted there, and a duplicate suite is a maintenance cost with no new information.
Does NOT touch jobs-flow.spec.ts, which proves the same journey on Inngest and must keep passing against the same server — that side-by-side is the cutover switch's whole promise.
NO ACCEPTANCE VIDEO. MOTIR-3415 ships no user-observable surface of its own — the board, the bell and the jobs dashboard all keep the shape they already have — so it is a non-UI story and accepts on its tests, per the scoped acceptance-video rule. The surfaces this spec drives are the ones it uses as instruments, not ones it delivers.
succeeded job_run rows — one per FAST_LANE_CONSUMER_IDS entry — visible in /settings/workspace/jobs.succeeded; the spec asserts the survivors, not merely that nothing threw.⚠️ AMENDED 2026-08-25, in this card's own run — this criterion is UNDISCHARGEABLE AS WRITTEN, and is replaced rather than dropped. It describes a journey the product does not have.
lib/services/workspaceInvitesService.tscallsgenerateToken()unconditionally — one call site, line 198, no reuse-existing branch — and passes that token ASidempotencyKey. So two invites to one address are two DIFFERENT invites with two different keys and CORRECTLY produce two emails. No UI path can mint the same key twice, so no E2E can assert same-key dedup.The story's
verification_recipesentence “send the same workspace invite twice and confirm one email” is wrong about the product for the same reason.The guarantee is real and IS proven, one tier down and against real Postgres:
tests/jobs/engine-idempotency.test.ts(two same-key events → one queued run, including a genuinely CONCURRENT duplicate) andtests/jobs/event-cutover-story-gate.test.ts§1 (two same-key events → one delivery, read back through the operator DTO).Replaced by: one invite produces exactly ONE
job_runrow and ONE outbox entry with the fast lane on the engine — which the shipped spec asserts, and which pins that the dedup neither double-sends nor swallows a legitimate first send. Planning bug under MOTIR-1465.
afterEach, and jobs-flow.spec.ts passes unchanged in the same run.waitForTimeout anywhere in the spec.tests/e2e/jobs-postgres-engine.spec.ts — the sibling spec, its header on why routing is per-spec, and the DLQ coverage this must not duplicatetests/e2e/_helpers/job-routing.ts · job-worker-process.ts · email-capture.ts · email-fault.ts — the four helpersplaywright.config.ts — the webServer commands, workers: 1, and build:workerlib/jobs/latencyBudget.ts — FAST_LANE_CONSUMER_IDS, the four ids to asserttests/e2e/_helpers/db-reset.ts — adminDb, and the singleton-statement ratchet jobs-postgres-engine.spec.ts had to respect