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: 2b62ad39f22f4f66ab03f4628b4dc395
Branch: dev/2-leaf-file-link
Base: 8626664
At: 2026-11-01T18:35:34Z
What happens today
Two host writers ask what their folder is and never ask what their file is. A leaf file swapped for a link is therefore written straight through it, into a file outside the home, with no warning:
- memory journal —
core/src/host/memory.js:527-531journal()guards withwriteFolder(root)(:473-478, which asks every segment down tomemory/) and thenappendFile(journalPath, …)at:530.journalPath = join(root, 'journal.jsonl')(:426,root = join(home, 'memory')at:422). The leaf file is neverlstated, somemory/journal.jsonl→ link appends into whatever file it names. Four call sites reach it::662add,:693replace,:715remove,:736archive. - workflow store —
core/src/host/workflow-store.js:148save()guards withfolderWritable()(:55-60, onedirKindSync(dir)) and thenwriteFileSync(fileFor(def.id), bytes)at:169;fileForisjoin(dir, '${id}.json')(:42).setDefault()(:183-190) has the same gap at:188, writingproject.json(PROJECT_FILE,:30). Both refuse a linked folder, neither refuses a linked file.
The correct shape is already in this repository twice: core/src/host/memory.js:521-526 writeText() writes temp + rename, so a link there is replaced rather than followed; and core/src/host/journal.js (fixed in #2074) lstats the leaf before appending and writes only when it is absent or a plain non-link file.
Reproduction
Both probes import the real modules from a live main tree (8626664d), no stubs, each on a fresh temp home.
- Q9, memory journal: link
home/memory/journal.jsonlto<outside>/journal.jsonlholding one linePRE-EXISTING LINE, thenremember {text: 'Ship on Friday', scope: 'team'}. Today:
Q9 remember: Saved to the team memory, in MEMORY.md ( | outside journal lines: 2
— the outside file grew a line and no warning was printed. - Q10, workflow store: link
workflows/mine.jsonto<outside>/mine.json, thencreateWorkflowStore({dir}).save(...). Today:
Q10 save ok: true | outside file rewritten: true | link still a link: true
— the write lands in the outside file and still reports success.
Expected
Ask what the file is before every write, the same way #2056/#2065/#2074 ask about the folder: lstat the leaf and write only when it is absent (still created, mode 0600) or a plain non-link file; a link, a directory or any other entry is refused.
- memory journal: that one record is dropped, one warning is printed, and the memory write itself still succeeds — the journal is a side channel and saving memory must never depend on it.
- workflow
save/setDefault:ok: falsewith a new code (e.g.workflow/not-a-file), one warning, and neither the link nor the file it names is touched.
Acceptance (red first, on main today)
core/test/memory.test.js: linkmemory/journal.jsonlat an outside file → assert the outside text is byte-identical after the write, exactly one warning, andMEMORY.mdstill took the write. Today: outside gains a line, zero warnings → red.core/test/workflow-store.test.js:savethrough a linkedmine.json→ assertok: false, outside bytes unchanged, the link is still a link, one warning;setDefaultthrough a linkedproject.jsonthe same shape. Today:ok: trueand the outside file is rewritten → two reds.- Unchanged and green: the A symlinked memory or workflows folder reads another folder's files as this team's own #2057/A linked memory folder is still followed: snapshots, summaries, reviewed.json, the bots segment, and the entry writers #2068 folder-link cases, a missing folder still being created, and normal journal/save behaviour.
Scope
core/src/host/memory.js, core/src/host/workflow-store.js, patches/ds-bot/0001-core-fixes.patch (memory.js travels in that patch — there is no patches/ds-bot/files/src/host/memory.js mirror), patches/ds-bot/files/src/host/workflow-store.js (workflow-store does have a mirror), core/test/memory.test.js, core/test/workflow-store.test.js, one sentence each in docs/subsystems/core-host.md (memory section and the workflow-store row) and a changelog.d/ fragment.
Tier T2 (host write paths, security surface). Small: two lstat guards plus two warned sets, about 25–40 source lines and three red-first cases.
Verify with TZ=UTC ARCH_DOCS_BASE=<base> npm run verify, ds-bot-patches generate twice (no drift on the second run) plus check, and changelog check. Security surface, so the merge ping waits for the Security advisory at the tip.
Found by
Read-only post-merge audit by FlyBot Security of the #2071 squash (e7a295338800); line numbers re-checked here against live main 8626664d.
Duplicate check
journal symlink after start / journal folder link write / state journal link swap / symlink memory / workflow symlink / link memory folder across open and closed issues: #2055 (decisions folder), #2056 (journal folder), #2057 (memory/workflows folder read), #2068 (linked memory folder read side and the entry writers — closed 18:14:16Z, absorbed into #2071) are all folder links and all closed. The one open neighbour is #2074, which is the state journal's folder swapped after start-up in core/src/host/journal.js — a different module, fixed by the lstat guard this issue copies. Leaf single-file link write-through has no open issue.
Activity
HugoXK commentedon Nov 1, 2026
Claimed for FlyBot.
2b62ad39f22f4f66ab03f4628b4dc395dev/2-leaf-file-link8626664d0d1d84fdd8fdc28869d8f4e032225123(live main)The branch and the pull request follow within 24h. Do not start this issue without a
pickup-okcomment.Note on bookkeeping: this account is a repository contributor, so it cannot add the
agent-workinglabel or theBirfyassignee (the API answers403 Must have admin rights to Repository). This comment is the claim record until a maintainer sets them.HugoXK commentedon Nov 2, 2026
Heartbeat
Written-by: FlyBot Lead
Still-working: 2b62ad39f22f4f66ab03f4628b4dc395
At: 2026-11-02T00:18:10Z
Ask the file, not only its folder, before a memory or workflow write (T…
HugoXK commentedon Nov 2, 2026
Release
Written-by: FlyBot Lead
Released: 2b62ad39f22f4f66ab03f4628b4dc395
Reason: Merged as 4551bb3 (#2083) at 2026-11-02T01:21:36Z; issue closed completed.
At: 2026-11-02T01:25:04Z