diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 2c442cee3..791d1f867 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,23 @@ +## 2026-09-03 — 5+3 COUNCIL on the canon entries that shipped unreviewed (#1154) + +- **Why convened:** three EPIPHANIES entries + two source-doc blocks sat on an OPEN PR that neither review bot had seen (Codex reviewed only `a1c9488e`; CodeRabbit at its org spend cap). EPIPHANIES is append-only, so this was the last moment the text was amendable rather than permanent-with-regrade — the card's "a spec whose wrong resolution silently corrupts downstream sessions (canon entries)". CI status, merge mechanics and the spend decision were explicitly EXCLUDED from the object. +- **The 5 (Phase 1, one parallel spawn, read-only):** `prior-art-savant` (Opus — the multi-source lens, ~25.8k lines of EPIPHANIES plus knowledge docs), `iron-rule-savant`, `runtime-archaeologist` (code truth), `cascade-impact-savant`, `creative-explorer-savant` (Sonnet). Panel emphasis declared in the spec: lens 2 pointed at the P0 falsifiability rule and board hygiene rather than the four substrate iron rules, because the object changes no substrate, layout or public type. +- **The 3 (Phase 3, on draft v2 ONLY, never the raw fan-out):** `overclaim-auditor`, `dilution-collapse-sentinel`, `firewall-warden`. +- **Verdicts (stricter wins):** Spec PASS · Entry A FIX(P1) · Entry B FIX(P1) · Entry C FIX(P2) · Item D FIX(P2) · Item E FIX(P1) · Board-hygiene FIX(P2) · Gates FIX(P2). **No BLOCK(P0)** — Phase 4 was fix, not re-spec. Firewall: no PII, no secret, no model identifier in added FILE text, ADR-022 not engaged. +- **v1 → v2:** spec v1's position was "ratify all five as written, zero edits". Five savants falsified it; v2 carried 27 ledger entries. Largest: entry B's "only in a PR body" is FALSE (the same sentence is on the board at `EPIPHANIES.md:355`); entry C is a new INSTANCE of a pattern `preflight-drift-patterns.md` Axis 1 already rules, not a new rule; the corrected census block still was not falsifiable; and **the spec's own F3 citation was stale** (`:555-568` → `:640`) — an append-only board renumbers under every prepend, which is the exact defect entry C is about, committed in the spec written to catch it. +- **v2 → v3 (what the 3 changed):** entry A's confidence split in two (mechanism High, coverage-table Medium — an absence proves no review COMMENT, not no coverage) and the n=1 mitigation separated from the still-inferred gap; entry A's defect-hedge ADDED alongside the new absence-hedge rather than replaced (the sentinel caught that L5's new evidence made the old hedge necessary, not redundant); entry C narrowed on **provenance** (a value this run computed vs one the author transcribed) rather than on subject matter, which preserved the credibility mechanism the regrade had just named as its only novel part; entry B's evidence sentence corrected — I had widened a savant's `crates/`-scoped grep (0 hits) into "repo-wide" (17 hits), i.e. shipped a false verification inside an amendment about false verifications; item D's deferral restated as "correct but admittedly incomplete"; item E's fix made mandatory after the sentinel showed the alternative horn was already satisfied by shipped text and therefore a no-op. +- **Two spec-design errors of my own, recorded:** (1) F3's stale citation, above. (2) I gave `git show`-shaped questions to two seats that turned out to have no Bash (the code-truth savant and the firewall warden), so both returned GAP for a mechanical reason rather than an evidentiary one; the orchestrator ran those checks. A future council must match question shape to seat capability. +- **Gates:** planner lib 411/0, contract `witness_fabric` 46/0, probe exits 0 with `C1 PASS · C2 RESTATED · C3 PASS`, clippy + fmt clean, supersession index regenerated LAST. The new census assertion is disable-verified: removing the delegation in `insight.rs` panics the probe with "found 0"; restored, green. +- **Escalated, not resolved:** the commit-trailer / model-identifier conflict (see LATEST_STATE). Reviewers were barred from re-litigating it. + +## 2026-09-03 — D-DCR-4 consolidation: orchestrator-only, no worker spawned + +- **Why:** the census (PROBE-ENTROPY-SURFACE-CENSUS-1) licensed exactly one routing and named its one hazard, so the remaining work was a ~15-line edit plus two falsifiers. Tiering judgment: briefing a Sonnet worker on a spec this precise costs more than executing it — the grindwork/accumulation split says delegate when the brief is cheaper than the work, and here it was not. +- **Orchestrator:** routed `confidence_entropy` to `thought_atoms::normalized_entropy` preserving the empty-arena guard; wrote the two-sided test pair; ran the disable (delete the early return ⇒ 13 passed / 1 failed on exactly the named test, restored ⇒ 14/14). +- **Mid-turn:** fast-forwarded onto #1152 (another session's `rubicon-loco-rung-fabric`) and audited the overlap — board-ledger contention only, zero code overlap on `thought_atoms`/`witness_fabric`/`insight.rs`; the one real synergy (their §130 entropy-fragmentation row) is recorded in the epiphany rather than merged into either arc. +- **Gates:** `cargo test -p lance-graph-planner --lib` 411 passed / 0 failed; clippy `--all-targets` clean; fmt clean (the initial edit needed a reformat, caught by `--check`). +- **Board:** EPIPHANIES `E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1`, LATEST_STATE delta, STATUS_BOARD D-DCR-4, plan W4 gate; supersession index regenerated last. + ## 2026-09-03 — PROBE-ENTROPY-SURFACE-CENSUS-1: one Sonnet transcription worker, orchestrator-measured - **Why:** D-DCR-4's gate ("entropy-surface CONSOLIDATION decision recorded first"). Tiering: Opus on the main thread for the census, the fixture design, the three pre-registered claims, the C2 restatement after falsification and both disable-runs; one Sonnet worker for the transcription grindwork (six forms copied verbatim out of unimportable crates, with `file:line` citations), edit-only, no cargo. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 9f42dad05..04bd74a6b 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,276 @@ +## 2026-09-03 — E-THE-FIX-FOR-A-REVIEW-FINDING-SHIPS-UNREVIEWED-BY-DEFAULT-1 — the cap was the visible half + +**Status:** FINDING (measured on this PR's own review metadata). +**Confidence:** High for the MECHANISM (the trigger list is quoted verbatim +from the reviewer's own notice). **Medium for the coverage table**, which is an +inference from an ABSENCE: the same reviewer's stated rule is "comment when I +have suggestions, otherwise react 👍", so a missing review object proves absence +of a review *comment*, not absence of *coverage*. The two are graded apart +deliberately — conflating them is the error this entry is otherwise about. +**Prompted by:** the lance-graph-java session's flag that five consecutive PRs +merged with zero external review. + +**The visible half is a spending cap.** CodeRabbit reports 84 review attempts +in 7 days against an org cap, throttling to one review per hour, and it is why +recent PRs merged unreviewed. + +**The half nobody had named is worse, because it is not a billing setting.** +The second reviewer, Codex, is NOT capped — and it still did not see three of +the four commits on #1154. Its own notice states its triggers: opening a PR, +marking a draft ready, or an explicit `@codex review` comment. **A push is not +a trigger.** So the sequence every reviewed PR follows — + +> review lands → author fixes the finding → author pushes → merge + +— ends with **the fix for the finding as the single least-reviewed commit on +the PR.** On #1154 the reviewed commit was the one with the defect, and the +three unreviewed ones included the correction to that exact defect. Raising the +spend cap does not touch this; it is a trigger-semantics gap, and it applies to +every PR in the workspace that has ever received a finding and fixed it. + +**Why it is easy to miss.** The PR *looks* reviewed — there is a review, it +found something real, the thread is resolved, the badge is green. Review +coverage is silently attributed to the PR when it was only ever attached to one +commit. This is the same shape as the two probe findings from this same session +(`E-A-PROBE-CAN-STATE-A-MEASUREMENT-THAT-WAS-FALSE-WHEN-IT-WAS-WRITTEN-1`): a +claim measured once at one point in time, then read as a standing property of a +thing that has since changed. + +**The cheap mitigation, available today and unrelated to spend:** after pushing +a fix for a review finding, post `@codex review`. It costs one comment, is not +rate-limited, and re-points the reviewer at the head that actually contains the +fix. Done on #1154. The expensive mitigation (raise the cap) is the operator's +call and buys a different thing — breadth across PRs, not depth after a fix. + +**The mitigation is MEASURED; the gap is still INFERRED.** After that comment +the reviewer returned two P2 findings on a head it had not commented on when +that head was pushed. That is n=1 and it establishes the mitigation FIRES — it +does not establish that the push failed to trigger a review, which remains an +inference from the quoted trigger list plus absent comments. Stated apart +because the entry's own subject is a claim that outran its evidence. + +**What this does NOT claim — two independent disclaimers, both load-bearing.** +(i) No assertion that the unreviewed commits are *defective*; two are +board-only and one is a doc header. This one became MORE necessary, not less, +once the paragraph above reported that a re-triggered review found two real +defects — that result makes the "the gap caused those defects" reading +available for the first time, and it is not claimed. (ii) No assertion that an +absent review object means *not looked at*; see the Confidence line. The finding +is that the apparatus reports more coverage than it has evidence for. + +**See also** `E-A-PROBE-CAN-STATE-A-MEASUREMENT-THAT-WAS-FALSE-WHEN-IT-WAS-WRITTEN-1` +(below), which shares the abstraction *a property measured once is later read as +standing* but NOT the mechanism: that entry is about a claim decaying as the +tree changes; this one is about coverage never having attached to the commits it +is read as covering. Adjacent, not the same — do not merge them. + +## 2026-09-03 — E-THE-VACANCY-RULE-IS-NOT-ABOUT-ENTROPY-1 — a second instance on its first day, from a session that does not share the arc + +**Status:** FINDING (cross-session; the second instance was found and acted on +by another session, not by this one). +**Confidence:** **Medium-High for the rule**, **Medium for the second +instance.** The instance is a CONFIRMATION DRAW, not an independent +replication: the other session had READ this rule before applying it, same day, +same fleet, same discipline — n=2 from one primed process, not n=2 from minting +in general. And it is unverifiable here: `ogar_loco` is an external dependency, +`grep -rn TERNLOG crates/` returns **0**, so the symbol, its mint site and its +caller count are all *reported*, not measured, from this tree. (The string does +appear elsewhere in this repo — in board and knowledge prose, including this +entry — so a repo-wide grep is NOT the check; the crate-scoped one is. Prior +art for exactly this hazard: `.claude/knowledge/preflight-drift-patterns.md` +Axis 4, cross-repo PR-merge claims.) +**Generalizes:** `E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1` +(entropy, same day). + +**The rule travelled.** It was written about one entropy atom with zero +callers. Within hours the lance-graph-java mask-RISC session applied it to +`ogar_loco::TERNLOG` = `FnIndex(0x86)` — minted 2026-09-01, op description +verbatim, **zero callers in either tree** — and their own plan was about to +mint an identical op kind beside it. They amended to consume the existing +address instead (lgj #70). Same shape, different domain, different repo, +different arc, no shared code: a ruled artifact that nothing calls is not a +home, and the next session's default move is to build its twin next door. + +That is worth more than a second data point. The original entry was written as +a fact about `thought_atoms`; it is really a fact about **minting**, and the +generalisation was demonstrated rather than argued — by someone with no stake +in the entropy arc. + +**A second finding from the same audit, sharper than it looks.** That session +also audited `witness_fabric` and correctly declined the tempting conclusion +(*"contradiction is already implemented, drop my verb"*): this module's +quorum/contradiction is family (1) episodic loci, theirs is family (3) +epistemic population basins, which +`E-SIX-SEMANTIC-FAMILIES-MUST-NOT-IMPERSONATE-EACH-OTHER-1` records as an +accepted vacancy. They got it right — and then observed that the module's own +header never says which family it is in. + +**They were right, and the reason is the useful part — but the first version of +this paragraph overstated it and is corrected here.** It said the distinction +was recorded *"only in a PR body."* That is FALSE: it is also on this very +board, at `EPIPHANIES.md:355` (the D-POP-2 entry), verbatim — *"No +population-basin work: family 3 stays the accepted vacancy."* The knowledge was +in TWO durable places and was still unavailable at the site of the misreading, +which makes the finding **stronger**, not weaker: the defect is not that the +boundary lived somewhere fragile, it is that **it was absent from the SOURCE**. +The next reader greps the tree, not the board's 25,000 lines and not the PR +archive. Prose that +exists to stop a future misreading has to live where the misreading will +happen — in this case, the module header, which now names family (1), quotes +the ruling, and names the specific confusion (family 3) it forecloses. + +The generalisation for this workspace: the board is where a decision is +JUSTIFIED; the source is where it must be ENFORCED. Neither a PR body nor a +board entry enforces anything at the call site. + +**Two claims, one id — and the reason is an AUDIT, not a cause.** This entry +carries the vacancy-rule generalisation and the placement finding above. They +have no common cause; they were found in one cross-session audit, and F1 +(append-only) makes a new prepended id the only lawful vehicle for either, so +splitting after the fact is not available. Signposted so a future search for +"PR body" reaches this entry rather than only the vacancy half. Prior art for +the placement claim: `.claude/board/entries/2026-08-13-e-a-figure-you-tallied-yourself-is-a-derived-figure-1.md:49-54`. + +## 2026-09-03 — E-A-PROBE-CAN-STATE-A-MEASUREMENT-THAT-WAS-FALSE-WHEN-IT-WAS-WRITTEN-1 — the census's own caller count, wrong twice + +**Status:** FINDING — a **new INSTANCE of an already-ruled pattern**, not a new +rule. `.claude/knowledge/preflight-drift-patterns.md` Axis 1 ("Stale-Claim +Verification") already names this failure mode *including its worse half*: +"…OR was never true and was an error in the planner's self-report", with the +remedy "run the actual git command… never trust the assertion". Two further +predecessors: `EPIPHANIES.md:8660` (a figure cited twice is not confirmed once) +and `:10275` (prose duplicating a machine-readable value has a half-life); +`:252` (`E-A-CORRECTION-CAN-SUBSTITUTE-ONE-WRONG-NOUN-FOR-ANOTHER-1`) is the +same shape one level up — a correction that carries a correction's authority +while being itself incomplete, which is precisely what the review finding that +prompted this entry turned out to be. +**Confidence:** High — the falsifying lines are in the probe file itself. What +is genuinely NOVEL here is narrower than the entry first implied, and it is the +credibility mechanism below, not the decay. +**Corrects:** `crates/lance-graph-planner/examples/entropy_surface_census.rs` +(the caller-census block), shipped in `e5e2520` and fixed in `abcdb0d5`. + +**What happened.** The entropy census printed, as part of its output, that +form A (`thought_atoms::normalized_entropy`) "has ZERO callers in the tree +today", citing a grep "returning nothing" as verification. A review bot caught +that the consolidation in the very next commit gave form A a production caller, +making the printed claim false. + +**Re-running the cited command found the worse half** (measured at `e5e2520`, +the census commit: the probe imported form A at line 38 and called it at line +325, and the cited grep returned **6 hits**, not nothing — pinned to that +commit because the count is exactly the kind of number this entry is about). +The claim was **already +false at the commit that introduced it**: the probe file itself imports form A +(line 38) and calls it (line 325), so the grep it cites had a non-empty result +the moment the file existed. The measurement was taken *before* the file was +written and was never re-run against the tree it then described. The reviewer +found the second staleness; the first shipped unnoticed. + +**Why an executable probe is the worst host for this.** Prose in a plan reads +as a claim. The same sentence inside a running probe reads as *output* — as +something the program checked — and this workspace's whole probe discipline +trains readers to believe measured numbers over asserted ones. A stale line in +a probe therefore borrows credibility that nothing earned. **That borrowing is +this entry's one novel contribution** — the decay itself was already ruled (see +Status). + +**The line is PROVENANCE, not subject matter.** A first version of this +paragraph ended "an unasserted `println!` is not a measurement, it is a comment +with better formatting", and that proves too much: two lines above the offending +block, the same file legitimately prints `max-min spread … reported, not +dramatized`, a value THIS RUN computed, and the file's own design comment +("assertions moved to the END because a probe that aborts early hides +evidence") presupposes that printed output IS evidence. The discriminator that +exonerates that line and still condemns the census block is **who produced the +number**: a value computed by the running probe carries the run's authority; a +value TRANSCRIBED BY THE AUTHOR into a `println!` carries none, and is a +comment wearing the run's clothes. Narrowing it to "claims about the state of +the tree" would have been the wrong axis and would have duplicated the rule +below. + +**The rule this leaves.** A claim about the STATE OF THE TREE decays the moment +the tree changes, and the commit most likely to change it is the one the claim +was written to motivate. So such a claim must carry (a) the commit it was +measured at and (b) the command that measures it, so a reader re-runs rather +than trusts — the same "measurements go in the ledger with a date, methods go +in the doc" split `CLAUDE.md` already applies to the clippy-count trap in +medcare-rs. The corrected block does exactly that, and separates the probe's +own use of a symbol from its production callers, which is the distinction the +original grep silently elided. + +**What it does NOT change.** C1/C2/C3 and their fixtures are untouched — they +assert, they run, and they still pass. Only the unasserted census line was +wrong, which is itself the point: every claim in that file that was gated by an +`assert!` survived, and the one claim printed without one did not. + +## 2026-09-03 — E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1 — the entropy atom's first caller, and the one convention that had to be defended + +**Status:** FINDING (shipped; disable-verified). +**Confidence:** High — the substitution's safety was measured before it was +made, and its one unsafe edge is pinned by a test that fails when the guard +is removed. +**Follows:** `E-THE-ENTROPY-HOME-WAS-RULED-AND-LEFT-EMPTY-1` (the census that +licensed exactly this consolidation and nothing wider). + +**What shipped.** `lance_graph_planner::nars::insight::confidence_entropy` no +longer carries its own Shannon loop; it extracts a 10-bin histogram and hands +it to `lance_graph_contract::thought_atoms::normalized_entropy`. That atom — +operator-ruled 2026-08-31 as a *universal thinking atom* — had **zero +consumers** until this commit. A ruled home with no caller is not a home; it +is a vacancy that the next session re-implements beside. + +**The measurement is what made this a two-line change rather than a +hypothesis.** PROBE-ENTROPY-SURFACE-CENSUS-1 (`e5e2520`) had already +established C1: the log base is inert under normalization +(`log2/log2(10)` vs `ln/ln(10)` agreed to `0.00000000` on every fixture). So +the caller's `log2`-and-divide-by-`log2(10)` and the atom's +`ln`-and-divide-by-`ln(n)` are the same function, and the routing needed no +tolerance argument at all. + +**The one place the two disagree is the whole risk, and it is silent.** The +census's C2 falsification recorded that the caller's convention and the +atom's are OPPOSITE on zero mass: the caller returns `0.0` for an empty +arena, the atom returns `Some(1.0)` ("nothing prefers anything — +indistinguishable from uniform"). An empty arena builds an all-zero +histogram, so dropping the caller's `is_empty` early return would report +**maximal uncertainty for an arena that holds none** — and `1.0` is in range, +so nothing downstream would object. The guard is therefore load-bearing and +now says so in its own doc comment, pinned by +`an_empty_arena_has_zero_truth_entropy_not_one` (disable-verified: deleting +the early return fails that test and only that test, 13 passed / 1 failed). + +**The paired can-fire half exists because the guard test is satisfiable by a +stub.** `an_empty_arena_has_zero_truth_entropy_not_one` would also pass for a +`confidence_entropy` hardcoded to `0.0`, so +`the_routed_atom_still_spans_the_confidence_range` asserts the routed atom +covers the full range on non-trivial inputs — one occupied bin ⇒ `0.0`, ten +evenly occupied ⇒ `1.0` — and, third, that the normalization is by the **bin +count** and not the occupied count (five bins of two ⇒ `ln 5 / ln 10`). That +third assertion is the one that would catch a plausible-looking rewrite which +normalized by however many bins happened to be non-empty. + +**What is still NOT licensed, restated so the scope does not drift.** Forms +C/D/E were left alone (two live in workspace-excluded crates, so no +in-workspace caller can route to them), and forms F/G were left alone +deliberately: C3 measured them moving `92.103409` between the same +distribution at 1× and 10× mass and going negative on elements above 1, which +means they are not entropies of a distribution at all. "Fixing" them is a lab +behaviour change and needs its own gate, not this one's. + +**A convergence worth naming, because it was found twice independently.** +`rubicon-loco-rung-cognitive-fabric-v1.md` §130 (another session, merged the +same day as #1152) reached the same fragmentation finding from the opposite +direction and recorded it as *"Shannon entropy — EXISTS BUT NOT +LOCO-ADDRESSABLE — ≥6 uncoordinated `entropy()` surfaces"*; the census +measured seven. The two arcs are complementary rather than competing, and the +distinction is worth keeping sharp: that plan wants entropy to have an +**address** (a loco-addressable Frozen atom), this arc gives it a **home** +(one canonical implementation with a real caller). An address for a function +that still exists in seven copies would just name one of them. Its +accompanying rule — *"Do not rewrite good SIMD in R2IL to claim purity"* — is +the same restraint the census's F/G verdict already imposed here. + ## 2026-08-31 — E-A-CORRECTION-CAN-SUBSTITUTE-ONE-WRONG-NOUN-FOR-ANOTHER-1 — three readings of one mechanism, and the source named itself the whole time **Status:** FINDING (verified against source at `cc0046f8`; every claim carries diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 3b35aac5d..b92fe33c2 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,42 @@ +## 2026-09-03 — branch (5+3 council on the unreviewed canon entries, #1154) — INVENTORY DELTA + +- CHANGED `crates/lance-graph-planner/src/nars/insight.rs` — histogram counts kept `usize` and widened once at the boundary (`core::array::from_fn`). Accumulating into `f32` loses counts above 2^24 (`f32(2^24) + 1.0 == f32(2^24)`, measured), so two bins at 20M and 40M would read equal. Unbounded in principle, unobserved today — which is not a reason to accumulate in f32. External P2, verified. +- CHANGED `crates/lance-graph-planner/examples/entropy_surface_census.rs` — the caller census is now ASSERTED, not printed: `include_str!` pulls the caller's source at compile time and `production_call_sites()` is checked against 1 at run time. **Disable-verified**: removing the delegation in `insight.rs` panics the probe with "found 0". Two prose revisions of this block shipped decaying claims; provenance (commit + command) was added last round and was not enough, because nothing failed when the count changed. Form B is relabelled a FOSSIL as-of `a1c9488e` rather than re-transcribed — re-pointing it at the new code would silently convert C2's measured falsification into a comparison of form A against itself. +- CHANGED `crates/lance-graph-contract/src/witness_fabric.rs` — restored the ruling's own `signed-i4` qualifier on family (3)'s vacancy. Dropping it widened a scoped vacancy at the site that enforces it. +- CHANGED `.claude/board/EPIPHANIES.md` — entries A, B, C amended before merge (they are unmerged; this is the last moment amendment is possible rather than regrade-only). A: confidence split, mechanism High / coverage-table Medium, mitigation measured at n=1 while the gap stays inferred, both disclaimers kept. B: **"only in a PR body" corrected to FALSE** — the same sentence is on this board at `:355`; the finding survives, stronger, as "absent from the SOURCE". Confidence regraded Medium for the cross-repo instance, which `grep -rn TERNLOG crates/` = 0 cannot verify from here. C: regraded to a new INSTANCE of a pattern `preflight-drift-patterns.md` Axis 1 already rules, and its aphorism narrowed on PROVENANCE rather than subject matter. +- CHANGED `.claude/knowledge/codex-p1-anti-patterns.md` — its standing claim that the bot "reviews every PR diff" is falsified by entry A; corrected in the same commit so the next reader does not load the false one. +- CHANGED `.claude/board/STATUS_BOARD.md` D-DCR-4 — the leading "ZERO consumers" clause disambiguated (production consumers, at gate time). The cell was chronologically narrated, not stale; a reviewer caught the draft overstating it. +- ESCALATED, NOT RESOLVED: the four commits on this PR carry a `Co-Authored-By: ` trailer, mandated by this session's harness directive. OGAR / tesseract-rs / stockfish-rs carry a "no model identifier in any committed artifact" non-negotiable; **lance-graph's own CLAUDE.md does not**, so the spec wrongly imported it as frozen and the rule was struck from the spec rather than enforced or overridden. The underlying question — whether that convention is workspace-wide, in which case it conflicts with the harness — is the operator's, and is unfixable after merge without a history rewrite. +- FLAGGED, OUT OF SCOPE: `.claude/specs/pr-ogit-ttl-smb-hydration.md` carries German PII labels. Pre-existing, verified untouched by this PR (`git diff --name-only a1c9488e..HEAD` does not list it). Reported rather than fixed, to avoid widening the PR. +- AGENT_LOG carries the council run: which 5, which 3, verdict counts, v1→v2→v3 deltas, and two spec-design errors of my own. + +## 2026-09-03 — branch (cross-session audit from the lance-graph-java mask-RISC arc) — INVENTORY DELTA + +- CHANGED `crates/lance-graph-contract/src/witness_fabric.rs` — module header now names its semantic family. It declares family (1) *episodic / Markov loci* verbatim from `E-SIX-SEMANTIC-FAMILIES-MUST-NOT-IMPERSONATE-EACH-OTHER-1`, states that `Locus::Contradiction` here is a signed OFFSET and NOT family (3) *epistemic population basins* (the accepted vacancy), and tells a reader arriving with a population-basin contradiction verb not to unify the two. Doc-only; 46/46 `witness_fabric` tests unchanged, fmt clean. +- WHY: an outside session audited the module against a plan minting a contradiction verb and had to derive the family distinction themselves. It WAS recorded — in #1145's PR body — which is precisely the problem: a boundary in a PR body is invisible to anyone reading the tree. +- RECEIVED (no action needed here): `E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1` was applied by that session to `ogar_loco::TERNLOG` = `FnIndex(0x86)` (minted 2026-09-01, zero callers in either tree) and changed their design — they now consume the existing address instead of minting an identical op kind beside it (lgj #70). The rule's first cross-domain instance, found by a session with no stake in the entropy arc. +- ANSWERED (architectural, no code): D-MRL-2c as scoped — bring `ndarray::simd` mask lowering into `witness_fabric` — cannot land in `lance-graph-contract`. That crate is zero-dep by design and its manifest forbids the usual escape hatch by name: an optional path dep is still resolved at workspace-load time, and one killed the whole PR pipeline on 2026-07-07. A masked lowering belongs where ndarray already is, over the same borrowed row slice, with the scalar fabric as the reference implementation. +- Epiphany: `E-THE-VACANCY-RULE-IS-NOT-ABOUT-ENTROPY-1`. + +## 2026-09-03 — branch (#1154 review round 1): the census's caller count corrected — INVENTORY DELTA + +- CHANGED `crates/lance-graph-planner/examples/entropy_surface_census.rs` — the caller-census `println!` no longer claims "ZERO callers in the tree today". It now carries its measurement commit and its command, names the one production caller by file:line (`insight.rs:210`), and separates that from the probe's OWN use of form A. +- FOUND, by re-running the cited grep rather than trusting the sentence: the claim was **false when written**, not merely stale. The probe file imports form A (line 38) and calls it (line 325), so the grep it cites as "returning nothing" never returned nothing at the census commit either. The review bot caught the second staleness; the first shipped unnoticed in `e5e2520`. +- UNCHANGED: C1/C2/C3, their fixtures, and every `assert!`. Probe still exits 0 with C1 PASS / C2 RESTATED / C3 PASS. Every claim in that file gated by an assertion survived; the one printed without one did not. +- Review: Codex P2 on #1154 (thread replied and resolved). CodeRabbit: no actionable comments, 5/5 pre-merge checks, merge risk minimal. +- Epiphany: `E-A-PROBE-CAN-STATE-A-MEASUREMENT-THAT-WAS-FALSE-WHEN-IT-WAS-WRITTEN-1`. + +## 2026-09-03 — branch (D-DCR-4 consolidation, after the census): the entropy atom gets its first consumer — INVENTORY DELTA + +- CHANGED `crates/lance-graph-planner/src/nars/insight.rs` — `confidence_entropy` no longer carries its own Shannon loop; it extracts a 10-bin `[f32; 10]` histogram and routes to `lance_graph_contract::thought_atoms::normalized_entropy`. Public surface unchanged (private fn, same signature, same `[0, 1]` range); one new `use`. +- SAFE BY MEASUREMENT, not by argument: the census's C1 established the log base is inert under normalization (`log2/log2(10)` ≡ `ln/ln(10)`, agreement `0.00000000`), so the caller's and the atom's normalizations are the same function and the routing needs no tolerance. +- PRESERVED DELIBERATELY: the `arena.entries().is_empty() → 0.0` early return. The atom's zero-mass convention is `Some(1.0)`; an empty arena builds an all-zero histogram, so removing the guard would report maximal uncertainty for an arena that holds none, silently (1.0 is in range). This is the C2 falsification's one OPPOSITE fixture, now load-bearing in production and documented as such in the fn's doc comment. +- ADDED two tests in `insight.rs`: `an_empty_arena_has_zero_truth_entropy_not_one` (can-stay-silent; disable-verified — deleting the early return fails this and only this test, 13 passed / 1 failed) and `the_routed_atom_still_spans_the_confidence_range` (can-fire on non-trivial inputs, because the first test is satisfiable by a stub returning 0.0: one occupied bin ⇒ 0.0, ten evenly occupied ⇒ 1.0, and five bins of two ⇒ `ln 5 / ln 10` — the third assertion catches a rewrite that normalized by the OCCUPIED bin count instead of the bin count). +- CONSUMER COUNT for `contract::thought_atoms`: **0 → 1**. The module was operator-ruled 2026-08-31 and had never been called. +- NOT IN THIS DELTA: forms C/D/E (two live in workspace-excluded crates — no in-workspace caller can route to them) and forms F/G (C3 measured them mass-sensitive by `92.103409` and negative above 1, so they are not entropies of a distribution; changing them is a lab behaviour change needing its own gate). +- CROSS-SESSION: `rubicon-loco-rung-cognitive-fabric-v1.md` §130 (#1152, merged the same day) independently records the same fragmentation as *"Shannon entropy — EXISTS BUT NOT LOCO-ADDRESSABLE — ≥6 uncoordinated `entropy()` surfaces"* (the census measured seven). Complementary, not overlapping: that arc wants an ADDRESS, this one supplies a HOME. Zero code contention — the only shared files are the append-only board ledgers. +- Plan: `dismech-causal-replay-v1.md` W4 gate. Epiphany: `E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1`. + ## 2026-09-03 — branch (PROBE-ENTROPY-SURFACE-CENSUS-1, after #1149): the D-DCR-4 gate measured — INVENTORY DELTA - ADDED `crates/lance-graph-planner/examples/entropy_surface_census.rs` — probe only, no library surface, no consolidation. Six of the seven forms are TRANSCRIBED with `file:line` citations (lance-graph-cognitive and thinking-engine are workspace-excluded and unimportable from the planner); form A is the live `contract::thought_atoms::normalized_entropy`. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index ce8e994fd..7afd9a9e3 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -80,7 +80,7 @@ earns a row because it has a merged artifact of its own. | D-DCR-2 | Mengenlehre candidate evaluation via `contract::revision::EvidenceMask` (support ∩ / refute ∖ over `dismech_evidence::Supports`) | **In PR** — `lance-graph-planner/src/dismech_candidates.rs` (`EvidenceItem` / `apply` / `evaluate` / `Evaluation` / `is_informative`). Only `Support` and `Refute` are set operations; `Partial` and `NoEvidence` are INERT by design (full-strength elimination must not be bought with partial evidence, and an asserted absence is not a licence to cut) — reported via `decisive`, never silently dropped. `narrowing` separates "decisive by stance" from "actually taught something", the primitive W5's frontier needs. 6 gates, 4 disable-verified. Spec corrected in preflight: the refute class is the evidence STANCE (`Supports`, shipped + measured), NOT the graph-construction skip filter the plan first named. The skip filter decides whether an item becomes an edge at all, so a candidate set built from the graph has already excluded it — `∖` would subtract twice. Plan §W2 carries the full correction | | D-DCR-2b | **the field map** — propagate precision about a knowledge stage over the WHOLE field; agreement / disagreement / support chains / MISSING LINKS into the HHTL nodes; the boring `is_a`/`part_of` rails lifted into a causality graph with propagated node edges | **In progress** (operator ruling 2026-09-01: three kinds of Mengenlehre; W2 shipped only kind 3, the question mask). Kind 2 (threshold elimination — Shannon / EWA / Hambly / Lyons) is a READING of this map and belongs with W4. **Carrier named 2026-09-01** (`E-AN-HHTL-POSITION-IS-A-NODE-AND-A-NODE-HAS-A-VALUE-1`): an HHTL position is an SoA node whose VALUE lane carries the 12-byte payload read as **24 signed i4** lanes — `+` agreement / `−` disagreement / `0` silence. Still unruled: which lane, versioned vs live, sweep granularity, and a node-level hydrate step for rail-implicit positions. **Census + one-node falsifier shipped 2026-09-01** (`E-G24N4-ALREADY-SHIPS-AND-THAT-IS-WHY-W2B-CANNOT-USE-IT-1`): `G24N4` already ships on `ValueTenant::CausalWitness`, so the carrier is not greenfield — and its operator-locked loci-never-magnitude value law plus its reserved slots `16..24` rule that lane OUT for W2b; 260 of 480 slab bytes free, so space is not the constraint. `tests/w2b_one_node_field.rs` pins the carrier at one-node scale (5 falsifiers, each disable-verified) incl. the whale case; it mints no lane and reserves no byte, and gap 3 (sweep convergence) is untouched. **Slices 1+2 shipped 2026-09-01 (this branch)**: DN dissolution + mechanical/epistemic split + one-hop law; `basin_lanes::BasinLanes` (magnitude register, G24N4 shape) + `accumulate_children` (one-hop, exact-sum-then-clamp) + `hhtl::{missing_ancestors, direct_children}`. Open: multi-register contested-mass semantics, provenance marker, tenant mint for the magnitude register (census: NOT CausalWitness; append margin at slab 220). **⊘ Superseded 2026-09-01 (co-architect ruling, `E-THE-SIGNED-NET-WAS-FALSIFIED-NOT-LIMITED-AND-THE-LOCI-LAW-WAS-SCOPED-TOO-WIDE-1`):** signed net falsified → `epistemic_bassin::EpistemicBassin24` pair (contested ≠ silence, survives accumulation); loci law re-scoped to the A9 READING so the bassin is a classid-selected reading of tenant 14 — NO new tenant until one real row needs both readings; Shannon/EWA adapters shipped against `dismech_candidates` counts + `sigma_propagation` certificates; Hambly-Lyons laneless while jc Pillar 11 is red. The named 24-axis catalogue SHIPPED as v3 2026-09-01 (`ogar-epistemic` 0x0334 + `epistemic_bassin::axes` mirror; supersedable by a v4 mint). Still open: the child-mask index, the provenance marker (PROVENANCE is now axis 20 — the marker's ROW placement is still unruled), armed catalogue parity after the OGAR merge **⊘ RETRACTED 2026-09-02 (operator semantic-family ruling; see `E-SIX-SEMANTIC-FAMILIES-MUST-NOT-IMPERSONATE-EACH-OTHER-1`):** `basin_lanes`, `epistemic_bassin` (the 24-byte pair), the fixed 24-axis basis (`ogar-epistemic` 0x0334) and the pair-specific loco band 0x87..0x8B are REMOVED — they aliased the episodic-loci, qualia-magnitude and population-basin families into one register. Population-basin geometry is an accepted VACANCY (no tenant, no ClassView, no axis set); tenants 14/15, Qualia, Cam96/PairPalette and the #1128 HHTL helpers stand. Kind-1 field map returns to **Open — falsifier-first design step next**. | | D-DCR-3 | counterfactual replay (edge cut through `contract::counterfactual`, Pearl rung 3), two-sided load-bearing/redundant gates | **In PR** — `lance-graph-planner/src/dismech_counterfactual.rs`. Both arms go through W1's `replay_chain` (no second replay path); the cut arm reserves the range AFTER the factual one and is tagged `InferenceType::Counterfactual` (−6) so the road not taken can never read as observed truth. **Measured correction:** the verdict reads FREQUENCY, not confidence — confidence saturates at 170 across every fixture, so a confidence bar would have been a vacuous threshold. `EdgeRole` carries the cut edge's own `CausalTopology` (59-60) + `ReasoningBand` (61-63) so "explains" stays distinguishable from "relates to". Also lands `impl EpisodicEdge for CausalEdge64` (the bridge `contract::counterfactual` documents as BLOCKED — the planner is the first crate depending on both sides). 8 gates, 4 disable-verified | -| D-DCR-4 | Σ transport via `jc::ewa_sandwich` + candidate-entropy readout; entropy-surface CONSOLIDATION decision recorded first | **Gate DONE 2026-09-03** (`e5e2520`, PROBE-ENTROPY-SURFACE-CENSUS-1): 7 surfaces / 4 conventions measured. Target is `contract::thought_atoms` (operator 2026-08-31), NOT `jc` — and the module has ZERO consumers. C1 PASS (base inert), C2 FALSIFIED as pre-registered (B ≡ A on non-degenerate input, OPPOSITE on zero mass — routing is not a drop-in), C3 PASS (F/G are not entropies of a distribution: 92.1 apart at 10× mass, negative above 1). See `E-THE-ENTROPY-HOME-WAS-RULED-AND-LEFT-EMPTY-1`. Σ-transport half still Queued | +| D-DCR-4 | Σ transport via `jc::ewa_sandwich` + candidate-entropy readout; entropy-surface CONSOLIDATION decision recorded first | **Gate DONE 2026-09-03** (`e5e2520`, PROBE-ENTROPY-SURFACE-CENSUS-1): 7 surfaces / 4 conventions measured. Target is `contract::thought_atoms` (operator 2026-08-31), NOT `jc` — and the module had ZERO PRODUCTION consumers at the time of the gate (the census probe's own use is not one; the row narrates chronologically and the consolidation below closes it). C1 PASS (base inert), C2 FALSIFIED as pre-registered (B ≡ A on non-degenerate input, OPPOSITE on zero mass — routing is not a drop-in), C3 PASS (F/G are not entropies of a distribution: 92.1 apart at 10× mass, negative above 1). See `E-THE-ENTROPY-HOME-WAS-RULED-AND-LEFT-EMPTY-1`. **Consolidation SHIPPED 2026-09-03**: `insight::confidence_entropy` routed to `thought_atoms::normalized_entropy` (consumer count 0 → 1), the empty-arena guard preserved and pinned two-sided — C1 made the substitution safe, C2 named the one hazard. Forms C/D/E (excluded crates) and F/G (not entropies of a distribution) deliberately untouched. See `E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1`. Σ-transport half still Queued | | D-DCR-5 | frontier scheduling (info-gain / rung-cost via `EpistemicMode::for_rung`) | **HELD** — operator rung 5-9 table ruling + W0 KILL check | | D-DCR-6 | consumer-leg pointer honoured: corpus bake + live evidence stay consumer-side; only synthetic fixtures here | standing gate | diff --git a/.claude/knowledge/codex-p1-anti-patterns.md b/.claude/knowledge/codex-p1-anti-patterns.md index c741a7b94..6cf34f4ab 100644 --- a/.claude/knowledge/codex-p1-anti-patterns.md +++ b/.claude/knowledge/codex-p1-anti-patterns.md @@ -21,8 +21,17 @@ ## 1. The codex bot's review shape (observed) -The `chatgpt-codex-connector` bot reviews every PR diff and posts inline -comments with one of three severity badges: +> **⊘ CORRECTED 2026-09-03** — "reviews every PR diff" is FALSE and was +> falsified on lance-graph #1154. The bot reviews on three triggers only: +> opening a PR, marking a draft ready, and an explicit `@codex review` comment. +> **A push is not a trigger**, so on a PR that receives a finding, the commit +> fixing that finding is reviewed only if someone asks. Measured: it reviewed +> one of four commits, then reviewed a later head and returned two P2 findings +> after an explicit request. See `E-THE-FIX-FOR-A-REVIEW-FINDING-SHIPS-UNREVIEWED-BY-DEFAULT-1`. + +The `chatgpt-codex-connector` bot reviews a PR diff (on the triggers named in +the correction above, NOT on every push) and posts inline comments with one of +three severity badges: | Badge | Meaning | Sprint-11/12 frequency | |---|---|---| diff --git a/.claude/plans/dismech-causal-replay-v1.md b/.claude/plans/dismech-causal-replay-v1.md index 98168c7e0..c0851363b 100644 --- a/.claude/plans/dismech-causal-replay-v1.md +++ b/.claude/plans/dismech-causal-replay-v1.md @@ -826,3 +826,31 @@ in excluded crates); the two thinking-engine forms, which are not entropies of a distribution at all and whose correction is a lab behaviour change with its own gate. The Σ-transport half of D-DCR-4 is untouched. Full result: `E-THE-ENTROPY-HOME-WAS-RULED-AND-LEFT-EMPTY-1`. + +### W4 gate — the consolidation half, shipped 2026-09-03 (append-only) + +The gate's measurement (PROBE-ENTROPY-SURFACE-CENSUS-1) licensed exactly one +routing, and it is now in: `lance_graph_planner::nars::insight::confidence_entropy` +delegates to `lance_graph_contract::thought_atoms::normalized_entropy`, taking +that operator-ruled module's **consumer count from 0 to 1**. + +Two things the census decided that this commit simply obeyed: + +- **C1 made it a substitution rather than a hypothesis.** The log base is inert + under normalization, so `log2/log2(10)` and `ln/ln(n)` are the same function + and no tolerance argument was needed. +- **C2 named the one hazard, and it is silent.** The caller's empty-arena + convention (`0.0`) and the atom's zero-mass convention (`Some(1.0)`) are + OPPOSITE. An empty arena builds an all-zero histogram, so the caller's early + return is load-bearing: without it an arena holding no uncertainty reports + maximal uncertainty, in range and unremarked. Pinned by + `an_empty_arena_has_zero_truth_entropy_not_one`, disable-verified. + +Still Queued on D-DCR-4: the Σ-transport half (`jc::ewa_sandwich`). Still +deliberately out of scope: forms C/D/E (two live in workspace-excluded crates, +so no in-workspace caller can reach them) and forms F/G (C3 measured them +moving `92.103409` under a 10× mass scaling and going negative above 1 — they +are not entropies of a distribution, and correcting them is a lab behaviour +change that needs its own gate). + +Epiphany: `E-A-RULED-HOME-NEEDS-A-FIRST-CONSUMER-OR-IT-IS-A-VACANCY-1`. diff --git a/crates/lance-graph-contract/src/witness_fabric.rs b/crates/lance-graph-contract/src/witness_fabric.rs index 12a758968..3fb37be88 100644 --- a/crates/lance-graph-contract/src/witness_fabric.rs +++ b/crates/lance-graph-contract/src/witness_fabric.rs @@ -11,6 +11,29 @@ //! never a materialized `W×W` fabric struct (AGI-as-SoA: methods over the //! existing carrier, not a new stored layer). //! +//! # This module is semantic family (1), and only family (1) +//! +//! `E-SIX-SEMANTIC-FAMILIES-MUST-NOT-IMPERSONATE-EACH-OTHER-1` (operator, +//! 2026-09-02) ratifies six distinct families and this module is the first: +//! *"episodic / Markov loci — `CausalWitnessFacet`, tenant 14, sign = +//! orientation, pointer semantics closed to the `Locus`/ClassView API."* +//! +//! Stated here because from OUTSIDE the module the word "contradiction" reads +//! as one concept, and it is not. `Locus::Contradiction` here is an episodic +//! locus — a signed OFFSET to a peer row that disagrees. It is NOT family (3), +//! *epistemic population basins*, which the same ruling records as having "NO +//! shipped **signed-i4** ABI, tenant, ClassView or axis vocabulary" and being +//! an **accepted vacancy**. The `signed-i4` qualifier is the ruling's own and +//! is load-bearing: dropping it widens a scoped vacancy at the site that +//! enforces it, which is the direction that later reads as canon. A reader arriving with a population-basin contradiction verb +//! will find the shapes here familiar and the semantics wrong; the ruling's +//! own invariant is the guard — *same physical shape ≠ same semantics*. +//! +//! So: do not "unify" a population-basin contradiction with this one, and do +//! not read a quorum here as a population statistic. Nothing in this module +//! reads or writes family (3), and closing that vacancy is a mint, not a +//! refactor of this file. +//! //! # Loci converge on the SAME EVENT, not the same offset //! //! A locus offset is relative to its OWN row's stream position. Two rows agree diff --git a/crates/lance-graph-planner/examples/entropy_surface_census.rs b/crates/lance-graph-planner/examples/entropy_surface_census.rs index 814b0df12..ff3a88483 100644 --- a/crates/lance-graph-planner/examples/entropy_surface_census.rs +++ b/crates/lance-graph-planner/examples/entropy_surface_census.rs @@ -37,6 +37,18 @@ use lance_graph_contract::thought_atoms::normalized_entropy; +/// The caller's source, pulled in at COMPILE time so the caller census is a +/// checked fact rather than a sentence. If `insight.rs` moves, this breaks the +/// build — which is the point: a census that cannot fail is not a census. +const INSIGHT_SRC: &str = include_str!("../src/nars/insight.rs"); + +/// Call sites of form A inside the caller's source. Counts `normalized_entropy(` +/// — the `use` line and every doc-comment mention lack the paren, so they do not +/// inflate it, and this comment lives in a different file so it cannot self-match. +fn production_call_sites() -> usize { + INSIGHT_SRC.matches("normalized_entropy(").count() +} + // ═══════════════════════════════════════════════════════════════════════ // The convention table (printed verbatim, then measured against it) // ═══════════════════════════════════════════════════════════════════════ @@ -68,7 +80,17 @@ mod transcribed { /// **Form B** — transcribed from /// `crates/lance-graph-planner/src/nars/insight.rs:176-198` /// (`fn confidence_entropy(arena: &BeliefArena) -> f32`), verified at - /// that range on read. + /// that range on read **as of `a1c9488e`**. + /// + /// ⊘ **SUPERSEDED IN THE TREE, DELIBERATELY PRESERVED HERE.** The + /// consolidation this census gated replaced that implementation: the live + /// `confidence_entropy` now delegates to form A and no longer builds its + /// own histogram or runs its own log2 loop. This transcription is + /// therefore a FOSSIL, and it is kept rather than re-transcribed because + /// C2's falsification result is a statement about the OLD B — re-pointing + /// it at the new code would silently convert a measured falsification into + /// a comparison of form A against itself. Read every B row below as + /// "B as of `a1c9488e`", never as "the planner's current entropy". /// /// Source shape: builds a fixed `[0usize; 10]` histogram from /// `BeliefArena` confidences, then computes normalized Shannon entropy @@ -609,16 +631,45 @@ fn main() { tm_max - tm_min ); + // The caller census is a MEASUREMENT, so it is ASSERTED, not printed. + // + // Two earlier revisions of this block were prose. The first claimed "ZERO + // callers ... verified by a grep returning nothing" and was wrong twice + // over: the consolidation it was written to motivate gave form A a + // production caller, AND the grep had never returned nothing, because THIS + // FILE imports form A and calls it. The second revision added the commit + // and the command — real provenance — but was still a bare `println!`, so + // nothing failed when the count changed. Provenance is not falsifiability. + // + // The fix is to source the claim from the tree at COMPILE time and assert + // it at run time: `PRODUCTION_CALLERS` below breaks the build's own probe + // the moment a call site is added or removed. Note the self-reference this + // block cannot escape — the printed command's own text matches the pattern + // it prints, so a reader re-running it by hand sees hits generated by this + // very comment. That is why the machine-checked count reads the CALLER's + // source rather than counting grep lines. + let production_callers = production_call_sites(); println!( - "\n caller census: `normalized_entropy` (form A) has ZERO callers in the tree today \ - (verified by the orchestrator with \ - `grep -rn 'normalized_entropy' --include=*.rs crates | grep -v /target/ | grep -v thought_atoms.rs` \ - returning nothing)." + "\n caller census (ASSERTED below, not merely printed): form A has {} \ + production call site(s) in `nars::insight` — the consolidation this \ + census gated. Before it the count was zero, which is the vacancy the \ + census was run to establish. A by-hand grep also returns this file's \ + import, doc comments, and THIS COMMENT's own copy of the pattern; the \ + asserted number below counts call sites in the caller's source and is \ + immune to that self-match.", + production_callers ); // ── every claim has now RUN; assert at the very end so no later claim // ── is hidden behind an earlier panic (a probe that aborts early hides // ── evidence — that is how C3 went unmeasured on the first run). + assert_eq!( + production_callers, 1, + "caller census: form A must have exactly ONE production call site in \ + nars::insight (found {production_callers}). This is the assertion the \ + two prose revisions of this block lacked — if a call site was added or \ + removed, re-measure and re-pin deliberately rather than silencing it." + ); assert!(c2a_pass, "C2a: B and A disagree on non-degenerate input"); assert!( c2b_pass, diff --git a/crates/lance-graph-planner/src/nars/insight.rs b/crates/lance-graph-planner/src/nars/insight.rs index cd24950fc..90af4e975 100644 --- a/crates/lance-graph-planner/src/nars/insight.rs +++ b/crates/lance-graph-planner/src/nars/insight.rs @@ -20,6 +20,7 @@ use super::belief::BeliefArena; use crate::temporal::QueryReference; use lance_graph_contract::mul::FlowState; use lance_graph_contract::sensorium::GraphSignals; +use lance_graph_contract::thought_atoms::normalized_entropy; /// A snapshot of the arena's cognitive signals at one instant — the contract /// [`GraphSignals`] (reused) plus the two scalars S10 needs that it does not @@ -176,26 +177,44 @@ fn wonder(arena: &BeliefArena) -> f32 { /// Normalized Shannon entropy of the confidence distribution over 10 bins /// (`[0, 1]`; 0 = a single peaked confidence, 1 = uniform spread). +/// +/// Routed through the operator-ruled atom +/// [`lance_graph_contract::thought_atoms::normalized_entropy`] rather than +/// carrying its own Shannon loop. The census +/// (`examples/entropy_surface_census.rs`, PROBE-ENTROPY-SURFACE-CENSUS-1) +/// measured this substitution safe: the log base is inert under +/// normalization (`log2/log2(10)` and `ln/ln(10)` agreed to `0.00000000` on +/// every fixture), and the histogram counts are unnormalized weights, which +/// the atom divides by their own sum. +/// +/// **The `n == 0` early return is load-bearing and must not be removed.** +/// An empty arena builds an all-zero histogram, and the atom's zero-mass +/// convention is `Some(1.0)` ("nothing prefers anything — indistinguishable +/// from uniform"). Dropping the guard therefore inverts an empty arena's +/// entropy from 0.0 to 1.0 — silently, since both are in range. That is the +/// one fixture where the census measured the two conventions OPPOSITE, and +/// it is pinned by `an_empty_arena_has_zero_truth_entropy_not_one`. #[must_use] fn confidence_entropy(arena: &BeliefArena) -> f32 { const BINS: usize = 10; - let n = arena.entries().len(); - if n == 0 { + if arena.entries().is_empty() { return 0.0; } + // Counts stay INTEGRAL and are widened once, at the boundary. Accumulating + // into `f32` instead loses counts above 2^24: `f32(2^24) + 1.0 == f32(2^24)` + // (measured), so two bins holding 20M and 40M beliefs would both read + // 16_777_216.0 and the atom would see them as equally populated. Nothing in + // `BeliefArena` bounds its entry count, so this is unbounded in principle + // and merely unobserved today — which is not a reason to accumulate in f32. let mut hist = [0usize; BINS]; for b in arena.entries() { let idx = ((b.truth.confidence.clamp(0.0, 1.0) * BINS as f32) as usize).min(BINS - 1); hist[idx] += 1; } - let mut h = 0.0f32; - for &c in &hist { - if c > 0 { - let p = c as f32 / n as f32; - h -= p * p.log2(); - } - } - h / (BINS as f32).log2() // normalize to [0, 1] + let weights: [f32; BINS] = core::array::from_fn(|i| hist[i] as f32); + // BINS is a non-zero constant > 1, so the atom's empty/single arms are + // unreachable here; the default is defensive, never taken. + normalized_entropy(&weights).unwrap_or(0.0) } #[must_use] @@ -317,6 +336,82 @@ mod tests { } } + /// Build an arena whose beliefs carry exactly the given confidences. + fn arena_with_confidences(cs: &[f32]) -> BeliefArena { + let mut arena = BeliefArena::new(); + for (i, &c) in cs.iter().enumerate() { + arena.observe( + inh(i as u16, 900), + TruthValue::new(0.9, c), + Stamp::source(i as u32), + ); + } + arena + } + + /// **The load-bearing guard of the atom routing.** `confidence_entropy` + /// now delegates to `thought_atoms::normalized_entropy`, whose zero-mass + /// convention is `Some(1.0)` — the OPPOSITE of this caller's, and the one + /// fixture where PROBE-ENTROPY-SURFACE-CENSUS-1 measured the two + /// conventions maximally apart. An empty arena builds an all-zero + /// histogram, so without the caller's own early return the atom would + /// report maximal uncertainty for an arena that holds no uncertainty at + /// all — silently, since 1.0 is in range. + /// + /// Can-stay-silent. Disable by deleting the `is_empty` early return in + /// `confidence_entropy`: this fails with 1.0. + #[test] + fn an_empty_arena_has_zero_truth_entropy_not_one() { + let entropy = Snapshot::of(&BeliefArena::new(), 0.0).signals.truth_entropy; + assert_eq!( + entropy, 0.0, + "an empty arena has no confidence distribution to be uncertain \ + about; the atom's zero-mass convention (1.0) must not reach here" + ); + assert!( + (entropy - 1.0).abs() > 0.5, + "guard removed: the all-zero histogram reached the atom and came \ + back as uniform ({entropy})" + ); + } + + /// Can-fire, on NON-TRIVIAL inputs — otherwise the assertion above would + /// also hold for a `confidence_entropy` stubbed to always return 0.0. + /// The routed atom must still span the full range: one occupied bin is + /// zero uncertainty, ten evenly occupied bins is maximal, and the + /// normalization is by the BIN COUNT (10), not by the occupied count. + #[test] + fn the_routed_atom_still_spans_the_confidence_range() { + let peaked = Snapshot::of(&arena_with_confidences(&[0.42; 8]), 0.0) + .signals + .truth_entropy; + assert!( + peaked.abs() < 1e-6, + "eight beliefs in one bin is a degenerate distribution: {peaked}" + ); + + let spread: Vec = (0..10).map(|k| 0.05 + 0.1 * k as f32).collect(); + let uniform = Snapshot::of(&arena_with_confidences(&spread), 0.0) + .signals + .truth_entropy; + assert!( + (uniform - 1.0).abs() < 1e-5, + "ten beliefs, one per bin, is maximal spread: {uniform}" + ); + + // Half-occupied: five bins of two. H = log(5), normalized by log(10). + let half: Vec = (0..10).map(|k| 0.05 + 0.2 * (k / 2) as f32).collect(); + let mid = Snapshot::of(&arena_with_confidences(&half), 0.0) + .signals + .truth_entropy; + let expected = 5f32.ln() / 10f32.ln(); + assert!( + (mid - expected).abs() < 1e-5, + "normalization is by the bin count, not the occupied count: \ + {mid} vs {expected}" + ); + } + /// A deterministic SplitMix64 — the size-preserving null needs reproducible /// randomness (no clock/`rand`), like the certification-officer seed. struct SplitMix64(u64);