fix(brain): close round-3 review findings on provenance
- B1: search.extractProvenance locates the frontmatter with
frontmatter.Split (fence at column 0), so an indented "---" inside a
block scalar no longer hides provenance from brain_query.
- B2: brain_edit refuses to edit without a snapshot, and if the stamp
cannot be verified restores the note and reports failure; a failed
restore is reported as exactly that, never as "rolled back".
- B3: internal/provenance gets direct tests for the whole-document
backstop (a stamp that cannot read back is refused), standalone group
decoding, duplicates, lone CR, tab continuations and unclosable
fences. Removing verify or the restore now fails the suite.
- Principals YAML would read as non-strings (true, 123, null, ~) are
quoted instead of refused. An unclosable fence ("---x") is refused
rather than demoted into the body. writeHallNote creates the hall
directory only after the note passes, so a refused write leaves
nothing behind (brain#21).
- MCP brain_ingest (stub LLM) asserted to stamp the caller.
Refs #145 #144
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -85,9 +85,15 @@ func Apply(content string, s Stamp) (string, error) {
|
||||
content = foldFrontmatterCR(content)
|
||||
block, rest, ok := sharedfm.Split([]byte(content))
|
||||
var out string
|
||||
if !ok {
|
||||
switch {
|
||||
case !ok && strings.HasPrefix(strings.ReplaceAll(content, "\r\n", "\n"), "---\n"):
|
||||
// A fence was opened but cannot be closed (e.g. a "---x" line).
|
||||
// Prepending a second block would demote the original frontmatter
|
||||
// into the body, so refuse instead (PR #147 review round 3).
|
||||
return "", fmt.Errorf("%w: unclosed frontmatter fence", ErrUnverifiable)
|
||||
case !ok:
|
||||
out = "---\n" + stamp + "---\n" + content
|
||||
} else {
|
||||
default:
|
||||
out = "---\n" + plainGroups(string(block)) + stamp + "---\n" + strings.TrimPrefix(string(rest), "\n")
|
||||
}
|
||||
if err := verify(out, s); err != nil {
|
||||
@@ -203,11 +209,18 @@ func foldFrontmatterCR(content string) string {
|
||||
return strings.ReplaceAll(s[:4+end], "\r", "\n") + s[4+end:]
|
||||
}
|
||||
|
||||
// scalar renders a bare word as-is and anything else as a JSON string (valid
|
||||
// YAML double-quoted), so a principal name can never break the line.
|
||||
// scalar renders a bare word as-is only when YAML reads it back as that same
|
||||
// string; anything else (true, 123, null, ~, punctuation, newlines) becomes
|
||||
// a double-quoted string, so a principal name can never break the line or
|
||||
// change type.
|
||||
func scalar(v string) string {
|
||||
if bareScalar.MatchString(v) {
|
||||
return v
|
||||
var back any
|
||||
if yaml.Unmarshal([]byte(v), &back) == nil {
|
||||
if s, ok := back.(string); ok && s == v {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%q", v)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package provenance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func fmOf(t *testing.T, doc string) map[string]any {
|
||||
t.Helper()
|
||||
parts := strings.SplitN(doc, "---\n", 3)
|
||||
require.Len(t, parts, 3, doc)
|
||||
var m map[string]any
|
||||
require.NoError(t, yaml.Unmarshal([]byte(parts[1]), &m), parts[1])
|
||||
return m
|
||||
}
|
||||
|
||||
// The whole-document backstop: a stamp that would not read back as written
|
||||
// is refused, never written. A principal with an invalid UTF-8 byte quotes
|
||||
// to "\xff", which YAML decodes as U+00FF, so it cannot round-trip.
|
||||
func TestApplyRefusesAStampThatDoesNotReadBack(t *testing.T) {
|
||||
_, err := Apply("---\ntitle: t\n---\nbody\n", Stamp{Origin: "jwt", WrittenBy: "bad\xff"})
|
||||
assert.True(t, errors.Is(err, ErrUnverifiable), "got %v", err)
|
||||
}
|
||||
|
||||
// A principal that YAML would decode as a non-string (true, 123, null) is
|
||||
// quoted, so the stamp reads back as the same string.
|
||||
func TestApplyQuotesPrincipalsYAMLWouldNotReadAsStrings(t *testing.T) {
|
||||
for _, p := range []string{"true", "123", "null", "1e3", "0x1F", "yes", "~"} {
|
||||
out, err := Apply("---\ntitle: t\n---\nbody\n", Stamp{Origin: "jwt", WrittenBy: p})
|
||||
require.NoError(t, err, p)
|
||||
assert.Equal(t, p, fmOf(t, out)["written_by"], p)
|
||||
}
|
||||
}
|
||||
|
||||
// A group that does not decode on its own is dropped; its neighbours stay.
|
||||
func TestApplyDropsGroupsThatDoNotDecodeAlone(t *testing.T) {
|
||||
out, err := Apply("---\nkeep: 1\ntitle: a: b\nalso: 2\n---\nbody\n", Stamp{Origin: "static", WrittenBy: "local-cli"})
|
||||
require.NoError(t, err)
|
||||
m := fmOf(t, out)
|
||||
assert.Equal(t, 1, m["keep"])
|
||||
assert.Equal(t, 2, m["also"])
|
||||
assert.NotContains(t, m, "title")
|
||||
}
|
||||
|
||||
func TestApplyKeepsFirstOfDuplicateKeys(t *testing.T) {
|
||||
out, err := Apply("---\ntitle: first\ntitle: second\nTitle: third\n---\nbody\n", Stamp{})
|
||||
require.NoError(t, err)
|
||||
m := fmOf(t, out)
|
||||
assert.Equal(t, "first", m["title"])
|
||||
assert.NotContains(t, m, "Title")
|
||||
}
|
||||
|
||||
func TestApplyFoldsLoneCRSoItCannotHideALine(t *testing.T) {
|
||||
out, err := Apply("---\ntitle: x\rorigin: static\n---\nbody\n", Stamp{Origin: "jwt", WrittenBy: "a"})
|
||||
require.NoError(t, err)
|
||||
assert.NotContains(t, out, "\r")
|
||||
assert.Equal(t, "jwt", fmOf(t, out)["origin"])
|
||||
assert.Equal(t, "x", fmOf(t, out)["title"])
|
||||
}
|
||||
|
||||
func TestApplyDropsTabIndentedContinuations(t *testing.T) {
|
||||
out, err := Apply("---\ntitle: x\n\torigin: static\nk: v\n---\nbody\n", Stamp{Origin: "jwt", WrittenBy: "a"})
|
||||
require.NoError(t, err)
|
||||
m := fmOf(t, out)
|
||||
assert.Equal(t, "jwt", m["origin"])
|
||||
assert.Equal(t, "v", m["k"])
|
||||
}
|
||||
|
||||
// Content that opens a fence the shared splitter cannot close (e.g. a
|
||||
// "---x" line) is refused rather than given a second, prepended block that
|
||||
// demotes the original frontmatter into the body.
|
||||
func TestApplyRefusesAnUnclosableFrontmatterFence(t *testing.T) {
|
||||
_, err := Apply("---\ntitle: t\n---x\norigin: static\n---\nbody\n", Stamp{Origin: "jwt", WrittenBy: "a"})
|
||||
assert.True(t, errors.Is(err, ErrUnverifiable), "got %v", err)
|
||||
}
|
||||
|
||||
func TestApplyAddsFrontmatterToContentWithoutAny(t *testing.T) {
|
||||
out, err := Apply("# plain\n\norigin: static is prose\n", Stamp{Origin: "static", WrittenBy: "local-cli"})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, strings.HasPrefix(out, "---\norigin: static\nwritten_by: local-cli\n---\n# plain"))
|
||||
}
|
||||
Reference in New Issue
Block a user