fix(brain): move the wikilink strip into WriteNote/UpdateNote

The BrainStore is not the only door. POST /write — the HTTP path CLAUDE.md
hands to curl and scripts — calls api.WriteNote directly, so the strip
shipped one commit ago covered brain_write and capture while leaving the raw
door ungated. A gate one door wide is not a gate.

The BrainStore keeps its own call so it can still report Ref.Unlinked to the
MCP caller; running the strip twice is a no-op on content the primitive has
already cleaned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 23:59:44 +02:00
co-authored by Claude Opus 5
parent 6e5a9cfce3
commit 83cf6028bf
3 changed files with 60 additions and 0 deletions
+10
View File
@@ -133,6 +133,16 @@ func WriteNote(brainDir string, opts WriteNoteOptions) (string, error) {
return "", fmt.Errorf("content is required")
}
// In the primitive, not in a caller: POST /write reaches here directly,
// without the BrainStore, and a gate one door wide is not a gate.
// BrainStore still calls the strip itself to report Ref.Unlinked; the
// second pass is a no-op on already-clean content.
clean, _, err := brain.StripUnresolvedLinks(brainDir, opts.Content)
if err != nil {
return "", err
}
opts.Content = clean
if opts.Wing != "" && opts.Hall != "" {
return writeHallNote(brainDir, opts)
}
+42
View File
@@ -497,3 +497,45 @@ func TestWriteNote_RejectsClaimFieldWithoutProvenance(t *testing.T) {
_, statErr := os.Stat(filepath.Join(dir, "wiki/homelab/facts/no-provenance.md"))
assert.True(t, os.IsNotExist(statErr))
}
// The HTTP POST /write door reaches WriteNote directly, without the
// BrainStore, so the unresolved-wikilink strip has to live in the primitive
// or that door stays ungated.
func TestWriteNote_UnwrapsUnresolvedWikilinks(t *testing.T) {
dir := t.TempDir()
_, err := api.WriteNote(dir, api.WriteNoteOptions{
Content: "# Target\n\nbody\n", Filename: "target", Wing: "a", Hall: "facts",
})
require.NoError(t, err)
rel, err := api.WriteNote(dir, api.WriteNoteOptions{
Content: "See [[target]] and [[gitea-ci]].\n",
Filename: "src", Wing: "a", Hall: "facts",
})
require.NoError(t, err)
got, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel)))
require.NoError(t, err)
assert.Contains(t, string(got), "[[target]]")
assert.Contains(t, string(got), "and gitea-ci.")
assert.NotContains(t, string(got), "[[gitea-ci]]")
}
func TestUpdateNote_UnwrapsUnresolvedWikilinks(t *testing.T) {
dir := t.TempDir()
_, err := api.WriteNote(dir, api.WriteNoteOptions{
Content: "old\n", Filename: "n", Wing: "a", Hall: "facts",
})
require.NoError(t, err)
rel, _, _, err := api.UpdateNote(dir, api.UpdateNoteOptions{
Wing: "a", Hall: "facts", Slug: "n",
Content: "Related: [[secret-hygiene]].\n", Reason: "r",
})
require.NoError(t, err)
got, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel)))
require.NoError(t, err)
assert.Contains(t, string(got), "Related: secret-hygiene.")
assert.NotContains(t, string(got), "[[secret-hygiene]]")
}
+8
View File
@@ -64,6 +64,14 @@ func UpdateNote(brainDir string, opts UpdateNoteOptions) (relPath, contentHash,
return "", "", "", fmt.Errorf("content is required")
}
// Same reason as WriteNote: the strip belongs in the primitive so every
// door inherits it.
clean, _, err := brain.StripUnresolvedLinks(brainDir, opts.Content)
if err != nil {
return "", "", "", err
}
opts.Content = clean
var rel string
if opts.Path != "" {
rel = opts.Path