PR #160 review:
- The planter chooses the paths, and one alert listed them all, while the
queue retried oldest-first forever: a message ntfy refuses (too large)
blocked every later alert (2000 links: 0 alerts delivered), and an
outage queued one message per removal, unbounded. Alerts now name at
most 10 paths and count the rest; the queue holds at most 5, further
alerts become one suppression summary; a message failing 5 times is
dropped so it cannot block the rest.
- removeSymlinks' Lstat re-check had no test: a direct test passes a
regular file, a path through an in-brain dir link, a ../ escape and a
missing path, and asserts all fail with nothing deleted. A Watch test
covers dir and in-brain targets. root.Remove (not RemoveAll) is
commented: it refuses a dir swapped in after the Lstat.
- Every queued alert is delivered after an outage (truncating the queue
after the first send is now red).
- hyperguild#162: race tests did not wait for their swapper goroutines
before t.TempDir cleanup ("directory not empty" in CI run 8912). Each
swapper is now stopped and awaited in a t.Cleanup.
Fixes#162
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decision (#157): the tripwire only alerted, so a symlink planted in the
Syncthing-shared brain dir stayed readable until a human removed it.
Watch now unlinks every symlink it finds through an os.Root on the brain
dir (re-checking it is still a symlink; the target is never touched),
and the default scan interval drops from 5 min to 30 s, so a planted
link is readable for at most one interval.
Each removal is an alert naming what was removed; a persistent finding
(a link it could not remove, an unreadable dir, a scan error) alerts
once per distinct state. Alerts queue until Notify succeeds, so an ntfy
outage delays them without losing them, even though the next scan of
the cleaned brain is clean.
The startup check still refuses to start on any symlink.
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>