Review round 1 on PR #194:
- The edit lock did not exclude other processes (also on main): unlock
removes the lock file, so a waiter could wake holding a flock on the
orphaned inode while the next caller locked a fresh file. flockFile now
re-checks after flock that the locked file is still the one at the path
(brainfs LockIsCurrent) and retries otherwise; OpenLock reports a lock
replaced mid-open as ErrLockChanged, which is retried instead of
failing the edit. New cross-process test (3 re-executed processes x 400
rounds, O_EXCL marker under the lock): 0 overlaps; red without the
re-check.
- OpenLock unit test: relative dangling link (only the Lstat pre-check
stops it; os.Root refuses absolute links on its own), live link, FIFO,
directory, behind a symlinked dir, unclean path, positive control.
- Rollback test: a symlink swapped in for the note before the rollback
(test seam beforeStampHook) is refused, target untouched in and outside
the brain.
- The in-brain dangling-lock case uses a relative link and asserts the
refusal names the lock. OpenLock reuses #189's RealParent; RemoveLock is
dropped for brainfs.Remove. The doc comment names the swap residual.
Rebased onto main after #189.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New brainfs PutRegular (create or replace), CreateRegular (create only), AppendRegular, Remove and RealParent: missing parents created one component at a time through the root, every existing component a real directory, new files created with O_EXCL and confirmed before writing. brain_write, promote/reject (O_EXCL create, source kept on refusal), StampProvenanceFile, BuildWingIndex, RebuildIndex, AppendLog and the tunnel candidate log use them; provenance.ApplyFile removed.
Fixes#188. Refs #158. Adversarial review: BLOCKED, BLOCKED, PASS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
ReadRegular, ReadRegularMax and WriteRegular require a clean, local path and a real directory at every level above the file, so an in-brain directory symlink (or a ".." after one) can no longer redirect brain_update, brain_tunnel, brain_read/get or the other readers elsewhere in the brain. session.Append opens through an os.Root on the brain dir, refuses a non-directory sessions dir, and writes only after confirming the log path is the regular file it opened.
Fixes#173. Adversarial review: BLOCKED, PASS. Follow-up: #188 (brain_write create path).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
brain_tunnel accepts only clean wiki/.../*.md paths and reads/rewrites both notes through brainfs; new brainfs.WriteRegular never creates, never writes through a final symlink (Lstat + O_NOFOLLOW|O_NONBLOCK + os.SameFile on the handle, since os.Root follows in-root final symlinks even with O_NOFOLLOW). brain_update resolves like brain_edit (knowledge/ and wiki/ notes only) and reads/rewrites through brainfs. session_log requires a plain-name session id and opens with O_NOFOLLOW|O_NONBLOCK.
Fixes#169. Adversarial review: BLOCKED, BLOCKED, PASS (round 3 by the estate session's reviewer). Follow-ups: #173, #174.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
BuildWingIndex/BuildAllWingIndexes list wings and halls with the new brainfs.ReadDirDirs and ReadDirRegular and read titles with ReadRegular: a symlinked note no longer reaches _index.md, a symlinked wing is refused (nothing written into its target), a symlinked hall is skipped. linkcheck entryIndex walks with WalkRegular, so a symlinked .md cannot make a link resolve, and a linked tree that is not a real directory is skipped with a warning (brain_write still lands).
Refs #157. Adversarial review: BLOCKED, PASS. Follow-up: #178.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
graphsync, wiki inventory/index read through brainfs; brainfs refuses an in-brain file swapped in behind a symlink (os.SameFile). Gatekeeper round 2 PASS on d90ce66, CI run 9180 green.
Refs #157
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.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>