⚠️ AMENDMENT — the
step.runENUMERATION in this card is WRONG (motir run MOTIR-3414, 2026-08-23)Re-measured before the first line of work, on the ref rather than on a working tree, per the run-time rule that a card counting a population is re-measured against the ref it names (or
origin/mainwhen it names none). Branch base:origin/main@165f1485.$ git grep -c 'step\.run(' origin/main -- '*.ts' '*.tsx' 60 occurrences across 27 files $ git grep -c 'step\.run(' origin/main -- '*.ts' '*.tsx' ':!tests/*' 58 occurrences across 26 files"84 call sites across 37 files" is in fact 58 across 26 (60 across 27 if the one test file is counted). Read every "84" below as 58, and every "37 files" as 26.
The second limb is falsified outright, and it is the one that was load-bearing. The story's
explanationMdargued the shim earns its cost because the call sites live "including services well outsidelib/jobs/". They do not:$ git grep -l 'step\.run(' origin/main -- '*.ts' '*.tsx' ':!tests/*' 25 of 26 files are under lib/jobs/ ; the 26th is scripts/plan-seed/data/story-1.6.ts — seed FIXTURE DATA, not a serviceNo service file outside
lib/jobs/callsstep.run. The blast radius of rewriting the call sites islib/jobs/and nothing else — which is where the seam already is.What changes and what does not. The DELIVERABLE is untouched and the CONCLUSION stands: 58 call sites are still 58 that should not be rewritten, and a shim that keeps the existing job definitions compiling unchanged is still the right shape. What weakens is the COST argument — the shim is bought for compatibility, risk and reversibility, not for a blast radius that reaches outside the jobs layer. Recorded rather than quietly deleted, because an amendment with evidence and a silently-dropped clause look identical in the diff and are opposite in kind.
Where
37probably came from:lib/jobs/registry.tsexports 37 job functions across 24 definition files. Two real populations, correctly observed, fused into a sentence about a third.Planning bug: MOTIR-3428. Nothing else in this card is changed by the amendment — no deliverable, edge, repo pin, estimate or acceptance criterion.
The compatibility layer that makes this migration affordable. It reimplements the part of Inngest's step contract our code actually uses, over job_step, so that 84 step.run call sites across 37 files change by zero characters.
step.run(id, fn)Look up (run_id, id) in job_step. If a row exists, return its stored result without executing. Otherwise execute fn, persist the result, return it. That is the entire contract our jobs rely on: a step already done is not redone when the run is retried or resumed.
Two details that are easy to get wrong:
Date returned from a step comes back as a string on replay. Production already behaves this way and tests already account for it; the shim must not accidentally be more faithful in-process than it is on resume, or a bug appears only after a restart.step.sleep(id, ms)Persist a wake deadline, then throw a typed Yield the worker catches and turns into a re-enqueue with run_at = deadline. The run leaves memory entirely and resumes later, which is what lets a 30-minute supervisor survive a deploy.
And this is the piece that has to be durable rather than convenient. An in-process await would be simpler and would work until the first deploy, at which point a sleeping supervisor vanishes and leaves a container running with nothing watching it.
waitForEvent. It has zero real call sites — the single grep hit in the tree is a comment inside codeGraphRefresh.ts quoting Inngest's documentation. Building it would be building for a consumer that does not exist; if a later card needs it, that card adds it.
job_step rows.step.sleep survives a worker restart — the run resumes at the right step after the process is killed and started again. This is the card's load-bearing test and it must genuinely restart, not simulate.step.run / step.sleep call site in lib/ is edited by this card. The diff proves it.lib/jobs/indexFleetSteps.ts — the heaviest consumer, and the source of the memoization rules the shim must honour (step ids keyed by projectId and iteration, never by loop position)lib/jobs/defineJob.ts — where ctx.step is handed to a handlerprisma/schema.prisma — job_step from the schema cardtests/jobs/ — the existing job tests, several of which stub step.sleep by id and will exercise the new implementation