fix(brain): provenance keeps only provably plain frontmatter; pipeline stamps
CI / Lint / Test / Vet (pull_request) Successful in 43s
CI / Mirror to GitHub (pull_request) Skipped

Round 2 of the PR #147 review forged origin: static for yaml.v3 with an
alias key plus a "..." document-end line: per-line judgement cannot see
context-dependent YAML. It also found REST ingest writing pages with no
provenance (stampPages lived only in the MCP server).

- New package internal/provenance. Apply keeps only top-level groups
  `key: value` with a bare-identifier key whose group decodes on its own
  to exactly that key, drops reserved and duplicate keys and everything
  else (aliases, anchors as keys, document markers, complex/merge keys,
  flow maps, tags, quoted keys), appends the stamp, then decodes the
  whole result with yaml.v3 and refuses the write unless it reads back
  exactly the stamp. api.StampProvenance wraps it and now returns an
  error, which every caller propagates.
- The ingest pipeline stamps every page it writes or merges, from
  provenance.FromContext(ctx); MCP attaches the caller's stamp, REST and
  the watcher stamp unknown. RunRaw takes a ctx.
- brain_edit snapshots the note and rolls the edit back if the stamp
  cannot be verified.

Tests: R1/R2 payloads join the write/update variant table, plus
brain_edit with the alias payload; REST /ingest-raw (title newline
checked with the line-oriented reader) and REST /ingest (stub LLM) stamp
unknown. Two existing byte-exact tests updated for the stamp line.

Refs #145 #144

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 22:55:53 +02:00
co-authored by Claude Opus 5.5
parent e615d6f144
commit fcae16569d
11 changed files with 367 additions and 204 deletions
+6 -11
View File
@@ -125,15 +125,6 @@ type WriteNoteOptions struct {
WrittenBy string
}
// provenanceOrigin normalises an origin for the frontmatter: absence must
// never read as trusted, so empty becomes "unknown".
func provenanceOrigin(o string) string {
if o == "" {
return "unknown"
}
return o
}
// WriteNote writes a markdown note into the brain. Returns the path
// relative to brainDir (forward-slashed). Filename traversal is rejected.
func WriteNote(brainDir string, opts WriteNoteOptions) (string, error) {
@@ -233,7 +224,10 @@ func writeHallNote(brainDir string, opts WriteNoteOptions) (string, error) {
}
fm.WriteString("---\n")
final := StampProvenance(fm.String()+body, opts.Origin, opts.WrittenBy)
final, err := StampProvenance(fm.String()+body, opts.Origin, opts.WrittenBy)
if err != nil {
return "", err
}
if err := os.WriteFile(dest, []byte(final), 0o644); err != nil {
return "", fmt.Errorf("write: %w", err)
}
@@ -553,7 +547,8 @@ func (h *Handler) IngestRaw(w http.ResponseWriter, r *http.Request) {
return
}
result, err := pipeline.RunRaw(h.brainDir, req.Source, req.Pages, req.DryRun)
// No principal on this door: pages are stamped origin: unknown (#145).
result, err := pipeline.RunRaw(r.Context(), h.brainDir, req.Source, req.Pages, req.DryRun)
if err != nil {
h.logger.Error("ingest-raw failed", "source", req.Source, "err", err)
writeError(w, http.StatusInternalServerError, "ingest error")