First half of brain#21 — the applicability envelope. Sprint B item 1 in the re-sequenced order, because this half improves a check that already runs rather than waiting on anything.
half_life:volatile | seasonal | durable # how fast the claim decaysapplies:local | homelab | universal # how widely it holdsclaim_provenance:asserted | inferred # who set the two above
What was actually missing
writeHallNote already passes unknown frontmatter keys through verbatim, so preservation was free — I found that before writing any code. A test now pins it so a future refactor of the merge loop cannot silently drop the fields.
What was missing is validation, and it fails closed:
Rejected
Why
value outside the enum
Worse than an absent value — a lint skips what it cannot parse, so the note reads as classified while nothing acts on it
envelope field without claim_provenance
A value a model inferred must never look like one you asserted. Without this, one classifier pass launders guesses into apparent human judgement across 1113 notes — the exact failure the field exists to prevent
claim_provenance with no envelope field
It says who set fields that aren't there
Validation runs beforeMkdirAll, so a rejected write leaves nothing behind — not even an empty hall directory. A note with none of these fields is byte-identical to before.
applies, not scope
brain#19 proposed scope. Measured against the live corpus first: scope: is already in use by 10 notes, all glossary terms on brain#5's schema (homelab-core, project:jepa-fx-risk) — same key, disjoint value space. The shipped schema keeps the name; the unshipped proposal takes another. Free now, expensive after any backfill. brain#19 is closed as superseded by brain#21 with this recorded.
Tests
Red-first, in both places. The unit table failed on the four reject cases against a stub; the two write-path reject tests failed against the real handler:
The preservation test passed from the start — it pins existing behaviour, and I've said so in its comment rather than letting it look like new work. One case asserts the glossary's scope: homelab-core is untouched by any of this.
task check exit 0.
Documentation shipped with it
brain/CLAUDE.md gained a Claim properties section — the enums, why applies isn't scope, and what asserted vs inferred is protecting against. Written through brain_edit_root, which produced the audit record #97 added:
That is #97's first use on a real governance change rather than a test edit. The ntfy half of #97 is still dark — see #100.
Deliberately not in this PR
The strength half (confidence, contested) — it wants the contradiction detector first, or contested has nothing to point at.
The lint that acts on these fields. brain#21 requires it to declare an expected finding count before merging, because the audit report already carries 1668 findings and a loosely-calibrated check would bury it. That belongs with the strength half.
Any backfill. New writes only.
Risk: LOW — additive and opt-in. The one behaviour change is that a previously-silent invalid value now fails the write; nothing in the corpus uses these keys today, so no existing caller is affected.
First half of brain#21 — the applicability envelope. Sprint B item 1 in the re-sequenced order, because this half improves a check that already runs rather than waiting on anything.
```yaml
half_life: volatile | seasonal | durable # how fast the claim decays
applies: local | homelab | universal # how widely it holds
claim_provenance: asserted | inferred # who set the two above
```
## What was actually missing
`writeHallNote` already passes unknown frontmatter keys through verbatim, so **preservation was free** — I found that before writing any code. A test now pins it so a future refactor of the merge loop cannot silently drop the fields.
What was missing is **validation**, and it fails closed:
| Rejected | Why |
|---|---|
| value outside the enum | Worse than an absent value — a lint skips what it cannot parse, so the note reads as classified while nothing acts on it |
| envelope field without `claim_provenance` | A value a model inferred must never look like one you asserted. Without this, one classifier pass launders guesses into apparent human judgement across 1113 notes — the exact failure the field exists to prevent |
| `claim_provenance` with no envelope field | It says who set fields that aren't there |
Validation runs **before** `MkdirAll`, so a rejected write leaves nothing behind — not even an empty hall directory. A note with none of these fields is byte-identical to before.
## `applies`, not `scope`
brain#19 proposed `scope`. Measured against the live corpus first: `scope:` is already in use by 10 notes, all glossary terms on brain#5's schema (`homelab-core`, `project:jepa-fx-risk`) — same key, disjoint value space. The shipped schema keeps the name; the unshipped proposal takes another. Free now, expensive after any backfill. brain#19 is closed as superseded by brain#21 with this recorded.
## Tests
Red-first, in both places. The unit table failed on the four reject cases against a stub; the two write-path reject tests failed against the real handler:
```
--- FAIL: TestWriteNote_RejectsInvalidClaimEnvelopeAndWritesNothing
--- FAIL: TestWriteNote_RejectsClaimFieldWithoutProvenance
```
The preservation test passed from the start — it pins existing behaviour, and I've said so in its comment rather than letting it look like new work. One case asserts the glossary's `scope: homelab-core` is untouched by any of this.
`task check` exit 0.
## Documentation shipped with it
`brain/CLAUDE.md` gained a **Claim properties** section — the enums, why `applies` isn't `scope`, and what `asserted` vs `inferred` is protecting against. Written through `brain_edit_root`, which produced the audit record #97 added:
```
actor: brain_edit_root | items: ['CLAUDE.md']
sec: governance file rewritten: CLAUDE.md before=6a8b96f8… after=0ec99b7f…
```
That is #97's first use on a real governance change rather than a test edit. The ntfy half of #97 is still dark — see #100.
## Deliberately not in this PR
- **The strength half** (`confidence`, `contested`) — it wants the contradiction detector first, or `contested` has nothing to point at.
- **The lint that acts on these fields.** brain#21 requires it to declare an expected finding count before merging, because the audit report already carries 1668 findings and a loosely-calibrated check would bury it. That belongs with the strength half.
- **Any backfill.** New writes only.
**Risk:** LOW — additive and opt-in. The one behaviour change is that a previously-silent invalid value now fails the write; nothing in the corpus uses these keys today, so no existing caller is affected.
First half of brain#21: the applicability envelope — half_life (volatile|
seasonal|durable), applies (local|homelab|universal), and claim_provenance
(asserted|inferred). The strength half (confidence, contested) follows once
the ingest-time contradiction detector exists to populate it.
writeHallNote already passed unknown frontmatter keys through verbatim, so
preservation was free — a test now pins it so a refactor of the merge loop
cannot silently drop the fields. What was missing is validation, and it
fails closed:
- a value outside the enum is rejected. An unvalidated value is worse than
an absent one, because a lint skips what it cannot parse and the note then
reads as classified while nothing acts on it;
- an envelope field without claim_provenance is rejected. A value a model
inferred must never be indistinguishable from one Mathias asserted —
otherwise a single classifier pass launders machine guesses into apparent
human judgement across 1113 notes, which is precisely how a weak claim
hardens into accepted fact;
- provenance without any envelope field is rejected too: it says who set
fields that are not there.
Validation runs before MkdirAll, so a rejected write leaves nothing behind —
not even an empty hall directory.
The field is `applies`, not `scope`, as brain#19 proposed: `scope:` has been
in use since brain#5 for glossary terms (homelab-core, project:<repo>), a
disjoint value space on the same key. The shipped schema keeps the name.
Notes with none of these fields are byte-identical to before. Bulk backfill
across the corpus stays out of scope — the same failure mode as the
extraction pass that produced 28 wrong wikilinks out of 67.
Schema documented in brain/CLAUDE.md (via brain_edit_root, which produced
the #97 audit record for the rewrite).
Refs brain#21, brain#19 (superseded), brain#5, hyperguild#97.
mathias
merged commit 54237af7ad into main2026-08-22 08:58:34 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
First half of brain#21 — the applicability envelope. Sprint B item 1 in the re-sequenced order, because this half improves a check that already runs rather than waiting on anything.
What was actually missing
writeHallNotealready passes unknown frontmatter keys through verbatim, so preservation was free — I found that before writing any code. A test now pins it so a future refactor of the merge loop cannot silently drop the fields.What was missing is validation, and it fails closed:
claim_provenanceclaim_provenancewith no envelope fieldValidation runs before
MkdirAll, so a rejected write leaves nothing behind — not even an empty hall directory. A note with none of these fields is byte-identical to before.applies, notscopebrain#19 proposed
scope. Measured against the live corpus first:scope:is already in use by 10 notes, all glossary terms on brain#5's schema (homelab-core,project:jepa-fx-risk) — same key, disjoint value space. The shipped schema keeps the name; the unshipped proposal takes another. Free now, expensive after any backfill. brain#19 is closed as superseded by brain#21 with this recorded.Tests
Red-first, in both places. The unit table failed on the four reject cases against a stub; the two write-path reject tests failed against the real handler:
The preservation test passed from the start — it pins existing behaviour, and I've said so in its comment rather than letting it look like new work. One case asserts the glossary's
scope: homelab-coreis untouched by any of this.task checkexit 0.Documentation shipped with it
brain/CLAUDE.mdgained a Claim properties section — the enums, whyappliesisn'tscope, and whatassertedvsinferredis protecting against. Written throughbrain_edit_root, which produced the audit record #97 added:That is #97's first use on a real governance change rather than a test edit. The ntfy half of #97 is still dark — see #100.
Deliberately not in this PR
confidence,contested) — it wants the contradiction detector first, orcontestedhas nothing to point at.Risk: LOW — additive and opt-in. The one behaviour change is that a previously-silent invalid value now fails the write; nothing in the corpus uses these keys today, so no existing caller is affected.