fix(engine): recover strict spawn claims after crashes

Draft
KH
1d ago
+10899-23

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 spawnReservedAt atomically via runAtomicWrites(..., { 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
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.