fix(engine): recover strict spawn claims after crashes
Outcome
Successor to #644 for the two strict-spawn pre-dispatch crash windows identified in the exact-head review.
- persists the immutable exact target in the initial strict claim (state A);
- couples exact-node capacity reservation with
spawnReservedAtatomically viarunAtomicWrites(..., { requireAtomic: true })and the existing node-capacity sentinel (state B); - resumes state A or B through the existing dispatch CAS from strict invoke and
/spawn/replay, without creating claims from replay; - returns a stranded state-B reservation and terminalizes its exact claim in one atomic cleanup, losing safely to concurrent dispatch;
- leaves legacy and generic spawn behavior on the existing path.
Exact scope
- Base:
c74c34065c3fa7bcc95d98a3ea4038e44dee9340(unchanged #644 head) - Head:
afba5d631672a2dd80910f90d380137e9e84db50 - This is a separate stacked draft; it does not modify #644's branch.
Verification
Literal output is included in the commands below.
$ node_modules/.bin/vitest run packages/engine/src/__tests__/conformance/node.test.ts --reporter=dot
Test Files 1 passed (1)
Tests 92 passed (92)
Duration 11.84s
$ npm test --workspace @relaycast/engine -- --reporter=dot
Test Files 189 passed | 2 skipped (191)
Tests 2678 passed | 42 skipped (2720)
Duration 44.63s
$ npm run typecheck --workspace @relaycast/engine
> @relaycast/engine@12.8.0 typecheck
> tsc --noEmit -p tsconfig.typecheck.json
$ npm run lint --workspace @relaycast/engine
> @relaycast/engine@12.8.0 lint
> eslint src/
$ git diff --check c74c34065c3fa7bcc95d98a3ea4038e44dee9340..afba5d631672a2dd80910f90d380137e9e84db50
# exit 0, no output
The focused crash/restart matrix covers state A, state B, concurrent invoke/replay, D1 first- and second-write failure, interactive-transaction rollback, missing atomic capability, exact-once sweep cleanup, dispatch-vs-sweep, recovered proof, and no alternate-node fallback:
Test Files 1 passed (1)
Tests 9 passed | 83 skipped (92)
Duration 3.13s
Mutation evidence
Specific mutation: replace the atomic reservation+marker batch with two sequential awaits. The D1 marker-failure test then leaves one reserved slot:
$ node_modules/.bin/vitest run packages/engine/src/__tests__/conformance/node.test.ts -t "rolls back the strict reservation when the D1-style marker write fails" --reporter=verbose
FAIL ... > rolls back the strict reservation when the D1-style marker write fails
AssertionError: expected 1 to be +0
- Expected
+ Received
- 0
+ 1
Test Files 1 failed (1)
Tests 1 failed | 91 skipped (92)
After byte-for-byte restoring runAtomicWrites(..., { requireAtomic: true }):
$ node_modules/.bin/vitest run packages/engine/src/__tests__/conformance/node.test.ts -t "rolls back the strict reservation when the D1-style marker write fails" --reporter=verbose
Test Files 1 passed (1)
Tests 1 passed | 91 skipped (92)
Environment note
A fresh npm ci in this /tmp clone could not complete because the host exhausted /tmp inodes. Verification used the existing node_modules from the exact same #644 head worktree; CI remains the independent clean-install check.
No merge, deploy, publish, or gate edit is included.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.