Review caught a real bypass in the #111 fix before merge: the previous
commit validated only the frontmatter fence the caller supplied, not the
merged result that actually reaches disk. A caller could blank
claim_provenance alone (absent per claim.present's "empty is unset" rule)
while half_life/applies survived untouched from the prior note — each side
individually looked fine, the merged envelope on disk was the
unprovenanced one the whole check exists to catch. That's the exact
laundering #111 named as the worse of its two gaps.
Fixed by computing the envelope from fm (post-merge, pre-write) instead of
contentFM (the caller's raw fragment). Flip-tested: reverting to
fragment-only validation turns the new adversarial test red; the merged
validation turns it green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
hyperguild#111: writeHallNote validates half_life/applies/claim_provenance
before a note is born; UpdateNote's supersede path didn't, so the fields
that need provenance most (a whole-note regenerate is more likely to carry
model-authored frontmatter than a fresh create) went unchecked.
Fixed by extracting any frontmatter fence the caller leads Content with,
validating it with the same claim.ValidateEnvelope, and merging its fields
into the restamped frontmatter instead of leaving it as a second, inert
`---`-fenced block in the body — that merge was a real gap independent of
validation: a caller's supersede-time frontmatter was previously silently
dropped, never taking effect at all.
Reject leaves the note byte-identical (fail-closed, nothing written).
Fence-less content (the common case today) is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements the api-layer half of #45. UpdateNote supersedes a note in
place (whole-note body replace, frontmatter re-stamp: updated_at,
supersedes=prior content hash, supersede_reason), preserving created_at,
wing, hall, and any custom fields. Never creates — a missing target is
an error so callers fall back to brain_write. ReadNote is the read-after-
write primitive (frontmatter + body + content_hash). ContentHash is the
sha256 handle that round-trips write/update → get.
Frontmatter is edited via a line-preserving ordered editor rather than a
yaml.v3 round-trip, which would reorder keys and strip comments — the
brain writes flat key:value frontmatter by hand.
Embeddings are not refreshed here: the rewritten file's mtime advances,
which the out-of-band vectorstore.Sync ticker uses to re-embed it — the
same mechanism brain_write relies on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>