Skip to content

fix(atlas): write the Pulse snapshot atomically - #1740

Open
elhoim wants to merge 1 commit into
danielmiessler:mainfrom
elhoim:fix/atlas-atomic-write
Open

fix(atlas): write the Pulse snapshot atomically#1740
elhoim wants to merge 1 commit into
danielmiessler:mainfrom
elhoim:fix/atlas-atomic-write

Conversation

@elhoim

@elhoim elhoim commented Aug 3, 2026

Copy link
Copy Markdown

One commit, one fix2df5e0c, applies cleanly onto 47df8ee on its own.

LIFEOS/ATLAS/Atlas.ts writes the Pulse snapshot with writeFileSync at two call sites (runSync and case "export"). That is truncate-then-write, and there is a documented concurrent reader: PULSE/modules/atlas.ts:160 does JSON.parse(readFileSync(SNAPSHOT_PATH)) on every GET /api/atlas, and ATLAS/Store.ts:5 states "Pulse reads a redacted snapshot, never the live DB". A reader landing mid-write sees a truncated prefix and throws.

Both sites now use the existing atomicWriteText helper (temp file + rename), so a reader sees either the whole previous snapshot or the whole new one.

  • atomicWriteText, not atomicWriteJSON — the latter reformats to JSON.stringify(data, null, 2), which would change the snapshot's bytes. This keeps the compact output identical.
  • Import path ../PULSE/lib/atomic-write follows the convention already used by LIFEOS/TOOLS/MergeSettings.ts:16 and SettingsBackport.ts:45.
  • writeFileSync dropped from the node:fs import; it had no other use in the file.

Verified by bundling: bun build --target=bun LIFEOS/ATLAS/Atlas.ts resolves 10 modules and the helper's renameSync(tmp, filePath) is inlined in the output.

Out of scope but same class: PULSE/modules/atlas.ts writes its own INSIGHTS_CACHE with writeFileSync. Left alone here.


Testing. Commands and output are in the per-file notes above. I did not do a fresh-system install verification (contributing step 3) — these are targeted fixes verified per-file, not an install run.

Atlas.ts wrote snapshot.json with writeFileSync at both call sites (sync
and export), which truncates the target before writing. Pulse reads that
same file concurrently — PULSE/modules/atlas.ts JSON.parses it on every
GET /api/atlas — so a request landing mid-write saw a truncated file and
threw. Store.ts and AtlasSystem.md both document Pulse as the reader of
record ("one writer class, one reader artifact").

Switch both writes to atomicWriteText from PULSE/lib/atomic-write (temp
file + rename), matching how LIFEOS/TOOLS reaches that helper. Byte
output is unchanged: atomicWriteText writes the string as given, so the
snapshot stays compact JSON with no trailing newline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant