PR #159 round 2: with the brain root at mode 0100 (enterable, not
listable) WalkDir failed at the root, Scan discarded the error and
reported clean, and startup passed while search, which goes straight
into wiki/, served a planted symlink. Scan now returns the root's walk
error, so startup refuses on anything but a missing root. Also pins
that a cleared set is reported again when it reappears.
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Startup passed on ANY scan error: WalkDir stopped at the first
unreadable dir and Scan discarded what it had found, while search
kept walking and served a planted symlink. Scan now records
unreadable dirs and keeps going; startup refuses on symlinks or
unreadable dirs and only lets a missing brain dir pass.
- Watch went silent while a scan error persisted, and marked a set as
reported before Notify, so an ntfy outage lost the alert. It now
alerts on unreadable dirs and scan errors too, and records a set as
reported only after Notify succeeds (retried each tick).
- Only the root .git is skipped; a nested .git is scanned.
- A non-positive BRAIN_SYMLINK_SCAN_INTERVAL fell into time.NewTicker
and crashlooped the server; it now falls back to the default.
- Findings are quoted in alerts, so a newline in a name cannot merge
two sets; alertingWarnings names the tripwire when ntfy is unset.
- Test names match what they assert (two-set dedupe, symlinked entries
inside a symlinked root, nested .git).
Flipped: startup ignoring unreadable dirs, .git skipped at any depth,
and no retry each turn a test red. Watch tests stable over -count=10
and under -race.
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The brain dir is a Syncthing sendreceive folder and Syncthing syncs
symlinks; search, vectorstore, context and graph readers follow one out
of the brain and would index or return what it points to (reproduced
with search.Query). Decision (#157): a tripwire now, brain-wide os.Root
readers next.
- symlinkguard.Scan lists every symlink inside the brain dir (skipping
.git; the brain dir itself may be a mount symlink).
- At startup the server refuses to start while any exists, naming them.
- symlinkguard.Watch rescans every BRAIN_SYMLINK_SCAN_INTERVAL seconds
(default 300) and sends one ntfy alert per new set of symlinks.
The live brain dir had 0 symlinks on 2026-10-03, checked before this
change, so deploying it does not trip.
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>