fix(watcher): never walk into raw/rejected/ — brain_reject (closes #81)
Adds brain_reject (RejectNote + MCP tool) and fixes the watcher to skip raw/rejected/, preventing declined notes from being silently re-ingested and republished. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #133.
This commit is contained in:
@@ -119,3 +119,85 @@ func TestPromoteRebuildsWingIndex(t *testing.T) {
|
||||
require.NoError(t, err, "wing _index regenerated")
|
||||
assert.Contains(t, string(idx), "x", "promoted note appears in the index")
|
||||
}
|
||||
|
||||
// --- RejectNote (hyperguild#81) ---------------------------------------------
|
||||
|
||||
func TestRejectHappyPath(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeRaw(t, dir, "2026-04-23-fed-sentiment-scrape.md",
|
||||
"---\ncreated_at: 2026-04-23T13:20:07Z\n---\n# Sentiment scrape\n\nbody.\n")
|
||||
|
||||
rel, err := RejectNote(dir, RejectOptions{
|
||||
Filename: "2026-04-23-fed-sentiment-scrape.md", Reason: "generic scrape, not homelab-relevant",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "raw/rejected/2026-04-23-fed-sentiment-scrape.md", rel)
|
||||
|
||||
// Source removed from the pending queue.
|
||||
_, statErr := os.Stat(filepath.Join(dir, "raw", "2026-04-23-fed-sentiment-scrape.md"))
|
||||
assert.True(t, os.IsNotExist(statErr), "source removed from raw/ after reject")
|
||||
|
||||
got, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel)))
|
||||
require.NoError(t, err)
|
||||
s := string(got)
|
||||
assert.Contains(t, s, "created_at: 2026-04-23T13:20:07Z", "original created_at preserved")
|
||||
assert.Contains(t, s, "rejected_at:")
|
||||
assert.Contains(t, s, "reject_reason: generic scrape, not homelab-relevant")
|
||||
assert.Contains(t, s, "# Sentiment scrape", "body preserved for later reference")
|
||||
|
||||
// Rejected notes never entered the wiki, and are no longer pending.
|
||||
pending, err := ListPending(dir)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, pending)
|
||||
}
|
||||
|
||||
func TestRejectWithoutReason(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeRaw(t, dir, "2026-06-01-x.md", "body\n")
|
||||
rel, err := RejectNote(dir, RejectOptions{Filename: "2026-06-01-x.md"})
|
||||
require.NoError(t, err)
|
||||
got, _ := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel)))
|
||||
assert.NotContains(t, string(got), "reject_reason:", "no reason given, none stamped")
|
||||
}
|
||||
|
||||
func TestRejectMissingSourceErrors(t *testing.T) {
|
||||
_, err := RejectNote(t.TempDir(), RejectOptions{Filename: "ghost.md"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRejectRejectsTraversalFilename(t *testing.T) {
|
||||
_, err := RejectNote(t.TempDir(), RejectOptions{Filename: "../escape.md"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRejectCollisionNoOverwrite(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
dest := filepath.Join(dir, "raw", "rejected", "x.md")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(dest), 0o755))
|
||||
require.NoError(t, os.WriteFile(dest, []byte("EXISTING\n"), 0o644))
|
||||
writeRaw(t, dir, "x.md", "NEW\n")
|
||||
|
||||
_, err := RejectNote(dir, RejectOptions{Filename: "x.md"})
|
||||
require.Error(t, err, "collision must error, not overwrite")
|
||||
|
||||
got, _ := os.ReadFile(dest)
|
||||
assert.Equal(t, "EXISTING\n", string(got), "target not overwritten")
|
||||
_, statErr := os.Stat(filepath.Join(dir, "raw", "x.md"))
|
||||
assert.NoError(t, statErr, "source preserved on collision (atomic: no delete without write)")
|
||||
}
|
||||
|
||||
func TestRejectedNotesAreNotListedAsPendingAgain(t *testing.T) {
|
||||
// ListPending already skips tunnel-candidates-*.md; confirm raw/rejected/
|
||||
// (a subdirectory) doesn't leak into the top-level pending listing either
|
||||
// — os.ReadDir on raw/ doesn't recurse, but pin it so a future change to
|
||||
// ListPending can't silently start surfacing rejected notes as pending
|
||||
// again.
|
||||
dir := t.TempDir()
|
||||
writeRaw(t, dir, "2026-06-01-x.md", "body\n")
|
||||
_, err := RejectNote(dir, RejectOptions{Filename: "2026-06-01-x.md"})
|
||||
require.NoError(t, err)
|
||||
|
||||
pending, err := ListPending(dir)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, pending, "a rejected note must not still appear as pending")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user