From 26c554e3366216ea7feb69ec0a32f10505d46e45 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 15:19:48 +0000 Subject: [PATCH 1/5] S3.0 corrected: exact causal-literal address, retracting #973's overclaim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cold-start recovery session. PR #973 ("S3.0 — the exact HHTL causal-literal address") was closed unmerged by the operator: its CausalLiteral struct and nine tests were sound, but its board-finding title/framing (E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1) turned a correct local fact -- a domain.subject.predicate.object literal exactly fills NiblePath::MAX_DEPTH (16 nibbles) -- into an implied global claim that exact identity cannot live in HHTL addressing and evidence must "ref-escape" out of address space. That inference forgot three already-measured counterexamples in this same repository: - E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1 (#875/#876, MERGED): a full-width 4-ary HHTL fold of real WordNet ancestry is an EXACT structural encoding, not lossy hashing. - tekamolo_facet.rs / facet::FacetCascade (#839/#844, MERGED, live code): one 16-byte content-blind register supports several simultaneous ClassView readings (G3D4/G4D3/G6D2/24xi4) -- new capability lands as a new reading, never a deeper path. - E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1 (measured, 7,657 real clauses): a local representation exhausting cleanly marks a TYPE boundary, not a substrate ceiling -- the sanctioned response is a different reading of the same address, never an unbounded escape hatch. This commit reintroduces CausalLiteral verbatim (the struct and all nine tests were correct) with the module doc and board findings rewritten to scope the NiblePath-depth fact correctly, retract the overclaiming finding in place (append-only), and leave S3.1's evidence/meta placement explicitly OPEN with the orthogonal-facet pattern named as the leading candidate rather than pre-decided by a forced "ref-escape" conclusion. Full audit trail: .claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md Gates: lance-graph-contract 1180/1180 (9 new), fmt clean, clippy --all-targets --no-deps -D warnings clean, downstream lance-graph-planner Stage-2.6a harness still 4/4. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AZLe87ssKXGuXFbrmHdGnG --- .claude/board/EPIPHANIES.md | 96 +++ .claude/board/LATEST_STATE.md | 47 ++ .claude/board/STATUS_BOARD.md | 30 + ...26-08-20-s3-0-cold-start-recovery-audit.md | 180 ++++++ .../src/causal_literal.rs | 599 ++++++++++++++++++ crates/lance-graph-contract/src/lib.rs | 1 + 6 files changed, 953 insertions(+) create mode 100644 .claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md create mode 100644 crates/lance-graph-contract/src/causal_literal.rs diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 566441a92..ffca8a572 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,99 @@ +## 2026-08-20 — E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1 + +**Status:** FINDING (measured + corrected; supersedes and retracts +`E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1`, PR #973, which was closed +unmerged before landing). **Confidence:** High — the arithmetic (16 nibbles = +`NiblePath::MAX_DEPTH`) is unchanged and correct; what changes is the scope of +what it is evidence FOR. + +**The retracted claim.** #973 measured, correctly, that a +`domain·subject·predicate·object` literal at `u16` each is exactly 16 nibbles +— the ENTIRE budget of [`hhtl::NiblePath`], a single sequential `u64`-backed +router path purpose-built for the `subClassOf` Abstammung tree. From that true +local fact it concluded that identity and an evidence subtree "cannot both be +one `NiblePath`" and therefore evidence must **ref-escape** out of address +space into a structurally separate mechanism — framed under the title +"the literal cannot live in the path it roots". That title, read on its own, +overclaims: it reads as a statement about HHTL addressing in general, not +about one particular depth-limited path type. + +**Why the retraction, with receipts already in this repository.** Three +already-MEASURED counterexamples show the general shape #973 needed and did +not reach for: + +1. **`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1`** (PR #875/#876, MERGED) + — a full-width 4-ary HHTL fold of real WordNet ancestry is an EXACT + structural encoding (corr +0.494 real vs −0.036 shuffled), not a lossy + hash. "HHTL identity" and "one `NiblePath`'s 16-nibble ceiling" are + different claims; #973 conflated them. +2. **`tekamolo_facet.rs` / `facet::FacetCascade`** (PR #839/#844, MERGED, + live code) — a 16-byte content-blind register supports SEVERAL + SIMULTANEOUS `ClassView`-selected readings of the SAME bytes + (`G3D4`/`G4D3`/`G6D2`/`24×i4`). New semantic capability (TEKAMOLO's four + orthogonal Temporal/Kausal/Modal/Lokal lanes) lands as a new READING, never + a deeper path. This is the concrete precedent for where an evidence/meta + facet over `CausalLiteral` should be sought first, instead of ref-escaping. +3. **`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1`** (measured, 7,657 real German + relative clauses) — the general pattern: a LOCAL fixed representation + (there, `i4`, `-8..+7`) exhausting cleanly at a real boundary marks a TYPE + boundary, and the sanctioned response is to switch to a DIFFERENT reading + of the SAME address register — never to declare the substrate exhausted or + to invent an unbounded escape hatch. + +**What survives, unchanged.** The measured fact itself: `LITERAL_PATH_NIBBLES +== hhtl::MAX_DEPTH` (`const _`-asserted in `causal_literal.rs`), and +`NiblePath::routing_prefix(16)` is `is_full()` — nothing can descend further +via THAT router. `CausalLiteral { domain, subject, predicate, object }` as a +free-standing 8-byte exact-identity struct (four `u16`, component equality, +no CAM-PQ/evidence/source/version field possible by construction) is +reintroduced verbatim from #973 — the struct and its nine tests were correct; +only the surrounding architectural inference was overclaimed. + +**What is now OPEN rather than pre-decided.** Where S3.1's +`CausalMeta`/`EpistemicMeta` evidence tree lands is NOT settled by this +finding. The leading candidate, per (2) above, is an orthogonal facet/column +keyed by `CausalLiteral` — not a "ref-escape" forced by NiblePath's ceiling. +S3.1 decides this from measurement when it lands, not from this PR. + +**The process lesson — see the companion entry below +(`E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1`).** + +--- + +## 2026-08-20 — E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1 + +**Status:** RULING (operator, cold-start recovery session following PR #973's +closure). **Confidence:** High — process rule, not a technical claim. + +**The failure mode, named exactly.** A locally correct arithmetic derivation +about ONE representation (`NiblePath`'s 16-nibble depth ceiling) was promoted, +via a dramatic board-finding title, into an implied GLOBAL substrate +conclusion (exact causal-literal identity cannot live in HHTL addressing; +evidence must structurally escape address space) — while three +already-measured counterexamples in the SAME repository +(`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1`, the shipped +`tekamolo_facet.rs`/`FacetCascade` orthogonal-reading pattern, +`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1`) demonstrate the opposite pattern +working. None of the three were consulted before the finding was written. + +**The rule going forward, for this and every future session.** Before any +board finding declares that the substrate "cannot" do something architectural: +search `EPIPHANIES.md` and the relevant `.claude/knowledge/`/`docs/` for +existing measurements and counterexamples FIRST. A new local proof whose +premise silently omits an already-established substrate behaviour is not a +discovery — it is exactly the shape of error this entry exists to name. Use +explicit labels (`[MERGED]` / `[MEASURED]` / `[RULING]` / `[PROPOSED]` / +`[REJECTED]`) so the provenance of every claim stays legible, and never blur +"true fact about one type" into "true fact about the substrate" without +saying so in the same sentence. + +**Cross-ref:** `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` (the +technical retraction this ruling explains), PR #973 (closed unmerged, the +originating incident), `.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md` +(the full audit). + +--- + ## 2026-08-20 — E-THE-COMPAT-ENUM-WAS-EATING-HALF-THE-REGISTER-1 **Status:** FINDING (measured + fixed, PR #971). `CausalEdgeV3::rehydrate` diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 946b7600e..c5c228a73 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,50 @@ +## 2026-08-20 — branch `claude/lance-graph-stage-3-recovery-2wrdbd` — S3.0 corrected: the exact causal-literal address (retracts #973's overclaim, salvages its code) + +### Current Contract Inventory — 1 new zero-dep contract type, no packed tenant + +- **`lance_graph_contract::causal_literal`** (new module, reintroduces #973's + struct verbatim; rewrites the surrounding claim): + - **`CausalLiteral { domain, subject, predicate, object }`** — four `u16` + canonical ordinals, **8 bytes of pure address**, `const _`-asserted at 8. + - `new` / `domain` / `subject` / `predicate` / `object` / `is_fully_bound` + - `as_u64` / `from_u64` / `to_le_bytes` / `from_le_bytes` — exact reversible + identity + - `routing_prefix(depth)` / `full_path()` — a **lossy** `NiblePath` cohort + projection, explicitly scoped as one property of `NiblePath` specifically, + never as "the HHTL form" of the literal + - `ConceptId` / `DomainId` / `UNBOUND` / `NIBBLES_PER_COMPONENT` / + `LITERAL_PATH_NIBBLES` (`const _`-asserted `== hhtl::MAX_DEPTH`) +- **No `ValueTenant`, no CE64 bit added, no V3 reserved byte spent, no + `ENVELOPE_LAYOUT_VERSION` bump.** Same Ruling-E gate #973 satisfied. +- **Corrected vs #973:** the module doc no longer claims identity+evidence + "cannot both be HHTL" — it names `NiblePath`'s 16-nibble ceiling as a fact + about ONE router type, cross-references the three counterexamples + (WordNet #875/#876, `tekamolo_facet.rs`/`FacetCascade`, the 24×i4 anaphora + boundary) that show orthogonal-facet/exact-address patterns already working + in this repo, and leaves S3.1's evidence/meta placement explicitly OPEN + rather than forcing a "ref-escape" conclusion. + +### Why this session exists + +PR #973 ("S3.0 — the exact HHTL causal-literal address") was closed unmerged +by the operator: its code was sound but its board-finding title/framing +(`E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1`) turned a correct local +fact about `NiblePath`'s depth budget into an implied global HHTL-impossibility +claim, forgetting three already-measured counterexamples in this same +repository. Full audit: `.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md`. +Retraction + corrected finding: `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` ++ `E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1` in +`EPIPHANIES.md`. + +### Not in this PR (the brief's own order, unchanged from #973) + +S3.0b causal regime · S3.1 Meta tree (evidence placement now explicitly OPEN, +not pre-decided) · S3.2 V3 local proxy bridge · S3.3 `ResolvedPredicate` · +S3.4 DisMech · S3.5 NARS evidence mass · S3.6 JC measurement · S3.7 semantic +recipe projection · S3.8 potholes/backcast · S3.9 Pearl validation. +Stage-2/2.5/2.6 (#971) remain the frozen baseline — untouched here. + +--- ## 2026-08-20 — lance-graph #971 (MERGED, `2cbe62d`, head `d627f5c`) — Stage 2 carves + Stage 2.5 census + Stage 2.6a V3 invariance + CE64 ⇄ V3 losslessness Six commits, four stages. The per-stage Contract Inventory and results are in diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 5c29f4981..341c4ded5 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,33 @@ +## stage-3 — exact causal literals + amortized epistemic trees (operator brief, 2026-08-20; recovery session after #973's overclaim retraction) + +Delivery order is the brief's own §38; **not** to be attempted in one PR. +Stage-2/2.5/2.6 (PR #971) is the frozen substrate baseline: representation +totality and reasoning parity are two SEPARATE proofs and neither is reopened +without a real falsifier. PR #973 (S3.0, first attempt) was closed unmerged — +its `CausalLiteral` struct was correct and is reintroduced here verbatim; its +board-finding framing overclaimed and is retracted in `EPIPHANIES.md` +(`E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1`). + +| D-id | Deliverable | Status | +|---|---|---| +| S3.0 | exact causal-literal address (`contract::causal_literal`) | **In PR** | +| S3.0b | qualified causal regime | Queued | +| S3.1 | `CausalMeta` + `EpistemicMeta` tree contract; rebuild-from-leaves; evidence placement OPEN (orthogonal-facet candidate, not ref-escape) | Queued | +| S3.2 | V3 local-proxy bridge (absolute identity survives local indirection) | Queued | +| S3.3 | `ResolvedPredicate`; unknown fails CLOSED; explicit composition | Queued | +| S3.4 | DisMech adapter — map sources onto EXISTING literals, never mint per source | Queued | +| S3.5 | NARS evidence mass: raw/source/HEEL/effective, deterministic W+/W− | Queued | +| S3.6 | JC measurement per predicate × cohort × horizon × instrument | Queued | +| S3.7 | `ReasoningSituation` → ThoughtCtx projection; measure the 17 mute kernels | Queued | +| S3.8 | potholes, first_possible vs first_derived, strict historical replay | Queued | +| S3.9 | Pearl validation — earn the SPO 2³ projections empirically | Queued | + +The ones S3.0 answers: **#1** (same canonical tuple → same literal), **#2** +(distinct predicates never collide), **#3** (CAM-PQ is not identity) and +**#19** (a local target is not absolute identity) via the *proven-lossy* +`NiblePath` routing projection — same falsifiers #973 answered, code reused. + +--- ## preparation-arc plan wave — 2026-08-19 (operator: "integration plans for all open arcs") Five plans, each PROPOSED (no code — the reset charter's audit-first order diff --git a/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md b/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md new file mode 100644 index 000000000..bc3e9ed9b --- /dev/null +++ b/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md @@ -0,0 +1,180 @@ +# S3.0 Cold-Start Recovery Audit — 2026-08-20 + +## A. MERGED substrate truth + +- **PR #970** [MERGED, `781c3b9b`]: `CausalEdge64` v2 layout final. Bits 59-60 = + `TrustTexture` (canonical) with an ADDITIVE second reading `CausalTopology` + over the *same* bits — no bits move, no auto-derivation. Bits 61-63 = spare, + with an ADDITIVE `ReasoningBand` reading — explicit `with_reasoning_band()` + only, never auto-derived from mantissa/confidence/style. Deprecated v1 + `temporal` (bits 52-63) is NOT valid v2 chronology; a v1 edge with + `temporal >= 512` reads a nonzero band under v2 — version-gate required. + Confirmed live in `crates/causal-edge/src/layout.rs`. +- **PR #971** [MERGED, `93dc57e`]: Stage-2/2.5/2.6a frozen baseline. Key + correction carried: `InferenceType` is a LOSSY compat projection of the + 4-bit signed mantissa (8/16 states corrupted on round-trip, incl. the + `pack_v2` default `0 -> +1`) — never route a conversion through it; carry + the raw nibble. CE64<->V3 is bit-identical except (a) dedup'd 24-bit in-edge + SPO and (b) deprecated v1 temporal (deliberately NOT lifted — TE stays an + independent producer-set signed chain offset). "Recipe capability != NARS + reachability" measured (`Mcp` truthfully declares `moves_confidence()` and + is still silent 0/180). +- **PR #973** [REJECTED, CLOSED UNMERGED, no comments from reviewers — + operator judgment call before any bot review completed]. Confirmed via + `git log`/`grep`: nothing from this PR is on `main` — `causal_literal.rs` + does not exist in the working tree. + +## B. MEASURED substrate truth (the counterexamples #973 forgot) + +All confirmed live/merged in this repo, read in full this session: + +1. **WordNet #875/#876** [MERGED, MEASURED]. A full 4-ary depth-4 (16-nibble) + HHTL fold of real ground-truth taxonomy is NOT lossy hashing — the ADDRESS + itself encodes ancestry (corr +0.494 vs shuffled -0.036), the sub-nibble + rung is load-bearing (2.47 hops the 16-ary router can't see), and #876 + separately proved a *consumer* (the ruler) can be structure-blind to an + address's hierarchy without that meaning the address lacks structure — + "the address encodes taxonomy" vs "the calculator reads the address as a + number" are different, separable claims. +2. **TEKAMOLO #839/#844** [MERGED, live code: `facet.rs` + `tekamolo_facet.rs`]. + `FacetCascade = facet_classid(4) | 6×(8:8) = 16B`, ONE 128-bit register with + MULTIPLE simultaneous ClassView-selected readings (`G3D4`/`G4D3`/`G6D2`, + `24×i4`), never nested path-depth. TEKAMOLO names the `G4D3` carving as + 4 ORTHOGONAL 256:256:256 lanes (Temporal/Kausal/Modal/Lokal) over the SAME + bytes — new semantic capability lands as a new *reading*, never a deeper + path. This is the concrete, shipped instance of "orthogonal facets over one + address" the mission brief's OSM/TEKAMOLO doctrine describes. +3. **24×i4 anaphora #850** [MEASURED, 88.01% real German relative clauses]. + Proves the general shape #973 needed but didn't reach for: a LOCAL fixed + representation (i4, -8..+7) exhausts cleanly at a real linguistic boundary, + and the correct response is NOT "widen the local pointer" — it's "the + exhausted local representation marks a TYPE boundary; switch to a + DIFFERENT sanctioned reading of the SAME address register (a basin edge), + never invent a deeper/wider path." Directly antithetical to reading + NiblePath's 16-nibble ceiling as grounds to abandon HHTL identity. +4. **`hhtl.rs` `NiblePath`** [MERGED, confirmed]: `MAX_DEPTH: u8 = 16`, one + `u64`, single SEQUENTIAL router path for the specific `subClassOf` + Abstammung tree. This is ONE HHTL-shaped type among several in this repo — + NOT the only or canonical HHTL substrate. `FacetCascade` (item 2 above) is + a materially different HHTL-adjacent type: a fixed 16-byte register with + *simultaneous* multi-lens reads, not a depth-limited single path. + +## C. REJECTED #973 claims — precisely scoped + +**What #973's CODE actually did (and got right):** `CausalLiteral{domain:u16, +subject:u16, predicate:u16, object:u16}`, 8 bytes, `const _`-asserted size, +component equality, no CAM-PQ/evidence/source/version fields possible by +construction. This is *exactly* the mission brief's own §25 recommended +minimal S3.0 shape. The 9 tests (injectivity, field isolation, round-trip, +unbound sentinel) are sound and reusable verbatim. + +**What #973's REASONING (the EPIPHANIES title + doc-comment framing) +overclaimed:** the finding is titled +`E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` and its argument runs: +"a `domain·S·P·O` address exactly fills `NiblePath`'s 16-nibble budget with +zero nibbles left for an evidence subtree beneath it -> therefore identity +and routing SPLIT -> the evidence subtree must *ref-escape* out of address +space entirely." The arithmetic (16 nibbles = `MAX_DEPTH`, zero slack) is +correct and TRUE ONLY OF `NiblePath` — a single sequential depth-limited path +type. The invalid generalization is treating that as proof that **HHTL +identity itself** (not just this one path type) cannot carry the literal, and +that evidence/meta state must therefore live in some conceptually separate, +disconnected mechanism rather than as an **orthogonal facet keyed by the same +8-byte address** — exactly the TEKAMOLO/anaphora/OSM pattern already proven +in this repo. Nothing in #973 acknowledges that a `FacetCascade`-style +multi-reading register (or simply: `CausalLiteral` as its own SoA +column/plane, with a `CausalMeta`/`EpistemicMeta` column keyed by the SAME +`CausalLiteral`) sidesteps the "budget" problem entirely, because it was +never modeled as depth-based descent from a single path in the first place. + +**Verdict: `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` is RETRACTED as +stated.** Superseded by a narrower, correctly-scoped finding (drafted below). +This is the precise process failure named in the mission brief §0/§9: a local +representation limit (NiblePath's depth ceiling) promoted into an implied +global architecture conclusion (identity+evidence must structurally split +away from HHTL) while forgetting three already-measured counterexamples in +the SAME repository that show the opposite pattern working. + +## D. Salvageable #973 code/math + +- `CausalLiteral { domain, subject, predicate, object }` as 4×`u16`, 8 bytes, + `const _`-asserted, component equality — REUSE VERBATIM. +- `packed_identity_is_injective`, `changing_only_the_predicate_changes_the_literal`, + `three_sources_asserting_the_same_proposition_mint_one_literal`, + `component_isolation_matrix`, `identity_round_trips_exactly_in_both_forms`, + `unbound_components_are_addressable_but_not_fully_bound` — REUSE VERBATIM + (these test IDENTITY, which #973 got right). +- `routing_prefix(depth) -> NiblePath` / `full_path()` — REUSE, but RELABEL + the doc comments: this is *one particular* NiblePath-shaped projection + useful for cohort/locality queries against the existing `subClassOf`-style + router, not "the" HHTL reading of the literal, and its lossiness at + `depth < 16` is a fact about `NiblePath` specifically, not about HHTL + identity in general. +- DROP/REWRITE: `routing_prefix_is_not_identity` and + `the_full_literal_path_exhausts_the_nibble_budget` stay as tests (they are + correct, falsifiable facts about `NiblePath`) but their surrounding prose + must not imply "therefore identity lives outside HHTL." +- DROP the `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` framing; + replace per §E below. + +## E. Remaining unknowns + +- Whether `CausalLiteral` should ALSO be constructible as a `FacetCascade` + reading (a 5th `CascadeShape`, `4×u16`-over-16B) for consumers that want it + to live inside the existing content-blind register alongside TEKAMOLO/rails/ + SPO-triplet readings, vs. staying a free-standing 8-byte contract type + consumed by reference. Not resolved this session — S3.0 replacement below + ships the free-standing type only (matches the brief's own minimal-first + guidance in §25), and defers the FacetCascade-reading question to S3.1/S3.2 + where the V3 local-proxy bridge is actually built. +- CausalRegimeAddr (S3.0b), Meta tree (S3.1), predicate resolution (S3.3) — + all explicitly out of scope for this PR per the brief's own delivery order. +- Rubicon threshold semantics (§19) — not investigated this session; will be + searched before any Rubicon-touching work, not asserted from memory. + +## F. Proposed minimal S3.0 replacement + +Reintroduce `crates/lance-graph-contract/src/causal_literal.rs` with: +1. The same `CausalLiteral` struct, accessors, packing, and all 9 original + tests (salvaged per §D). +2. Module-doc and EPIPHANIES entry REWRITTEN to state the narrow, correct + claim: `NiblePath::MAX_DEPTH` (16 nibbles) is exactly consumed by a + 4×`u16` literal, so a literal's *own* identity cannot ALSO be expressed as + a strictly-shorter `NiblePath` prefix while remaining exact — a fact about + `NiblePath` depth budget, not about HHTL addressing dimensionality in + general. Explicitly cross-references WordNet #875/#876 (exact structural + HHTL address is real and proven), TEKAMOLO #839 (orthogonal-facet pattern + is the sanctioned way to attach evidence/meta without deepening a path), + and anaphora #850 (a locally-exhausted representation marks a TYPE + boundary, not a substrate ceiling) so a future session reads the + NiblePath-specific finding correctly. +3. New finding name: `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` + (supersedes/retracts `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1`, + which is marked ⊘ RETRACTED in place per this repo's append-only + convention, not deleted). +4. A second, process-level finding: + `E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1`, + naming the general failure mode for future sessions. +5. Evidence/Meta placement is left EXPLICITLY OPEN (not "ref-escaped" as a + forced conclusion) — noted as an S3.1 design question with the + FacetCascade-orthogonal-plane option named as the leading candidate, + rather than asserted-and-closed by this PR. + +## G. Falsifiers that would kill this proposal + +- If a future S3.1/S3.2 session finds that keying `CausalMeta`/`EpistemicMeta` + as orthogonal facets over `CausalLiteral` (rather than ref-escaping) hits a + real capacity/addressing wall analogous to NiblePath's, that would validate + more of #973's original instinct — investigate before assuming either way. +- If `CausalLiteral` needs to itself be routable through the DOLCE/basin + `NiblePath` tree (not just an opaque 8-byte key), the routing_prefix + question reopens for real; this PR does not build that consumer. + +## Central-constitution check (brief §27.5) + +Does the proposed S3.0 replacement contradict OSM, WordNet #875/#876, Bible +Rosetta, TEKAMOLO, or the 24×i4 anaphora boundary? **NO.** It ships the same +minimal exact-identity primitive the brief itself specifies in §25, corrects +only the RETRACTED overclaim in the board narrative, and explicitly leaves +the evidence/meta placement question open rather than pre-deciding it against +the TEKAMOLO/anaphora precedent. diff --git a/crates/lance-graph-contract/src/causal_literal.rs b/crates/lance-graph-contract/src/causal_literal.rs new file mode 100644 index 000000000..750643bc9 --- /dev/null +++ b/crates/lance-graph-contract/src/causal_literal.rs @@ -0,0 +1,599 @@ +//! S3.0 — the **exact causal literal**: an absolute, deterministic address for +//! one causal proposition, independent of every quantity that can revise. +//! +//! ```text +//! CausalLiteral (domain, S, P, O) ← exact proposition identity, THIS module +//! │ +//! ├── world/causal evidence leaves (S3.1, orthogonal facet — +//! ├── epistemic/reasoning leaves NOT nested path depth; +//! ├── contradiction / mediator / pothole see "Evidence placement" +//! │ leaves below) +//! ▼ +//! CausalEdgeV3 local hot proxy (S3.2) +//! ▼ +//! CausalEdge64 NARS register +//! ``` +//! +//! # The one thing this module asserts +//! +//! **Identity is the component tuple. Nothing else.** For a fixed canonical +//! `domain + S + P + O` the literal is byte-identical across replay, across +//! sources, across Lance versions, and across every amount of evidence that +//! ever accumulates beneath it. Two papers and a model asserting the same +//! canonical proposition converge on ONE literal with THREE witnesses — never +//! three literals. +//! +//! What identity is NOT, stated because each has been reached for before: +//! +//! | not identity | why | where it belongs | +//! |---|---|---| +//! | CAM-PQ nearest centroid | learned, approximate, re-trainable | candidate discovery, basin search, ranking | +//! | NARS `f` / `c` | revisable evidence state | the Meta accumulator | +//! | evidence count | grows monotonically; identity must not | the leaf set | +//! | the asserting source | many sources, one proposition | witness leaves | +//! | the Lance version | history is immutable, identity is timeless | the horizon | +//! | a V3 `target` u16 | tenant-LOCAL, may be repacked | [`crate::hhtl`] resolution | +//! +//! # Why this is an address and not a packed tenant +//! +//! Operator ruling E (`docs/architecture/ARC-B-OWNERSHIP-AND-ADDRESSING-REASSESSMENT.md` +//! §4) gates every new `ValueTenant` behind one question: *is this genuinely +//! missing canonical information, or a container minted to avoid completing the +//! address transition?* This is the former, and the ruling names it as such — +//! it says the missing canonical reference "is the prerequisite", and that the +//! tenant gap and the addressing gap "are the same problem wearing two hats". +//! So `CausalLiteral` adds no bits to `CausalEdge64` and no slot to any tenant. +//! It is 8 bytes of pure address, const-asserted below so that a future edit +//! cannot quietly hang evidence off it. +//! +//! # `NiblePath`'s depth budget — a fact about ONE consumer type, not about HHTL +//! +//! **Read this section before citing it past its scope — a prior draft of this +//! module did exactly that and was retracted; see +//! `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` in `EPIPHANIES.md`.** +//! +//! [`crate::hhtl::NiblePath`] is a `u64`-backed SEQUENTIAL router path with +//! [`MAX_DEPTH`](crate::hhtl::MAX_DEPTH) = 16 nibbles, purpose-built for the +//! `subClassOf` Abstammung tree. Four `u16` components are 16 nibbles +//! **exactly**: +//! +//! ```text +//! domain 4 nibbles ┐ +//! subject 4 nibbles ├─ 16 nibbles = 64 bits = the ENTIRE NiblePath budget +//! predicate 4 │ +//! object 4 ┘ depth remaining for a NiblePath descent: 0 +//! ``` +//! +//! That is a real, measured, zero-slack fact about `NiblePath` specifically — +//! `MAX_DEPTH` is a property of one particular single-path router, not of HHTL +//! addressing as a concept. This repo has OTHER HHTL-shaped substrates that do +//! not share this ceiling because they are not modelled as sequential depth +//! descent from one root: +//! +//! - [`crate::facet::FacetCascade`] is a fixed 16-byte register +//! (`classid(4) | 6×(8:8)`) that supports SEVERAL SIMULTANEOUS +//! `ClassView`-selected readings of the same bytes (`G3D4`/`G4D3`/`G6D2`, +//! `24×i4`) — new semantic capability lands as a new *reading*, never a +//! deeper path. [`crate::tekamolo_facet`] is the shipped instance: four +//! orthogonal 256:256:256 lanes over one register, not nested depth. +//! - WordNet's real hypernym hierarchy (`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1`, +//! `probe_wordnet_44_activation.rs`) proves a full-width 4-ary HHTL fold is a +//! genuinely EXACT structural encoding of real ancestry (corr +0.494 vs +//! shuffled −0.036) — "HHTL identity" and "one `NiblePath`'s depth ceiling" +//! are not the same claim. +//! - The 24×i4 anaphora boundary (`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1`) +//! is the general pattern this module follows: a LOCAL representation +//! reaching its limit marks a TYPE boundary — switch to a different +//! sanctioned reading of the SAME address, never invent a deeper/wider path +//! to route around it. +//! +//! **What follows from the measured fact, and no more:** a `CausalLiteral`'s +//! own identity is not expressible as a STRICTLY SHORTER `NiblePath` prefix +//! while staying exact, and a `NiblePath` built by walking all four +//! components is `is_full()` — nothing can descend beneath it via THAT router. +//! This module therefore exposes [`CausalLiteral::routing_prefix`] as an +//! explicitly LOSSY, depth-truncated cohort projection into the `NiblePath` +//! tree (useful for locality/cohort queries against the existing router), and +//! never treats it as identity. `CausalLiteral` itself is the identity — an +//! 8-byte struct, addressable and hashable on its own terms, independent of +//! whether any particular router can also walk it as a path. +//! +//! # Evidence placement — OPEN, not pre-decided by this module +//! +//! A prior draft of this module concluded that because a full-depth +//! `NiblePath` walk of the literal is `is_full()`, an evidence/meta subtree +//! must therefore "ref-escape" out of address space into some structurally +//! separate mechanism. **That inference is withdrawn as a forced conclusion.** +//! The `NiblePath`-descent option is indeed closed (see above) — but the +//! TEKAMOLO/`FacetCascade` pattern above suggests a live alternative: `S3.1`'s +//! `CausalMeta`/`EpistemicMeta` may land as an ORTHOGONAL FACET/COLUMN keyed +//! by this same 8-byte [`CausalLiteral`] (same shape as "temporal/kausal/ +//! modal/lokal are four lanes over one register, not four levels of depth"), +//! rather than a disconnected side structure. This module does not build that +//! — S3.1 decides it, from measurement, when it lands. +//! +//! # Predicate meaning is NOT decided here +//! +//! HHTL supplies hierarchy, locality and exact addressing. It does not decide +//! what `CAUSES` means. A raw `u8`/`u16` ordinal is meaningless without its +//! codebook: the same integer denotes different relations under different +//! families. Resolution — `literal → tenant/ClassView → canonical codebook → +//! ResolvedPredicate`, with unknown predicates failing CLOSED and never +//! composing transitively by default — is S3.3 and lives elsewhere. This +//! module deliberately exposes no `is_transitive`, no relation class, and no +//! composition policy. + +use crate::hhtl::{NiblePath, FAN_OUT, MAX_DEPTH}; + +/// A canonical concept ordinal, resolved through a codebook UPSTREAM of this +/// module (`ogar_codebook::canonical_concept_id` and friends). Raw palette +/// integers are not concepts until a codebook says so. +pub type ConceptId = u16; + +/// A canonical semantic-domain / `ClassView` ordinal — the interpretation scope +/// under which `subject`/`predicate`/`object` are resolved. +pub type DomainId = u16; + +/// The zero-fallback sentinel, shared by every component: *not routed / not yet +/// bound*, never "concept 0". +/// +/// This mirrors the canon's zero-fallback ladder — a zero tier means *not +/// consulted*, never *compacted away*. A literal with an unbound component is +/// still a perfectly well-formed address (construction is total; an address is +/// an address), it is simply not yet fully bound — ask [`CausalLiteral::is_fully_bound`] +/// rather than inferring from the value. +pub const UNBOUND: u16 = 0; + +/// The exact, absolute identity of ONE causal proposition. +/// +/// Equality is component equality — there is no hash, no learned assignment, +/// and no tolerance, so two distinct canonical tuples **cannot** collide and +/// one canonical tuple **cannot** produce two literals. Both directions are +/// swept in the tests rather than asserted here. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct CausalLiteral { + domain: DomainId, + subject: ConceptId, + predicate: ConceptId, + object: ConceptId, +} + +// 8 bytes of PURE ADDRESS. This assert is the structural guard behind the +// module's headline claim: identity cannot depend on evidence, confidence, a +// source id, or a Lance version, because there is nowhere to put them. Adding +// such a field is a compile error, not a review catch. +const _: () = assert!(core::mem::size_of::() == 8); + +/// Nibbles consumed by one `u16` component of the address. +pub const NIBBLES_PER_COMPONENT: u8 = 4; +/// Nibbles consumed by the full `domain·S·P·O` address — exactly [`MAX_DEPTH`]. +pub const LITERAL_PATH_NIBBLES: u8 = 4 * NIBBLES_PER_COMPONENT; + +// The measured NiblePath-depth fact, compiled in: the full literal path is +// exactly the whole NiblePath budget. If MAX_DEPTH ever widens, this fails +// and the "NiblePath specifically, not HHTL in general" scoping in the module +// doc must be re-derived rather than silently inherited. +const _: () = assert!(LITERAL_PATH_NIBBLES == MAX_DEPTH); + +impl CausalLiteral { + /// Address a canonical proposition. Total by construction — every `u16` + /// quadruple is a valid address, including partly-[`UNBOUND`] ones. + /// + /// Binding *meaning* to the ordinals is a separate, later act (S3.3); this + /// only says *which* proposition is being spoken about. + #[must_use] + pub const fn new( + domain: DomainId, + subject: ConceptId, + predicate: ConceptId, + object: ConceptId, + ) -> Self { + Self { + domain, + subject, + predicate, + object, + } + } + + /// The semantic domain / `ClassView` scope. + #[must_use] + pub const fn domain(self) -> DomainId { + self.domain + } + /// The canonical subject ordinal. + #[must_use] + pub const fn subject(self) -> ConceptId { + self.subject + } + /// The canonical predicate ordinal. Its *meaning* resolves through the + /// domain's codebook (S3.3), never from the integer alone. + #[must_use] + pub const fn predicate(self) -> ConceptId { + self.predicate + } + /// The canonical object ordinal. + #[must_use] + pub const fn object(self) -> ConceptId { + self.object + } + + /// Is every component bound (non-[`UNBOUND`])? + /// + /// A partly-unbound literal is a legal address but not yet a complete + /// proposition; callers that require a complete one should gate on this + /// rather than test components against 0 by hand. + #[must_use] + pub const fn is_fully_bound(self) -> bool { + self.domain != UNBOUND + && self.subject != UNBOUND + && self.predicate != UNBOUND + && self.object != UNBOUND + } + + /// The packed exact identity, root-first coarse→fine: + /// `domain << 48 | subject << 32 | predicate << 16 | object`. + /// + /// Injective over the component space by construction (four disjoint 16-bit + /// fields tiling a `u64` exactly), so it is safe as a map key. Swept in + /// `packed_identity_is_injective` rather than trusted. + #[must_use] + pub const fn as_u64(self) -> u64 { + ((self.domain as u64) << 48) + | ((self.subject as u64) << 32) + | ((self.predicate as u64) << 16) + | (self.object as u64) + } + + /// Inverse of [`as_u64`](Self::as_u64) — total, and exactly reversible. + #[must_use] + pub const fn from_u64(v: u64) -> Self { + Self { + domain: (v >> 48) as u16, + subject: (v >> 32) as u16, + predicate: (v >> 16) as u16, + object: v as u16, + } + } + + /// The 8-byte little-endian persisted form of [`as_u64`](Self::as_u64). + /// + /// This is the identity that goes to storage: canonical ordinals, never + /// runtime strings. A literal minted from the same canonical tuple in a + /// different process, a different Lance version, or a different source + /// serializes to the same eight bytes. + #[must_use] + pub const fn to_le_bytes(self) -> [u8; 8] { + self.as_u64().to_le_bytes() + } + + /// Inverse of [`to_le_bytes`](Self::to_le_bytes). + #[must_use] + pub const fn from_le_bytes(b: [u8; 8]) -> Self { + Self::from_u64(u64::from_le_bytes(b)) + } + + /// A **routing / cohort** projection into the `NiblePath` Abstammung tree + /// — the first `depth` nibbles of the root-first `domain·S·P·O` sequence. + /// + /// This is ONE way to relate a literal to the existing `subClassOf` + /// router; it is not "the HHTL form" of the literal, and `CausalLiteral` + /// itself (not this projection) is the identity — see the module doc's + /// "NiblePath's depth budget" section for why the two are not the same + /// claim. + /// + /// # This is NOT identity + /// + /// At `depth < LITERAL_PATH_NIBBLES` the projection is **many-to-one** by + /// design: that is what makes it useful as a deterministic cohort slice + /// (all literals in a domain; all literals sharing a domain and subject). + /// Two different propositions genuinely share a prefix, and + /// `routing_prefix_is_not_identity` proves it on real values so the + /// projection can never be quietly promoted into an equality test — the + /// exact confusion Stage-3 falsifiers #3 and #19 name. + /// + /// At `depth == LITERAL_PATH_NIBBLES` it is injective — and simultaneously + /// `is_full()` for the `NiblePath` router, so nothing can descend beneath + /// it VIA THAT ROUTER. That is a fact about `NiblePath`'s single-path + /// design, not a ceiling on where evidence/meta state may live (see + /// "Evidence placement" above). + /// + /// `depth` saturates at [`LITERAL_PATH_NIBBLES`]. + #[must_use] + pub fn routing_prefix(self, depth: u8) -> NiblePath { + let depth = depth.min(LITERAL_PATH_NIBBLES); + let packed = self.as_u64(); + let mut path = NiblePath::EMPTY; + for i in 0..depth { + // root-first: nibble 0 is the most significant of the 16. + let shift = 4 * (LITERAL_PATH_NIBBLES - 1 - i) as u32; + let nibble = ((packed >> shift) & 0xF) as u8; + debug_assert!(nibble < FAN_OUT, "a 4-bit value is always < FAN_OUT"); + path = if i == 0 { + NiblePath::root(nibble) + } else { + path.child(nibble) + }; + } + path + } + + /// The full-depth routing projection — exact, and exactly `is_full()` for + /// the `NiblePath` router. + /// + /// Provided for completeness and for the budget falsifier; prefer + /// [`as_u64`](Self::as_u64) / [`to_le_bytes`](Self::to_le_bytes) as the + /// identity, and a SHORTER [`routing_prefix`](Self::routing_prefix) as the + /// cohort. A caller reaching for this as a `NiblePath` tree root is about + /// to discover it cannot descend further via that router. + #[must_use] + pub fn full_path(self) -> NiblePath { + self.routing_prefix(LITERAL_PATH_NIBBLES) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::{HashMap, HashSet}; + + /// A deliberately varied sweep: every component takes low, mid, high and + /// boundary values, and the values are REUSED across positions so a + /// field-order bug shows up as a collision rather than hiding. + fn sweep() -> Vec { + let vals: [u16; 6] = [0, 1, 2, 0x00FF, 0x8000, u16::MAX]; + let mut out = Vec::new(); + for &d in &vals { + for &s in &vals { + for &p in &vals { + for &o in &vals { + out.push(CausalLiteral::new(d, s, p, o)); + } + } + } + } + out + } + + /// FALSIFIER #1 — the same canonical tuple always produces the same + /// literal, and FALSIFIER #2 — distinct tuples never collide. + /// + /// Both directions on one sweep of 1,296 tuples. The reused value set means + /// a swapped field order (e.g. predicate and object transposed) collapses + /// distinct tuples onto one `u64` and fails the injectivity half. + #[test] + fn packed_identity_is_injective() { + let all = sweep(); + // anti-vacuity: the sweep must actually contain distinct tuples that + // differ in ONLY one position, or injectivity is trivially satisfiable. + assert!(all.len() >= 1000, "sweep too small: {}", all.len()); + + let mut seen: HashMap = HashMap::new(); + for lit in &all { + // determinism: rebuilding from the same components is identical + let again = + CausalLiteral::new(lit.domain(), lit.subject(), lit.predicate(), lit.object()); + assert_eq!(*lit, again, "same canonical tuple produced two literals"); + assert_eq!( + lit.as_u64(), + again.as_u64(), + "identity is not deterministic" + ); + + if let Some(prev) = seen.insert(lit.as_u64(), *lit) { + assert_eq!( + prev, *lit, + "two DISTINCT canonical tuples collided on one identity" + ); + } + } + assert_eq!(seen.len(), all.len(), "identity is not injective"); + } + + /// FALSIFIER #2, sharpened — changing ONLY the predicate must change the + /// literal. A "causes" and a "treated_with" between the same two concepts + /// are different propositions, and the address has to say so. + /// + /// Two-sided: the paired half proves that changing nothing changes nothing, + /// so this cannot pass by an implementation that simply returns fresh + /// values. + #[test] + fn changing_only_the_predicate_changes_the_literal() { + let causes = CausalLiteral::new(7, 100, 42, 200); + let treated_with = CausalLiteral::new(7, 100, 43, 200); + assert_ne!(causes, treated_with, "distinct predicates collided"); + assert_ne!(causes.as_u64(), treated_with.as_u64()); + assert_ne!(causes.to_le_bytes(), treated_with.to_le_bytes()); + // …and the silence half + let same = CausalLiteral::new(7, 100, 42, 200); + assert_eq!(causes, same, "identical tuples must be one literal"); + assert_eq!(causes.to_le_bytes(), same.to_le_bytes()); + } + + /// FALSIFIER #4 — many sources, ONE proposition. + /// + /// Three "sources" independently address the same canonical tuple. They + /// must converge on a single identity: one literal, three witnesses, never + /// three literals. Modelled as three separately-constructed values whose + /// set collapses to size one. + #[test] + fn three_sources_asserting_the_same_proposition_mint_one_literal() { + let paper_1 = CausalLiteral::new(3, 8_001, 42, 9_002); + let paper_2 = CausalLiteral::new(3, 8_001, 42, 9_002); + let model_3 = CausalLiteral::from_le_bytes(paper_1.to_le_bytes()); + let distinct: HashSet = [paper_1, paper_2, model_3] + .iter() + .map(|l| l.as_u64()) + .collect(); + assert_eq!( + distinct.len(), + 1, + "same proposition minted several literals" + ); + // anti-vacuity: a genuinely different proposition still separates + let other = CausalLiteral::new(3, 8_001, 42, 9_003); + assert!(!distinct.contains(&other.as_u64())); + } + + /// Exact reversibility — the address resolves back to its components, in + /// both serialized forms, over the whole sweep. + #[test] + fn identity_round_trips_exactly_in_both_forms() { + for lit in sweep() { + assert_eq!(CausalLiteral::from_u64(lit.as_u64()), lit, "u64 round trip"); + assert_eq!( + CausalLiteral::from_le_bytes(lit.to_le_bytes()), + lit, + "le-bytes round trip" + ); + // and the components survive individually, not merely in aggregate + let r = CausalLiteral::from_le_bytes(lit.to_le_bytes()); + assert_eq!( + (r.domain(), r.subject(), r.predicate(), r.object()), + (lit.domain(), lit.subject(), lit.predicate(), lit.object()) + ); + } + } + + /// Field isolation (the layout discipline `I-LEGACY-API-FEATURE-GATED` + /// prescribes for any new packing): each component, changed from a FULLY + /// NON-ZERO baseline, moves its own accessor and leaves the other three + /// bit-identical. A zeroed baseline can hide a field that ORs into a + /// neighbour's set bits, so the baseline is deliberately all-`0xABCD`. + #[test] + fn component_isolation_matrix() { + const B: u16 = 0xABCD; + let base = CausalLiteral::new(B, B, B, B); + let probe: u16 = 0x1234; + + let cases: [(&str, CausalLiteral); 4] = [ + ("domain", CausalLiteral::new(probe, B, B, B)), + ("subject", CausalLiteral::new(B, probe, B, B)), + ("predicate", CausalLiteral::new(B, B, probe, B)), + ("object", CausalLiteral::new(B, B, B, probe)), + ]; + for (name, got) in cases { + assert_ne!(got, base, "{name}: changing it changed nothing"); + let moved = [ + got.domain() != base.domain(), + got.subject() != base.subject(), + got.predicate() != base.predicate(), + got.object() != base.object(), + ]; + assert_eq!( + moved.iter().filter(|m| **m).count(), + 1, + "{name}: exactly one component may move, got {moved:?}" + ); + assert!( + match name { + "domain" => moved[0], + "subject" => moved[1], + "predicate" => moved[2], + _ => moved[3], + }, + "{name}: the wrong component moved" + ); + } + } + + /// THE ANTI-VACUITY TWIN, and the point of the whole split: the routing + /// prefix is a **cohort**, not an identity. + /// + /// Guards Stage-3 falsifiers #3 and #19 at their shared root — mistaking an + /// approximate/positional projection for exact proposition identity. If a + /// future edit made `routing_prefix` injective at shallow depth (say by + /// folding all four components in), this test goes red and forces the + /// author to say so out loud. + #[test] + fn routing_prefix_is_not_identity() { + // same domain + subject + predicate, different object + let a = CausalLiteral::new(0x1111, 0x2222, 0x3333, 0x4444); + let b = CausalLiteral::new(0x1111, 0x2222, 0x3333, 0x5555); + assert_ne!(a, b, "fixture is degenerate: the two literals are equal"); + + // 12 nibbles = domain+subject+predicate — they MUST share it + assert_eq!( + a.routing_prefix(12), + b.routing_prefix(12), + "the prefix failed to group two literals that share domain+S+P — \ + it is not usable as a cohort" + ); + // 4 nibbles = the domain cohort + assert_eq!(a.routing_prefix(4), b.routing_prefix(4)); + + // …and the paired half: at full depth it discriminates, so the + // projection is lossy by DEPTH rather than simply broken. + assert_ne!( + a.full_path(), + b.full_path(), + "the full-depth path failed to separate distinct literals" + ); + } + + /// The `NiblePath` depth-budget finding, pinned as a guard rather than + /// left in prose: the full-depth path is exactly `MAX_DEPTH`, therefore + /// `is_full()` for that router — a fact about `NiblePath`'s single-path + /// design, scoped exactly to that type (see the module doc). + /// + /// If `MAX_DEPTH` ever widens this fails alongside the `const _` assert, and + /// the module-doc scoping gets re-derived instead of silently inherited. + #[test] + fn the_full_literal_path_exhausts_the_niblepath_budget() { + let lit = CausalLiteral::new(0x1234, 0x5678, 0x9ABC, 0xDEF0); + let full = lit.full_path(); + assert_eq!(full.depth(), MAX_DEPTH, "full path is not the whole budget"); + assert!(full.is_full(), "full path must report is_full"); + // the operational consequence: a descent is a silent no-op… + assert_eq!(full.child(1), full, "descent past MAX_DEPTH is not a no-op"); + // …and the explicit form refuses instead of colliding. + assert!( + full.try_child(1).is_none(), + "try_child must refuse at the ceiling" + ); + // a SHORTER prefix, by contrast, still has room — proving the ceiling + // is a property of the full-depth NiblePath walk, not of the literal + // or of HHTL addressing in general. + assert!(!lit.routing_prefix(12).is_full()); + } + + /// The prefix is monotone and deterministic: extending the depth extends + /// the path, and the same literal always yields the same prefix. + #[test] + fn routing_prefix_is_deterministic_and_monotone_in_depth() { + let lit = CausalLiteral::new(0x0102, 0x0304, 0x0506, 0x0708); + for d in 0..=LITERAL_PATH_NIBBLES { + assert_eq!( + lit.routing_prefix(d), + lit.routing_prefix(d), + "prefix is not deterministic at depth {d}" + ); + assert_eq!( + lit.routing_prefix(d).depth(), + d, + "prefix depth mismatch at {d}" + ); + } + // saturates rather than wrapping or panicking + assert_eq!( + lit.routing_prefix(200), + lit.full_path(), + "depth must saturate at the budget" + ); + } + + /// Zero-fallback: an unbound component is a legal address that is visibly + /// incomplete — callers ask, rather than testing components against 0. + #[test] + fn unbound_components_are_addressable_but_not_fully_bound() { + assert!(!CausalLiteral::default().is_fully_bound()); + assert!(!CausalLiteral::new(1, 1, UNBOUND, 1).is_fully_bound()); + assert!(CausalLiteral::new(1, 1, 1, 1).is_fully_bound()); + // an unbound-predicate literal is still a DISTINCT address, not a + // sentinel that collapses onto something else + assert_ne!( + CausalLiteral::new(1, 1, UNBOUND, 1), + CausalLiteral::new(1, 1, 1, 1) + ); + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 02d2a4451..e4e46a819 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -55,6 +55,7 @@ pub mod callcenter; pub mod cam; pub mod canonical_node; pub mod causal_audit; +pub mod causal_literal; pub mod causal_witness; pub mod class_view; /// D-V3-W6a — classid adoption-scan counting logic (`ClassidForm`, From e4a3cbae1ded1f9ca913193528880e4f1f33657b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 15:40:39 +0000 Subject: [PATCH 2/5] S3.0 closed as NOT-NEEDED: withdraw the address type, keep the retraction Operator stopped the previous commit before merge. It retracted #973's overclaim and then committed four of its own. All four are corrected here by WITHDRAWING the type entirely; this commit adds no code. 1. WRONG UNIVERSALITY. `CausalLiteral`'s own test asserted TREATED_WITH, proving the structure is generic. ASSOCIATED_WITH / TREATED_WITH / CAUSES / MEDIATES / PART_OF are exact predicate identities over one generic literal substrate; causality is a predicate family, never universal identity. 2. 4xu16 IS NOT ABSOLUTE. MedCare-rs docs/ONTOLOGY_BAKE_STATE.md:182 states it plainly: real OBO ids run past u16 (MONDO:0700092 = 700,092). A u16 subject would have silently mis-addressed the largest ontology in the bake. The substrate already solved this with the V3 rail. 3. THE WORDNET CLAIM WAS INFLATED. #875 was cited as proving an EXACT structural encoding. Its own W5 gate reports 256/256 cells at occupancy median 255 over 65,292 leaves -- many-to-one by hundreds. What it measured is a taxonomy-informed HHTL locality/search prior (corr +0.494 vs -0.036 shuffled; 24.71x out-of-cell band recall), never an identity encoding. Citing a measured result past its own Boundaries section is the #973 failure mode with the sign flipped, and it is easier to commit while correcting someone else. 4. routing_prefix() WAS UNEARNED. A lexicographic prefix over concatenated ordinals was labelled an HHTL locality/cohort projection with no consumer and no measurement that it preserves HHTL semantic locality. The closing question, per the operator's rule: WHAT EXACT INFORMATION CANNOT BE EXPRESSED BY THE ADDRESSING THAT ALREADY EXISTS? Nothing demonstrable. identity_quad::IdentityQuad (operator-ratified 2026-08-17) already carries four exact external identities as 4 x u24 in one 96-bit V3 facet behind a classid(4), refuse-don't-truncate, resolving the crosswalk once at bake time. It strictly dominates the withdrawn type. No falsifier => no new type. S3.0 is closed as NOT-NEEDED rather than filled because the plan had a slot. Separately surfaced, NOT fixed here: ClassId = u16 is near-exhausted for RELATIONS (MedCare-rs CLAUDE.md #10) -- a classid-mint capacity question for the operator, not something a literal type addresses. The ladder matrix says the empty column is not ADDRESS but HYDRATED SoA for Bible/Rosetta; the real next work is hydrate epistemic/causal nodes -> reason over them -> think about the reasoning, gated by the DisMech oracle experiment. Board: EPIPHANIES x4 (E-S3-0-NEEDED-NO-NEW-ADDRESS-1, E-WORDNET-IS-A-LOCALITY-PRIOR-NOT-AN-IDENTITY-ENCODING-1, E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1, E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1 with both instances) + LATEST_STATE + STATUS_BOARD re-scope + the full audit. Gates: zero code diff vs main; lance-graph-contract back to its 1171 baseline. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AZLe87ssKXGuXFbrmHdGnG --- .claude/board/EPIPHANIES.md | 284 ++++++--- .claude/board/LATEST_STATE.md | 112 ++-- .claude/board/STATUS_BOARD.md | 36 +- ...26-08-20-s3-0-cold-start-recovery-audit.md | 364 ++++++----- .../src/causal_literal.rs | 599 ------------------ crates/lance-graph-contract/src/lib.rs | 1 - 6 files changed, 470 insertions(+), 926 deletions(-) delete mode 100644 crates/lance-graph-contract/src/causal_literal.rs diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index ffca8a572..7e15b06db 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,96 +1,206 @@ -## 2026-08-20 — E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1 +## 2026-08-20 — E-S3-0-NEEDED-NO-NEW-ADDRESS-1 — the Stage-3 "S3.0 address" slot is closed as NOT-NEEDED; `IdentityQuad` already carries an exact four-component identity, at u24, inside the sanctioned V3 facet + +**Status:** FINDING (operator-directed audit; measured against merged code). +**Confidence:** High — every claim below is a read of shipped source or a +merged measurement, not a derivation. + +**The question that closes it** (operator's rule, stated during this session): +*"WHAT EXACT INFORMATION CANNOT BE EXPRESSED BY THE ADDRESSING THAT ALREADY +EXISTS?"* — and if there is no concrete falsifier showing existing addressing +insufficient, **no new absolute-address type is minted.** + +**Answer: nothing that could be demonstrated.** `identity_quad::IdentityQuad` +(operator-RATIFIED 2026-08-17, `ISS-IDENTITY-QUAD-WIDE-CARVING-HOME`) already +materializes **four external identity spaces as `4 × u24` contiguous in ONE +96-bit V3 facet payload** behind a `classid(4)`, via +`LegacyOutlier::WideTriple`. It refuses rather than truncates +(`QuadError::OrdinalTooLarge`, `MAX_ORDINAL = 2^24 − 2`); its codebooks refuse +rather than saturate (`CodebookError::TooLarge`). Its stated purpose is to +resolve a crosswalk ONCE at bake time so a read becomes a fixed-offset register +read — no join, no crosswalk walk. + +**A proposed `4 × u16` literal type was WITHDRAWN, on two independent grounds:** + +1. **`u16` cannot hold a real ontology identity.** MedCare-rs + `docs/ONTOLOGY_BAKE_STATE.md`:182 states it directly — *"real OBO ids run + past `u16` (MONDO:0700092 = 700,092) and one V3 field cannot hold that."* + The substrate already solved this with the V3 rail + (`family:identity = (num >> 16, num & 0xFFFF)`, read via + `obo_store::row_addr`). A u16 subject would have silently mis-addressed the + largest ontology in the bake. Calling such a tuple "absolute identity" was + an overclaim. +2. **`CausalLiteral` was the wrong universality.** Its own test asserted + `TREATED_WITH` — proving the structure is GENERIC. `ASSOCIATED_WITH` / + `TREATED_WITH` / `CAUSES` / `MEDIATES` / `PART_OF` are exact predicate + identities over one generic literal substrate. **Causality is a predicate + family / qualification, never universal identity.** Had a primitive been + needed it would have been `ExactLiteralAddr(D,S,P,O)` — but per the rule + above, none was. + +**Sibling absolute-address surfaces already merged**, for a future session's +map: `ogar_elk::ClassAddr` (`classid: u32 + identity: u32`, explicitly a +pre-bake **join key**, *"not an ABI address, and deliberately not documented as +one"*), `canonical_node::NodeGuid` + the HHTL cascade, and the V3 rail above. + +**The genuinely open addressing gap is a DIFFERENT one, and is not fixed by a +literal type:** `ClassId = u16` (`class_view.rs:54`) is near-exhausted for +RELATIONS — MedCare-rs `CLAUDE.md` commitment #10: *"cannot address a relation +— 11 prefixes, 8 of 280 ids over the ceiling"*, echoed in +`RAIL_OFFENE_POSTEN.md`. That is a **classid-mint capacity** question owned by +OGAR/lance-graph, to be raised with the operator in session. + +**What the ladder says the real work is** (operator-requested matrix): + +| | ADDRESS | HYDRATED SoA | TRAVERSAL | +|---|---|---|---| +| Bible / Rosetta | yes | **NO** | partial / context | +| OSM | yes | yes | overlay / junction | +| MedCare ontology | yes | yes | **YES, Stage 1** | +| DisMech oracle | source | structured | causal oracle | + +**The empty column is not ADDRESS.** The missing work is *hydrate epistemic / +causal nodes → reason over them → think about the reasoning*. Next target is +the DisMech oracle experiment: hide known mechanism intermediates, hydrate the +addressed ontology neighbourhood, let NARS/recipes recover candidates, compare +against DisMech truth (`dismech-rs` `graph::build_causal_graph`, falsified at +1,995 diseases / 33,458 edges; 1,903 committed `pathographs/MONDO_*.json` as +ground truth). + +**Also withdrawn with the type: `routing_prefix()`.** Concatenating D/S/P/O +nibbles into a `NiblePath` yields deterministic **lexicographic** prefixes. It +was labelled an "HHTL locality / cohort projection" with no consumer and no +measurement establishing that it preserves HHTL *semantic* locality — +`NiblePath` is built for the `subClassOf` Abstammung tree, where a prefix is an +ancestry claim; a lexicographic prefix over concatenated ordinals carries no +such guarantee. If it returns it needs a real consumer and a measurement first. + +**Cross-ref:** `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` (below), +`E-WORDNET-IS-A-LOCALITY-PRIOR-NOT-AN-IDENTITY-ENCODING-1` (below), PR #973 +(closed unmerged), `.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md`. + +--- + +## 2026-08-20 — E-WORDNET-IS-A-LOCALITY-PRIOR-NOT-AN-IDENTITY-ENCODING-1 — #875 measured a taxonomy-informed HHTL *search prior*; it did NOT prove an injective WordNet address, and a session citing it as one was corrected + +**Status:** CORRECTION (operator-caught, 2026-08-20). **Confidence:** High — +#875's own W5 numbers settle it. + +**The overclaim.** A recovery session (this one) cited +`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1` as proving *"a full-width 4-ary +HHTL fold of real WordNet ancestry is an EXACT structural encoding"*, and used +that as a counterexample licensing exact HHTL literal identity. **False.** + +**#875's own W5 gate says so:** *256/256 cells used, occupancy min 29 / median +255 / max 1270* over **65,292 addressed leaves**. That is ~255 leaves per cell. +The fold is **many-to-one by a factor of hundreds** — not injective, not an +identity encoding, and never claimed to be one in #875's own text. + +**What #875 DID measure**, and it is a strong result on its own terms: a +**deterministic, taxonomy-informed HHTL locality / search prior.** +corr(shared address levels, LCA depth) **+0.494** real vs **−0.036** shuffled; +out-of-cell band recall **0.763 vs 0.031 random = 24.71×**; a **2.47-hop** +sub-nibble distinction the 16-ary router cannot address. Explicitly a +*discriminating* prior that does NOT cover everything — #875 dropped its own +cover guard as inert for exactly that reason, and its Boundaries section +already warns that *"a concept with two genuine parents is representable at +only one address"* (first-`@`-parent tree projection of a DAG). + +**Therefore the Bible/Rosetta + WordNet composition reads:** +`frozen verse identity × quasi-absolute taxonomy-informed semantic coordinate × +witness / qualia planes` — **not** "256 HHTL cells uniquely identify a lexicon." + +**The transferable lesson, and it is the same shape as the one below.** The +retraction of #973 was correct; the replacement reached for the nearest +merged-and-measured result to license the opposite conclusion and **inflated +what that result said** in the process. A counterexample cited past its own +measured Boundaries section is not a counterexample — it is the original +failure mode with the sign flipped. Read the gate table, not the headline. + +**Cross-ref:** `E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1` (unchanged; its +own Boundaries section was always correct), `E-S3-0-NEEDED-NO-NEW-ADDRESS-1`. + +--- + +## 2026-08-20 — E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1 — retracts #973's `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` -**Status:** FINDING (measured + corrected; supersedes and retracts -`E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1`, PR #973, which was closed -unmerged before landing). **Confidence:** High — the arithmetic (16 nibbles = -`NiblePath::MAX_DEPTH`) is unchanged and correct; what changes is the scope of -what it is evidence FOR. +**Status:** FINDING (retraction; PR #973 closed unmerged, so the retracted +entry never landed on `main` — recorded here so the reasoning is not repeated). +**Confidence:** High. **The retracted claim.** #973 measured, correctly, that a -`domain·subject·predicate·object` literal at `u16` each is exactly 16 nibbles -— the ENTIRE budget of [`hhtl::NiblePath`], a single sequential `u64`-backed -router path purpose-built for the `subClassOf` Abstammung tree. From that true -local fact it concluded that identity and an evidence subtree "cannot both be -one `NiblePath`" and therefore evidence must **ref-escape** out of address -space into a structurally separate mechanism — framed under the title -"the literal cannot live in the path it roots". That title, read on its own, -overclaims: it reads as a statement about HHTL addressing in general, not -about one particular depth-limited path type. - -**Why the retraction, with receipts already in this repository.** Three -already-MEASURED counterexamples show the general shape #973 needed and did -not reach for: - -1. **`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1`** (PR #875/#876, MERGED) - — a full-width 4-ary HHTL fold of real WordNet ancestry is an EXACT - structural encoding (corr +0.494 real vs −0.036 shuffled), not a lossy - hash. "HHTL identity" and "one `NiblePath`'s 16-nibble ceiling" are - different claims; #973 conflated them. -2. **`tekamolo_facet.rs` / `facet::FacetCascade`** (PR #839/#844, MERGED, - live code) — a 16-byte content-blind register supports SEVERAL - SIMULTANEOUS `ClassView`-selected readings of the SAME bytes - (`G3D4`/`G4D3`/`G6D2`/`24×i4`). New semantic capability (TEKAMOLO's four - orthogonal Temporal/Kausal/Modal/Lokal lanes) lands as a new READING, never - a deeper path. This is the concrete precedent for where an evidence/meta - facet over `CausalLiteral` should be sought first, instead of ref-escaping. -3. **`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1`** (measured, 7,657 real German - relative clauses) — the general pattern: a LOCAL fixed representation - (there, `i4`, `-8..+7`) exhausting cleanly at a real boundary marks a TYPE - boundary, and the sanctioned response is to switch to a DIFFERENT reading - of the SAME address register — never to declare the substrate exhausted or - to invent an unbounded escape hatch. - -**What survives, unchanged.** The measured fact itself: `LITERAL_PATH_NIBBLES -== hhtl::MAX_DEPTH` (`const _`-asserted in `causal_literal.rs`), and -`NiblePath::routing_prefix(16)` is `is_full()` — nothing can descend further -via THAT router. `CausalLiteral { domain, subject, predicate, object }` as a -free-standing 8-byte exact-identity struct (four `u16`, component equality, -no CAM-PQ/evidence/source/version field possible by construction) is -reintroduced verbatim from #973 — the struct and its nine tests were correct; -only the surrounding architectural inference was overclaimed. - -**What is now OPEN rather than pre-decided.** Where S3.1's -`CausalMeta`/`EpistemicMeta` evidence tree lands is NOT settled by this -finding. The leading candidate, per (2) above, is an orthogonal facet/column -keyed by `CausalLiteral` — not a "ref-escape" forced by NiblePath's ceiling. -S3.1 decides this from measurement when it lands, not from this PR. - -**The process lesson — see the companion entry below -(`E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1`).** +`domain·subject·predicate·object` at `u16` each is exactly 16 nibbles — the +entire budget of `hhtl::NiblePath`, a single sequential `u64`-backed router +path built for the `subClassOf` Abstammung tree. From that true LOCAL fact it +concluded, under a title that reads as a global claim, that exact identity +cannot live in HHTL addressing and evidence must **ref-escape** out of address +space into a structurally separate mechanism. + +**Why the inference is not licensed.** `MAX_DEPTH` is a property of ONE +depth-limited single-path router, not of HHTL addressing as a concept. The +substrate carries other HHTL-adjacent shapes that are not sequential descent: +`facet::FacetCascade` is a fixed 16-byte register supporting SEVERAL +SIMULTANEOUS ClassView-selected readings of the same bytes +(`G3D4`/`G4D3`/`G6D2`/`24×i4`), with `tekamolo_facet` as the shipped instance — +four orthogonal 256:256:256 lanes over one register, not nested depth. And +`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1` (7,657 real German relative clauses, +88.01% in-window) is the general pattern: a local representation exhausting +cleanly marks a TYPE boundary — switch to another sanctioned reading of the +same address, never widen the pointer or declare the substrate exhausted. + +**What survives.** The arithmetic, scoped: `4 × u16 == 16 nibbles == +NiblePath::MAX_DEPTH`, and such a path is `is_full()` for that router. + +**What does NOT follow from it**, and was separately settled by +`E-S3-0-NEEDED-NO-NEW-ADDRESS-1` above: that a new address type was needed at +all. It was not. --- -## 2026-08-20 — E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1 - -**Status:** RULING (operator, cold-start recovery session following PR #973's -closure). **Confidence:** High — process rule, not a technical claim. - -**The failure mode, named exactly.** A locally correct arithmetic derivation -about ONE representation (`NiblePath`'s 16-nibble depth ceiling) was promoted, -via a dramatic board-finding title, into an implied GLOBAL substrate -conclusion (exact causal-literal identity cannot live in HHTL addressing; -evidence must structurally escape address space) — while three -already-measured counterexamples in the SAME repository -(`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1`, the shipped -`tekamolo_facet.rs`/`FacetCascade` orthogonal-reading pattern, -`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1`) demonstrate the opposite pattern -working. None of the three were consulted before the finding was written. - -**The rule going forward, for this and every future session.** Before any -board finding declares that the substrate "cannot" do something architectural: -search `EPIPHANIES.md` and the relevant `.claude/knowledge/`/`docs/` for -existing measurements and counterexamples FIRST. A new local proof whose -premise silently omits an already-established substrate behaviour is not a -discovery — it is exactly the shape of error this entry exists to name. Use -explicit labels (`[MERGED]` / `[MEASURED]` / `[RULING]` / `[PROPOSED]` / -`[REJECTED]`) so the provenance of every claim stays legible, and never blur -"true fact about one type" into "true fact about the substrate" without -saying so in the same sentence. - -**Cross-ref:** `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` (the -technical retraction this ruling explains), PR #973 (closed unmerged, the -originating incident), `.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md` -(the full audit). +## 2026-08-20 — E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1 — and its twin: a counterexample cited past its own Boundaries section is the same failure with the sign flipped + +**Status:** RULING (operator, cold-start recovery session after #973's +closure). **Confidence:** High — process rule, with two instances measured in +ONE session. + +**Instance 1 (#973).** A locally correct derivation about one representation +(`NiblePath`'s 16-nibble ceiling) was promoted, via a dramatic finding title, +into an implied GLOBAL substrate conclusion — while three already-measured +counterexamples in the same repository showed the opposite pattern working. +None were consulted before the finding was written. + +**Instance 2 (the recovery PR itself, caught by the operator).** The session +correcting instance 1 then (a) cited #875 as proving an *exact* WordNet +encoding when its own W5 gate reports ~255 leaves per cell, (b) called a +`4 × u16` tuple "absolute identity" when a merged doc states plainly that real +OBO ids exceed `u16`, (c) named a generic `(D,S,P,O)` structure `CausalLiteral` +while its own test used `TREATED_WITH`, and (d) called a lexicographic prefix an +"HHTL locality projection" with no consumer and no measurement. **Four unearned +claims inside the PR whose entire purpose was retracting one.** + +**The rules that follow, for this and every future session:** + +1. Before declaring the substrate "cannot" do something architectural, search + `EPIPHANIES.md` and the relevant `.claude/knowledge/` / `docs/` for existing + measurements FIRST. A local proof whose premise omits established substrate + behaviour is not a discovery. +2. **A counterexample must be read to its Boundaries section, not its + headline.** Citing a measured result past what it measured is the same + error, inverted — and it is *easier* to commit while correcting someone + else, because the counterexample feels like it is on your side. +3. Before minting any new absolute-address type, answer in writing: *what exact + information cannot be expressed by the addressing that already exists?* No + concrete falsifier ⇒ no new type. A slot in a plan is not a falsifier. +4. Name a type for what it structurally IS, not for the first use case that + motivated it. If a test can substitute a non-causal predicate and the type + still works, "Causal" does not belong in the name. +5. Use explicit `[MERGED]` / `[MEASURED]` / `[RULING]` / `[PROPOSED]` / + `[REJECTED]` labels, and never blur "true of one type" into "true of the + substrate" without saying so in the same sentence. + +**Cross-ref:** `E-S3-0-NEEDED-NO-NEW-ADDRESS-1`, +`E-WORDNET-IS-A-LOCALITY-PRIOR-NOT-AN-IDENTITY-ENCODING-1`, +`E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1`, PR #973 (closed unmerged), +`.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md`. --- diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index c5c228a73..d49a9860f 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,48 +1,70 @@ -## 2026-08-20 — branch `claude/lance-graph-stage-3-recovery-2wrdbd` — S3.0 corrected: the exact causal-literal address (retracts #973's overclaim, salvages its code) - -### Current Contract Inventory — 1 new zero-dep contract type, no packed tenant - -- **`lance_graph_contract::causal_literal`** (new module, reintroduces #973's - struct verbatim; rewrites the surrounding claim): - - **`CausalLiteral { domain, subject, predicate, object }`** — four `u16` - canonical ordinals, **8 bytes of pure address**, `const _`-asserted at 8. - - `new` / `domain` / `subject` / `predicate` / `object` / `is_fully_bound` - - `as_u64` / `from_u64` / `to_le_bytes` / `from_le_bytes` — exact reversible - identity - - `routing_prefix(depth)` / `full_path()` — a **lossy** `NiblePath` cohort - projection, explicitly scoped as one property of `NiblePath` specifically, - never as "the HHTL form" of the literal - - `ConceptId` / `DomainId` / `UNBOUND` / `NIBBLES_PER_COMPONENT` / - `LITERAL_PATH_NIBBLES` (`const _`-asserted `== hhtl::MAX_DEPTH`) -- **No `ValueTenant`, no CE64 bit added, no V3 reserved byte spent, no - `ENVELOPE_LAYOUT_VERSION` bump.** Same Ruling-E gate #973 satisfied. -- **Corrected vs #973:** the module doc no longer claims identity+evidence - "cannot both be HHTL" — it names `NiblePath`'s 16-nibble ceiling as a fact - about ONE router type, cross-references the three counterexamples - (WordNet #875/#876, `tekamolo_facet.rs`/`FacetCascade`, the 24×i4 anaphora - boundary) that show orthogonal-facet/exact-address patterns already working - in this repo, and leaves S3.1's evidence/meta placement explicitly OPEN - rather than forcing a "ref-escape" conclusion. - -### Why this session exists - -PR #973 ("S3.0 — the exact HHTL causal-literal address") was closed unmerged -by the operator: its code was sound but its board-finding title/framing -(`E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1`) turned a correct local -fact about `NiblePath`'s depth budget into an implied global HHTL-impossibility -claim, forgetting three already-measured counterexamples in this same -repository. Full audit: `.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md`. -Retraction + corrected finding: `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` -+ `E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1` in -`EPIPHANIES.md`. - -### Not in this PR (the brief's own order, unchanged from #973) - -S3.0b causal regime · S3.1 Meta tree (evidence placement now explicitly OPEN, -not pre-decided) · S3.2 V3 local proxy bridge · S3.3 `ResolvedPredicate` · -S3.4 DisMech · S3.5 NARS evidence mass · S3.6 JC measurement · S3.7 semantic -recipe projection · S3.8 potholes/backcast · S3.9 Pearl validation. -Stage-2/2.5/2.6 (#971) remain the frozen baseline — untouched here. +## 2026-08-20 — branch `claude/lance-graph-stage-3-recovery-2wrdbd` — S3.0 CLOSED AS NOT-NEEDED (no new type); #973 retraction + two overclaim corrections + +### Contract Inventory — NO CHANGE + +**This PR adds no type, no module, no bit, no tenant, no layout version.** It +is a retraction + audit. `crates/lance-graph-contract/src/lib.rs` is byte-clean +against `main`. + +### What was withdrawn, and why + +A first draft of this recovery PR reintroduced #973's `CausalLiteral` +(`4 × u16`, 8 bytes) with corrected prose. **The operator stopped it before +merge, and was right on four counts** — all now recorded as +`E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1` instance 2: + +1. **Wrong universality.** Its own test asserted `TREATED_WITH` — the structure + is GENERIC. Causality is a predicate family / qualification, never universal + identity. +2. **`4 × u16` is not absolute.** MedCare-rs `ONTOLOGY_BAKE_STATE.md`:182: + *"real OBO ids run past `u16` (MONDO:0700092 = 700,092)."* The V3 rail + already solves this. +3. **A WordNet overclaim.** #875 was cited as an "EXACT structural encoding"; its + own W5 gate reports **256 cells, occupancy median 255, over 65,292 leaves** — + a locality/search prior, never an identity encoding. +4. **`routing_prefix()` was unearned.** Lexicographic prefix over concatenated + ordinals, labelled an "HHTL locality projection", with no consumer and no + measurement. + +### The question that closed the slot + +> **WHAT EXACT INFORMATION CANNOT BE EXPRESSED BY THE ADDRESSING THAT ALREADY +> EXISTS?** + +**Nothing demonstrable.** `identity_quad::IdentityQuad` (operator-RATIFIED +2026-08-17) already carries **four exact external identities as `4 × u24` in one +96-bit V3 facet** behind a `classid(4)`, refuse-don't-truncate, bake-time +crosswalk resolution. It strictly dominates the withdrawn type. Siblings: +`ogar_elk::ClassAddr` (`u32 + u32`, a pre-bake join key by its own doc), +`canonical_node::NodeGuid` + HHTL, the V3 OBO rail. + +Per the operator's rule — no concrete falsifier ⇒ **no new absolute-address +type.** S3.0 is closed as NOT-NEEDED, not filled because a plan had a slot. + +### The genuinely open addressing gap (different, not fixed here) + +`ClassId = u16` (`class_view.rs:54`) is near-exhausted for **relations** — +MedCare-rs `CLAUDE.md` #10: *"cannot address a relation — 11 prefixes, 8 of 280 +ids over the ceiling."* A classid-mint capacity question for OGAR/lance-graph; +to be raised with the operator in session, not patched from a consumer. + +### Where the work actually is + +| | ADDRESS | HYDRATED SoA | TRAVERSAL | +|---|---|---|---| +| Bible / Rosetta | yes | **NO** | partial / context | +| OSM | yes | yes | overlay / junction | +| MedCare ontology | yes | yes | **YES, Stage 1** | +| DisMech oracle | source | structured | causal oracle | + +The empty column is not ADDRESS. Next: **hydrate epistemic / causal nodes → +reason over them → think about the reasoning**, with the DisMech oracle +experiment as the gate (hide mechanism intermediates → hydrate the addressed +neighbourhood → let NARS/recipes recover candidates → compare against DisMech +truth). + +Full audit: `.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md`. +Stage-2/2.5/2.6 (#971) untouched; #970's CE64 layout untouched. --- ## 2026-08-20 — lance-graph #971 (MERGED, `2cbe62d`, head `d627f5c`) — Stage 2 carves + Stage 2.5 census + Stage 2.6a V3 invariance + CE64 ⇄ V3 losslessness diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 341c4ded5..61dc53e5b 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,31 +1,31 @@ -## stage-3 — exact causal literals + amortized epistemic trees (operator brief, 2026-08-20; recovery session after #973's overclaim retraction) +## stage-3 — RE-SCOPED 2026-08-20: S3.0 closed as NOT-NEEDED; the ladder's empty column is HYDRATION, not ADDRESS -Delivery order is the brief's own §38; **not** to be attempted in one PR. -Stage-2/2.5/2.6 (PR #971) is the frozen substrate baseline: representation -totality and reasoning parity are two SEPARATE proofs and neither is reopened -without a real falsifier. PR #973 (S3.0, first attempt) was closed unmerged — -its `CausalLiteral` struct was correct and is reintroduced here verbatim; its -board-finding framing overclaimed and is retracted in `EPIPHANIES.md` -(`E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1`). +PR #973 (S3.0 address, first attempt) closed unmerged. Its retraction PR then +attempted a corrected `CausalLiteral` and was **stopped before merge by the +operator** — four unearned claims, recorded in +`E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1`. No new +address type is minted: `IdentityQuad` (4 × u24 in one V3 facet, ratified +2026-08-17) already carries exact four-component identity, and no falsifier +showed existing addressing insufficient. | D-id | Deliverable | Status | |---|---|---| -| S3.0 | exact causal-literal address (`contract::causal_literal`) | **In PR** | +| S3.0 | exact literal address | **CLOSED — NOT NEEDED** (use `IdentityQuad` / `ClassAddr` / V3 rail) | | S3.0b | qualified causal regime | Queued | -| S3.1 | `CausalMeta` + `EpistemicMeta` tree contract; rebuild-from-leaves; evidence placement OPEN (orthogonal-facet candidate, not ref-escape) | Queued | -| S3.2 | V3 local-proxy bridge (absolute identity survives local indirection) | Queued | +| S3.1 | hydrate `CausalMeta` + `EpistemicMeta` over EXISTING addresses | **Next** | +| S3.1b | EntropyWork · BasinSet · Attention; `RowFocusMask × WideFieldMask` | Queued | +| S3.2 | V3 / CE64 leg from the hydrated node | Queued | | S3.3 | `ResolvedPredicate`; unknown fails CLOSED; explicit composition | Queued | -| S3.4 | DisMech adapter — map sources onto EXISTING literals, never mint per source | Queued | +| S3.4 | DisMech ORACLE experiment — hide intermediates, hydrate neighbourhood, NARS recovers, compare vs truth | **the gate** | | S3.5 | NARS evidence mass: raw/source/HEEL/effective, deterministic W+/W− | Queued | | S3.6 | JC measurement per predicate × cohort × horizon × instrument | Queued | -| S3.7 | `ReasoningSituation` → ThoughtCtx projection; measure the 17 mute kernels | Queued | +| S3.7 | `ReasoningEpisode`; measure the 17 confidence-mute kernels | Queued | | S3.8 | potholes, first_possible vs first_derived, strict historical replay | Queued | -| S3.9 | Pearl validation — earn the SPO 2³ projections empirically | Queued | +| S3.9 | Meta / Rubicon → OGAR-loco; Pearl qualification earned by receipts | Queued | -The ones S3.0 answers: **#1** (same canonical tuple → same literal), **#2** -(distinct predicates never collide), **#3** (CAM-PQ is not identity) and -**#19** (a local target is not absolute identity) via the *proven-lossy* -`NiblePath` routing projection — same falsifiers #973 answered, code reused. +**Open, and NOT an S3 deliverable:** `ClassId = u16` near-exhausted for +relations (MedCare-rs commitment #10) — an OGAR/lance-graph classid-mint +capacity question for the operator. --- ## preparation-arc plan wave — 2026-08-19 (operator: "integration plans for all open arcs") diff --git a/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md b/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md index bc3e9ed9b..07e83c875 100644 --- a/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md +++ b/.claude/handovers/2026-08-20-s3-0-cold-start-recovery-audit.md @@ -1,180 +1,192 @@ # S3.0 Cold-Start Recovery Audit — 2026-08-20 +**Outcome: no new address type. S3.0 as specified is WITHDRAWN.** +This PR retracts #973's overclaim, corrects a WordNet overclaim this session +itself introduced, and records why the S3.0 address slot needs no new type. + ## A. MERGED substrate truth -- **PR #970** [MERGED, `781c3b9b`]: `CausalEdge64` v2 layout final. Bits 59-60 = - `TrustTexture` (canonical) with an ADDITIVE second reading `CausalTopology` - over the *same* bits — no bits move, no auto-derivation. Bits 61-63 = spare, - with an ADDITIVE `ReasoningBand` reading — explicit `with_reasoning_band()` - only, never auto-derived from mantissa/confidence/style. Deprecated v1 - `temporal` (bits 52-63) is NOT valid v2 chronology; a v1 edge with - `temporal >= 512` reads a nonzero band under v2 — version-gate required. - Confirmed live in `crates/causal-edge/src/layout.rs`. -- **PR #971** [MERGED, `93dc57e`]: Stage-2/2.5/2.6a frozen baseline. Key - correction carried: `InferenceType` is a LOSSY compat projection of the - 4-bit signed mantissa (8/16 states corrupted on round-trip, incl. the - `pack_v2` default `0 -> +1`) — never route a conversion through it; carry - the raw nibble. CE64<->V3 is bit-identical except (a) dedup'd 24-bit in-edge - SPO and (b) deprecated v1 temporal (deliberately NOT lifted — TE stays an - independent producer-set signed chain offset). "Recipe capability != NARS - reachability" measured (`Mcp` truthfully declares `moves_confidence()` and - is still silent 0/180). -- **PR #973** [REJECTED, CLOSED UNMERGED, no comments from reviewers — - operator judgment call before any bot review completed]. Confirmed via - `git log`/`grep`: nothing from this PR is on `main` — `causal_literal.rs` - does not exist in the working tree. - -## B. MEASURED substrate truth (the counterexamples #973 forgot) - -All confirmed live/merged in this repo, read in full this session: - -1. **WordNet #875/#876** [MERGED, MEASURED]. A full 4-ary depth-4 (16-nibble) - HHTL fold of real ground-truth taxonomy is NOT lossy hashing — the ADDRESS - itself encodes ancestry (corr +0.494 vs shuffled -0.036), the sub-nibble - rung is load-bearing (2.47 hops the 16-ary router can't see), and #876 - separately proved a *consumer* (the ruler) can be structure-blind to an - address's hierarchy without that meaning the address lacks structure — - "the address encodes taxonomy" vs "the calculator reads the address as a - number" are different, separable claims. -2. **TEKAMOLO #839/#844** [MERGED, live code: `facet.rs` + `tekamolo_facet.rs`]. - `FacetCascade = facet_classid(4) | 6×(8:8) = 16B`, ONE 128-bit register with - MULTIPLE simultaneous ClassView-selected readings (`G3D4`/`G4D3`/`G6D2`, - `24×i4`), never nested path-depth. TEKAMOLO names the `G4D3` carving as - 4 ORTHOGONAL 256:256:256 lanes (Temporal/Kausal/Modal/Lokal) over the SAME - bytes — new semantic capability lands as a new *reading*, never a deeper - path. This is the concrete, shipped instance of "orthogonal facets over one - address" the mission brief's OSM/TEKAMOLO doctrine describes. -3. **24×i4 anaphora #850** [MEASURED, 88.01% real German relative clauses]. - Proves the general shape #973 needed but didn't reach for: a LOCAL fixed - representation (i4, -8..+7) exhausts cleanly at a real linguistic boundary, - and the correct response is NOT "widen the local pointer" — it's "the - exhausted local representation marks a TYPE boundary; switch to a - DIFFERENT sanctioned reading of the SAME address register (a basin edge), - never invent a deeper/wider path." Directly antithetical to reading - NiblePath's 16-nibble ceiling as grounds to abandon HHTL identity. -4. **`hhtl.rs` `NiblePath`** [MERGED, confirmed]: `MAX_DEPTH: u8 = 16`, one - `u64`, single SEQUENTIAL router path for the specific `subClassOf` - Abstammung tree. This is ONE HHTL-shaped type among several in this repo — - NOT the only or canonical HHTL substrate. `FacetCascade` (item 2 above) is - a materially different HHTL-adjacent type: a fixed 16-byte register with - *simultaneous* multi-lens reads, not a depth-limited single path. - -## C. REJECTED #973 claims — precisely scoped - -**What #973's CODE actually did (and got right):** `CausalLiteral{domain:u16, -subject:u16, predicate:u16, object:u16}`, 8 bytes, `const _`-asserted size, -component equality, no CAM-PQ/evidence/source/version fields possible by -construction. This is *exactly* the mission brief's own §25 recommended -minimal S3.0 shape. The 9 tests (injectivity, field isolation, round-trip, -unbound sentinel) are sound and reusable verbatim. - -**What #973's REASONING (the EPIPHANIES title + doc-comment framing) -overclaimed:** the finding is titled -`E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` and its argument runs: -"a `domain·S·P·O` address exactly fills `NiblePath`'s 16-nibble budget with -zero nibbles left for an evidence subtree beneath it -> therefore identity -and routing SPLIT -> the evidence subtree must *ref-escape* out of address -space entirely." The arithmetic (16 nibbles = `MAX_DEPTH`, zero slack) is -correct and TRUE ONLY OF `NiblePath` — a single sequential depth-limited path -type. The invalid generalization is treating that as proof that **HHTL -identity itself** (not just this one path type) cannot carry the literal, and -that evidence/meta state must therefore live in some conceptually separate, -disconnected mechanism rather than as an **orthogonal facet keyed by the same -8-byte address** — exactly the TEKAMOLO/anaphora/OSM pattern already proven -in this repo. Nothing in #973 acknowledges that a `FacetCascade`-style -multi-reading register (or simply: `CausalLiteral` as its own SoA -column/plane, with a `CausalMeta`/`EpistemicMeta` column keyed by the SAME -`CausalLiteral`) sidesteps the "budget" problem entirely, because it was -never modeled as depth-based descent from a single path in the first place. - -**Verdict: `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` is RETRACTED as -stated.** Superseded by a narrower, correctly-scoped finding (drafted below). -This is the precise process failure named in the mission brief §0/§9: a local -representation limit (NiblePath's depth ceiling) promoted into an implied -global architecture conclusion (identity+evidence must structurally split -away from HHTL) while forgetting three already-measured counterexamples in -the SAME repository that show the opposite pattern working. - -## D. Salvageable #973 code/math - -- `CausalLiteral { domain, subject, predicate, object }` as 4×`u16`, 8 bytes, - `const _`-asserted, component equality — REUSE VERBATIM. -- `packed_identity_is_injective`, `changing_only_the_predicate_changes_the_literal`, - `three_sources_asserting_the_same_proposition_mint_one_literal`, - `component_isolation_matrix`, `identity_round_trips_exactly_in_both_forms`, - `unbound_components_are_addressable_but_not_fully_bound` — REUSE VERBATIM - (these test IDENTITY, which #973 got right). -- `routing_prefix(depth) -> NiblePath` / `full_path()` — REUSE, but RELABEL - the doc comments: this is *one particular* NiblePath-shaped projection - useful for cohort/locality queries against the existing `subClassOf`-style - router, not "the" HHTL reading of the literal, and its lossiness at - `depth < 16` is a fact about `NiblePath` specifically, not about HHTL - identity in general. -- DROP/REWRITE: `routing_prefix_is_not_identity` and - `the_full_literal_path_exhausts_the_nibble_budget` stay as tests (they are - correct, falsifiable facts about `NiblePath`) but their surrounding prose - must not imply "therefore identity lives outside HHTL." -- DROP the `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` framing; - replace per §E below. - -## E. Remaining unknowns - -- Whether `CausalLiteral` should ALSO be constructible as a `FacetCascade` - reading (a 5th `CascadeShape`, `4×u16`-over-16B) for consumers that want it - to live inside the existing content-blind register alongside TEKAMOLO/rails/ - SPO-triplet readings, vs. staying a free-standing 8-byte contract type - consumed by reference. Not resolved this session — S3.0 replacement below - ships the free-standing type only (matches the brief's own minimal-first - guidance in §25), and defers the FacetCascade-reading question to S3.1/S3.2 - where the V3 local-proxy bridge is actually built. -- CausalRegimeAddr (S3.0b), Meta tree (S3.1), predicate resolution (S3.3) — - all explicitly out of scope for this PR per the brief's own delivery order. -- Rubicon threshold semantics (§19) — not investigated this session; will be - searched before any Rubicon-touching work, not asserted from memory. - -## F. Proposed minimal S3.0 replacement - -Reintroduce `crates/lance-graph-contract/src/causal_literal.rs` with: -1. The same `CausalLiteral` struct, accessors, packing, and all 9 original - tests (salvaged per §D). -2. Module-doc and EPIPHANIES entry REWRITTEN to state the narrow, correct - claim: `NiblePath::MAX_DEPTH` (16 nibbles) is exactly consumed by a - 4×`u16` literal, so a literal's *own* identity cannot ALSO be expressed as - a strictly-shorter `NiblePath` prefix while remaining exact — a fact about - `NiblePath` depth budget, not about HHTL addressing dimensionality in - general. Explicitly cross-references WordNet #875/#876 (exact structural - HHTL address is real and proven), TEKAMOLO #839 (orthogonal-facet pattern - is the sanctioned way to attach evidence/meta without deepening a path), - and anaphora #850 (a locally-exhausted representation marks a TYPE - boundary, not a substrate ceiling) so a future session reads the - NiblePath-specific finding correctly. -3. New finding name: `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` - (supersedes/retracts `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1`, - which is marked ⊘ RETRACTED in place per this repo's append-only - convention, not deleted). -4. A second, process-level finding: - `E-A-LOCAL-DERIVATION-CANNOT-OVERRULE-A-MEASURED-COUNTEREXAMPLE-1`, - naming the general failure mode for future sessions. -5. Evidence/Meta placement is left EXPLICITLY OPEN (not "ref-escaped" as a - forced conclusion) — noted as an S3.1 design question with the - FacetCascade-orthogonal-plane option named as the leading candidate, - rather than asserted-and-closed by this PR. - -## G. Falsifiers that would kill this proposal - -- If a future S3.1/S3.2 session finds that keying `CausalMeta`/`EpistemicMeta` - as orthogonal facets over `CausalLiteral` (rather than ref-escaping) hits a - real capacity/addressing wall analogous to NiblePath's, that would validate - more of #973's original instinct — investigate before assuming either way. -- If `CausalLiteral` needs to itself be routable through the DOLCE/basin - `NiblePath` tree (not just an opaque 8-byte key), the routing_prefix - question reopens for real; this PR does not build that consumer. - -## Central-constitution check (brief §27.5) - -Does the proposed S3.0 replacement contradict OSM, WordNet #875/#876, Bible -Rosetta, TEKAMOLO, or the 24×i4 anaphora boundary? **NO.** It ships the same -minimal exact-identity primitive the brief itself specifies in §25, corrects -only the RETRACTED overclaim in the board narrative, and explicitly leaves -the evidence/meta placement question open rather than pre-deciding it against -the TEKAMOLO/anaphora precedent. +- **PR #970** [MERGED, `781c3b9b`]: CE64 v2 layout final. Bits 59-60 = + `TrustTexture` (canonical) + ADDITIVE `CausalTopology` reading over the same + bits. Bits 61-63 = spare + ADDITIVE `ReasoningBand`, explicit + `with_reasoning_band()` only, never auto-derived. Deprecated v1 `temporal` + (52-63) is NOT valid v2 chronology; version-gate edges of unknown + provenance. Confirmed in `crates/causal-edge/src/layout.rs`. +- **PR #971** [MERGED]: Stage-2/2.5/2.6a frozen baseline. `InferenceType` is a + LOSSY compat projection of the 4-bit signed mantissa (8/16 states corrupted, + incl. `pack_v2` default `0 -> +1`) — carry the raw nibble. CE64<->V3 + bit-identical modulo dedup'd SPO + deprecated temporal. Capability != + reachability measured. +- **PR #973** [REJECTED, closed unmerged]: nothing on `main`. + +## B. MEASURED substrate truth + +1. **`identity_quad::IdentityQuad`** [MERGED, operator-RATIFIED 2026-08-17, + `ISS-IDENTITY-QUAD-WIDE-CARVING-HOME`]. **Four external identity spaces, + `4 × u24` contiguous, in ONE 96-bit V3 facet payload** via + `LegacyOutlier::WideTriple`, behind a `classid(4)`. Refuses rather than + truncates (`QuadError::OrdinalTooLarge`, `MAX_ORDINAL = 2^24 - 2`); + codebooks refuse rather than saturate (`CodebookError::TooLarge`). Its + stated purpose is resolving a crosswalk ONCE at bake time so a read is a + fixed-offset register read — no join, no crosswalk walk. +2. **`ogar_elk::ClassAddr`** [MERGED]: `classid: u32 + identity: u32` — 8 + bytes for ONE node, and its own doc is explicit that it is a pre-bake + **join key**, "not an ABI address, and deliberately not documented as one". +3. **Real OBO identities exceed `u16`** [MEASURED, MedCare-rs + `docs/ONTOLOGY_BAKE_STATE.md`:182]: *"real OBO ids run past `u16` + (MONDO:0700092 = 700,092) and one V3 field cannot hold that."* The + substrate ALREADY solved this: the V3 rail splits a 24-bit CURIE numeric as + `family:identity = (num >> 16, num & 0xFFFF)`, read via + `obo_store::row_addr`. Corpus scale: MONDO 32,095 · HP 19,836 · + UBERON 14,975 · PATO 1,887, and relations cross families. +4. **`ClassId = u16` is near-exhausted for RELATIONS** [MEASURED, MedCare-rs + `CLAUDE.md` commitment #10 + `RAIL_OFFENE_POSTEN.md`]: *"`ClassId = u16` + cannot address a relation — 11 prefixes, 8 of 280 ids over the ceiling."* + That is a real open gap — and it is a **classid-mint capacity** question + owned by OGAR/lance-graph, NOT something a new literal type fixes. +5. **WordNet #875/#876** [MERGED, MEASURED] — see §C2 for the corrected + reading. Also #876: a consumer can be structure-blind to an address's + hierarchy without that meaning the address lacks structure. +6. **TEKAMOLO #839/#844** [MERGED, live code]: one 16-byte content-blind + register, several simultaneous ClassView-selected readings + (`G3D4`/`G4D3`/`G6D2`/`24×i4`). Capability lands as a new READING. +7. **24×i4 anaphora #850** [MEASURED, 7,657 real German relative clauses, + 88.01% in-window]: a local representation exhausting cleanly marks a TYPE + boundary — switch reading, never widen the pointer. + +## C. REJECTED claims + +### C1. #973's `E-THE-LITERAL-CANNOT-LIVE-IN-THE-PATH-IT-ROOTS-1` — RETRACTED + +#973 measured correctly that a `domain·S·P·O` at `u16` each is exactly 16 +nibbles = `NiblePath::MAX_DEPTH`, zero slack. It then promoted that local fact +about ONE sequential depth-limited router path into an implied global claim +that exact identity cannot live in HHTL addressing and evidence must +"ref-escape" out of address space. The three counterexamples in §B5-B7 were +never consulted. The arithmetic survives; the inference does not. + +### C2. This session's OWN WordNet overclaim — RETRACTED + +The first draft of this recovery PR said #875 proved a *"full-width 4-ary HHTL +fold of real WordNet ancestry is an EXACT structural encoding"*. **That is +false, and #875's own numbers say so:** W5 reports *256/256 cells used, +occupancy min 29 / median 255 / max 1270* over **65,292 leaves**. That is +~255 leaves per cell — emphatically NOT injective, not an identity encoding. + +What #875 actually measured is a **deterministic, taxonomy-informed HHTL +locality / search prior**: shared-address-levels vs LCA-depth corr +0.494 (vs +−0.036 shuffled), out-of-cell band recall 0.763 vs 0.031 random = 24.71×, and +a 2.47-hop sub-nibble distinction the 16-ary router cannot address. A +discriminating prior that deliberately does NOT cover everything — #875 itself +dropped its cover guard as inert for exactly this reason. + +So the Bible/Rosetta + WordNet composition is: +`frozen verse identity × quasi-absolute taxonomy-informed semantic coordinate +× witness/qualia planes` — **not** evidence that 256 HHTL cells uniquely +identify a lexicon. Corrected in the PR body, EPIPHANIES, and this audit; +the module doc carrying it is deleted with the type. + +### C3. `CausalLiteral` was the wrong universal type — WITHDRAWN + +Two independent defects: + +- **Wrong name / wrong universality.** Its own test asserts + `CausalLiteral(7, 100, 43, 200)` as `TREATED_WITH` — demonstrating the + structure is generic, not causal. `ASSOCIATED_WITH` / `TREATED_WITH` / + `CAUSES` / `MEDIATES` / `PART_OF` are exact predicate identities over one + generic literal substrate. Causality is a **predicate family / + qualification**, never universal identity. If a primitive were needed it + would be `ExactLiteralAddr(D,S,P,O)`. +- **`4 × u16` is NOT absolute, and cannot be.** A `u16` subject cannot hold + MONDO:0700092 = 700,092 (§B3). The type would have silently mis-addressed + the single largest ontology in the MedCare bake. Calling it "absolute + identity" was a second global overclaim in the same PR that retracted one. + +## D. Salvageable + +- The nine tests' SHAPE (injectivity sweep, component-isolation matrix, + many-sources-one-literal, unbound sentinel) is good discipline and should be + reused by whatever type actually lands — but it belongs on + `IdentityQuad`/`ClassAddr`-based composition, not on a new 4×u16 plane. +- The retraction of C1 and the two process findings. +- `routing_prefix()` is REMOVED, not merely deferred — see §G. + +## E. The development ladder (operator-requested matrix) + +| | ADDRESS | HYDRATED SoA | TRAVERSAL | +|--------------------|---------|--------------|-----------| +| Bible / Rosetta | yes | **NO** | partial / context | +| OSM | yes | yes | overlay / junction | +| MedCare ontology | yes | yes | **YES, Stage 1** | +| DisMech oracle | source | structured | causal oracle | + +**The column that is empty is not ADDRESS.** Address is solved three times +over (`ClassAddr`, the V3 rail, `IdentityQuad`, `NodeGuid`/HHTL). The missing +work is: **hydrate epistemic / causal nodes → reason over them → think about +the reasoning.** + +### The question S3.0 had to answer, and its answer + +> **WHAT EXACT INFORMATION CANNOT BE EXPRESSED BY THE ADDRESSING THAT ALREADY +> EXISTS?** + +**Nothing that this session could demonstrate.** `IdentityQuad` (classid + 4 × +u24, ratified 2026-08-17) strictly dominates the withdrawn 4×u16 type on every +axis: wider (handles real OBO ids, which u16 does not), already V3-carving +conformant (rides a sanctioned reading rather than minting a parallel identity +plane), already refuse-don't-truncate, already bake-time-join-resolving. + +There is therefore **no concrete falsifier showing existing addressing is +insufficient**, and per the operator's rule no new absolute-address type is +minted. The S3.0 slot in the Stage-3 plan is closed as NOT-NEEDED rather than +filled because the plan had a slot. + +**The one genuinely open addressing gap found** is different and is NOT this: +`ClassId = u16` is near-exhausted for RELATIONS (§B4). That is a classid-mint +capacity question for OGAR/lance-graph, to be raised with the operator in +session — not something a new literal type addresses. + +## F. Next target (per operator §6) + +``` + EXISTING absolute address (ClassAddr / V3 rail / IdentityQuad / NodeGuid) + | + hydrate node + | + CausalMeta + EpistemicMeta + | + EntropyWork · BasinSet · Attention + | + RowFocusMask × WideFieldMask + | + V3 / CE64 -> NARS -> ReasoningEpisode -> Meta/Rubicon -> OGAR-loco +``` + +**DisMech as the oracle experiment:** hide known mechanism intermediates → +hydrate the addressed ontology neighbourhood → let NARS/recipes recover +candidates → compare against DisMech truth. Grounded: `dismech-rs` +`graph::build_causal_graph` is a real transcode of the upstream Python +resolver, falsified at **1,995 diseases / 33,458 edges** against the private +`medcare-dismech` measurement (33,328, within 0.4%), and 1,903 committed +`pathographs/MONDO_*.json` exist as ground truth. Not started this session. + +## G. `routing_prefix()` — REMOVED + +Concatenating D/S/P/O nibbles into a `NiblePath` yields deterministic +**lexicographic** prefixes. It was labelled an "HHTL locality / cohort +projection" — but no consumer exists and no measurement establishes that it +preserves HHTL *semantic* locality. `NiblePath` is built for the `subClassOf` +Abstammung tree, where a prefix is an ancestry claim; a lexicographic prefix +over concatenated ordinals carries no such guarantee. Calling it one was a +third unearned claim. Removed with the type; if it returns it needs a real +consumer and a measurement first. + +## H. Falsifiers that would reopen S3.0 + +1. A concrete case where `IdentityQuad` (4 × u24 + classid) provably cannot + express an exact proposition identity the substrate needs. +2. A measured need for a 4th component space beyond `IdentityQuad`'s four + slots — noting its own doc says a fifth identifier space is a **second + facet**, never a wider field. +3. Resolution of the `ClassId = u16` relation-capacity gap requiring a new + addressable relation identity (a classid-mint question first). diff --git a/crates/lance-graph-contract/src/causal_literal.rs b/crates/lance-graph-contract/src/causal_literal.rs deleted file mode 100644 index 750643bc9..000000000 --- a/crates/lance-graph-contract/src/causal_literal.rs +++ /dev/null @@ -1,599 +0,0 @@ -//! S3.0 — the **exact causal literal**: an absolute, deterministic address for -//! one causal proposition, independent of every quantity that can revise. -//! -//! ```text -//! CausalLiteral (domain, S, P, O) ← exact proposition identity, THIS module -//! │ -//! ├── world/causal evidence leaves (S3.1, orthogonal facet — -//! ├── epistemic/reasoning leaves NOT nested path depth; -//! ├── contradiction / mediator / pothole see "Evidence placement" -//! │ leaves below) -//! ▼ -//! CausalEdgeV3 local hot proxy (S3.2) -//! ▼ -//! CausalEdge64 NARS register -//! ``` -//! -//! # The one thing this module asserts -//! -//! **Identity is the component tuple. Nothing else.** For a fixed canonical -//! `domain + S + P + O` the literal is byte-identical across replay, across -//! sources, across Lance versions, and across every amount of evidence that -//! ever accumulates beneath it. Two papers and a model asserting the same -//! canonical proposition converge on ONE literal with THREE witnesses — never -//! three literals. -//! -//! What identity is NOT, stated because each has been reached for before: -//! -//! | not identity | why | where it belongs | -//! |---|---|---| -//! | CAM-PQ nearest centroid | learned, approximate, re-trainable | candidate discovery, basin search, ranking | -//! | NARS `f` / `c` | revisable evidence state | the Meta accumulator | -//! | evidence count | grows monotonically; identity must not | the leaf set | -//! | the asserting source | many sources, one proposition | witness leaves | -//! | the Lance version | history is immutable, identity is timeless | the horizon | -//! | a V3 `target` u16 | tenant-LOCAL, may be repacked | [`crate::hhtl`] resolution | -//! -//! # Why this is an address and not a packed tenant -//! -//! Operator ruling E (`docs/architecture/ARC-B-OWNERSHIP-AND-ADDRESSING-REASSESSMENT.md` -//! §4) gates every new `ValueTenant` behind one question: *is this genuinely -//! missing canonical information, or a container minted to avoid completing the -//! address transition?* This is the former, and the ruling names it as such — -//! it says the missing canonical reference "is the prerequisite", and that the -//! tenant gap and the addressing gap "are the same problem wearing two hats". -//! So `CausalLiteral` adds no bits to `CausalEdge64` and no slot to any tenant. -//! It is 8 bytes of pure address, const-asserted below so that a future edit -//! cannot quietly hang evidence off it. -//! -//! # `NiblePath`'s depth budget — a fact about ONE consumer type, not about HHTL -//! -//! **Read this section before citing it past its scope — a prior draft of this -//! module did exactly that and was retracted; see -//! `E-NIBLEPATH-DEPTH-IS-NOT-HHTL-DIMENSIONALITY-1` in `EPIPHANIES.md`.** -//! -//! [`crate::hhtl::NiblePath`] is a `u64`-backed SEQUENTIAL router path with -//! [`MAX_DEPTH`](crate::hhtl::MAX_DEPTH) = 16 nibbles, purpose-built for the -//! `subClassOf` Abstammung tree. Four `u16` components are 16 nibbles -//! **exactly**: -//! -//! ```text -//! domain 4 nibbles ┐ -//! subject 4 nibbles ├─ 16 nibbles = 64 bits = the ENTIRE NiblePath budget -//! predicate 4 │ -//! object 4 ┘ depth remaining for a NiblePath descent: 0 -//! ``` -//! -//! That is a real, measured, zero-slack fact about `NiblePath` specifically — -//! `MAX_DEPTH` is a property of one particular single-path router, not of HHTL -//! addressing as a concept. This repo has OTHER HHTL-shaped substrates that do -//! not share this ceiling because they are not modelled as sequential depth -//! descent from one root: -//! -//! - [`crate::facet::FacetCascade`] is a fixed 16-byte register -//! (`classid(4) | 6×(8:8)`) that supports SEVERAL SIMULTANEOUS -//! `ClassView`-selected readings of the same bytes (`G3D4`/`G4D3`/`G6D2`, -//! `24×i4`) — new semantic capability lands as a new *reading*, never a -//! deeper path. [`crate::tekamolo_facet`] is the shipped instance: four -//! orthogonal 256:256:256 lanes over one register, not nested depth. -//! - WordNet's real hypernym hierarchy (`E-WORDNET-MAKES-THE-4-ARY-ADDRESS-SEMANTIC-1`, -//! `probe_wordnet_44_activation.rs`) proves a full-width 4-ary HHTL fold is a -//! genuinely EXACT structural encoding of real ancestry (corr +0.494 vs -//! shuffled −0.036) — "HHTL identity" and "one `NiblePath`'s depth ceiling" -//! are not the same claim. -//! - The 24×i4 anaphora boundary (`E-ANAPHORA-BEYOND-I4-IS-A-BASIN-EDGE-1`) -//! is the general pattern this module follows: a LOCAL representation -//! reaching its limit marks a TYPE boundary — switch to a different -//! sanctioned reading of the SAME address, never invent a deeper/wider path -//! to route around it. -//! -//! **What follows from the measured fact, and no more:** a `CausalLiteral`'s -//! own identity is not expressible as a STRICTLY SHORTER `NiblePath` prefix -//! while staying exact, and a `NiblePath` built by walking all four -//! components is `is_full()` — nothing can descend beneath it via THAT router. -//! This module therefore exposes [`CausalLiteral::routing_prefix`] as an -//! explicitly LOSSY, depth-truncated cohort projection into the `NiblePath` -//! tree (useful for locality/cohort queries against the existing router), and -//! never treats it as identity. `CausalLiteral` itself is the identity — an -//! 8-byte struct, addressable and hashable on its own terms, independent of -//! whether any particular router can also walk it as a path. -//! -//! # Evidence placement — OPEN, not pre-decided by this module -//! -//! A prior draft of this module concluded that because a full-depth -//! `NiblePath` walk of the literal is `is_full()`, an evidence/meta subtree -//! must therefore "ref-escape" out of address space into some structurally -//! separate mechanism. **That inference is withdrawn as a forced conclusion.** -//! The `NiblePath`-descent option is indeed closed (see above) — but the -//! TEKAMOLO/`FacetCascade` pattern above suggests a live alternative: `S3.1`'s -//! `CausalMeta`/`EpistemicMeta` may land as an ORTHOGONAL FACET/COLUMN keyed -//! by this same 8-byte [`CausalLiteral`] (same shape as "temporal/kausal/ -//! modal/lokal are four lanes over one register, not four levels of depth"), -//! rather than a disconnected side structure. This module does not build that -//! — S3.1 decides it, from measurement, when it lands. -//! -//! # Predicate meaning is NOT decided here -//! -//! HHTL supplies hierarchy, locality and exact addressing. It does not decide -//! what `CAUSES` means. A raw `u8`/`u16` ordinal is meaningless without its -//! codebook: the same integer denotes different relations under different -//! families. Resolution — `literal → tenant/ClassView → canonical codebook → -//! ResolvedPredicate`, with unknown predicates failing CLOSED and never -//! composing transitively by default — is S3.3 and lives elsewhere. This -//! module deliberately exposes no `is_transitive`, no relation class, and no -//! composition policy. - -use crate::hhtl::{NiblePath, FAN_OUT, MAX_DEPTH}; - -/// A canonical concept ordinal, resolved through a codebook UPSTREAM of this -/// module (`ogar_codebook::canonical_concept_id` and friends). Raw palette -/// integers are not concepts until a codebook says so. -pub type ConceptId = u16; - -/// A canonical semantic-domain / `ClassView` ordinal — the interpretation scope -/// under which `subject`/`predicate`/`object` are resolved. -pub type DomainId = u16; - -/// The zero-fallback sentinel, shared by every component: *not routed / not yet -/// bound*, never "concept 0". -/// -/// This mirrors the canon's zero-fallback ladder — a zero tier means *not -/// consulted*, never *compacted away*. A literal with an unbound component is -/// still a perfectly well-formed address (construction is total; an address is -/// an address), it is simply not yet fully bound — ask [`CausalLiteral::is_fully_bound`] -/// rather than inferring from the value. -pub const UNBOUND: u16 = 0; - -/// The exact, absolute identity of ONE causal proposition. -/// -/// Equality is component equality — there is no hash, no learned assignment, -/// and no tolerance, so two distinct canonical tuples **cannot** collide and -/// one canonical tuple **cannot** produce two literals. Both directions are -/// swept in the tests rather than asserted here. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct CausalLiteral { - domain: DomainId, - subject: ConceptId, - predicate: ConceptId, - object: ConceptId, -} - -// 8 bytes of PURE ADDRESS. This assert is the structural guard behind the -// module's headline claim: identity cannot depend on evidence, confidence, a -// source id, or a Lance version, because there is nowhere to put them. Adding -// such a field is a compile error, not a review catch. -const _: () = assert!(core::mem::size_of::() == 8); - -/// Nibbles consumed by one `u16` component of the address. -pub const NIBBLES_PER_COMPONENT: u8 = 4; -/// Nibbles consumed by the full `domain·S·P·O` address — exactly [`MAX_DEPTH`]. -pub const LITERAL_PATH_NIBBLES: u8 = 4 * NIBBLES_PER_COMPONENT; - -// The measured NiblePath-depth fact, compiled in: the full literal path is -// exactly the whole NiblePath budget. If MAX_DEPTH ever widens, this fails -// and the "NiblePath specifically, not HHTL in general" scoping in the module -// doc must be re-derived rather than silently inherited. -const _: () = assert!(LITERAL_PATH_NIBBLES == MAX_DEPTH); - -impl CausalLiteral { - /// Address a canonical proposition. Total by construction — every `u16` - /// quadruple is a valid address, including partly-[`UNBOUND`] ones. - /// - /// Binding *meaning* to the ordinals is a separate, later act (S3.3); this - /// only says *which* proposition is being spoken about. - #[must_use] - pub const fn new( - domain: DomainId, - subject: ConceptId, - predicate: ConceptId, - object: ConceptId, - ) -> Self { - Self { - domain, - subject, - predicate, - object, - } - } - - /// The semantic domain / `ClassView` scope. - #[must_use] - pub const fn domain(self) -> DomainId { - self.domain - } - /// The canonical subject ordinal. - #[must_use] - pub const fn subject(self) -> ConceptId { - self.subject - } - /// The canonical predicate ordinal. Its *meaning* resolves through the - /// domain's codebook (S3.3), never from the integer alone. - #[must_use] - pub const fn predicate(self) -> ConceptId { - self.predicate - } - /// The canonical object ordinal. - #[must_use] - pub const fn object(self) -> ConceptId { - self.object - } - - /// Is every component bound (non-[`UNBOUND`])? - /// - /// A partly-unbound literal is a legal address but not yet a complete - /// proposition; callers that require a complete one should gate on this - /// rather than test components against 0 by hand. - #[must_use] - pub const fn is_fully_bound(self) -> bool { - self.domain != UNBOUND - && self.subject != UNBOUND - && self.predicate != UNBOUND - && self.object != UNBOUND - } - - /// The packed exact identity, root-first coarse→fine: - /// `domain << 48 | subject << 32 | predicate << 16 | object`. - /// - /// Injective over the component space by construction (four disjoint 16-bit - /// fields tiling a `u64` exactly), so it is safe as a map key. Swept in - /// `packed_identity_is_injective` rather than trusted. - #[must_use] - pub const fn as_u64(self) -> u64 { - ((self.domain as u64) << 48) - | ((self.subject as u64) << 32) - | ((self.predicate as u64) << 16) - | (self.object as u64) - } - - /// Inverse of [`as_u64`](Self::as_u64) — total, and exactly reversible. - #[must_use] - pub const fn from_u64(v: u64) -> Self { - Self { - domain: (v >> 48) as u16, - subject: (v >> 32) as u16, - predicate: (v >> 16) as u16, - object: v as u16, - } - } - - /// The 8-byte little-endian persisted form of [`as_u64`](Self::as_u64). - /// - /// This is the identity that goes to storage: canonical ordinals, never - /// runtime strings. A literal minted from the same canonical tuple in a - /// different process, a different Lance version, or a different source - /// serializes to the same eight bytes. - #[must_use] - pub const fn to_le_bytes(self) -> [u8; 8] { - self.as_u64().to_le_bytes() - } - - /// Inverse of [`to_le_bytes`](Self::to_le_bytes). - #[must_use] - pub const fn from_le_bytes(b: [u8; 8]) -> Self { - Self::from_u64(u64::from_le_bytes(b)) - } - - /// A **routing / cohort** projection into the `NiblePath` Abstammung tree - /// — the first `depth` nibbles of the root-first `domain·S·P·O` sequence. - /// - /// This is ONE way to relate a literal to the existing `subClassOf` - /// router; it is not "the HHTL form" of the literal, and `CausalLiteral` - /// itself (not this projection) is the identity — see the module doc's - /// "NiblePath's depth budget" section for why the two are not the same - /// claim. - /// - /// # This is NOT identity - /// - /// At `depth < LITERAL_PATH_NIBBLES` the projection is **many-to-one** by - /// design: that is what makes it useful as a deterministic cohort slice - /// (all literals in a domain; all literals sharing a domain and subject). - /// Two different propositions genuinely share a prefix, and - /// `routing_prefix_is_not_identity` proves it on real values so the - /// projection can never be quietly promoted into an equality test — the - /// exact confusion Stage-3 falsifiers #3 and #19 name. - /// - /// At `depth == LITERAL_PATH_NIBBLES` it is injective — and simultaneously - /// `is_full()` for the `NiblePath` router, so nothing can descend beneath - /// it VIA THAT ROUTER. That is a fact about `NiblePath`'s single-path - /// design, not a ceiling on where evidence/meta state may live (see - /// "Evidence placement" above). - /// - /// `depth` saturates at [`LITERAL_PATH_NIBBLES`]. - #[must_use] - pub fn routing_prefix(self, depth: u8) -> NiblePath { - let depth = depth.min(LITERAL_PATH_NIBBLES); - let packed = self.as_u64(); - let mut path = NiblePath::EMPTY; - for i in 0..depth { - // root-first: nibble 0 is the most significant of the 16. - let shift = 4 * (LITERAL_PATH_NIBBLES - 1 - i) as u32; - let nibble = ((packed >> shift) & 0xF) as u8; - debug_assert!(nibble < FAN_OUT, "a 4-bit value is always < FAN_OUT"); - path = if i == 0 { - NiblePath::root(nibble) - } else { - path.child(nibble) - }; - } - path - } - - /// The full-depth routing projection — exact, and exactly `is_full()` for - /// the `NiblePath` router. - /// - /// Provided for completeness and for the budget falsifier; prefer - /// [`as_u64`](Self::as_u64) / [`to_le_bytes`](Self::to_le_bytes) as the - /// identity, and a SHORTER [`routing_prefix`](Self::routing_prefix) as the - /// cohort. A caller reaching for this as a `NiblePath` tree root is about - /// to discover it cannot descend further via that router. - #[must_use] - pub fn full_path(self) -> NiblePath { - self.routing_prefix(LITERAL_PATH_NIBBLES) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::{HashMap, HashSet}; - - /// A deliberately varied sweep: every component takes low, mid, high and - /// boundary values, and the values are REUSED across positions so a - /// field-order bug shows up as a collision rather than hiding. - fn sweep() -> Vec { - let vals: [u16; 6] = [0, 1, 2, 0x00FF, 0x8000, u16::MAX]; - let mut out = Vec::new(); - for &d in &vals { - for &s in &vals { - for &p in &vals { - for &o in &vals { - out.push(CausalLiteral::new(d, s, p, o)); - } - } - } - } - out - } - - /// FALSIFIER #1 — the same canonical tuple always produces the same - /// literal, and FALSIFIER #2 — distinct tuples never collide. - /// - /// Both directions on one sweep of 1,296 tuples. The reused value set means - /// a swapped field order (e.g. predicate and object transposed) collapses - /// distinct tuples onto one `u64` and fails the injectivity half. - #[test] - fn packed_identity_is_injective() { - let all = sweep(); - // anti-vacuity: the sweep must actually contain distinct tuples that - // differ in ONLY one position, or injectivity is trivially satisfiable. - assert!(all.len() >= 1000, "sweep too small: {}", all.len()); - - let mut seen: HashMap = HashMap::new(); - for lit in &all { - // determinism: rebuilding from the same components is identical - let again = - CausalLiteral::new(lit.domain(), lit.subject(), lit.predicate(), lit.object()); - assert_eq!(*lit, again, "same canonical tuple produced two literals"); - assert_eq!( - lit.as_u64(), - again.as_u64(), - "identity is not deterministic" - ); - - if let Some(prev) = seen.insert(lit.as_u64(), *lit) { - assert_eq!( - prev, *lit, - "two DISTINCT canonical tuples collided on one identity" - ); - } - } - assert_eq!(seen.len(), all.len(), "identity is not injective"); - } - - /// FALSIFIER #2, sharpened — changing ONLY the predicate must change the - /// literal. A "causes" and a "treated_with" between the same two concepts - /// are different propositions, and the address has to say so. - /// - /// Two-sided: the paired half proves that changing nothing changes nothing, - /// so this cannot pass by an implementation that simply returns fresh - /// values. - #[test] - fn changing_only_the_predicate_changes_the_literal() { - let causes = CausalLiteral::new(7, 100, 42, 200); - let treated_with = CausalLiteral::new(7, 100, 43, 200); - assert_ne!(causes, treated_with, "distinct predicates collided"); - assert_ne!(causes.as_u64(), treated_with.as_u64()); - assert_ne!(causes.to_le_bytes(), treated_with.to_le_bytes()); - // …and the silence half - let same = CausalLiteral::new(7, 100, 42, 200); - assert_eq!(causes, same, "identical tuples must be one literal"); - assert_eq!(causes.to_le_bytes(), same.to_le_bytes()); - } - - /// FALSIFIER #4 — many sources, ONE proposition. - /// - /// Three "sources" independently address the same canonical tuple. They - /// must converge on a single identity: one literal, three witnesses, never - /// three literals. Modelled as three separately-constructed values whose - /// set collapses to size one. - #[test] - fn three_sources_asserting_the_same_proposition_mint_one_literal() { - let paper_1 = CausalLiteral::new(3, 8_001, 42, 9_002); - let paper_2 = CausalLiteral::new(3, 8_001, 42, 9_002); - let model_3 = CausalLiteral::from_le_bytes(paper_1.to_le_bytes()); - let distinct: HashSet = [paper_1, paper_2, model_3] - .iter() - .map(|l| l.as_u64()) - .collect(); - assert_eq!( - distinct.len(), - 1, - "same proposition minted several literals" - ); - // anti-vacuity: a genuinely different proposition still separates - let other = CausalLiteral::new(3, 8_001, 42, 9_003); - assert!(!distinct.contains(&other.as_u64())); - } - - /// Exact reversibility — the address resolves back to its components, in - /// both serialized forms, over the whole sweep. - #[test] - fn identity_round_trips_exactly_in_both_forms() { - for lit in sweep() { - assert_eq!(CausalLiteral::from_u64(lit.as_u64()), lit, "u64 round trip"); - assert_eq!( - CausalLiteral::from_le_bytes(lit.to_le_bytes()), - lit, - "le-bytes round trip" - ); - // and the components survive individually, not merely in aggregate - let r = CausalLiteral::from_le_bytes(lit.to_le_bytes()); - assert_eq!( - (r.domain(), r.subject(), r.predicate(), r.object()), - (lit.domain(), lit.subject(), lit.predicate(), lit.object()) - ); - } - } - - /// Field isolation (the layout discipline `I-LEGACY-API-FEATURE-GATED` - /// prescribes for any new packing): each component, changed from a FULLY - /// NON-ZERO baseline, moves its own accessor and leaves the other three - /// bit-identical. A zeroed baseline can hide a field that ORs into a - /// neighbour's set bits, so the baseline is deliberately all-`0xABCD`. - #[test] - fn component_isolation_matrix() { - const B: u16 = 0xABCD; - let base = CausalLiteral::new(B, B, B, B); - let probe: u16 = 0x1234; - - let cases: [(&str, CausalLiteral); 4] = [ - ("domain", CausalLiteral::new(probe, B, B, B)), - ("subject", CausalLiteral::new(B, probe, B, B)), - ("predicate", CausalLiteral::new(B, B, probe, B)), - ("object", CausalLiteral::new(B, B, B, probe)), - ]; - for (name, got) in cases { - assert_ne!(got, base, "{name}: changing it changed nothing"); - let moved = [ - got.domain() != base.domain(), - got.subject() != base.subject(), - got.predicate() != base.predicate(), - got.object() != base.object(), - ]; - assert_eq!( - moved.iter().filter(|m| **m).count(), - 1, - "{name}: exactly one component may move, got {moved:?}" - ); - assert!( - match name { - "domain" => moved[0], - "subject" => moved[1], - "predicate" => moved[2], - _ => moved[3], - }, - "{name}: the wrong component moved" - ); - } - } - - /// THE ANTI-VACUITY TWIN, and the point of the whole split: the routing - /// prefix is a **cohort**, not an identity. - /// - /// Guards Stage-3 falsifiers #3 and #19 at their shared root — mistaking an - /// approximate/positional projection for exact proposition identity. If a - /// future edit made `routing_prefix` injective at shallow depth (say by - /// folding all four components in), this test goes red and forces the - /// author to say so out loud. - #[test] - fn routing_prefix_is_not_identity() { - // same domain + subject + predicate, different object - let a = CausalLiteral::new(0x1111, 0x2222, 0x3333, 0x4444); - let b = CausalLiteral::new(0x1111, 0x2222, 0x3333, 0x5555); - assert_ne!(a, b, "fixture is degenerate: the two literals are equal"); - - // 12 nibbles = domain+subject+predicate — they MUST share it - assert_eq!( - a.routing_prefix(12), - b.routing_prefix(12), - "the prefix failed to group two literals that share domain+S+P — \ - it is not usable as a cohort" - ); - // 4 nibbles = the domain cohort - assert_eq!(a.routing_prefix(4), b.routing_prefix(4)); - - // …and the paired half: at full depth it discriminates, so the - // projection is lossy by DEPTH rather than simply broken. - assert_ne!( - a.full_path(), - b.full_path(), - "the full-depth path failed to separate distinct literals" - ); - } - - /// The `NiblePath` depth-budget finding, pinned as a guard rather than - /// left in prose: the full-depth path is exactly `MAX_DEPTH`, therefore - /// `is_full()` for that router — a fact about `NiblePath`'s single-path - /// design, scoped exactly to that type (see the module doc). - /// - /// If `MAX_DEPTH` ever widens this fails alongside the `const _` assert, and - /// the module-doc scoping gets re-derived instead of silently inherited. - #[test] - fn the_full_literal_path_exhausts_the_niblepath_budget() { - let lit = CausalLiteral::new(0x1234, 0x5678, 0x9ABC, 0xDEF0); - let full = lit.full_path(); - assert_eq!(full.depth(), MAX_DEPTH, "full path is not the whole budget"); - assert!(full.is_full(), "full path must report is_full"); - // the operational consequence: a descent is a silent no-op… - assert_eq!(full.child(1), full, "descent past MAX_DEPTH is not a no-op"); - // …and the explicit form refuses instead of colliding. - assert!( - full.try_child(1).is_none(), - "try_child must refuse at the ceiling" - ); - // a SHORTER prefix, by contrast, still has room — proving the ceiling - // is a property of the full-depth NiblePath walk, not of the literal - // or of HHTL addressing in general. - assert!(!lit.routing_prefix(12).is_full()); - } - - /// The prefix is monotone and deterministic: extending the depth extends - /// the path, and the same literal always yields the same prefix. - #[test] - fn routing_prefix_is_deterministic_and_monotone_in_depth() { - let lit = CausalLiteral::new(0x0102, 0x0304, 0x0506, 0x0708); - for d in 0..=LITERAL_PATH_NIBBLES { - assert_eq!( - lit.routing_prefix(d), - lit.routing_prefix(d), - "prefix is not deterministic at depth {d}" - ); - assert_eq!( - lit.routing_prefix(d).depth(), - d, - "prefix depth mismatch at {d}" - ); - } - // saturates rather than wrapping or panicking - assert_eq!( - lit.routing_prefix(200), - lit.full_path(), - "depth must saturate at the budget" - ); - } - - /// Zero-fallback: an unbound component is a legal address that is visibly - /// incomplete — callers ask, rather than testing components against 0. - #[test] - fn unbound_components_are_addressable_but_not_fully_bound() { - assert!(!CausalLiteral::default().is_fully_bound()); - assert!(!CausalLiteral::new(1, 1, UNBOUND, 1).is_fully_bound()); - assert!(CausalLiteral::new(1, 1, 1, 1).is_fully_bound()); - // an unbound-predicate literal is still a DISTINCT address, not a - // sentinel that collapses onto something else - assert_ne!( - CausalLiteral::new(1, 1, UNBOUND, 1), - CausalLiteral::new(1, 1, 1, 1) - ); - } -} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index e4e46a819..02d2a4451 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -55,7 +55,6 @@ pub mod callcenter; pub mod cam; pub mod canonical_node; pub mod causal_audit; -pub mod causal_literal; pub mod causal_witness; pub mod class_view; /// D-V3-W6a — classid adoption-scan counting logic (`ClassidForm`, From f08750f30b3725af9fe7d9fb15d64c2d5bf9492f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 16:44:13 +0000 Subject: [PATCH 3/5] Record I-STRINGS-ARE-CAM-INDEX-ONLY-1 (operator ruling) Strings in the hot path must resolve through a codebook. The only string home is the CAM index codebook (content_store / codebook.rs); never in any SoA row, value tenant, overlay, or reasoning struct. In the hot path a string may only be patient data or a KV side-car entry. Records the measured compliance audit: nars/belief.rs and stance.rs's Interner are already correct; literal_graph.rs and exploration.rs carry string identity through the live ranking and NARS-revision paths, and literal_graph.rs maintains an ad-hoc fourth codebook whose ordinal it computes and discards. Also records two corrections earned this session: predicate ordinals are (vocabulary, FnIndex) two bytes -- ogar-dismech and ogar-ro both mint into the same domain-local 0x90.. range -- and ClassRowSchema/RowField/ValueSchema exist (an earlier audit pass wrongly reported them absent); what is absent is a slot-to-ROLE mapping, not a row schema. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AZLe87ssKXGuXFbrmHdGnG --- .claude/board/EPIPHANIES.md | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 7e15b06db..749b01545 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,68 @@ +## 2026-08-20 — I-STRINGS-ARE-CAM-INDEX-ONLY-1 — strings in the hot path resolve through a codebook; the ONLY string home is the CAM index codebook; NEVER in any SoA + +**Status:** OPERATOR RULING (verbatim: *"All strings in hot path are mandatory to +use codebooks, the only occurrence of strings is in content addressable memory +index codebook / Never in any SoA"*; sharpened same message: *"In hot path, only +patient data or KV Side Car table"*). **Confidence:** High — iron rule. + +**The rule.** In the hot path a semantic identity is an ORDINAL. A `String` may +exist in exactly two places: (a) the **CAM index codebook** — the +content-addressed cold store / family codebook that maps bytes or a label to its +ordinal; (b) a **KV side-car table** or patient data. A string may NEVER live in +an SoA row, a value tenant, an overlay, or any reasoning-path struct. + +**The sanctioned homes already exist — this rule is not a request for new +machinery, it is a demand to stop bypassing what is built:** +- `content_store.rs` — content-addressed cold text/blob store, `ContentAddress` + = fnv1a-64 of the stored bytes, write side membrane-only. This IS the CAM index. +- `codebook.rs` — `family -> Codebook`, <=255 entries, 1-byte in-family index, + index 0 reserved as the `EdgeBlock` empty-slot sentinel; a family that + outgrows 255 SPLITS rather than widening the byte. + +**Measured compliance in this repo (2026-08-20 audit).** + +COMPLIANT, do not touch: `nars/belief.rs` carries ZERO strings — `CStmt { s: u16, +cop: Copula, p: u16 }`, `Copula::Rel(u16)`; `stance.rs:51 Interner +{ map: HashMap, names: Vec }` is the correct string->ordinal +membrane feeding it; `recipes.rs` `code`/`name`/`substrate` are `&'static str` +catalogue metadata reached by `id: u8`, never used for dispatch or equality. + +VIOLATIONS: +- `literal_graph.rs:72` `label_codebook: Vec` — an ad-hoc codebook + duplicating `codebook::Codebook`'s exact shape. Worse: `ensure_label` COMPUTES + the ordinal on every `add_node`/`add_edge` and then DISCARDS it, keeping only + the `String` on the node/edge. +- `literal_graph.rs:21,25,39,41,43` — `id`/`label`/`source`/`target` as `String` + IDENTITY, plus three `HashMap` adjacency indices. +- `exploration.rs:140-142` `FrontierEdge.source/target/label: String` — the LIVE + ranking substrate (`curiosity()` + MUL-weighted next-edge choice). +- `exploration.rs:309-311` `ExplorationResult.confirmed/denied: + Vec<(String,String,String,NarsTruth)>` — an SPO triple driving NARS revision + as three heap Strings, in a repo whose NARS statement type is three `u16`s. +- Additional interners beside the sanctioned two: `deepnsm/vocabulary.rs:82` + `forms: HashMap`; `lance-graph-cognitive/fabric/gel.rs:97` + `labels: HashMap`. + +**Predicate ordinals are TWO bytes, not one.** `ogar-loco/src/lib.rs:347` sets +`DOMAIN_FLOOR = 0x90`: `0x00..=0x8F` is universal ABI forever, `0x90..=0xFF` is +DOMAIN-LOCAL. `ogar-dismech` mints `0x90..=0xA2` (19, test-pinned) and `ogar-ro` +mints `0x90..=0xA5` (22) — the SAME range. A bare `FnIndex` is therefore +ambiguous; predicate identity is `(vocabulary, FnIndex)`, or the vocabulary is +implied by the lane's classid exactly as `ogar-obo` already does it. + +**Correction recorded with the rule:** an audit pass this session reported +`ClassRowSchema`/`RowField`/`ValueSchema` as ABSENT. They exist — +`ogar-obo/src/layout.rs:36,61` and `lance_graph_contract::canonical_node::ValueSchema`. +What IS absent is narrower: a slot->ROLE mapping (subject/predicate/mediator). +`ClassRowSchema` carves FIELDS (`entity_type`, `edge_lanes`), not roles. + +**Consequence for the reasoning overlay.** An overlay stores an ordinal, a slot +position and state bits — never a label, a relation name, or a path. Slot +position and schema ARE information; re-encoding them as prose is the redundancy +this rule exists to kill. + +--- + ## 2026-08-20 — E-S3-0-NEEDED-NO-NEW-ADDRESS-1 — the Stage-3 "S3.0 address" slot is closed as NOT-NEEDED; `IdentityQuad` already carries an exact four-component identity, at u24, inside the sanctioned V3 facet **Status:** FINDING (operator-directed audit; measured against merged code). From 0a99c3565c0bfdb6289dc4afb3ade7437eabaacd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 17:14:39 +0000 Subject: [PATCH 4/5] DisMech compact evidence vocabulary + typed citation sidecar Lands only what measurement has closed. Source-side only: this module deliberately does not reference CausalEdge64 -- the durable causal overlay must not become a pile of hot reasoning registers. DismechTopology maps to CE64 bits 59..60 at hydration, in the consumer. Measured on the upstream corpus (2,100 files, fetched ephemeral, not committed): causal_link_type has exactly the four CausalTopology states over 17,998 causal edges, so the mapping is source-authoritative and never inferred. Every evidence enum is bits: supports 4, evidence_source 5, modifier 7, frequency 19. Every parse fails closed. UNKNOWN is a value the corpus asserts 408 times, so minting it from a parse failure would forge an assertion the source never made. Citation identity is (namespace, id), never a hash of the title: the corpus is LLM-generated, so wording drifts between regenerations while the citation does not. 131,904 title occurrences over 31,361 distinct (4.21x reuse), 12.12 MB inline vs 3.58 MB keyed. Where no stable identifier exists the key says so explicitly rather than synthesising a bibliographic identity. Deliberately NOT frozen: the endpoint codebook and DisMech-local identity scheme, pending the unprefixed-population probe. Recorded with the census. Gates: contract 1178/1178 (7 new), fmt clean, clippy -D warnings clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AZLe87ssKXGuXFbrmHdGnG --- .claude/board/EPIPHANIES.md | 88 +++ .claude/board/LATEST_STATE.md | 43 ++ .../src/dismech_evidence.rs | 504 ++++++++++++++++++ crates/lance-graph-contract/src/lib.rs | 1 + 4 files changed, 636 insertions(+) create mode 100644 crates/lance-graph-contract/src/dismech_evidence.rs diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 749b01545..20cee6c58 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,91 @@ +## 2026-08-20 — E-DISMECH-CORPUS-CENSUS-1 — the DisMech corpus measured: 87.2 MB of strings, of which the entire causal semantics is bits + codebook ordinals + +**Status:** FINDING (measured on upstream `monarch-initiative/dismech`, 2,100 +disorder files, fetched ephemeral to `/tmp` — never committed, per the +`/tmp`-fixture rule). **Confidence:** High — every number is a count. + +**Total string bytes across all properties: 87,222,222.** The top ~27 +properties by volume are free text (descriptions / snippets / explanations / +reference titles). Everything that carries CAUSAL SEMANTICS is tiny. + +**`causal_link_type` is EXACTLY the four `CausalTopology` states** — measured, +not assumed, so the CE64 bits 59+60 mapping is source-authoritative and needs +no inference from confidence, edge count, or predicate name: + +| state | count | +|---|---| +| DIRECT | 9,073 | +| INDIRECT_UNKNOWN_INTERMEDIATES | 4,539 | +| INDIRECT_KNOWN_INTERMEDIATES | 3,978 | +| UNKNOWN | 408 | +| **total causal edges** | **17,998** | + +**The two experimental populations this hands us (better than hiding paths):** +`IndirectKnownIntermediates` (3,978) is the ORACLE population — the source +names the mediators, so they can be hidden and recovery measured. +`IndirectUnknownIntermediates` (4,539) is the RESTRAINT CONTROL — the source +itself does not know, so a reasoner that "recovers" a mediator there is +hallucinating closure. Success is therefore two-sided: recovery sensitivity +AND epistemic restraint. + +**Every evidence enum is bits:** `supports` 4 (2 b), `evidence_source` 5 (3 b), +`modifier` 7 (3 b), `frequency` 19 (5 b), `treatment_effect` 5, +`genetic[].relationship_type` 10, `prevalence[].measure_type` 8. +⚠ `phenotypes[].category` is **261 distinct — OVER the 255 `Codebook` cap**, +and its top values include both `Neurologic` AND `Neurological`: lexical noise +inside an "enum". It needs normalization or a deliberate split, never a silent +widening (`Codebook::intern` returning `None` IS the split signal). + +**Bibliography — LLM-generated, so identity must not be the title.** 131,904 +reference-title occurrences over 31,361 distinct (4.21x reuse); 12,124,736 B +inline -> 3,051,438 B deduped (74.8% saved); with a u32 key per occurrence, +3,579,054 B total = 3.4x smaller. A stable key ALREADY exists on ~104,700 +occurrences: PMID (dominant), DOI, ORPHA, CGGV, ClinicalTrials, URL. So the +key is `(namespace, id)` — never a hash of the title, because wording drifts +between regenerations while the citation does not. + +**"MONDO-derived" applies to the DISORDERS, not the EDGE ENDPOINTS.** Measured +prefix distribution over edge endpoints: no prefix at all **25.2%**, HP 24.9%, +GO 11.6%, NCIT 10.5%, hgnc 7.6%, CL 7.3%, **MONDO only 4.4%**, UBERON 3.3%, +CHEBI 3.1%, NCBITaxon/ECTO/RO 2.0%. OBO_CORE's five namespaces cover **32.7%**. +This is the single largest correction to the DisMech-overlay plan: grounding +cannot lean on MONDO. + +**Deterministic resolution ladder — no LLM used.** Phenotypes carry NO CURIE at +all (25,120 entries, 0.0%), so the mapping must be MADE. Against the real +HP/MONDO/UBERON/PATO labels: + +| population | exact | +case/punct | +singular | RESOLVED | unresolved | +|---|---|---|---|---|---| +| phenotype labels (25,120) | 55.3% | 23.2% | 2.1% | **80.7%** | 19.3% (3,647 distinct) | +| unprefixed endpoints (30,872) | 25.6% | 9.9% | 0.9% | **36.4%** | 63.6% (11,385 distinct) | + +**The unresolved tails are three DIFFERENT kinds, not one backlog:** +(a) provenance leaking into the endpoint slot — `Orphanet` (120), `OMIM` (32), +`ClinGen` (23) are database names, not concepts; (b) qualified mechanism +PROPOSITIONS — `Impaired Terminal Electron Transfer and ATP Synthesis` (24), +`Impaired Neurodevelopment` (20) — genuinely DisMech-local, and must not be +bullied into an ontology node; (c) lexical variants of real concepts. Note +`Sensorineural Hearing Loss` and `Sensorineural hearing loss` BOTH fail, so +they are a SYNONYM miss (HPO's own label is "Sensorineural hearing +impairment"), not a case miss — the next deterministic rung is the synonym +table, before any model is invoked. + +**⚠ PHENOTYPES NEED THEIR OWN RESOLUTION DOMAIN (operator, confirmed by +measurement).** Collapsing HP with MONDO does not merely strip edges — it +MISROUTES them, which is harder to detect. Measured: **1,169 labels exist in +more than one namespace, 1,129 of them HP+MONDO**. Of 19,738 resolved +phenotype labels, **26.2% landed in MONDO rather than HP**, and **23.7% +(4,678) are genuinely ambiguous** — a collapsed resolver picks by insertion +order, silently reattaching a phenotype edge to a same-named disease node. +HP must be scoped FIRST, with any cross-namespace fallback deliberate. + +**UBERON is the capstone, measurably:** 10 collisions across 14,975 UBERON +labels = **0.07%**. Anatomy is near-collision-free exactly where phenotype and +disease are not, so it is the one layer safe to anchor against. + +--- + ## 2026-08-20 — I-STRINGS-ARE-CAM-INDEX-ONLY-1 — strings in the hot path resolve through a codebook; the ONLY string home is the CAM index codebook; NEVER in any SoA **Status:** OPERATOR RULING (verbatim: *"All strings in hot path are mandatory to diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index d49a9860f..f7d7ecac0 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,46 @@ +## 2026-08-20 — branch `claude/lance-graph-stage-3-recovery-2wrdbd` — DisMech compact evidence vocabulary + citation sidecar + +### Current Contract Inventory — 1 new zero-dep module + +- **`lance_graph_contract::dismech_evidence`** (new): + - `DismechTopology` — the four measured `causal_link_type` states, 2 bits, + `from_source`/`as_source`/`to_bits_2`/`from_bits_2`, plus + `source_knows_intermediates()` / `mediator_unresolved()` which separate the + 3,978-edge ORACLE population from the 4,539-edge RESTRAINT CONTROL. + - `Supports` (4, 2 b), `EvidenceSource` (5, 3 b) — round-tripped exhaustively. + - `CitationNamespace` (PMID/DOI/ORPHA/NCT/CGGV/URL), `CitationKey` + (`Identified{namespace,id}` | `ContentAddressed(ContentId)`), + `BibliographyRecord{key, title: ContentId}`. +- **SOURCE-SIDE ONLY — deliberately does NOT reference `CausalEdge64`.** The + durable causal overlay must not become a pile of hot reasoning registers + (operator ruling); `DismechTopology -> CE64 bits 59..60` happens at + HYDRATION, in the consumer, as a 1:1 read of `to_bits_2()`. +- **Every parse FAILS CLOSED.** `UNKNOWN` is a value the corpus asserts 408 + times, so minting it from a parse failure would forge an assertion the source + never made — pinned by + `unrecognised_topology_fails_closed_and_never_becomes_unknown`. +- **Citation identity never derives from the title** — pinned by + `reference_identity_survives_a_title_rewording`, the falsifier that matters + for an LLM-generated corpus. Where no stable identifier exists, + `CitationKey::ContentAddressed` says so explicitly rather than synthesising a + bibliographic id. + +### Gates + +`lance-graph-contract` **1178/1178** (7 new); `cargo fmt` clean; `cargo clippy +--all-targets --no-deps -D warnings` clean. + +### NOT frozen by this PR (measurement says do not) + +The endpoint codebook and the DisMech-local identity scheme stay OPEN until the +unprefixed population is probed — 63.6% of unprefixed endpoints are still +unresolved and the tail contains three different KINDS (provenance leakage, +mechanism propositions, lexical variants). Full numbers: +`E-DISMECH-CORPUS-CENSUS-1`. Also open: HP needs its own resolution domain +(23.7% of resolved phenotype labels are HP/MONDO-ambiguous), and +`phenotypes[].category` at 261 distinct exceeds the 255 `Codebook` cap. + +--- ## 2026-08-20 — branch `claude/lance-graph-stage-3-recovery-2wrdbd` — S3.0 CLOSED AS NOT-NEEDED (no new type); #973 retraction + two overclaim corrections ### Contract Inventory — NO CHANGE diff --git a/crates/lance-graph-contract/src/dismech_evidence.rs b/crates/lance-graph-contract/src/dismech_evidence.rs new file mode 100644 index 000000000..fd08cd6cd --- /dev/null +++ b/crates/lance-graph-contract/src/dismech_evidence.rs @@ -0,0 +1,504 @@ +//! `dismech_evidence` — the compact evidence vocabulary measured on the real +//! DisMech corpus, plus the typed citation sidecar. +//! +//! **Source-side only.** This module encodes what the DisMech YAML *says*. It +//! deliberately does NOT reference `CausalEdge64`: the durable causal overlay +//! must not become a pile of hot reasoning registers (operator ruling). The +//! mapping [`DismechTopology`] → CE64 bits 59..60 happens at HYDRATION, in the +//! consumer, and is a 1:1 read of [`DismechTopology::to_bits_2`]. +//! +//! # Measured, not designed (upstream `monarch-initiative/dismech`, 2,100 files) +//! +//! Every cardinality below was counted on the real corpus before this module +//! existed; each enum is exhaustively round-tripped in the tests. +//! +//! | field | states | bits | occurrences | +//! |---|---|---|---| +//! | `causal_link_type` | 4 | 2 | **17,998** | +//! | `supports` | 4 | 2 | ~89,800 | +//! | `evidence_source` | 5 | 3 | ~79,200 | +//! | `modifier` | 7 | 3 | 9,926 | +//! | `frequency` | 19 | 5 | 11,767 | +//! +//! `causal_link_type`'s four values are *exactly* the four `CausalTopology` +//! states, so the mapping is **source-authoritative** — never inferred from +//! confidence, edge count, or predicate name. +//! +//! ## Why every parse FAILS CLOSED +//! +//! An unrecognised source token returns `None`. It must never fold into a +//! neighbouring state and must never default to `Unknown`: `UNKNOWN` is a value +//! the corpus actually asserts (408 topology rows), so silently minting it from +//! a parse failure would forge an assertion the source never made. +//! +//! # The citation sidecar +//! +//! Measured: **131,904** reference-title occurrences over **31,361** distinct +//! titles (4.21× reuse); 12.12 MB inline → 3.05 MB deduped. But titles are the +//! WRONG key — the corpus is LLM-generated, so wording drifts between +//! regenerations while the citation identity does not. The corpus already +//! carries stable identifiers on ~104,700 occurrences: PMID (dominant), DOI, +//! ORPHA, CGGV, ClinicalTrials, URL. +//! +//! So a [`CitationKey`] is `(namespace, id)`, and the title is COLD content +//! reached by [`ContentId`]. Where no stable identifier exists, +//! [`CitationKey::ContentAddressed`] says so **explicitly** — a fake +//! bibliographic identity is never synthesised from a title. + +use crate::content_store::ContentId; + +/// Source `causal_link_type` — the four states, 2 bits. +/// +/// Maps 1:1 onto the CE64 `CausalTopology` ordinal at hydration; the mapping is +/// source-authoritative and is never inferred. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(u8)] +pub enum DismechTopology { + /// `DIRECT` — measured 9,073. + Direct = 0, + /// `INDIRECT_KNOWN_INTERMEDIATES` — measured 3,978. The hidden-mediator + /// oracle population: the source names the intermediates, so they can be + /// hidden and recovery measured. + IndirectKnownIntermediates = 1, + /// `INDIRECT_UNKNOWN_INTERMEDIATES` — measured 4,539. The genuine + /// known-unknown population and the epistemic-restraint control: the source + /// itself does not know the mediator, so a reasoner that "recovers" one is + /// hallucinating closure. + IndirectUnknownIntermediates = 2, + /// `UNKNOWN` — measured 408. An asserted value, never a parse fallback. + Unknown = 3, +} + +impl DismechTopology { + /// All four, ordinal order. + pub const ALL: [Self; 4] = [ + Self::Direct, + Self::IndirectKnownIntermediates, + Self::IndirectUnknownIntermediates, + Self::Unknown, + ]; + + /// Parse the source token. **Fails closed** — no folding, no default. + #[must_use] + pub fn from_source(s: &str) -> Option { + Some(match s { + "DIRECT" => Self::Direct, + "INDIRECT_KNOWN_INTERMEDIATES" => Self::IndirectKnownIntermediates, + "INDIRECT_UNKNOWN_INTERMEDIATES" => Self::IndirectUnknownIntermediates, + "UNKNOWN" => Self::Unknown, + _ => return None, + }) + } + + /// The exact source token — round-trips [`from_source`](Self::from_source). + #[must_use] + pub const fn as_source(self) -> &'static str { + match self { + Self::Direct => "DIRECT", + Self::IndirectKnownIntermediates => "INDIRECT_KNOWN_INTERMEDIATES", + Self::IndirectUnknownIntermediates => "INDIRECT_UNKNOWN_INTERMEDIATES", + Self::Unknown => "UNKNOWN", + } + } + + /// The 2-bit ordinal. Consumers map this onto CE64 bits 59..60; this module + /// never does, so the source bake stays free of hot registers. + #[must_use] + pub const fn to_bits_2(self) -> u8 { + self as u8 + } + + /// Inverse of [`to_bits_2`](Self::to_bits_2). Fails closed above 3. + #[must_use] + pub const fn from_bits_2(b: u8) -> Option { + Some(match b { + 0 => Self::Direct, + 1 => Self::IndirectKnownIntermediates, + 2 => Self::IndirectUnknownIntermediates, + 3 => Self::Unknown, + _ => return None, + }) + } + + /// Does the SOURCE claim to know the intermediates? + /// + /// True only for [`IndirectKnownIntermediates`](Self::IndirectKnownIntermediates). + /// This is what separates the oracle population from the restraint control: + /// for the other three a recovered mediator is not a success, it is an + /// unsupported claim. + #[must_use] + pub const fn source_knows_intermediates(self) -> bool { + matches!(self, Self::IndirectKnownIntermediates) + } + + /// Is the mediator slot legitimately unresolved in the SOURCE? + #[must_use] + pub const fn mediator_unresolved(self) -> bool { + matches!(self, Self::IndirectUnknownIntermediates | Self::Unknown) + } +} + +/// Source `supports` — 4 states, 2 bits. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum Supports { + /// `SUPPORT`. + Support = 0, + /// `PARTIAL`. + Partial = 1, + /// `REFUTE`. + Refute = 2, + /// `NO_EVIDENCE`. + NoEvidence = 3, +} + +impl Supports { + /// All four. + pub const ALL: [Self; 4] = [Self::Support, Self::Partial, Self::Refute, Self::NoEvidence]; + + /// Parse; fails closed. + #[must_use] + pub fn from_source(s: &str) -> Option { + Some(match s { + "SUPPORT" => Self::Support, + "PARTIAL" => Self::Partial, + "REFUTE" => Self::Refute, + "NO_EVIDENCE" => Self::NoEvidence, + _ => return None, + }) + } + + /// The exact source token. + #[must_use] + pub const fn as_source(self) -> &'static str { + match self { + Self::Support => "SUPPORT", + Self::Partial => "PARTIAL", + Self::Refute => "REFUTE", + Self::NoEvidence => "NO_EVIDENCE", + } + } + + /// 2-bit ordinal. + #[must_use] + pub const fn to_bits_2(self) -> u8 { + self as u8 + } +} + +/// Source `evidence_source` — 5 states, 3 bits. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum EvidenceSource { + /// `HUMAN_CLINICAL`. + HumanClinical = 0, + /// `MODEL_ORGANISM`. + ModelOrganism = 1, + /// `IN_VITRO`. + InVitro = 2, + /// `COMPUTATIONAL`. + Computational = 3, + /// `OTHER`. + Other = 4, +} + +impl EvidenceSource { + /// All five. + pub const ALL: [Self; 5] = [ + Self::HumanClinical, + Self::ModelOrganism, + Self::InVitro, + Self::Computational, + Self::Other, + ]; + + /// Parse; fails closed. `OTHER` is an asserted value, never a fallback. + #[must_use] + pub fn from_source(s: &str) -> Option { + Some(match s { + "HUMAN_CLINICAL" => Self::HumanClinical, + "MODEL_ORGANISM" => Self::ModelOrganism, + "IN_VITRO" => Self::InVitro, + "COMPUTATIONAL" => Self::Computational, + "OTHER" => Self::Other, + _ => return None, + }) + } + + /// The exact source token. + #[must_use] + pub const fn as_source(self) -> &'static str { + match self { + Self::HumanClinical => "HUMAN_CLINICAL", + Self::ModelOrganism => "MODEL_ORGANISM", + Self::InVitro => "IN_VITRO", + Self::Computational => "COMPUTATIONAL", + Self::Other => "OTHER", + } + } + + /// 3-bit ordinal. + #[must_use] + pub const fn to_bits_3(self) -> u8 { + self as u8 + } +} + +/// Where a citation's identity comes from. +/// +/// The namespace is part of the KEY, not a hint — `PMID:123` and `ORPHA:123` +/// are different citations. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(u8)] +pub enum CitationNamespace { + /// PubMed id — dominant in the corpus. + Pmid = 0, + /// DOI. + Doi = 1, + /// Orphanet. + Orpha = 2, + /// ClinicalTrials.gov (`NCT…`). + ClinicalTrials = 3, + /// ClinGen/CGGV. + Cggv = 4, + /// A bare URL. + Url = 5, +} + +impl CitationNamespace { + /// All six. + pub const ALL: [Self; 6] = [ + Self::Pmid, + Self::Doi, + Self::Orpha, + Self::ClinicalTrials, + Self::Cggv, + Self::Url, + ]; + + /// Parse a CURIE prefix (case-insensitive); fails closed. + #[must_use] + pub fn from_prefix(p: &str) -> Option { + Some(match p.to_ascii_uppercase().as_str() { + "PMID" => Self::Pmid, + "DOI" => Self::Doi, + "ORPHA" => Self::Orpha, + "NCT" | "CLINICALTRIALS" => Self::ClinicalTrials, + "CGGV" => Self::Cggv, + "URL" | "HTTP" | "HTTPS" => Self::Url, + _ => return None, + }) + } + + /// The canonical prefix. + #[must_use] + pub const fn prefix(self) -> &'static str { + match self { + Self::Pmid => "PMID", + Self::Doi => "DOI", + Self::Orpha => "ORPHA", + Self::ClinicalTrials => "NCT", + Self::Cggv => "CGGV", + Self::Url => "URL", + } + } +} + +/// The stable identity of one cited source. +/// +/// **Identity never derives from the title.** The corpus is LLM-generated, so +/// title wording drifts between regenerations while the citation does not — +/// pinned by `reference_identity_survives_a_title_rewording`. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum CitationKey { + /// A real, stable external identifier. + Identified { + /// Which identifier space. + namespace: CitationNamespace, + /// The bare id, prefix stripped (e.g. `"25252"`). + id: String, + }, + /// No stable identifier exists — the content address of the citation text, + /// **explicitly marked as such**. Never a synthesised bibliographic id. + ContentAddressed(ContentId), +} + +impl CitationKey { + /// Parse a source `reference` value such as `"PMID:25252"`. + /// + /// Falls back to [`ContentAddressed`](Self::ContentAddressed) when the value + /// carries no recognised namespace — deliberately explicit, so a consumer + /// can always tell a real citation from a hashed string. + #[must_use] + pub fn parse(raw: &str) -> Self { + let t = raw.trim(); + if let Some((p, rest)) = t.split_once(':') { + if let Some(ns) = CitationNamespace::from_prefix(p) { + let id = if matches!(ns, CitationNamespace::Url) { + t.to_string() + } else { + rest.trim().to_string() + }; + if !id.is_empty() { + return Self::Identified { namespace: ns, id }; + } + } + } + Self::ContentAddressed(ContentId::of_str(t)) + } + + /// Does this key rest on a real external identifier? + #[must_use] + pub const fn is_identified(&self) -> bool { + matches!(self, Self::Identified { .. }) + } +} + +/// One bibliography row: a stable key, with the title held as COLD content. +/// +/// The title is never stored inline — 131,904 occurrences over 31,361 distinct +/// titles measured, so inlining costs 12.12 MB to say 3.05 MB. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BibliographyRecord { + /// Stable identity. + pub key: CitationKey, + /// CAM handle for the title text. [`ContentId::EMPTY`]-equivalent (`0`) when + /// no title was supplied. + pub title: ContentId, +} + +impl BibliographyRecord { + /// Build from a raw `reference` and its (possibly drifting) title. + #[must_use] + pub fn new(reference: &str, title: &str) -> Self { + Self { + key: CitationKey::parse(reference), + title: ContentId::of_str(title), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Every source token round-trips, and the ordinal is stable. + #[test] + fn topology_round_trips_every_source_value() { + for t in DismechTopology::ALL { + assert_eq!(DismechTopology::from_source(t.as_source()), Some(t)); + assert_eq!(DismechTopology::from_bits_2(t.to_bits_2()), Some(t)); + } + // anti-vacuity: the four are genuinely distinct ordinals + let mut bits: Vec = DismechTopology::ALL.iter().map(|t| t.to_bits_2()).collect(); + bits.sort_unstable(); + bits.dedup(); + assert_eq!(bits.len(), 4, "topology ordinals collided"); + } + + /// FAIL CLOSED — and specifically, never mint `Unknown`, which the corpus + /// asserts 408 times and which would forge an assertion. + #[test] + fn unrecognised_topology_fails_closed_and_never_becomes_unknown() { + for bad in [ + "", + "direct", + "INDIRECT", + "INDIRECT_KNOWN", + "UNKNOWN_INTERMEDIATES", + "unknown", + ] { + assert_eq!( + DismechTopology::from_source(bad), + None, + "{bad:?} must not parse" + ); + } + assert_eq!(DismechTopology::from_bits_2(4), None); + // the silence half: the real token still parses + assert_eq!( + DismechTopology::from_source("UNKNOWN"), + Some(DismechTopology::Unknown) + ); + } + + /// The two experimental populations are distinguishable, which is what makes + /// the hidden-mediator test and its restraint control separable. + #[test] + fn only_indirect_known_claims_the_source_knows_intermediates() { + assert!(DismechTopology::IndirectKnownIntermediates.source_knows_intermediates()); + for t in [ + DismechTopology::Direct, + DismechTopology::IndirectUnknownIntermediates, + DismechTopology::Unknown, + ] { + assert!(!t.source_knows_intermediates(), "{t:?} must not claim it"); + } + assert!(DismechTopology::IndirectUnknownIntermediates.mediator_unresolved()); + assert!(!DismechTopology::Direct.mediator_unresolved()); + } + + #[test] + fn supports_and_evidence_source_round_trip_every_value() { + for s in Supports::ALL { + assert_eq!(Supports::from_source(s.as_source()), Some(s)); + assert!(s.to_bits_2() < 4); + } + for e in EvidenceSource::ALL { + assert_eq!(EvidenceSource::from_source(e.as_source()), Some(e)); + assert!(e.to_bits_3() < 8); + } + assert_eq!(Supports::from_source("MAYBE"), None); + assert_eq!(EvidenceSource::from_source("HUMAN"), None); + } + + /// THE LLM-DRIFT FALSIFIER: identity is the citation, not the wording. + #[test] + fn reference_identity_survives_a_title_rewording() { + let a = BibliographyRecord::new( + "PMID:25252", + "Insulin resistance in type 2 diabetes mellitus", + ); + let b = BibliographyRecord::new( + "PMID:25252", + "Insulin Resistance in Type 2 Diabetes Mellitus: A Review", + ); + assert_eq!(a.key, b.key, "citation identity must survive a re-wording"); + assert_ne!(a.title, b.title, "the titles genuinely differ"); + // …and a different citation is a different key even with the same title + let c = BibliographyRecord::new( + "PMID:99999", + "Insulin resistance in type 2 diabetes mellitus", + ); + assert_ne!(a.key, c.key); + assert_eq!(a.title, c.title); + } + + /// The namespace is part of the key — same digits, different source. + #[test] + fn namespace_is_part_of_the_identity() { + assert_ne!( + CitationKey::parse("PMID:123"), + CitationKey::parse("ORPHA:123") + ); + for ns in CitationNamespace::ALL { + assert_eq!(CitationNamespace::from_prefix(ns.prefix()), Some(ns)); + } + assert_eq!(CitationNamespace::from_prefix("PUBMED"), None); + } + + /// An unidentified citation says so, rather than pretending to a + /// bibliographic id synthesised from its title. + #[test] + fn unidentified_citations_are_explicitly_content_addressed() { + let k = CitationKey::parse("Smith et al, personal communication"); + assert!( + !k.is_identified(), + "must not claim a bibliographic identity" + ); + assert!(matches!(k, CitationKey::ContentAddressed(_))); + // deterministic + assert_eq!(k, CitationKey::parse("Smith et al, personal communication")); + assert!(CitationKey::parse("PMID:25252").is_identified()); + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 02d2a4451..c1c88b5ce 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -77,6 +77,7 @@ pub mod cycle_accumulator; /// `SquishedDawg` (dict/dawg binary loader) — Tesseract dictionary word-graph /// content store. See module docs for the `dawg.{h,cpp}` byte-parity scope. pub mod dawg; +pub mod dismech_evidence; pub mod dispatch_guard; pub mod dispatch_mode; pub mod distance; From b38fe21c213af5e5574258dd4bad2ee8ded07a3d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 17:48:27 +0000 Subject: [PATCH 5/5] Fix two P1 review blockers in dismech_evidence + correct the stale HP board claim P1 (1) Unknown was being turned into a known mediator hole. mediator_unresolved() matched IndirectUnknownIntermediates | Unknown, which conflates two epistemically different statements: IndirectUnknown establishes the mediator ROLE and leaves only its identity open (A -> ? -> B), while Unknown leaves direct-vs-indirect itself undecided (A ? B), so dispatching SeekMediator for it MINTS a schema slot the source never asserted. Narrowed to IndirectUnknownIntermediates and added topology_unresolved() for the Unknown case, so the two genuinely-unresolved states are partitioned rather than merged (403 vs 4,325 -- a ~10.7x population difference a merged predicate hid). Measured while fixing, and recorded on the predicate: the topology ordinal alone does NOT imply a mediator exists. Cross-tabbed on the upstream corpus (2,100 files, pathophysiology[].downstream[]): of 3,844 edges declaring INDIRECT_KNOWN_INTERMEDIATES, 1,347 (35.0%) carry zero intermediate_mechanisms, and 92 declaring INDIRECT_UNKNOWN carry some anyway. The usable hidden-mediator oracle population is 2,497 distinct (disease, source, target, mediators) tuples across 539 diseases -- deduped, raw and distinct coincide, so no many-to-many mention inflation. A consumer must filter on the actual intermediate list. P1 (2) the citation sentinel contract was false in two places. fnv1a(b"") is the nonzero FNV offset basis, so of_str("") minted a well-formed content address meaning "nothing supplied" -- indistinguishable on replay from a real citation and colliding every uncited row onto one identity. CitationKey:: parse now returns Option and refuses blank input; BibliographyRecord::new returns Option and writes the reserved ContentId(0) sentinel for a blank title. Also fixed a broken doc link to a nonexistent ContentId::EMPTY. Both fixes carry falsifiers, each disable-verified red-then-green against the exact assertion message. Board: the "PHENOTYPES NEED THEIR OWN RESOLUTION DOMAIN" conclusion is regraded in place (append-only, measurements untouched). quad_tenant.rs already ships Domain with 8 variants, a classid->domain map, an enforced classid+TUI two-witness contract, and FacetRegime::PerRowTui for the CUI horseshoe -- so HP is Domain::Phenomenology's currently populated vocabulary, not a new domain, and Anatomy is UBERON + FMA with UBERON a strong anchor rather than the domain itself. Left as written it is how a later session mints HpResolutionDomain beside the existing type. Gates: lance-graph-contract 1180/1180, fmt clean, clippy --all-targets --no-deps -D warnings clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AZLe87ssKXGuXFbrmHdGnG --- .claude/board/EPIPHANIES.md | 34 +++ .claude/board/LATEST_STATE.md | 2 +- .../src/dismech_evidence.rs | 196 ++++++++++++++++-- 3 files changed, 212 insertions(+), 20 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 20cee6c58..5526d7d98 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -84,6 +84,40 @@ HP must be scoped FIRST, with any cross-namespace fallback deliberate. labels = **0.07%**. Anatomy is near-collision-free exactly where phenotype and disease are not, so it is the one layer safe to anchor against. +> **⊘ ARCHITECTURAL CONCLUSION CORRECTED (2026-08-20, same day, measured).** +> **The MEASUREMENTS above stand unchanged** — 1,169 multi-namespace labels, +> 26.2% mis-landing, 23.7% ambiguous, UBERON at 0.07%. What is corrected is +> the word **"OWN"**: the sanctioned abstraction already exists and this entry +> did not know it. +> +> `medcare-cohorts/src/quad_tenant.rs` ships `Domain` (8 variants) with a +> classid→domain map, an enforced two-witness contract (classid AND TUI must +> agree), and `FacetRegime::PerRowTui` for the CUI horseshoe: +> +> | Domain | vocabularies mapped | +> |---|---| +> | `Domain::Phenomenology` | HP | +> | `Domain::Anatomy` | **UBERON + FMA** | +> | `Domain::Disease` | MONDO + ICD-10-GM + ORDO + OMIM + DECIPHER | +> | `Domain::Lab` / `Substance` / `Procedure` | LOINC / ATC+RxNorm+Gelbe Liste / OPS | +> | `Imaging`, `BiologicalProcess` | declared, no vocabulary yet | +> +> So the corrected statements are: +> +> - *Phenotype resolution must be constrained to the existing +> `Domain::Phenomenology`; **HP is its currently populated single-facet +> vocabulary, not a new resolution domain.*** +> - *Anatomy resolution targets `Domain::Anatomy` (UBERON **and** FMA); +> UBERON's 0.07% collision rate makes it a strong current +> projection/anchor, **not the semantic domain itself**.* +> +> **Why this pedantry earns its place:** the board is executable archaeology. +> Left as written, "HP needs its own resolution domain" is precisely how a +> later session mints an `HpResolutionDomain` beside the already-built +> `Domain::Phenomenology` — the duplicate-vocabulary failure this workspace +> has now recorded several times. Append-only: the original claim is regraded +> here, not deleted. + --- ## 2026-08-20 — I-STRINGS-ARE-CAM-INDEX-ONLY-1 — strings in the hot path resolve through a codebook; the ONLY string home is the CAM index codebook; NEVER in any SoA diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index f7d7ecac0..5808e50a2 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -36,7 +36,7 @@ The endpoint codebook and the DisMech-local identity scheme stay OPEN until the unprefixed population is probed — 63.6% of unprefixed endpoints are still unresolved and the tail contains three different KINDS (provenance leakage, mechanism propositions, lexical variants). Full numbers: -`E-DISMECH-CORPUS-CENSUS-1`. Also open: HP needs its own resolution domain +`E-DISMECH-CORPUS-CENSUS-1`. Also open: phenotype resolution must be scoped to the EXISTING `Domain::Phenomenology` (HP is its populated vocabulary, not a new domain — see the ⊘ correction on that entry) (23.7% of resolved phenotype labels are HP/MONDO-ambiguous), and `phenotypes[].category` at 261 distinct exceeds the 255 `Codebook` cap. diff --git a/crates/lance-graph-contract/src/dismech_evidence.rs b/crates/lance-graph-contract/src/dismech_evidence.rs index fd08cd6cd..c89f76ad2 100644 --- a/crates/lance-graph-contract/src/dismech_evidence.rs +++ b/crates/lance-graph-contract/src/dismech_evidence.rs @@ -132,9 +132,63 @@ impl DismechTopology { } /// Is the mediator slot legitimately unresolved in the SOURCE? + /// + /// **[`Unknown`](Self::Unknown) is deliberately NOT included**, and the + /// distinction is epistemic rather than cosmetic: + /// + /// ```text + /// IndirectUnknownIntermediates A -> ? -> B + /// the path is known indirect, the mediator ROLE is established, + /// only its IDENTITY is missing => SeekMediator is warranted + /// + /// Unknown A ? B + /// direct-vs-indirect is itself undecided, so the mediator role is + /// not established at all => SeekMediator would MINT a schema + /// slot the source never asserted + /// ``` + /// + /// Conflating them lets `Unknown` dispatch a mediator search for a hole + /// nobody claimed exists. Ask [`topology_unresolved`](Self::topology_unresolved) + /// for that case instead. + /// + /// **This predicate answers what the SOURCE asserts, never whether a + /// mediator is actually present**, and on real data those differ sharply. + /// + /// Measured on `monarch-initiative/dismech` (2,100 disorder files), + /// **scoped to `pathophysiology[].downstream[]`** — the edge list that + /// actually carries `intermediate_mechanisms`. This is a SUBSET of the + /// corpus-wide `causal_link_type` census in the module header (which counts + /// the field wherever it occurs), so the totals differ by scope, not by + /// disagreement: 3,844 here against 3,978 corpus-wide. + /// + /// | declared | with ≥1 intermediate | with ZERO | + /// |---|---|---| + /// | `INDIRECT_KNOWN_INTERMEDIATES` (3,844) | 2,497 | **1,347 = 35.0%** | + /// | `INDIRECT_UNKNOWN_INTERMEDIATES` (4,325) | 92 (contradictory) | 4,233 | + /// + /// So a third of the edges that CLAIM known intermediates list none, and a + /// few that claim none list some. A consumer building the hidden-mediator + /// oracle must filter on the ACTUAL intermediate list, never on the topology + /// ordinal alone: the usable population is **2,497 distinct + /// `(disease, source, target, mediators)` tuples across 539 diseases** + /// (deduped — the raw and distinct counts coincide, so no many-to-many + /// mention inflation), not 3,844. #[must_use] pub const fn mediator_unresolved(self) -> bool { - matches!(self, Self::IndirectUnknownIntermediates | Self::Unknown) + matches!(self, Self::IndirectUnknownIntermediates) + } + + /// Is the TOPOLOGY ITSELF unresolved — i.e. the source has not decided + /// direct vs indirect, so no mediator role is established? + /// + /// The companion to [`mediator_unresolved`](Self::mediator_unresolved): + /// together they partition the two genuinely-unresolved states without + /// collapsing them. Measured 403 `UNKNOWN` against 4,325 + /// `INDIRECT_UNKNOWN_INTERMEDIATES` — a ~10.7x population difference that a + /// merged predicate would hide. + #[must_use] + pub const fn topology_unresolved(self) -> bool { + matches!(self, Self::Unknown) } } @@ -329,9 +383,22 @@ impl CitationKey { /// Falls back to [`ContentAddressed`](Self::ContentAddressed) when the value /// carries no recognised namespace — deliberately explicit, so a consumer /// can always tell a real citation from a hashed string. + /// + /// Returns `None` for a blank or whitespace-only `raw`. **Absence is made + /// structurally unrepresentable**, because the alternative is worse than it + /// looks: `fnv1a(b"")` is the nonzero FNV offset basis + /// `0xcbf2_9ce4_8422_2325`, so hashing `""` would mint a perfectly + /// well-formed [`ContentAddressed`](Self::ContentAddressed) key meaning + /// "no citation supplied" — indistinguishable on replay from a real + /// content-addressed citation, and colliding every uncited row onto one + /// shared identity. `ContentId(0)` is the reserved sentinel + /// ([`ContentId::is_sentinel`]), and `of_str("")` does not produce it. #[must_use] - pub fn parse(raw: &str) -> Self { + pub fn parse(raw: &str) -> Option { let t = raw.trim(); + if t.is_empty() { + return None; + } if let Some((p, rest)) = t.split_once(':') { if let Some(ns) = CitationNamespace::from_prefix(p) { let id = if matches!(ns, CitationNamespace::Url) { @@ -340,11 +407,11 @@ impl CitationKey { rest.trim().to_string() }; if !id.is_empty() { - return Self::Identified { namespace: ns, id }; + return Some(Self::Identified { namespace: ns, id }); } } } - Self::ContentAddressed(ContentId::of_str(t)) + Some(Self::ContentAddressed(ContentId::of_str(t))) } /// Does this key rest on a real external identifier? @@ -362,19 +429,31 @@ impl CitationKey { pub struct BibliographyRecord { /// Stable identity. pub key: CitationKey, - /// CAM handle for the title text. [`ContentId::EMPTY`]-equivalent (`0`) when - /// no title was supplied. + /// CAM handle for the title text, or the reserved sentinel `ContentId(0)` + /// (see [`ContentId::is_sentinel`]) when no title was supplied. + /// + /// The sentinel must be written explicitly: `of_str("")` hashes the empty + /// byte string to the nonzero FNV offset basis, so an untitled record would + /// otherwise carry a real-looking content address that no store can resolve. pub title: ContentId, } impl BibliographyRecord { /// Build from a raw `reference` and its (possibly drifting) title. + /// + /// `None` when `reference` is blank — a bibliography row with no citation + /// is not a row (see [`CitationKey::parse`]). A blank `title` is legal and + /// lands on the `ContentId(0)` sentinel rather than on `fnv1a(b"")`. #[must_use] - pub fn new(reference: &str, title: &str) -> Self { - Self { - key: CitationKey::parse(reference), - title: ContentId::of_str(title), - } + pub fn new(reference: &str, title: &str) -> Option { + Some(Self { + key: CitationKey::parse(reference)?, + title: if title.trim().is_empty() { + ContentId(0) + } else { + ContentId::of_str(title) + }, + }) } } @@ -458,18 +537,21 @@ mod tests { let a = BibliographyRecord::new( "PMID:25252", "Insulin resistance in type 2 diabetes mellitus", - ); + ) + .expect("a real citation"); let b = BibliographyRecord::new( "PMID:25252", "Insulin Resistance in Type 2 Diabetes Mellitus: A Review", - ); + ) + .expect("a real citation"); assert_eq!(a.key, b.key, "citation identity must survive a re-wording"); assert_ne!(a.title, b.title, "the titles genuinely differ"); // …and a different citation is a different key even with the same title let c = BibliographyRecord::new( "PMID:99999", "Insulin resistance in type 2 diabetes mellitus", - ); + ) + .expect("a real citation"); assert_ne!(a.key, c.key); assert_eq!(a.title, c.title); } @@ -478,8 +560,8 @@ mod tests { #[test] fn namespace_is_part_of_the_identity() { assert_ne!( - CitationKey::parse("PMID:123"), - CitationKey::parse("ORPHA:123") + CitationKey::parse("PMID:123").unwrap(), + CitationKey::parse("ORPHA:123").unwrap() ); for ns in CitationNamespace::ALL { assert_eq!(CitationNamespace::from_prefix(ns.prefix()), Some(ns)); @@ -491,14 +573,90 @@ mod tests { /// bibliographic id synthesised from its title. #[test] fn unidentified_citations_are_explicitly_content_addressed() { - let k = CitationKey::parse("Smith et al, personal communication"); + let k = CitationKey::parse("Smith et al, personal communication") + .expect("non-blank input parses"); assert!( !k.is_identified(), "must not claim a bibliographic identity" ); assert!(matches!(k, CitationKey::ContentAddressed(_))); // deterministic - assert_eq!(k, CitationKey::parse("Smith et al, personal communication")); - assert!(CitationKey::parse("PMID:25252").is_identified()); + assert_eq!( + k, + CitationKey::parse("Smith et al, personal communication").unwrap() + ); + assert!(CitationKey::parse("PMID:25252").unwrap().is_identified()); + } + /// P1 FALSIFIER — `Unknown` must NOT dispatch a mediator search. + /// + /// `IndirectUnknownIntermediates` establishes the mediator ROLE and leaves + /// its identity open; `Unknown` leaves direct-vs-indirect itself undecided, + /// so treating it as an unresolved mediator MINTS a schema slot the source + /// never asserted. Two-sided, and the silence half is the point. + #[test] + fn unknown_topology_is_not_an_unresolved_mediator() { + assert!(DismechTopology::IndirectUnknownIntermediates.mediator_unresolved()); + assert!( + !DismechTopology::Unknown.mediator_unresolved(), + "Unknown must not claim a mediator hole the source never established" + ); + // …and the companion predicate covers it instead, exclusively. + assert!(DismechTopology::Unknown.topology_unresolved()); + assert!(!DismechTopology::IndirectUnknownIntermediates.topology_unresolved()); + // the two resolved states answer NEITHER + for t in [ + DismechTopology::Direct, + DismechTopology::IndirectKnownIntermediates, + ] { + assert!(!t.mediator_unresolved(), "{t:?} is not mediator-unresolved"); + assert!(!t.topology_unresolved(), "{t:?} is not topology-unresolved"); + } + // anti-vacuity: the two predicates are disjoint and neither is constant + let any_med = DismechTopology::ALL + .iter() + .filter(|t| t.mediator_unresolved()) + .count(); + let any_top = DismechTopology::ALL + .iter() + .filter(|t| t.topology_unresolved()) + .count(); + assert_eq!( + (any_med, any_top), + (1, 1), + "each predicate names exactly one state" + ); + } + + /// P1 FALSIFIER — absence must not become a well-formed content address. + /// + /// `fnv1a(b"")` is the nonzero FNV offset basis, so hashing a blank would + /// mint a real-looking citation meaning "none supplied" and collide every + /// uncited row onto it. + #[test] + fn a_blank_citation_is_absent_not_content_addressed() { + for blank in ["", " ", "\t\n "] { + assert_eq!( + CitationKey::parse(blank), + None, + "blank {blank:?} must not parse to a citation" + ); + assert_eq!(BibliographyRecord::new(blank, "a title"), None); + } + // the trap this guards, stated as a measurement rather than a memory: + assert_ne!( + ContentId::of_str("").0, + 0, + "of_str(\"\") is the FNV offset basis, NOT the sentinel" + ); + // a blank TITLE is legal, and lands on the sentinel rather than the basis + let r = BibliographyRecord::new("PMID:25252", " ").expect("real citation"); + assert!( + r.title.is_sentinel(), + "an untitled record uses ContentId(0)" + ); + assert_ne!(r.title, ContentId::of_str(""), "must not be the empty hash"); + // anti-vacuity: a real title is NOT the sentinel + let t = BibliographyRecord::new("PMID:25252", "A real title").expect("real citation"); + assert!(!t.title.is_sentinel()); } }