Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: a515913fcd2545b89cb983ac414a20a5
Branch: dev/4-397-journal-rotation-leaf
Base: 35f5f8e
At: 2026-11-02T10:00:41Z
What the user sees
A host that keeps stateJournal on runs out of room and rotates the journal. If something has replaced state.jsonl with a link by then — the same swap #2133 refuses at start-up and on every append — the rotation branch does not look, reads through the link, and stops the writer with a reason that points at the wrong thing:
<path> is not recording: JournalError: path revision does not hold the recorded earlier value
The honest answer is the journal path is not a plain file, the sentence the other two branches give (core/src/host/journal.js:263, :317). As it stands the reason reaches the user: #2135 (merged as 35f5f8e2) puts status().because into the diagnostics report, so the report line now tells them to go looking for a revision mismatch that does not exist.
No bytes escape through the link: rename replaces the name, not the inode, so the file the link names keeps its bytes (verified on the current main). This is a wrong-reason defect, not a data leak — it should be fixed as such, and not reported as a security hole.
Where it is
core/src/host/journal.js:293-306, the if (lastText !== undefined && (bytes + added > maxBytes || …)) branch: pruneEntries([...parseJournal(await readFile(path, 'utf8')), ...fresh]), then writeFile(temp, { flag: 'wx' }) + rename(temp, path). Neither lstat(path) nor the three predicates used at :263 and :317 (isSymbolicLink(), isFile(), nlink !== 1) is consulted here.
Reproduction
On the current main (35f5f8e2), with a real openJournal:
- open with
maxBytes: 350and record one entry (~326 B); rmSync(path)thenlinkSync(someOtherFile, path)— the journal path is now a hard link,nlink === 2;- record again, so the cap is crossed and rotation runs.
Current result: one warning, … is not recording: … JournalError: path revision does not hold the recorded earlier value; the other file's bytes are unchanged and its nlink is still 2. With the same setup and a symlink instead of a hard link the same wrong reason comes back.
Expected behaviour
The rotation branch asks the leaf question first and refuses with the same sentence the other branches use:
- before reading the file it is about to replace,
lstatthe path; if it is a symlink, not a file, ornlink !== 1, stop withthe journal path is not a plain file, as at:263/:317; - a missing path stays a normal case (rotation writes the temp and renames it into place), exactly as today;
- no change to
open(), the append branch,status()or the warning prefix — only which reason the rotation branch reports.
Acceptance
Red first on the current main with the source unmodified and only the new cases added:
- hard link standing in for the journal path plus a write over the cap →
warningshas one entry matching/not a plain file/(today it matches/revision does not hold/), the other file's bytes are unchanged and itsnlinkis still 2; - symlink standing in for the path plus a write over the cap → same sentence, and the target file is untouched;
- control case, green before and after: a plain journal file over the cap still rotates — content is the pruned set,
nlink === 1, no warning.
Then the same cases green, TZ=UTC ARCH_DOCS_BASE=<new base> npm run verify, and node scripts/ds-bot-patches.mjs check/canonical (this file is mirrored under patches/ds-bot/files/src/host/journal.js, so regenerate).
Scope
Only the rotation branch in core/src/host/journal.js and its test case in core/test/state-journal.test.js (plus the mirror/patches that regenerate from it). Not in this issue: the leaf predicates themselves, #2074/#2133 behaviour, or the warning text format.
Duplicate check
On live main 35f5f8e2: the rotation branch has no leaf check, and issue search for journal rotation leaf, not a plain file journal, rotation nlink returns only closed leaf-surface issues — #2133 (start-up + append hard link), #2094/#2083/#2079 (memory/work journal linked leaf), #2065/#2056 (linked journal folder), #2100/#2114 (temp name) — none of them the rotation branch. No duplicate.
Activity