fix(watcher): refusals end in a terminal state; pin every converted writer (#158)
Review round 1 on PR #195: - A refused marker or archive write left no marker, so the note was reprocessed every interval (archive copies piling up; an LLM call per round on the failure path). The marker check now uses Lstat (any entry at the marker path counts, so a dangling marker link skips the note instead of looping), and a note whose archive copy or marker write is refused is remembered (path, size, mtime) and not retried until it changes. - cleanRel no longer refuses a backslash: it is a legal filename byte on Linux, not a traversal, and a raw note named with one was never archived or marked. - Tests now reach every converted writer: the log.md append (real raw/failed so the log is reached, symlinked log.md), a dangling marker link, the refused-retry loop with a positive control, a backslash name, a buildAndWrite swap race, and the claude-session archive with in-brain and outside symlinked archive dirs. The stale lint allowlist entry for appendWatcherLog is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -190,3 +190,28 @@ func TestBackfillRefsNeverWritesThroughAPageSwappedForASymlink(t *testing.T) {
|
||||
assert.Equal(t, "untouched", string(got), "BackfillRefs wrote through a swapped-in symlink")
|
||||
assert.Positive(t, ok, "positive control: some backfills ran")
|
||||
}
|
||||
|
||||
// hyperguild#158: buildAndWrite wrote each page with os.WriteFile by path, so
|
||||
// a page read as regular and swapped for a symlink before the write was
|
||||
// written through. The target is not restored between rounds, so any
|
||||
// write-through shows.
|
||||
func TestBuildAndWriteNeverWritesThroughAPageSwappedForASymlink(t *testing.T) {
|
||||
brain := t.TempDir()
|
||||
mkdirs(t, brain, "wiki/sources", "wiki/concepts", "wiki/entities", "raw")
|
||||
page := filepath.Join(brain, "wiki", "concepts", "zq-concept.md")
|
||||
target := filepath.Join(brain, "raw", "secret.md")
|
||||
require.NoError(t, os.WriteFile(target, []byte("untouched"), 0o644))
|
||||
inside := "---\ntitle: Zq Concept\n---\nbody\n"
|
||||
require.NoError(t, os.WriteFile(page, []byte(inside), 0o644))
|
||||
pages := []RawPage{{Title: "Zq Concept", Type: "concept", Content: "## About\n\nmore\n"}}
|
||||
brainfstest.StartSwap(t, page, inside, target, false)
|
||||
ok := 0
|
||||
for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
|
||||
if _, err := RunRaw(context.Background(), brain, "zq source", pages, false); err == nil {
|
||||
ok++
|
||||
}
|
||||
}
|
||||
got, _ := os.ReadFile(target)
|
||||
assert.Equal(t, "untouched", string(got), "buildAndWrite wrote through a swapped-in symlink")
|
||||
assert.Positive(t, ok, "positive control: some runs wrote the page")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user