fix(brain_edit): snapshot, rollback and lock file go through the brain root (#192) #194

Open
mathias wants to merge 3 commits from fix/edit-path-io-192 into main
3 Commits
Author SHA1 Message Date
mathiasandClaude Opus 5.5 cdcff126ef test(brain_edit): the rollback test proves the swap happened (#192)
CI / Lint / Test / Vet (pull_request) Successful in 1m6s
CI / Mirror to GitHub (pull_request) Skipped
Review round 2 on PR #194: with the hook made a no-op the test still
passed (no swap, the rollback wrote the real note, victim trivially
untouched). It now asserts the error says the edit could NOT be rolled
back and that the note path is still the swapped-in symlink; red when
the hook does not fire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:43:27 +02:00
mathiasandClaude Opus 5.5 07ea5b01ec fix(notes): the edit lock excludes other processes; pin OpenLock and the rollback (#192)
CI / Lint / Test / Vet (pull_request) Successful in 1m9s
CI / Mirror to GitHub (pull_request) Skipped
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>
2026-10-05 23:36:56 +02:00
mathiasandClaude Opus 5.5 497cd5b6f3 fix(brain_edit): snapshot, rollback and lock file go through the brain root (#192)
brain_edit did its own I/O by path around notes.Edit:
- the pre-edit snapshot used os.ReadFile, which hung on a FIFO note held
  open by a writer;
- the rollback (when the provenance stamp fails) used os.WriteFile;
- the edit lock "<note>.lock" was opened with O_CREATE by path, so a
  planted dangling .lock symlink made a successful edit create a file at
  the link's target, in the brain or outside it.

Snapshot is brainfs ReadRegular, rollback WriteRegular, and the lock is
opened and removed through new brainfs OpenLock/RemoveLock: clean path,
real parent directories, an existing lock must be regular, and the opened
handle must be the file at the path (SameFile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:34:42 +02:00