Review round 1 on PR #168:
- ReadRegular/ReadRegularMax Lstat the path, then open it again by path;
os.Root follows an in-root final symlink even with O_NOFOLLOW, so a
symlink to another brain file swapped in between was read (8k-109k
raw/ leaks in RebuildIndex, LoadInventory, IndexDoc). The opened handle
must now be the same file as the Lstat (os.SameFile). This covers every
ReadRegular caller, slices 1-3 included.
- Every symlink test pointed outside the brain, which os.Root refuses on
its own. Each now has an in-brain case too: a static in-brain link per
reader, AssertNoLeakFrom with an in-brain target, and brainfs unit tests
for ReadDirRegular and the in-brain swap.
- brainfstest: swap links are relative (os.Root refuses every absolute
link, so an absolute in-brain target tested nothing), and each swapper
has its own temp name (two swappers on one path deadlocked on each
other's FIFO).
- IndexDoc indexes only a clean wiki/ or knowledge/ path (callers pass
caller-supplied paths).
- Rule 9: TestLoadInventoryReturnsErrNotRegularForASymlinkedTypeDir now
asserts the error; the backfill test says it aborts on a symlinked root.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
graphsync.IndexDoc/BackfillFromBrainDir and wiki.LoadInventory/
RebuildIndex read notes by path (os.ReadFile, os.Open, os.ReadDir +
open). A symlinked note put the target's title, aliases and wikilinks
into the knowledge graph, the wiki inventory (and the LLM prompts built
from it) and wiki/index.md; a FIFO hung each read.
- graphsync: IndexDoc and the backfill read through brainfs; the
backfill walks with WalkRegular, skips a missing root and aborts on
any other walk error, including a symlinked root.
- wiki: LoadInventory lists type dirs with the new
brainfs.ReadDirRegular (every component a real dir, regular files
only) and reads frontmatter through the root; a symlinked type dir is
an error. RebuildIndex reads first sentences through the root.
- brainfstest's outside file now carries the sentinel in frontmatter
title and aliases, a heading and a wikilink, so extractors that ignore
the body are tested too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vectorstore.Sync walks and reads via brainfs (regular files only, handle-checked), so symlinked notes are no longer embedded and their old chunks are purged. brainfs.WalkRegular requires every component of its root to be a real directory (fixes in-brain root aliasing in Sync and search Wing/Wing+Hall). Walk errors, including a symlinked scan root, abort Sync before the delete pass. brainfs gains Lstat. Directory-swap race recorded as accepted residual (#167).
Refs #157. Adversarial review: BLOCKED, BLOCKED, PASS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
readExcerpt, ReadNote and ReadScoped now read through brainfs (os.Root, regular files only, type checked on the opened handle), so a symlink swapped in after a check is not followed and a FIFO does not hang the reader. ReadRegularMax bounds the read itself with io.LimitReader(max+1). notes.Target carries the brain root.
Refs #157. Adversarial review round 2: PASS (round 1 BLOCKED: unbounded read, rule-9 test names).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>