1.6 Background job infrastructure
The durable async substrate every later Epic depends on: long-running LLM calls (Epic 7's planning agent passes run 30-60 s and cannot live inside an HTTP request), scheduled work (Epic 5 notification digests, Epic 7 auto-suggest cadences), webhook fan-out (Epic 6 search indexing, Epic 7's GitHub PR-merged → mark-Subtask-done flow), and transactional email retries (Story 1.1's password-reset + Story 1.2's workspace invites currently fire synchronously and silently swallow provider failures). Ships the runtime + the patterns (idempotency, retries, dead-letter, replay) + the first real production job + the operator surface so failures surface to a human instead of disappearing.
Prerequisites: Story 1.1 ships the lib/email.ts abstraction the first real job (1.6.3) wraps; the password-reset + workspace-invite send-sites become the first job's callers (replacing today's synchronous fire-and-pray sends — captured as a finding in PRODECT_FINDINGS.md). Story 1.2 ships WorkspaceMembership, which 1.6.5's job-runs dashboard filters by (so workspace admins only see their workspace's runs). Story 1.5 ships AppLayout + Sidebar; the dashboard route in 1.6.5 hangs off the existing sidebar's "Settings" group.
Verification
- Pull the merged Story branch;
pnpm install && pnpm dev; in a second terminal,npx inngest-cli dev. - Sign in as a workspace owner; navigate to Settings → Job runs; confirm both tabs render with empty states.
- Invite a new email address to the workspace → confirm the invite-acceptance row appears in "Recent runs" as
succeededwithin ~3 seconds; the dev-console email body matches the invite template. - Set
FORCE_EMAIL_FAILURE_FOR=fail@example.testin the dev shell; invitefail@example.test→ watch the run climb to attempts=3 then transition tofailed; confirm the DLQ tab badge increments. - Open the DLQ tab → click Replay on the failed row → confirm a new
succeededrun appears AND the DLQ row's "Replayed" column shows the timestamp. - Trigger a password reset → confirm the run appears in the "System" tab (gated to the configured platform-admin email).
- Sign out; sign in as a member (non-owner) of the same workspace → navigate to Job runs → confirm Recent runs renders but Replay is disabled with the tooltip.
- Open the Inngest dev UI in a third tab → confirm every run the dashboard shows is also present in Inngest's own UI, with the same attempt counts and durations. The two surfaces must agree.
- Stop and restart
pnpm dev; confirm the dashboard still shows historical runs (they're DB-persisted, not in-memory). - Resize the dashboard to a 375×812 viewport → confirm the table is horizontally scrollable inside the responsive shell; no layout regressions vs Story 1.5.