Files
nexu-io__open-design/apps/daemon/tests/plugins-patch-edit-atomic.test.ts
Cursor Agent b8394ae5b8 feat(plugins): patch-edit atomic file writes (Phase 7 safety patch)
Plan Z1 / spec §20.3.

apps/daemon/src/plugins/atoms/patch-edit.ts gains an
`atomicWriteFile(target, body)` helper:

  1. Write body to `<target>.<random>.tmp` (sibling of target).
  2. fs.rename(2) the tmp into place — atomic on POSIX when
     source + target share a filesystem (which they always do
     here because the tmp is sibling).
  3. On failure, best-effort unlink the orphan tmp.

Every fs.writeFile call inside patch-edit now routes through it:
  - file creation (--- /dev/null target)
  - plain in-place edits
  - plan/steps.json status updates
  - plan/receipts/step-<id>.json receipt writes

Effect: a daemon crash mid-write (SIGKILL, ENOSPC, etc.) can no
longer leave the source file truncated — the rename either
happens fully or not at all. The tmp file is the only collateral,
and the orphan-cleanup path keeps even that contained.

The test harness asserts both halves of the contract:
  - happy path leaves NO .tmp siblings on disk
  - context-mismatch rejection leaves the original byte-equal
    (atomicity covers the failure path too)
  - plan/ + plan/receipts/ never accumulate .tmp leftovers

Daemon tests: 1699 \u2192 1702 (+3 cases on
plugins-patch-edit-atomic: clean rename leaves no .tmp, rejected
patch preserves byte-equal source, plan/receipt writes never
leak .tmp siblings).

Co-authored-by: Tom Huang <1043269994@qq.com>
2026-05-09 16:38:46 +00:00

3.2 KiB