MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3427

(motir-core) Story E2E — a pilot job fails on the new engine, lands in the DLQ, and an operator replays it to success

Done
Description

⚠️ AMENDED by motir run MOTIR-3414 (2026-08-23) — two corrections, both verified in the tree

1. The blocked_by on MOTIR-3425 is REMOVED. This spec does not consume the Fly provisioning: it runs in the Playwright lane against a local server and a local Postgres, and nothing in it reads a Fly machine or the Machines API. The edge was what dragged the E2E behind an operator card that cannot itself start until the story's pull request has merged — a deadlock the story could not resolve from inside.

2. "No bespoke server configuration" CANNOT hold as written, and the card now says what it costs instead. Read on this branch:

playwright.config.ts → webServer: [
  { command: 'prisma generate && next build && next start …' },   // the app
  { command: '<inngest-cli> dev …' },                             // the OLD engine
]

Two processes, and neither of them is a Postgres-engine worker. The engine is a separate process group by design (MOTIR-3421 — that separation is the point: job load must not contend with request serving), so a run queued onto it has nothing to claim it in this lane and the spec would hang at its own step 3, forever, on a criterion that reads as satisfied.

So the lane gains a THIRD webServer entry — the worker — and that is this card's work rather than a violation of it. It is the exact analogue of the inngest-cli dev entry already sitting beside it: the old engine needed its process in the lane and got one. Concretely:

  • a webServer entry running the worker (pnpm build:worker && node .worker/worker.mjs, or tsx scripts/worker.ts — whichever the lane can start most cheaply), with MOTIR_POSTGRES_JOB_IDS set to the pilot job so only it is routed;
  • the spec waits on the AUTHORITATIVE signal at every step (the run row reaching a state, the response to the replay action) — never a waitForTimeout, and never the optimistic UI, which is the standing E2E rule and matters more here because the worker is asynchronous by construction.

The rest of the card is unchanged: the flow, the DLQ and replay path, the empty/loading/error assertions, and the acceptance-video exemption all stand.

Planning bug: MOTIR-3429. Amended on the record; nothing was silently dropped.


The story's Playwright E2E — the verification_recipe automated. It drives the OPERATOR's journey, because that is the only user-facing surface this story touches.

The flow

  1. Sign in and open /settings/workspace/jobs.
  2. Trigger the pilot job on the Postgres engine; wait on the authoritative signal — the run's own response or a committed-state read — never on the optimistic UI.
  3. See its run row appear and reach succeeded.
  4. Force a terminal failure; see the run retry, exhaust its budget, and appear in the DLQ tab.
  5. Press Replay; see the replayed run succeed.
  6. Assert the empty, loading and error states of the runs table along the way.

This is deliberately the same shape as MOTIR-65, the E2E that closed the ORIGINAL background-jobs story on Inngest — the point being that the operator's experience is unchanged by the substrate underneath it.

Not an acceptance-video story

The E2E discipline distinguishes a correctness test from an acceptance receipt. This story is exempt from the acceptance video: it ships no new user-observable surface — /settings/workspace/jobs already exists and is unchanged — and its deliverable is infrastructure. It accepts on its tests. Stated here explicitly so the exemption is a decision on the record rather than an omission.

Acceptance criteria

  • The full path — run, fail, DLQ, replay, succeed — passes against the Postgres engine, driven through the real UI.
  • Every assertion after a mutation waits on an authoritative signal: the write's response and status, or a committed-state read. No waitForTimeout is used as a synchronisation mechanism.
  • The empty, loading and error states of the runs table are each asserted.
  • The spec is not flaky: it passes repeatedly under CI load, and any wait it needs is on a deterministic signal rather than a duration.
  • It runs in the standard Playwright lane with no bespoke server configuration.

Context refs

  • app/(authed)/settings/workspace/jobs/page.tsx — the surface under test
  • tests/e2e/ — the lane, its fixtures and its sign-in helper
  • MOTIR-65 — the original DLQ-and-replay E2E this mirrors
  • motir-core/CLAUDE.md § E2E — the authoritative-signal discipline