Refs #205 (step 0). Risk: medium — the watcher now deletes files in a Syncthing-shared folder (raw/), after archiving them.
What
After a note is archived into raw/processed/<date>/ or raw/failed/, processFile removes the original from raw/ through the os.Root (no symlink escape). raw/ then holds only unprocessed input, so brain_pending / the pending nudge stop listing already-ingested notes. The .processed/.failed marker is written only as a fallback if the removal fails (so a stuck file is not re-ingested every 30s). Existing markers are still honoured.
Evidence
Tests first: TestStart_ProcessesFile and TestStart_MovesToFailedOnError were changed to assert original removed + no marker; run red (4 assertion failures), then green.
go vet ./..., go test -race ./... (0 FAIL), golangci-lint on the package: 0 issues.
Known limits (not in this PR)
Re-sync of a removed file from an offline Syncthing peer would re-ingest it; pipeline.Run's source-hash drift check skips an unchanged source. Pinning test is step 5 of #205.
Retired-schema output paths (wiki/sources|concepts|entities) are #205 steps 1–4.
The ~12 legacy originals and .processed markers already in raw/ stay until #205 step 4.
Review ask
Verify by execution: removal cannot follow a symlink out of raw/; a failed archive copy never removes the original.
Refs #205 (step 0). Risk: **medium** — the watcher now deletes files in a Syncthing-shared folder (raw/), after archiving them.
## What
After a note is archived into `raw/processed/<date>/` or `raw/failed/`, `processFile` removes the original from `raw/` through the `os.Root` (no symlink escape). `raw/` then holds only unprocessed input, so `brain_pending` / the pending nudge stop listing already-ingested notes. The `.processed`/`.failed` marker is written only as a fallback if the removal fails (so a stuck file is not re-ingested every 30s). Existing markers are still honoured.
## Evidence
- Tests first: `TestStart_ProcessesFile` and `TestStart_MovesToFailedOnError` were changed to assert original removed + no marker; run red (4 assertion failures), then green.
- `go vet ./...`, `go test -race ./...` (0 FAIL), `golangci-lint` on the package: 0 issues.
## Known limits (not in this PR)
- Re-sync of a removed file from an offline Syncthing peer would re-ingest it; `pipeline.Run`'s source-hash drift check skips an unchanged source. Pinning test is step 5 of #205.
- Retired-schema output paths (`wiki/sources|concepts|entities`) are #205 steps 1–4.
- The ~12 legacy originals and `.processed` markers already in raw/ stay until #205 step 4.
## Review ask
Verify by execution: removal cannot follow a symlink out of raw/; a failed archive copy never removes the original.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
_Created via git-mcp on behalf of @mathias_
After a note is archived into raw/processed/ or raw/failed/, remove the
original from raw/ so raw/ only holds unprocessed input. The .processed /
.failed marker is now only a fallback when the removal itself fails.
Existing markers are still honoured.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
When both Remove and the marker write fail (read-only raw/), remember the
path in memory so later polls skip it instead of writing another archive
copy every tick. Adds a test that a failed archive copy leaves the original
in raw/, mutation-checked against removing the early return.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Independent review pass (pr-gatekeeper agent, verified by execution; reviewer is an agent, not four-eyes). Verdict at first look: not mergeable yet, CI pending. Findings:
Symlink swap: pass. A symlinked note / symlinked subdir pointing outside raw/ is never ingested, copied or removed; Remove goes through os.Root.
Archive copy failure keeps the original: pass in code, but was untested. Now pinned by TestProcessDir_KeepsTheOriginalWhenTheArchiveCopyFails (mutation-checked: removing the early return fails it). The first draft of that test hit the mkdir branch instead of the copy branch and survived the mutant — fixed.
Existing markers honoured: pass.
Hole (confirmed by test, fixed in the follow-up commit): with a read-only raw/, Remove and the marker write both fail, so the note was re-ingested every poll, writing a new archive copy each time. Fixed with an in-memory skip set; TestProcessDir_DoesNotReingestANoteItCannotRetire.
Open, not fixed here: small race — if Syncthing replaces the note between archive and Remove, the newer content is deleted after the older was archived. To be tracked as a follow-up on #205.
Not verified: sticky-bit dir case (Remove fails, marker succeeds), Syncthing propagation itself.
**Independent review pass (pr-gatekeeper agent, verified by execution; reviewer is an agent, not four-eyes).** Verdict at first look: **not mergeable yet, CI pending**. Findings:
- Symlink swap: pass. A symlinked note / symlinked subdir pointing outside raw/ is never ingested, copied or removed; Remove goes through os.Root.
- Archive copy failure keeps the original: pass in code, but **was untested**. Now pinned by `TestProcessDir_KeepsTheOriginalWhenTheArchiveCopyFails` (mutation-checked: removing the early return fails it). The first draft of that test hit the mkdir branch instead of the copy branch and survived the mutant — fixed.
- Existing markers honoured: pass.
- **Hole (confirmed by test, fixed in the follow-up commit):** with a read-only raw/, Remove and the marker write both fail, so the note was re-ingested every poll, writing a new archive copy each time. Fixed with an in-memory skip set; `TestProcessDir_DoesNotReingestANoteItCannotRetire`.
- Open, not fixed here: small race — if Syncthing replaces the note between archive and Remove, the newer content is deleted after the older was archived. To be tracked as a follow-up on #205.
- Not verified: sticky-bit dir case (Remove fails, marker succeeds), Syncthing propagation itself.
Merge only on green CI.
---
_Created via git-mcp on behalf of @mathias_
The archive holds the content read at the start; if Syncthing replaced the
note during the (minutes-long) LLM pass, removing the original would drop the
newer content. Compare size+mtime before removing; leave it for the next poll.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Refs #205 (step 0). Risk: medium — the watcher now deletes files in a Syncthing-shared folder (raw/), after archiving them.
What
After a note is archived into
raw/processed/<date>/orraw/failed/,processFileremoves the original fromraw/through theos.Root(no symlink escape).raw/then holds only unprocessed input, sobrain_pending/ the pending nudge stop listing already-ingested notes. The.processed/.failedmarker is written only as a fallback if the removal fails (so a stuck file is not re-ingested every 30s). Existing markers are still honoured.Evidence
TestStart_ProcessesFileandTestStart_MovesToFailedOnErrorwere changed to assert original removed + no marker; run red (4 assertion failures), then green.go vet ./...,go test -race ./...(0 FAIL),golangci-linton the package: 0 issues.Known limits (not in this PR)
pipeline.Run's source-hash drift check skips an unchanged source. Pinning test is step 5 of #205.wiki/sources|concepts|entities) are #205 steps 1–4..processedmarkers already in raw/ stay until #205 step 4.Review ask
Verify by execution: removal cannot follow a symlink out of raw/; a failed archive copy never removes the original.
🤖 Generated with Claude Code
Created via git-mcp on behalf of @mathias
Independent review pass (pr-gatekeeper agent, verified by execution; reviewer is an agent, not four-eyes). Verdict at first look: not mergeable yet, CI pending. Findings:
TestProcessDir_KeepsTheOriginalWhenTheArchiveCopyFails(mutation-checked: removing the early return fails it). The first draft of that test hit the mkdir branch instead of the copy branch and survived the mutant — fixed.TestProcessDir_DoesNotReingestANoteItCannotRetire.Merge only on green CI.
Created via git-mcp on behalf of @mathias