diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 0644cf751..f47a71290 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,46 @@ +## 2026-08-21 — 5+3 council: D-ACR-7 (the 59..63 reading contract) — RATIFIED + +**Run shape:** Phase 0 spec (main thread) → 5 savants parallel (all Sonnet: +prior-art-savant, iron-rule-savant, code-truth via general-purpose, +cascade-impact-savant, creative-explorer-savant) → consolidate to draft v2 → +3 reviewers parallel on v2 ONLY (Sonnet: overclaim-auditor, +dilution-collapse-sentinel, firewall-warden) → Phase-4 fixes → v3 ratified. +**Incident:** reviewers 1+3 lost to a container restart (no notification, +`ListAgents` empty) and recast against the surviving draft v2 — verdicts were +never invented in the gap. + +**Verdict counts:** savants 39 findings (code-truth 6/6 CONFIRMS — the +inventory held). Reviewer 2: 8 PASS, 1 FIX(P2). Reviewer 3: 6 PASS, 1 +BLOCK(P0), 1 FIX(P1). Reviewer 1: 4 PASS, 2 BLOCK(P0), 2 FIX(P1), 1 FIX(P2). +**3 BLOCKs raised, 3 resolved in Phase 4, none argued away, none needing a +Phase-0 re-spec.** + +**v1→v2:** dual-carrier scope (operator: CE64 = muscle memory, CausalEdgeV3 = +granularity — a gap no savant could catch, the v1 spec never named V3); +Result-vs-total split (total `ClassView::band_reading` lookup / fallible +`BandReading::project`); `Option` for the opted-out-vs-never- +considered audit; writer-side `admits()`; G3′/G4′ can-stay-silent halves; G9 +(AP1 watch), G10 (carrier parity). + +**v2→v3 (the fixes):** §6′ board-hygiene commitment restored (firewall BLOCK — +Phase 2 had dropped Savant 4's answer entirely); `V3Register` redefined as a +caller assertion after `from_v1`'s provenance-agnostic bit copy falsified +"V3's bytes were never temporal" for populated instances (overclaim BLOCK); +G5 split G5a/G5b + tautological G10a deleted, G10b (lift-fidelity test in +`causal-edge`, a measured missing test) kept (overclaim BLOCK); L6 bullets +given file:line rigor; `admits()`-"cannot" grounded in +`causal-edge/Cargo.toml:20-23`; the 319-322 citation actually applied. + +**Post-ratification operator addendum:** all V3-generation carriers confirmed +unwired/planned; the temporal doctrine (implicit in the epistemic pothole via +Lance versions; explicit ONLY in Rubikon revision / CausalEdgeV3 TE byte / +future attention-v3; `EdgeProvenance` is layout epoch, never time). + +**Artifacts:** `.claude/plans/dacr7-band-reading-contract-v1.md` (v1+v2+v3 +ledgers, single file). Board: EPIPHANIES +E-FROM-V1-DROPS-PROVENANCE-…-1, STATUS_BOARD D-ACR-7 → Spec RATIFIED. +Implementation (the `band_reading` module + gates G1..G10b) is the next unit. + ## 2026-08-19 — oracle-boundary reassessment sweep (6 agents, workflow, orchestrator-consolidated) Operator-directed after two superseding architecture rulings (the diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index f3913dbc4..7fe35cffc 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -78,6 +78,138 @@ a hard, loud cap on one side**. D-ACR-1 must state its basis and its composition operator before composing, or it inherits one of two accidents: loud refusal past 256 (borrowing the wide cap) or silent truncation past 64 (borrowing the narrow rule). +## 2026-08-21 — E-FROM-V1-DROPS-PROVENANCE-AND-THE-COUNCIL-CAUGHT-THE-CONTRACT-ABOUT-TO-TRUST-IT-1 — three BLOCK(P0)s in one 5+3 run, and the sharpest one falsified the spec's own asymmetry claim against the code it cited + +**Status:** FINDING (D-ACR-7 council, spec ratified as +`.claude/plans/dacr7-band-reading-contract-v1.md`; every claim verified at +source in Phase 4). **Confidence:** High. + +The D-ACR-7 reading contract (which lens wrote CE64 bits 59..63 — +`TrustTexture` vs `CausalTopology` on 59-60, `ReasoningBand` presence on +61-63) went through the full 5+3 council. Three BLOCK(P0)s were raised and all +three survived verification; none was argued away. + +**BLOCK 1 (overclaim, the consequential one): "V3's bytes were never temporal" +is false for populated instances.** `CausalEdgeV3::from_v1(e, target)` +(`edge_v3.rs:117`) has **no provenance parameter** and copies truth/spare as a +raw bit copy (`:138-139`). The reassuring comment above it — "under the v1 +layout every one of these accessors is a documented zero stub" — is a +**compile-time feature condition, not a runtime provenance guarantee**: a CE64 +of v1/unknown provenance (bits 61-63 aliasing `temporal >= 512`) lifted under +a v2 build carries the stale bits into V3 byte 9, and the result is +**indistinguishable from a clean register**. The draft had declared +`EdgeProvenance::V3Register` "always readable" — the exact plausible-wrong- +answer the contract exists to refuse, at its own core. Resolution: +`V3Register` means *"the caller asserts this register was minted clean"*, +never *"V3 registers are clean"*; unstated origin = `Unknown` = refuse; the +`from_v1` provenance drop is filed as a `causal-edge` follow-up, not patched +from the contract side. The v1 trap thus applies to BOTH carriers — on CE64 +directly, on V3 **transitively through the lift**. + +**BLOCK 2 (overclaim): a gate contradicted the fix that preceded it, and +another gate was a tautology.** After the council split the resolver +(total `ClassView::band_reading` lookup / fallible `BandReading::project`), +G5's carried-forward text ("an undeclared one errors") contradicted the total +half — split into G5a (total: zero-fallback, no error) / G5b (fallible: +`Err(UndeclaredClass)` must fire). And G10a ("project is a pure function of +its arguments") fed the same input to the same function twice — the +vacuous-assertion house pattern, deleted. Only G10b survives: compare +`CausalEdge64::truth()` vs `CausalEdgeV3::truth_raw()` on the same edge +post-`from_v1` — which tests the exact bit-copy site BLOCK 1 indicted, is +hosted in `causal-edge` (both crates' zero-dep postures hold — measured: +BOTH refuse the other as a dependency, `causal-edge/Cargo.toml:20-23` +explicitly), and closes a **measured missing test**: `edge_v3.rs`'s module doc +claims truth/spare survive the round trip byte-exact, and no test asserts it. + +**BLOCK 3 (firewall): the consolidation dropped board hygiene entirely.** +Draft v2 contained zero occurrences of any board file — Savant 4 had answered +the hygiene question in full, and Phase-2 consolidation lost the finding. The +loss-prevention phase lost a finding; recorded as such, restored as the spec's +§6′ commitment table. + +**Meta-catch worth keeping:** the same reviewer caught the fix ledger claiming +a citation correction it had not applied ("fixed in §2.5" while §2.5 still +read the old span) — the draft's own "measured, not assumed" standard applied +to the draft. And the council's scope itself was corrected mid-flight by the +operator (CE64 = muscle memory, `CausalEdgeV3` = granularity), a gap **no +savant could have caught** because the v1 spec never mentioned V3 and savants +answer only their question sets — the spec-writing phase is the only place +scope errors can be prevented, which is why Phase 0 is "where the real work +happens." + +Sibling finding folded in rather than given its own id: `TrustTexture` is a +**×4 homonym with three arities** (4/4/5/3 — `causal-edge/layout.rs:141`, +`contract/mul.rs:82`, `planner/mul/trust.rs:30` with `Dissonant` +unrepresentable in 2 bits, `arigraph/orchestrator.rs:114`), against +`TYPE_DUPLICATION_MAP.md:9`'s stale "×2"; lines 9, 16 and 19 of that doc must +change together. + +## 2026-08-21 — E-THE-ATTENTION-ATOM-WAS-ALREADY-SHIPPED-WHAT-WAS-MISSING-WAS-A-COMPOSITION-THAT-IS-NOT-OR-1 — D-ACR-1's basis is a reuse, and the only real gap was that every set operation in the crate is a bitset union + +**Status:** FINDING (D-ACR-1, implemented; 14 new tests green, 1194 contract +tests green, clippy clean). **Confidence:** High — the atom's existence is a +read of `facet.rs`; the container's absence is a stated grep. + +D-ACR-1 was written as *"the one missing primitive"*. Half of that is right, +and the half that is wrong is the more useful half. + +**The atom already existed, exactly.** `contract::facet::FacetCascade` is +`facet_classid(4) | 6×(8:8) = 16 B` with `CascadeShape::G6D2` — literally the +`6 × 2 × u8` shape D-ACR-1 needed, with `index`/`group_of`/`level_of`/`shift` +shipped, `tier_bytes()` ordered coarse-first (`[t0.hi, t0.lo, …]`), and two +existing readings to copy the pattern from (`awareness_facet::SpoFacet`, +`tekamolo_facet::TekamoloFacet(pub FacetCascade)`). **Zero new bytes were +needed and none were added.** Proposing a new 12-byte type here would have been +the "type that already exists" rediscovery tax `CLAUDE.md` §Consult names. + +**What was genuinely absent is narrower and sharper: a composition that is not +a bitset union.** Measured — every `union`/`intersect` in the contract crate is +a bit operation over *field positions*: `FieldMask` (u64, `MAX_FIELDS = 64`), +`WideFieldMask` (u8 positions, capped at 256 with a loud +`UniverseExceedsSocCap`), `StepMask` (u64), `rbac`. **Not one of them composes +addresses.** And a bit-OR of two addresses is not a coarser focus — it is a +*third address neither side ever visited*. That is the defect the container had +to avoid, and it is now a test (`composition_is_not_a_blind_or`: OR-ing +`0b01` and `0b10` yields `0b11`, and the union must NOT contain it). + +**The composition that IS right was already in the canon, one type over.** +`NiblePath::is_ancestor_of` is coarse→fine prefix containment with an +**explicit** depth field. Reusing that rule on the 12-unit ladder gives +`covers`, and reusing its explicit-depth discipline avoids a real trap: +inferring the wildcard boundary from zero bytes would collide with the +zero-fallback ladder, where `0` is a legitimate *dormant tier*, not a +terminator. So `depth` lives OUTSIDE the 12 bytes and the wire shape stays +exactly `6 × 2 × u8`. + +**This is also the answer to ">256 rows".** The `u8` per level bounds *axis +resolution* (256 centroids), never population: one shallow facet is a wildcard +over an unbounded subtree. Measured in a test — a depth-2 focus covers 65,536 +addresses across only the two units varied, and a container holds 1000 distinct +entries where `FieldMask` would stop at 64 and `WideFieldMask` at 256. **The +container does not index rows at all**, which is why neither cap transfers. + +**Content-blindness had to be defended twice in one deliverable.** A first +draft named the axes `Heel/Hip/Twig/Leaf/Family/Identity` — baking the cascade +reading into the low-level type, which is exactly what `FacetCascade`'s own +contract forbids (*"only the CONSUMER projects meaning onto the bytes"*). The +operator caught it while raising a second candidate reading (six **ontology +scopes**: disease/anatomy/process/substance/evidence/context). Both readings +now live only in a test, over byte-identical input, proving the projection is +free — and `FocusAxis` is `Axis0..Axis5`, a position. The general lesson is the +one this arc keeps paying for: **the moment a second plausible reading appears, +any name in the substrate is a premature commitment to the first.** Four +homonym collisions (witness / nibble / hydration / attention-mask) were the +expensive form of this; naming the axes would have been the fifth, minted by +us rather than inherited. + +**Deferred, named rather than silently absent:** `RowFocusMask::difference` +keeps a partially-overlapped entry whole. Subtracting a subtree from a prefix +requires enumerating siblings — inventing addresses the focus never visited — +so splitting waits for a real consumer that needs it. `D-ACR-1`'s scope line +also asks for composability with `WideFieldMask`; per D-ACR-0's measurement +that is a **cardinality mismatch** (positions ≤ 256 vs an unbounded population) +and remains §6 **Y2**'s parked basis collision — this deliverable states its +own basis and does not cross into the other. ## 2026-08-21 — E-HHTL-IS-MINTED-IN-THE-ARTIFACT-NOBODY-CITES-1 — "zero on every baked row in both production bakes" is precise about the two it names and silent about the third, where the five OBO namespaces are 100% minted diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index 6e4cce31f..f4db6516f 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,59 @@ +## 2026-08-21 — D-ACR-7 BAND-READING CONTRACT (council-ratified spec) + +`.claude/plans/dacr7-band-reading-contract-v1.md` — the 59..63 reading +contract, ratified through a full 5+3 council (3 BLOCK(P0) raised and +resolved; run details in AGENT_LOG same date). One contract, TWO carriers: +`CausalEdge64` (muscle memory) and `CausalEdgeV3` (granularity — whose own +module doc states the problem the contract solves: *"which lens the ordinal +was written through is the producer's knowledge, not the conversion's"*). +Design: total `ClassView::band_reading` declaration lookup (sibling-consistent +with `rail_carving`/`edge_codec_flavor`) + fallible `BandReading::project` +(mismatch and stale-v1 bits must FAIL, never return a plausible value). +Headline finding: `from_v1` drops provenance, so the v1 temporal trap reaches +V3 transitively — `V3Register` is a caller assertion, never an inference. +Gates G1..G10b pre-registered. Implementation next. + +## 2026-08-21 — KNOWN-UNKNOWN HANDOVER NETWORK (the connective plan, not a new arc) + +`.claude/plans/known-unknown-handover-network-v1.md` — the operator's framing +the same day: *"an awareness to hand over missing links in a 6×2×8bit growing +BNN network as a self-organizing AGI-aspiring network … that includes expanding +CE64 59..63 as a known-unknowns exploring brain plasticity."* Scraped onto +existing homes; **mints no type**. + +**Headline: the loop's links are individually shipped or individually designed, +except exactly one — handover.** `D-ACR-16` (nested kanban cascade) is NOT +DESIGNED with zero shipped precedent, and this plan's contribution is naming +what handover IS so it has a shape to be designed against: **a Hole is a kanban +card for a missing link — an owned row moving between mailboxes**, never a +message and never a shared log (`E-CE64-MB-4` one-writer; +`E-AGENT-LOG-SHARED-SINK-ANTIPATTERN-1`). Self-organization follows from +existence-not-command (`E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1`), not from a +scheduler. + +**The fence, measured twice:** *"expanding 59..63"* **cannot** mean widening. +`causal-edge/src/layout.rs:93-111`'s `_LAYOUT_COVERAGE` const-asserts all 64 +bits covered exactly once, and `dismech-causality-v3-v1.md:503` states verbatim +*"Not in CE64 — it has zero free bits."* So `↑n` is **stacking** — the canon's +*"scale is the next cascade level, never field-widening"* applied to the +awareness axis. Three stages, none of them a bit: readable (D-ACR-7's reading +contract, spanning CE64 **and** `CausalEdgeV3`) → discriminating (`WitnessKind` +→ Hole) → granular (`HoleV3`). + +**Hard blocker recorded as such:** `HoleV3 = ValueTenant 16` is blocked on +`BoardAggregates = 15` resolving, because the discriminant→`VALUE_TENANTS` +index requires contiguous descriptors — a prerequisite, not a queue position. + +**Ground truth stated honestly:** the public DisMech corpus marks its own gaps, +but only **27** named intermediates match an actual graph node (113 have a real +2-hop path) — so a supervised evaluation has 27–113 usable cases, not +thousands. The gap count says how much work exists; the match count says how +much is checkable today. + +Deliverables D-KUH-1..4. Only **D-KUH-1** (handover design) is unblocked. +Grade discipline inherited from the parent plan: **a pruner, not a proof**; +"AGI-aspiring" names a direction, not a property any deliverable asserts. + ## 2026-08-21 — ALPHA-CHANNEL RUNG OVERLAY (scraping, not a new arc) `.claude/plans/alpha-channel-rung-overlay-v1.md` — the design for the ONE diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index bca850e75..2ebf79b85 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,54 @@ +## 2026-08-21 — D-ACR-7 IMPLEMENTED — `contract::band_reading` (the 59..63 reading contract) + +### Current Contract Inventory — 1 new zero-dep module + 1 ClassView provided method + 1 gate test in `causal-edge` + +- **`lance_graph_contract::band_reading`** (new, zero new bytes) — implements + the RATIFIED spec `.claude/plans/dacr7-band-reading-contract-v1.md`. **One + reading contract, TWO carriers**: `CausalEdge64` bits 59-60 (truth) / 61-63 + (band) — the muscle memory — and `CausalEdgeV3` byte [8] hi-2 / byte [9] lo-3 + via `truth_raw()` / `spare_raw()` — the granularity. The reading NEVER changes + stored bytes; it declares how a consumer projects them. + - `TruthLens { Trust, Topology }` — which vocabulary the 2-bit register means + for this `(class, rail)`. **Doc-comment pointers only**, never imports: + both crates are zero-dep and neither may depend on the other + (`lance-graph-contract/Cargo.toml:10-17`, `causal-edge/Cargo.toml:20-23`). + - `BandPresence { Absent, Present }` + `WitnessKind { None, Table, + CausalFacet, EpisodicBasin }` — the F5 evidence-kind discriminator. + - `EdgeProvenance { V2Stamped, V3Register, V1Legacy, Unknown }` with + `trusted()`. **`V3Register` is a CALLER ASSERTION ("minted clean"), never an + inference** — council BLOCK-1: `CausalEdgeV3::from_v1` (`edge_v3.rs:117`) + has no provenance parameter and raw-copies the tail (`:138-139`), so the v1 + temporal trap (`temporal >= 512` aliases a non-zero band) reaches V3 + **transitively**. Unstated ⇒ `Unknown` ⇒ refuse. + - `BandReading { truth_lens, band, witness }` + `ZERO_FALLBACK` + (`Trust`/`Absent`/`None`, `== Default`), `admits`, `admits_band`, + `project_truth`, `project_band`. **Projection order is provenance FIRST, + then lens** — an untrusted edge is refused before its lens is even asked. + - `BandDeclarations` — the registry. **L1 split (the council's Phase-4 fix):** + declaration lookup is TOTAL (`reading_or_default`, zero-fallback, sibling- + consistent with `edge_codec_flavor` / `rail_carving` / `value_schema`), + while raw-bit projection is FALLIBLE (`Result`, `UndeclaredClass`) — it must + FAIL, never return a plausible value. + - **L3 audit distinction preserved:** `get()` returns `Option` — + `None` = never declared vs `Some(Absent)` = explicit opt-out. + `declare()` returns `true` on replace: a redeclaration is visible, never silent. + - `sampling_admits(&dyn Tactic) -> bool` = `moves_confidence()` — the + **14/34** capability filter, never `maturity().is_production()` (**31/34**). + Asserted against the real `all_kernels()`, not a fixture. +- **`ClassView::band_reading(class, rail)`** — provided method, defaults to + `BandReading::ZERO_FALLBACK`. Same registry-resolution pattern as its two + siblings; selection only, never a stride change. +- **`causal-edge` `g10b_lift_preserves_truth_and_spare_ordinals_under_both_lenses`** + — hosted THERE because both crates refuse each other. Closes a measured + missing test: the module doc claimed the truth/spare round trip is byte-exact + and nothing asserted it through the accessors. Fires (nonzero `Unknown` / + `Transcendent` ordinals survive `from_v1` → `rehydrate`) **and** stays silent + (zero ordinals are not upgraded). +- **Mints nothing**: no new tenant, no bit, no `ENVELOPE_LAYOUT_VERSION` bump, + no `cfg` feature that re-means a stored bit (G9). +- Gates: G1 (1207 contract tests, +13), G2 clippy clean on the new module, + G3′/G4′/G5a/G5b/G6/G7′/G8/G9/G10b all green — pre-registered before any agent ran. + ## 2026-08-21 — #975 MERGED (4a43698) — two measured corrections + a retracted absence - **`DismechTopology` doc + this file corrected**: the label-KNOWN 3,978 are @@ -18,6 +69,32 @@ ## 2026-08-20 — #974 MERGED (8a93423) — DisMech compact evidence vocabulary + citation sidecar +### Current Contract Inventory — 1 new zero-dep module (D-ACR-1) + +- **`lance_graph_contract::attention_facet`** (new, zero new bytes): + - `AttentionFocusFacet` — the **Attention reading** of the shipped + `FacetCascade` (`classid(4) | 6×(8:8)`) under `CascadeShape::G6D2`, plus an + explicit `depth: 0..=12` held OUTSIDE the 12 bytes (the `NiblePath` + precedent — inferring a wildcard from zero bytes would collide with the + zero-fallback ladder, where `0` is a dormant tier, not a terminator). + `exact` / `prefix` (loud refusal past 12) / `whole_class` / `coarse` / + `fine` / `axis` (all `None` past `depth` — never `0`, which is centroid + zero's value) / `covers` / `common_prefix`. + - `FocusAxis` — `Axis0..Axis5`, a **position, not a meaning**. The module + names no axis semantics: the cascade reading (HEEL·HIP·…) and a candidate + ontology-scope reading (disease/anatomy/process/substance/evidence/context) + are both ClassView-resolved projections, demonstrated over identical bytes + in `the_same_atom_reads_as_cascade_and_as_ontology_scope_without_changing_a_byte`. + - `RowFocusMask` — the sparse container. Membership is `covers`, set ops are + containment-shaped (`union` absorbs into a minimal antichain; `intersect` + yields the deeper of a covering pair; `difference` is deliberately + conservative). **Never a bit-OR**, and no `FieldMask`/`WideFieldMask` + cardinality is inherited — it does not index rows at all. + - `FOCUS_AXES = CASCADE_UNITS / 2` — derived, never a second literal. + - **Mints nothing**: no new tenant, no bit, no `ENVELOPE_LAYOUT_VERSION` bump. + Does not, and structurally cannot, reference `cognitive-shader-driver`'s + `attention_mask*` (the dependency edge runs the other way). + ### Current Contract Inventory — 1 new zero-dep module - **`lance_graph_contract::dismech_evidence`** (new): diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 9fdcd9ba5..1b3bf62d3 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -50,8 +50,13 @@ the rest. | D-id | Scope | Status | Falsifier | |---|---|---|---| +| D-KUH-1 | Name the Hole's handover shape: an owned row moving between mailboxes, lifecycle `Open -> Proposed -> {Resolved \| Refuted}`. Design only — feeds D-ACR-16 | **Next** — design core operator-supplied 2026-08-21, then ⊘-corrected same day (plan §9+⊘): **no owner change** — handover = a focus entry in the NEXT rung layer of the alpha overlay at the same address (thinking-about-thinking IS the mechanism, D-ACR-4's read path). ⊘⊘ two-armed by substrate: static ontology → alpha layer; dynamic substrate → in place with Lance versioning (`QueryReference::at(v, rung)` replays — nothing to build on that arm). Remaining: the n+1-layer insert convention + lifecycle stamps | a design needing a scheduler / broadcast / shared log has rebuilt what `E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1` and `E-AGENT-LOG-SHARED-SINK-ANTIPATTERN-1` deleted | +| D-KUH-2 | Wire `WitnessKind` -> `HoleV3` | **BLOCKED** on `BoardAggregates = 15` resolving (contiguous `ValueTenant` discriminants) — a prerequisite, not a queue position | a KNOWN-UNKNOWN band read resolves to a Hole AND a low-trust KNOWN does not — both asserted | +| D-KUH-3 | Plasticity as the rewire signal — does `plasticity()` move when a Hole closes? | BLOCKED on D-KUH-2 | two-sided: moves on a real close, does NOT move on a refuted one; firing on both carries no information | +| D-KUH-4 | Growth = minting rows, never widening fields: add N Holes, assert `ENVELOPE_LAYOUT_VERSION` and every field width unchanged | Queued — cheap, any time after D-KUH-2 | any width change is the failure (the §0 fence as a test) | | D-ACR-0 | Audit `attention_mask.rs`/`attention_mask_actor.rs`: residue carrier, or a name collision? Report only | **Shipped** 2026-08-21 — `.claude/ATTENTION_MASK_AUDIT_2026_08_21.md`. Verdict **EXISTS-UNCALLED** + a name collision: the shipped type is a *rename register file* (`causaledge64-mailbox-rename-soa-v1.md` §4), not a residue carrier | recorded EXISTS-UNCALLED (0 callers workspace-wide + 3 sibling repos); piece E regraded — not a basis for D-ACR-1 | -| D-ACR-1 | `RowFocusMask` — the one missing primitive (S3.1b names it; no crate contains it) | **Next** — D-ACR-0 cleared it; starts clean (no reuse of `AttentionMaskSoA`). Must state its basis first: `WideFieldMask` is `u8`-capped at 256 positions (loud refusal above), `FieldMask` silently drops `>= 64` — a row population is neither | can-fire AND can-stay-silent on non-trivial input | +| D-ACR-1 | `RowFocusMask` — the one missing primitive (S3.1b names it; no crate contains it) | **Shipped** 2026-08-21 — `contract::attention_facet`. Basis: **reuse** `FacetCascade` + `CascadeShape::G6D2`, zero new bytes; `FocusAxis` is content-blind (`Axis0..Axis5`), composition is **prefix containment**, never bit-OR; explicit `depth` outside the 12 bytes (`NiblePath` precedent) | can-fire AND can-stay-silent on non-trivial input — **both green**, plus a not-a-blind-OR negative and a 1000-entry container (no `FieldMask` 64 / `WideFieldMask` 256 cap inherited) | +| D-ACR-7 | The 59..63 reading contract (§3b): name per `(classid, rail)` which lens applies and which witness carrier discriminates evidence-kind. Acceptance: tactic sampling filters on `delta_conf` capability (14/34), never on `maturity().is_production()` (31/34) | **Shipped** 2026-08-21 — `contract::band_reading` + `ClassView::band_reading` + the G10b gate test in `causal-edge`. Spec RATIFIED same day via 5+3 council (`.claude/plans/dacr7-band-reading-contract-v1.md`; **3×BLOCK(P0) raised, 3 resolved in Phase 4, none argued away**). Scope covers BOTH carriers (CE64 bits 59-63 + `CausalEdgeV3` bytes [8]/[9]); `EdgeProvenance::V3Register` is a caller ASSERTION because `from_v1` drops provenance — unstated ⇒ `Unknown` ⇒ refuse. L1 split shipped: declaration lookup TOTAL, raw-bit projection FALLIBLE | G1 (1207, +13), G2, G3′, G4′, G5a, G5b, G6 (14 admitted / 20 rejected against the real `all_kernels()`, ≠ the 31 production count), G7′, G8, G9, G10b — all green | | D-ACR-3 | The one-way invariant as a test: no ontology-owned write traces to a patient-tagged read through ANY call path (corrected from write-authorization-only after CodeRabbit found a session-derived value can flow to the ontology owner via a shared parameter/return, then be written as the owner's own act) | Queued — gates on D-ACR-1 | a write whose call graph includes a session-tagged read is the bug, even if the write itself is authored by the ontology owner | | D-ACR-2 | Mint the Rung-ladder rail | Queued — gates on operator mint decision (HTT §8 Q3) | `rail_carving` gains its first non-default consumer | | D-ACR-4 | Second-order row at the same address, separate table | Queued | a rung-2 read reconstructs where rung-1 looked, on a fixture with an independent answer | diff --git a/.claude/handovers/2026-08-21-2330-session-to-next.md b/.claude/handovers/2026-08-21-2330-session-to-next.md new file mode 100644 index 000000000..13cf9408d --- /dev/null +++ b/.claude/handovers/2026-08-21-2330-session-to-next.md @@ -0,0 +1,212 @@ +# Session handover — 2026-08-21 (D-ACR-0 / D-ACR-1 / D-ACR-7 council) + +**Why this exists:** this session took over the alpha-channel arc from the +prior session's token wall (`2026-08-21-2200-session-to-next.md`), shipped two +deliverables, and left a 5+3 council mid-flight. Assume the next session has +none of this context. + +## What shipped + +| branch | deliverable | state | +|---|---|---| +| `claude/d-acr-0-attention-mask-audit` | **D-ACR-0** — the attention_mask audit | pushed, no PR yet | +| `claude/d-acr-1-row-focus-mask` | **D-ACR-1** — `contract::attention_facet` | pushed, no PR yet; 14 tests, 1194 contract tests green, clippy+fmt clean | + +Both carry board hygiene in-commit (EPIPHANIES prepend, STATUS_BOARD flip, +LATEST_STATE contract inventory for D-ACR-1). + +### D-ACR-0 — the result is load-bearing for everything after it + +`attention_mask.rs` / `attention_mask_actor.rs` are **EXISTS-UNCALLED** (three +hits workspace-wide, all non-consumers; one is a doc comment saying *"NO +AttentionMask/LRU"*) **and are a different mechanism wearing the name**: a +finished **rename register file** (`causaledge64-mailbox-rename-soa-v1.md` §4) +— wide identity → scarce narrow slot, LRU because slots are scarce, keyed by +`MailboxId`, `Vec` + linear scan, no address, no mask algebra, no trajectory. + +**Do not build on them.** Piece E regrades from *"shipped; unaudited for this +use"* to *"shipped for a DIFFERENT use; uncalled; not a basis for piece D"*. +Also measured: `plasticity_residual` is write-once-zero (2 grep hits total), +`BindReply` is a NoOp handler, and the originating §4's **singleton** actor +would rebuild what the V3 mailbox ruling removed — treat "sprint-12+ work" as +superseded, not pending. + +### D-ACR-1 — the basis was a REUSE, not a new type + +`contract::facet::FacetCascade` already **is** `6 × 2 × u8` under +`CascadeShape::G6D2`. Zero new bytes were added. What was genuinely missing was +narrower: **a composition that is not a bitset union**. Every `union`/ +`intersect` in the crate is a bit op over *field positions* (`FieldMask` u64/64, +`WideFieldMask` u8/256, `StepMask`, `rbac`) — and a bit-OR of two addresses is a +third address neither side visited. + +Shipped instead: **prefix containment**, reusing `NiblePath::is_ancestor_of`'s +rule and its **explicit depth** (inferring the wildcard from zero bytes would +collide with the zero-fallback ladder, where `0` is a dormant tier). `depth` +lives OUTSIDE the 12 bytes; the wire shape stays exactly `6 × 2 × u8`. + +**`FocusAxis` is `Axis0..Axis5` — a position, not a meaning.** A first draft +named them `Heel/Hip/Twig/…` and the operator caught it while raising a second +candidate reading (six ontology scopes). Both readings now live only in a test, +over byte-identical input. **This would have been the fifth homonym collision +of the arc and the first we minted ourselves.** + +## The council, mid-flight — READ THIS BEFORE TOUCHING D-ACR-7 + +`/5plus3` was convened for **D-ACR-7** (the 59..63 reading contract). State: + +- **Phase 0** SPEC v1 — written, 6 sections, inventory measured +- **Phase 1** the 5 savants — **all returned**, 39 findings +- **Phase 2** draft v2 — **consolidated** +- **Phase 3** the 3 reviewers — **reviewer 2 returned (8 PASS / 1 FIX(P2) on + L6); reviewers 1 and 3 were still running when this handover was written** +- **Phase 4/5** — NOT DONE. **v3 is not ratified.** + +Artifacts (scratchpad, ephemeral — re-derive from this handover if gone): +`dacr7-spec-v1.md`, `dacr7-spec-v2.md`. + +**The sequencing is the point.** Do not ratify v3 without reviewers 1 and 3. +Do not let them see the raw savant output — draft v2 only. If a `BLOCK(P0)` +lands, return to Phase 0 rather than arguing it away in a commit message. + +### What the council found that matters most + +1. **Two savants converged independently on the same defect.** The spec's + `Result`-returning resolver is the **opposite** convention from every + shipped `ClassView` lens selector (`rail_carving`, `edge_codec_flavor`, + `value_schema` — all infallible with zero-fallback defaults), AND a hot-path + batch scan needs a total function. **Resolved by splitting:** the + *declaration lookup* is total and sibling-consistent; the *projection of raw + bits* is fallible, because that is where mismatch and stale-v1 bits actually + live. They were two operations, conflated in v1. +2. **Code truth returned 6/6 CONFIRMS.** The v1 inventory is measured, not + assumed — which was the one thing this council most needed to verify. +3. **The scope was wrong, and no savant could have caught it** (my spec did not + mention V3, so no question set asked). See below. + +## The operator input that changed the spec's scope (L0) + +*"causaledge64 is the muscle memory / causaledgev3 for granularity."* Verified: + +- `edge_v3.rs:96-103` — `CausalEdgeV3 { payload: [u8; 12] }`, const-asserted; + *"`classid(4) | payload(12)` = the canonical 16-byte facet, the payload half"* +- `edge_v3.rs:49-50` — the SAME two fields: `[8] w_slot(6) | truth/topology + RAW(2)`, `[9] spare/ReasoningBand RAW(3) | reserved(5)` +- `edge_v3.rs:199,206` — `truth_raw()` / `spare_raw()` +- `edge_v3.rs:16-26` — V3 **rehydrates into CE64 to reason** (`syllogize` reads + only SPO + freq/conf + causal_mask). The muscle-memory framing is literal. + +**And the V3 module doc already states D-ACR-7's problem, unsolved** +(`edge_v3.rs:86-90`): *"Which lens the ordinal was written through is **the +producer's knowledge, not the conversion's**."* The reading contract is exactly +what supplies that. So v2 spans **both carriers, one contract** — with an +honest asymmetry: the v1-provenance trap (a v1 edge with `temporal >= 512` +reads a non-zero band, `layout.rs:74-76`) does **not** apply to V3, whose bytes +were never temporal. + +## Deferred with its tension stated — do not silently adopt + +The operator also raised *"causaledgev3 can even use 6×2×8bit ↑n as BNN +planning equivalent"*. **Not taken into v2**, because the shipped doc forbids +that reading today (`edge_v3.rs:29-36`): *"a packed EDGE REGISTER, **NOT** a +slot-pure §3 facet … Do not read this as a content-blind facet."* Adopting it +would also be a **sixth** homonym against `attention_facet`'s `6×2×8bit` +(landed today). It needs its own deliverable and its own resolution of the +typed-register-vs-content-blind-facet contradiction. + +## `ogar-loco` resonance — measured, and the answer is DON'T wire + +Asked whether the same `6×2×8bit` should be wired into `ogar-loco`. It is +**already the same format, independently**: `ogar-loco/src/lib.rs:267-276` — +`Pairs = 6 × (u8:u8)`, `Triples = 4 × (u8:u8:u8)`, `Quads = 3 × (u8:u8:u8:u8)`, +`PAYLOAD_BYTES_PER_SLOT = SLOT_STRIDE(16) − CLASSID_BYTES(4) = 12`, selected +**per classid**, with `const _` asserts pinning `calls_per_lane × N == 12`. + +And `lib.rs:256-258` states the decision already: *"**Mirrors** the LE +contract's `CascadeShape` … **defined locally so this crate keeps its +plug-and-play posture and takes no substrate dependency**."* `ogar-loco`'s only +dep is optional `serde`. + +**So: deliberate mirroring, not overlooked duplication** — the opposite of the +five homonym collisions (those shared a *name* with different meanings; this +shares an *algebra* with identical meaning and a documented reason to stay +separate). Do not "fix" it by importing. + +The real finding: the 12-unit algebra has now appeared at **six independent +sites** (`FacetCascade`, `TekamoloFacet` G4D3, `AttentionFocusFacet` G6D2, +`CausalEdgeV3`'s 12-byte payload, le-contract §3, `ogar-loco::LaneShape`), two +of them carrying explicit "mirrors" notes. That is evidence `12 = 6·2 = 4·3 = +3·4` is **forced**, not chosen. + +## New plan written this session + +`.claude/plans/known-unknown-handover-network-v1.md` — the operator's +*"awareness to hand over missing links in a 6×2×8bit growing BNN network"* +framing, scraped onto existing homes. Headline results: + +- **`↑n` is stacking, never widening.** Two independent measurements forbid + widening: `_LAYOUT_COVERAGE` const-asserts all 64 bits covered exactly once, + and D-CV3-3 says verbatim *"Not in CE64 — it has zero free bits."* +- The loop's links are **individually shipped or individually designed, except + one**: **handover** (`D-ACR-16`, NOT DESIGNED, zero precedent). The plan's + contribution is naming what handover IS — a Hole as a kanban card moving as + an **owned row** between mailboxes, never a message, never a shared log. +- `HoleV3 = ValueTenant 16` is **hard-blocked** on `BoardAggregates = 15` being + resolved (contiguous discriminants), not merely queued. +- Ground truth is **27–113 usable cases**, not thousands — the gap count says + how much work exists, the match count says how much is checkable today. + +## Next steps, in order + +1. **Finish the council**: collect reviewers 1 and 3, apply Phase 4 fixes + (reviewer 2's L6 FIX(P2) — the "accepted-as-stated" bullets need the same + file:line rigor L0/L5 use; evidence for the weakest of them is already + gathered: no glob imports of `lance_graph_contract::*` exist, so a new + module is additive-only), ratify v3, implement, run gates G1–G10. +2. **Open PRs** for D-ACR-0 and D-ACR-1 (both pushed, neither has a PR). +3. **D-KUH-1** (handover design) is unblocked and is the only thing in the new + plan that can start today. + +## Standing discipline this session reinforced + +Content-blindness had to be defended twice in one deliverable, and the operator +caught it both times. **Once a second plausible reading of a byte exists, any +name in the substrate is a premature commitment to the first.** The five +homonym collisions of this arc (witness / nibble / hydration / attention-mask / +TrustTexture ×4) were the expensive form; naming the focus axes would have been +the first self-inflicted one. + +--- + +## ⊘ Nachtrag (später am selben Tag): das Handover-Framing oben ist superseded + +The line above — *"a Hole as a kanban card moving as an **owned row between +mailboxes**"* — is corrected by the operator: **kein Owner-Wechsel.** Handover +is a focus-of-attention entry in the NEXT rung layer of the alpha overlay at +the same address; the Hole never moves and ownership stays static (its absence +of a transfer operation is the design, not a gap). Full correction: +`known-unknown-handover-network-v1.md` §9's ⊘ block. The remaining work is an +overlay operation (`RowFocusMask::insert` in the n+1 layer — shipped, D-ACR-1), +and D-ACR-16's cascade is the stack of rung layers, the card being the focus +entry, never the row. + +**⊘⊘ Second refinement, same day:** handover is TWO-ARMED by substrate nature — +static ontology → alpha-layer entry (contamination boundary); **dynamic +substrate → in place with Lance versioning** (episodic = Lance versions; +rung n+1 reads "where rung n looked" via `QueryReference::at(v, rung)` — zero +copies, replayable). The dynamic arm needs nothing built. Full table: +`known-unknown-handover-network-v1.md` §9 ⊘⊘. + +--- + +## ⊘ Council COMPLETED — v3 RATIFIED (later the same day) + +The mid-flight state above is superseded: reviewers 1+3 were lost to a +container restart, recast against the surviving draft v2, and delivered. +**3 BLOCK(P0) raised, 3 resolved, v3 ratified** — +`.claude/plans/dacr7-band-reading-contract-v1.md` (single file: spec v1 + +v2 ledger + v3 fix ledger + post-ratification operator addendum on the +temporal doctrine). Headline: `from_v1` drops provenance, so the v1 temporal +trap reaches V3 transitively; `V3Register` = caller assertion. Next unit: +implement `contract::band_reading` against gates G1..G10b (G10b lives in +`causal-edge` — a measured missing lift-fidelity test). diff --git a/.claude/plans/dacr7-band-reading-contract-v1.md b/.claude/plans/dacr7-band-reading-contract-v1.md new file mode 100644 index 000000000..2dc3669f8 --- /dev/null +++ b/.claude/plans/dacr7-band-reading-contract-v1.md @@ -0,0 +1,649 @@ +# SPEC v1 — D-ACR-7: the CE64 59..63 reading contract + +> **Council:** 5+3, convened 2026-08-21. Orchestrator = main thread. +> **Qualified because:** `5plus3-council.md` §When to convene — *"a spec whose +> wrong resolution silently corrupts downstream sessions (canon entries, +> LE-layout adjacent decisions, classid/mask semantics)"*. §3b fence 3 states +> the failure verbatim: *"A consumer that reads one while a producer wrote the +> other gets a plausible wrong answer, silently."* +> **Deliverable:** `alpha-channel-rung-overlay-v1.md` §4 `D-ACR-7`. + +## §1 FROZEN DECISIONS (cite-or-VIOLATES; never re-opened on taste) + +| # | Frozen | Source | +|---|---|---| +| F1 | Bits 59-60 = `TRUTH_SHIFT` (2 bits), bits 61-63 = `SPARE_SHIFT` (3 bits). All 64 bits covered exactly once (`_LAYOUT_COVERAGE` const-assert). | `causal-edge/src/layout.rs:65,77,93-111` | +| F2 | **Nothing derives the band.** `ReasoningBand` is set ONLY by an explicit `with_reasoning_band()` — never from `CausalMask`, `InferenceType`, NARS, MUL, `ReasoningGap`, potholes or `ThinkingStyle`. | plan §3b fence 1; `layout.rs:70-72` | +| F3 | `ReasoningBand` is **never** `RungLevel`. Unrelated enums sharing variant names at different ordinals. | plan §3b fence 2; `contract/cognitive_shader.rs:157` | +| F4 | `TrustTexture` and `CausalTopology` are **the same 2 bits read differently**; the reading must be named per `(classid, rail)`, never assumed. | plan §3b fence 3; `layout.rs:58-63` | +| F5 | The band **grades**; the **witness reference discriminates** evidence-kind. No new bit is minted for the episodic-vs-epistemic axis. | plan §3b | +| F6 | Acceptance: any tactic-sampling pass filters on **`delta_conf` capability (14/34)**, never on `maturity().is_production()` (31/34). | plan §4 D-ACR-7 row; §3g; `contract/recipe_kernels.rs:319-322` | +| F7 | **No new CE64 bit, no `ENVELOPE_LAYOUT_VERSION` bump, no new address type.** | plan §5 Non-goals | +| F8 | `I-LEGACY-API-FEATURE-GATED`: a v1 path under the v2 feature must route to the canonical mapping OR be a documented feature-gated no-op. Field-isolation matrix mandatory when layout bits are touched. | `CLAUDE.md` iron rule | +| F9 | A guard needs BOTH a can-it-fire and a can-it-STAY-SILENT test on non-trivial input. | `CLAUDE.md` falsifiability rule | + +## §2 INPUT INVENTORY (measured this session; exact file:line) + +### 2.1 The bits and their accessors + +| item | file:line | shape | +|---|---|---| +| `TRUTH_SHIFT = 59` | `causal-edge/src/layout.rs:65` | 2 bits, `TRUTH_MASK = BITS2_MASK << 59` (`:87`) | +| `SPARE_SHIFT = 61` | `causal-edge/src/layout.rs:77` | 3 bits, `SPARE_MASK = BITS3_MASK << 61` (`:88`) | +| `TrustTexture` (CE64) | `causal-edge/src/layout.rs:141` | **4** variants: `Crystalline=0 / Solid=1 / Fuzzy=2 / Murky=3` | +| `CausalTopology` | `causal-edge/src/layout.rs:239` | **4** variants | +| `ReasoningBand` | `causal-edge/src/layout.rs:353` | **8** variants (fills 3 bits exactly) | +| `trust_texture()` | `edge.rs:927-928` | reads `(self.0 >> TRUTH_SHIFT) & BITS2_MASK` | +| `truth()` raw ordinal | `edge.rs:935-936` | same bits, un-projected | +| `topology()` | `edge.rs:951-953` (v2) / `edge.rs:1144` (v1 arm) | **same bits**, other projection | +| `reasoning_band()` | `edge.rs:978` (v2) / `edge.rs:1154` (v1 arm) | bits 61-63 | +| `with_topology()` | `edge.rs:1008` (v2) / `edge.rs:1166` (v1 no-op) | | +| `with_reasoning_band()` | `edge.rs:1056` (v2) / `edge.rs:1178` (v1 no-op) | the ONLY writer (F2) | + +### 2.2 The v1 provenance trap — measured, and NOT covered by the plan's §3b + +`layout.rs:74-76` states it directly: + +> *"**v1 provenance:** bits 61-63 were temporal bits 9-11, so a v1 edge with +> `temporal >= 512` reads a NON-ZERO band. Apply a version gate on edges of +> unknown provenance — the same rule `truth()` states for bits 59-60."* + +So a reading contract that only answers *"which lens"* is **incomplete**: on an +edge of unknown provenance the bits may be **stale v1 temporal payload**, and +every lens then returns a confidently wrong value. §3b names the lens ambiguity +and is silent on this one. + +### 2.3 Consumers of the BITS: **zero** + +Grep across the workspace, both sibling repos, excluding `causal-edge` itself: +no call site of `reasoning_band()` / `topology()` / `with_topology()` / +`with_reasoning_band()` exists. The contract is being written **before its +first consumer** — prescriptive, not retrofitted. + +`TrustTexture` as a **type** does have consumers (`contract/sensorium.rs:132`, +`contract/mul.rs:320-327`, `contract/benches/i4_batch.rs:71-88`) — **but none +of them reads it out of CE64 bits.** Type-consumed ≠ bit-consumed; the spec +must not conflate the two. + +### 2.4 ⚠ `TrustTexture` is a FOUR-way homonym with THREE arities + +`docs/TYPE_DUPLICATION_MAP.md:9` records *"TrustTexture (×2)"*. **Measured: ×4.** + +| # | file:line | variants | arity | +|---|---|---|---| +| 1 | `causal-edge/src/layout.rs:141` | `Crystalline / Solid / Fuzzy / Murky` | **4** | +| 2 | `lance-graph-contract/src/mul.rs:82` | `Calibrated / Overconfident / Uncertain / Underconfident` | **4** | +| 3 | `lance-graph-planner/src/mul/trust.rs:30` | `Crystalline / Solid / Fuzzy / Murky / **Dissonant**` | **5** | +| 4 | `lance-graph/src/graph/arigraph/orchestrator.rs:114` | `Crystalline / **Fibrous** / Fuzzy` | **3** | + +Two consequences the spec must carry: + +- **#3 cannot fit the bits at all.** 2 bits hold 4 values; the planner enum has + **5**. `Dissonant` is unrepresentable in CE64 — a producer holding a planner + `TrustTexture` and writing bits 59-60 either truncates or is undefined. +- **#1 and #2 share a name AND an arity but have disjoint meanings.** A + consumer resolving "TrustTexture" to the MUL one gets a plausible wrong + answer with no type error — F4's failure mode, one layer deeper than F4 + describes it. + +### 2.5 The `delta_conf` acceptance surface (F6) + +`contract/recipe_kernels.rs:319-322` (the doc on the capability method): +*"no kernel declares `ThoughtField::Confidence` in `writes`, and only **14** +can move `delta_conf` — while 31 are `Operational`."* +`contract/recipe_dispatch.rs:188` — `dispatch_order() -> [u8; 34]`. + +## §3 THE PROPOSED RESOLUTION (fully committed) + +**One new zero-dep contract module: `lance_graph_contract::band_reading`.** +It mints no bit, no tenant, no layout version (F7). It is a *declaration table +plus a resolver*, not a new carrier. + +### 3.1 The three things a reading must declare + +A `(classid, rail)` pair resolves to a `BandReading`: + +```rust +pub struct BandReading { + /// WHICH projection of bits 59-60 this class wrote. + pub truth_lens: TruthLens, // Trust | Topology + /// Whether bits 61-63 carry a ReasoningBand at all. + pub band: BandPresence, // Absent | Present + /// WHICH evidence-kind carrier discriminates (F5) — a reference, not a bit. + pub witness: WitnessKind, // None | Table | CausalFacet | EpisodicBasin +} +``` + +- `TruthLens` resolves F4: the reading is **named**, never assumed. +- `BandPresence::Absent` is the zero-fallback default — an unstamped class + declares no band, and reading one is a refusal (§3.3), not a `Surface(0)`. +- `WitnessKind` carries F5's evidence-kind axis as a *reference discriminator*; + no bit is minted. + +### 3.2 Provenance is part of the contract, not an afterthought (§2.2) + +```rust +pub enum EdgeProvenance { V2Stamped, V1Legacy, Unknown } +``` + +The resolver's entry point takes provenance and **refuses** on +`V1Legacy | Unknown` for bits 61-63, because a v1 `temporal >= 512` aliases a +non-zero band. This is F8 applied to a *read* path: the v1 arm is a documented +refusal, not a plausible value. + +### 3.3 Reads return `Result`, never a plausible fallback + +```rust +pub enum BandReadError { + LensMismatch { declared: TruthLens, requested: TruthLens }, + BandAbsent, + UnknownProvenance, + UndeclaredClass(u32), +} +``` + +A producer/consumer disagreement is an **error**, satisfying D-ACR-7's own +falsifier (*"must FAIL, not return a plausible value"*). No `unwrap_or(Surface)` +anywhere. + +### 3.4 The `TrustTexture` homonym (§2.4) + +**In scope:** the contract's `TruthLens::Trust` documents that it means +`causal_edge::layout::TrustTexture` (4 variants) and **explicitly not** +`contract::mul::TrustTexture`, `planner::mul::trust::TrustTexture` (5 — +unrepresentable) or the arigraph one (3). A `debug_assert`-style const check +pins the arity at 4. + +**Out of scope:** renaming any of the four (that is a cross-crate refactor with +its own blast radius). The spec records the collision and updates +`TYPE_DUPLICATION_MAP.md`'s stale "×2" to "×4" — a doc correction, not a +rename. + +### 3.5 The `delta_conf` acceptance condition (F6) + +A `SamplingPolicy` helper the overlay must route through: +`fn admits(kernel) -> bool` returns `delta_conf`-capability, and the module doc +states why `maturity().is_production()` is the wrong filter (a watcher that +cannot dissent). Enforced by a test, not a comment. + +## §4 NON-GOALS (each with its why) + +| Out of scope | Why | +|---|---| +| Renaming any `TrustTexture` | cross-crate refactor, own blast radius; §3.4 records + documents instead | +| Writing any band anywhere | F2 — only `with_reasoning_band()` writes; this deliverable is the READING contract | +| A registry populated with real classes | D-ACR-2's rail mint is a separate, operator-gated decision the plan does not pre-empt | +| Touching `RungLevel` | F3 — unrelated enum; naming it here would invite the conflation the fence forbids | +| `WideFieldMask` composition | §6 Y2's parked basis collision (D-ACR-0 measured the cardinality mismatch) | +| Any `attention_mask*` reuse | D-ACR-0: rename register file, different contract, EXISTS-UNCALLED | + +## §5 PRE-REGISTERED GATES (decided BEFORE any agent runs) + +| # | Gate | Pass | +|---|---|---| +| G1 | `cargo test -p lance-graph-contract` | ≥ 1194 (current) + new, 0 failed | +| G2 | `cargo clippy -p lance-graph-contract --all-targets` | 0 warnings from this module | +| G3 | Lens-mismatch test | a `Topology`-declared class read as `Trust` returns `Err(LensMismatch)` — **not** a value | +| G4 | Provenance refusal | `V1Legacy` + `Unknown` both `Err(UnknownProvenance)` on bits 61-63 | +| G5 | Can-fire AND can-stay-silent (F9) | a declared class resolves; an undeclared one errors; both on non-trivial input | +| G6 | `delta_conf` filter (F6) | a mute kernel (one of the 20) is REJECTED and a capable one (of the 14) is ADMITTED — both asserted | +| G7 | Arity pin | a const/test asserts the CE64 `TrustTexture` arity is 4, so a 5-variant sibling cannot be silently substituted | +| G8 | No bit written | the module contains no `with_*` call and no `<<`/`&` against CE64 masks (it declares, it does not stamp) | + +## §6 PER-SAVANT QUESTION SETS + +Answer each `CONFIRMS / VIOLATES / GAP / PRIOR-ART-AT / RISK` + `file:line` + +≤2 sentences. ≤10 findings total. **Do not redesign** — a redesign urge is one +`RISK` finding, then stop. + +### Savant 1 — prior art +1. Does a `(classid, rail) → reading` declaration table already exist anywhere (contract, planner, v3 docs)? +2. Is `BandReading`/`TruthLens`/`WitnessKind` (or a synonym) already a shipped type? +3. Does an E-id already record the 59..63 lens ambiguity, or the `TrustTexture` ×4 collision? +4. Is there prior art for a Result-returning reading resolver in this crate? +5. Does `ClassView` already carry a per-class lens selector this should extend instead? + +### Savant 2 — iron rules +1. Does §3 VIOLATE `I-LEGACY-API-FEATURE-GATED` anywhere (esp. the v1 arm treatment)? +2. Does anything in §3 derive a band, violating F2? +3. Does §3 mint a bit / tenant / layout version, violating F7? +4. Does the module stay zero-dep (contract crate constraint)? +5. Do G3–G7 satisfy the falsifiability rule's fire/silent pair on NON-trivial input? +6. Does §3.4's arity pin conflict with any AP1–AP9 anti-pattern? + +### Savant 3 — code truth (CODED / CLAIMED / ABSENT per claim) +1. Are all §2.1 file:line references real and correctly quoted? +2. Is §2.2's v1-provenance quote verbatim from `layout.rs`? +3. Is §2.3's "zero bit-consumers" claim reproducible by grep? +4. Is §2.4's ×4 / arity 3-4-5 table correct, and is `TYPE_DUPLICATION_MAP.md:9` really "×2"? +5. Is §2.5's 14/34 and 31/34 quote verbatim from `recipe_kernels.rs`? +6. Do `with_topology`/`with_reasoning_band` really have v1 no-op arms at the lines cited? + +### Savant 4 — cascade impact +1. Every file/test/doc/board row that MUST change if §3 lands (mandatory vs follow-up)? +2. Does adding a contract module force any downstream crate change? +3. Which board files does hygiene require in the same commit? +4. Does `TYPE_DUPLICATION_MAP.md`'s correction pull in other stale rows? +5. Does this create work for D-ACR-2 (rail mint) that the plan says it must not pre-empt? + +### Savant 5 — different views +1. What is the strongest alternative reading of "the reading contract" that §3 forecloses? +2. Is `Result`-on-mismatch the right severity, or does some call site need a total function? +3. Second-order: what does declaring `BandPresence::Absent` as default cost a future consumer? +4. Is there a reading of F5 under which `WitnessKind` belongs on the witness side, not here? +5. Does the provenance enum belong in `causal-edge` (where the bits live) rather than `contract`? + +--- +--- + +# DRAFT v2 — change ledger over SPEC v1 + +> Phase 2 consolidation, orchestrator only. 5 savants returned 39 findings. +> Raw output banked in the task transcripts; never forwarded to the reviewers. +> **Reviewers see this document ONLY.** + +## L0 — SCOPE CORRECTION (operator input mid-Phase-2, NOT a savant finding) + +**SPEC v1 addressed `CausalEdge64` only. That was a Phase-0 gap, and it is the +largest change in this ledger.** Operator, 2026-08-21: *"causaledge64 is the +muscle memory / causaledgev3 for granularity."* Verified against source: + +| | file:line | shape | +|---|---|---| +| `CausalEdgeV3` | `causal-edge/src/edge_v3.rs:96-103` | `payload: [u8; 12]`, `const _: () = assert!(size_of == 12)`; *"`classid(4) \| payload(12)` = the canonical 16-byte facet, the payload half"* | +| the SAME two fields | `edge_v3.rs:49-50` | `[8] w_slot(6 low) \| truth/topology RAW(2 high)` · `[9] spare/ReasoningBand RAW(3 low) \| reserved(5 high)` | +| their accessors | `edge_v3.rs:199` `truth_raw()`, `:206` `spare_raw()` | RAW ordinals, un-projected | + +**The V3 module doc already states D-ACR-7's problem, and leaves it open** +(`edge_v3.rs:86-90`): + +> *"`w_slot` / truth / spare are preserved as **RAW ORDINALS**. Copying a CE64 +> topology/truth ordinal `01` into V3 means "ordinal 01 preserved" — it is NOT +> an assertion that `IndirectKnown` (or `Solid`) is now source-authoritative for +> that row. **Which lens the ordinal was written through is the producer's +> knowledge, not the conversion's**."* + +That sentence IS the deliverable's justification, written in shipped code by a +prior session. **The reading contract is what supplies the producer knowledge +the conversion cannot carry.** Consequence: + +- **§3 now spans BOTH carriers.** `BandReading` is keyed by `(classid, rail)` + and is carrier-agnostic; the projection function takes the raw ordinals, so + it serves `CausalEdge64::{truth, reasoning_band}` and + `CausalEdgeV3::{truth_raw, spare_raw}` identically. One contract, two + carriers — never two contracts. +- **The v1-provenance trap (§2.2) does NOT apply to V3.** V3 has no v1 history; + its bytes were never temporal. `EdgeProvenance` gains a `V3Register` arm that + is always readable. This is a real asymmetry the spec must state, not smooth. +- **The muscle-memory/granularity split is now recorded**: CE64 reasons + (`syllogize` reads SPO + freq/conf + causal_mask); V3 **rehydrates into CE64** + to reason (`edge_v3.rs:16-26`). The reading contract sits above both and + privileges neither. + +**DEFERRED, with its tension stated rather than silently adopted.** The +operator also raised *"causaledgev3 can even use 6×2×8bit ↑n as BNN planning +equivalent."* This is NOT taken into v2, because the shipped module doc +explicitly forbids that reading today (`edge_v3.rs:29-36`): + +> *"a packed EDGE REGISTER, **NOT** a slot-pure §3 facet … Do not read this as a +> content-blind facet: it is a typed edge register whose carving is its own +> contract."* + +So "V3 as a `6×2×8bit` content-blind facet" contradicts a shipped doc-comment +**and** would be a sixth homonym against `attention_facet`'s `6×2×8bit` reading +(D-ACR-1, landed today). It is recorded as an open question for the operator — +a stacked (`↑n`) BNN-planning ladder over repeated 12-byte registers is a +plausible and interesting direction, but it needs its own deliverable and its +own resolution of the "typed register vs content-blind facet" contradiction. +**Not decided here.** + +## L1 — the Result-vs-total fork (Savant 1 #8 + Savant 5 Q2, independently) + +Two savants converged on the same defect from different lenses, which is the +strongest signal this council produced. + +- Savant 1: *"`Result`-returning refusal is the **opposite** convention from + every existing `ClassView` lens selector (`rail_carving`, + `edge_codec_flavor`, `value_schema`) — all infallible with a documented + zero-fallback default"* (`class_view.rs:1109-1111, 1127-1133`). +- Savant 5: a hot-path SIMD/batch scan needs a **total** function; per-row + `Result` unwrapping is exactly what the infallible accessors exist to avoid. + +**RESOLUTION — the spec conflated two different operations. They split:** + +| operation | shape | why | +|---|---|---| +| **declaration lookup** — *what did class C declare?* | `ClassView::band_reading(class) -> BandReading`, **total**, zero-fallback default | sibling-consistent with `rail_carving` / `edge_codec_flavor`; hot-path safe | +| **projection** — *read THESE raw bits under that declaration* | `BandReading::project(raw, provenance) -> Result` | this is where mismatch and stale-v1 bits actually live; D-ACR-7's falsifier demands a failure, not a plausible value | + +The lookup can never fail (an undeclared class yields the zero-fallback +`BandReading`). The projection can, and must. **This is not a compromise +between the two savants — it is the recognition that they were describing +different functions.** Savant 1's finding is recorded as ACCEPTED-AND-SPLIT; +neither finding is discarded. + +## L2 — writer-side is only half-closed (Savant 5 Q1) + +Accepted. §3 policed readers only, so nothing stops a producer stamping bits +inconsistent with its own declaration — leaving F4's failure mode half open. +**Added:** `BandReading::admits(lens) -> bool`, the cheap pre-write check a +producer calls before `with_topology` / `with_reasoning_band`. It does not (and +cannot) *enforce* — enforcement at the write site is `causal-edge`'s to add and +is recorded as a follow-up, not smuggled in here. + +## L3 — `Absent` vs never-declared (Savant 5 Q3) + +Accepted. An audit that asks *"who opted out vs who never considered this"* was +unrepresentable. **Fixed by shape:** the registry returns +`Option` — `None` = never declared, `Some(BandReading { band: +Absent, .. })` = explicitly declared no band. The total lookup in L1 folds +`None` to the zero-fallback default for callers who do not care; the audit path +reads the `Option` directly. + +## L4 — gate repairs (Savant 2 Q5 ×2) + +- **G3, G4** gained their can-stay-silent halves explicitly (F9 requires BOTH + stated): G3 now also asserts a correctly-matched lens returns `Ok`; G4 now + also asserts `V2Stamped` **and** `V3Register` resolve without error. +- **G7** regime named: it is a **compile-time type-level assertion**, explicitly + exempt from F9's runtime fire/silent duality. Stated rather than left + ambiguous. +- Savant 2 Q6's AP1 watch accepted as a **gate**, not prose: **G9** — no + `#[cfg(feature = ...)]` branch may change `BandReading` semantics under one + name; a feature split must error, never re-mean. + +## L5 — doc corrections (Savant 3, Savant 4 Q4) + +- Savant 3: the `recipe_kernels` quote spans **319-322**, not 320-322. Fixed in + §2.5. All other 5 code-truth questions returned CONFIRMS — **the v1 inventory + is measured, not assumed**, which is the one thing this council most needed to + verify. +- Savant 4 RISK accepted: correcting `TYPE_DUPLICATION_MAP.md:9` "×2"→"×4" also + requires fixing **line 16** (its per-copy table lists 2 of 4) and **line 19** + (*"rename one … in causal-edge"* is no longer a coherent single target across + arities 4/4/5/3). All three lines change together or the doc stays internally + inconsistent. + +## L6 — accepted-as-stated (no change needed) + +- Savant 2 Q1–Q4, Q6: no iron-rule violation. Zero-dep confirmed honest — + `TruthLens::Trust` is a **doc-comment pointer**, not an import + (`lance-graph-contract/Cargo.toml:10-17`). +- Savant 4 Q2: additive-only; no downstream crate is forced to change. +- Savant 4 Q5: D-ACR-2 is not pre-empted — `BandReading` is keyed by + caller-supplied `(classid, rail)`, so no class-population work is created. +- Savant 5 Q5: `EdgeProvenance` stays in `contract`; moving it to `causal-edge` + would force a dependency in one direction or the other. Cost recorded: it + lives one crate from the bits it describes. + +## L7 — recorded, deliberately NOT actioned (anti-collapse) + +- **Savant 1 Q5 / #7** — *should `band_reading` be a third method on `ClassView` + rather than a new module?* L1 adopts the `ClassView::band_reading` **method** + for the lookup half, so this is partially taken. The **types** stay in their + own module because `class_view.rs` is already 2000+ lines and the projection + half has no `ClassView` analogue. The losing half of the finding is recorded, + not deleted. +- **Savant 5 Q4** — *does `WitnessKind` belong on `Locus`/`WitnessEntry`?* + (`causal_witness.rs:116-134` has `Locus::BasinAnchor = 8`.) Fork named and + NOT taken: `WitnessKind` here is the *reference discriminator*, not the + witness. If a later deliverable moves it, this line is the record that the + alternative was seen and declined with a reason. + +## §5′ — GATES, amended (supersedes §5) + +G1, G2, G5, G6, G8 unchanged. G3, G4, G7 amended per L4. New: G9, G10. + +| # | Gate | Pass | +|---|---|---| +| G3′ | Lens mismatch **and** match | `Topology`-declared read as `Trust` → `Err(LensMismatch)`; read as `Topology` → `Ok` | +| G4′ | Provenance, both directions | `V1Legacy`/`Unknown` → `Err(UnknownProvenance)`; `V2Stamped` **and** `V3Register` → `Ok` | +| G7′ | Arity pin (compile-time, F9-exempt — stated) | a const assertion pins CE64 `TrustTexture` arity at 4 | +| G9 | AP1 watch | no `#[cfg(feature)]` branch re-means `BandReading`; a split errors | +| G10 | **Carrier parity (L0)** | the same declaration projecting `CausalEdge64::truth()` and `CausalEdgeV3::truth_raw()` yields the identical result for the identical ordinal | + +--- +--- + +# v3 — PHASE 4 FIX LEDGER + +> Reviewer verdicts in hand: **reviewer 2** (dilution-collapse) 8×PASS + +> 1×FIX(P2); **reviewer 3** (firewall) 6×PASS + **1×BLOCK(P0)** + 1×FIX(P1). +> **Reviewer 1 (overclaim) has not reported** — the first cast was lost with +> its sibling (no notification, `ListAgents` empty) and was recast. +> **v3 is NOT ratified until reviewer 1's verdict set is applied.** + +## FIX-1 — L5 `BLOCK(P0)` (firewall): board hygiene was never committed to + +**The BLOCK is correct, and it is a Phase-2 consolidation failure, not a spec +defect.** Verified: draft v2 contains **zero** occurrences of `LATEST_STATE`, +`STATUS_BOARD`, `EPIPHANIES`, `PR_ARC_INVENTORY` or `AGENT_LOG`. Savant 4 +**answered** this in its Q3 with the full mandatory list; **my consolidation +dropped the finding entirely.** That is precisely the loss Phase 2 exists to +prevent, and it happened in Phase 2. Recorded as such rather than repaired +silently. + +**Resolved by restoring the dropped answer as a committed section:** + +### §6′ — BOARD HYGIENE COMMITMENT (same commit, non-negotiable) + +| file | why it is required | content | +|---|---|---| +| `.claude/board/LATEST_STATE.md` | rule row *"a contract type / module"* | PREPEND a Contract Inventory entry for `band_reading` (types, what it mints: nothing) | +| `.claude/board/STATUS_BOARD.md` | rule row *"a new D-id / deliverable"* | flip the `D-ACR-7` row to Shipped with its falsifier outcome | +| `.claude/board/EPIPHANIES.md` | rule row *"a finding / correction"* — **three** qualify | PREPEND one entry covering: the `TrustTexture` ×4 homonym (arities 4/4/5/3, `Dissonant` unrepresentable in 2 bits), the v1-provenance trap and its **non**-application to V3, and the Result-vs-total split | +| `.claude/board/PR_ARC_INVENTORY.md` | rule row *"a merged PR"* | PREPEND on merge (post-merge commit, not at author time) | +| `docs/TYPE_DUPLICATION_MAP.md` | §3.4's own commitment | lines 9, 16 **and** 19 together (L5's cascade finding) | +| `.claude/board/AGENT_LOG.md` | rule row *"a completed agent run"* | ONE entry naming this council: which 5, which 3, verdict counts, v1→v2→v3 deltas — written by the orchestrator only (one-writer) | + +## FIX-2 — §5′ G10 `FIX(P1)` (firewall): the gate was unhostable, and its own fix is unhostable too + +The warden is right that G10 was not mechanically checkable, and its suggested +remedy — *"the test lives in `causal-edge`, importing the contract"* — **also +fails**, measured: `crates/causal-edge/Cargo.toml:20-23` reads *"No +dependencies — this crate is self-contained. `TrustTexture` is defined locally +(not imported from `lance-graph-contract`) to preserve the zero-dep +invariant."* **Both** crates are zero-dep by explicit design, so no single test +may hold both the contract and the carriers. + +**Resolved by splitting the gate along the seam that already exists — the +projection takes RAW ORDINALS, so it never needed a carrier at all:** + +| gate | host | asserts | zero-dep impact | +|---|---|---|---| +| **G10a** | `lance-graph-contract` | `project(raw, decl, provenance)` is a pure function of its arguments — identical `(raw, decl, provenance)` ⇒ identical result, for every `raw ∈ 0..4` (truth) and `0..8` (band). Carrier-agnosticism is then **by construction**: the function cannot see a carrier. | none — no carrier type is named | +| **G10b** | `causal-edge` | its OWN two accessors preserve the ordinal across `from_v1` → `rehydrate`: `CausalEdge64::truth()` == `CausalEdgeV3::truth_raw()` and the band likewise. | none — contract is not named | + +**G10b is a genuinely MISSING test today, measured:** `edge_v3.rs`'s test module +has `v3_le_round_trip` (V3's own LE bytes), `mantissa_round_trips_raw_for_all_16_states` +and `inference_type_is_a_lossy_projection_of_the_mantissa` — **none asserts +truth/spare preservation across the lift/rehydrate pair**, despite the module +doc claiming *"every meaningful CE64-v2 field survives the round trip +byte-exact"* including *"the truth/topology 2-bit ordinal, and the spare/band +3-bit ordinal"*. So the FIX(P1) surfaced an unverified doc claim in shipped +code — recorded, and G10b is the test that closes it. + +## FIX-3 — L6 `FIX(P2)` (dilution-collapse): "accepted" must read as verified, not asserted + +Each accepted-as-stated bullet gains the `file:line` rigor L0/L5 use: + +- **Savant 2 Q4 (zero-dep honest)** — `crates/lance-graph-contract/Cargo.toml:10-17`: + `[dependencies]` has **zero entries** plus the explicit comment *"Zero + dependencies by design … MUST stay dependency-free even of optional path + deps"*. `TruthLens::Trust` is a doc-comment pointer; no edge is created. + (Independently re-verified by reviewer 3 this pass.) +- **Savant 4 Q2 (additive-only)** — measured: **zero** glob imports of + `lance_graph_contract::*` exist workspace-wide, so a new module cannot + break any downstream build. Consumers adopt it or ignore it. +- **Savant 4 Q5 (D-ACR-2 not pre-empted)** — + `alpha-channel-rung-overlay-v1.md:1190-1192`: *"D-ACR-2 and everything after + it sit behind an operator mint decision that this plan does not pre-empt."* + `BandReading` is keyed by **caller-supplied** `(classid, rail)`, so it + populates no registry. +- **Savant 5 Q5 (`EdgeProvenance` placement)** — accepted WITH its cost named: + it lives one crate from the bits it describes, and (per FIX-2's measurement) + that is forced by BOTH crates' zero-dep posture, not a preference. + +## FIX-4 — L0 addendum: the two-armed handover (operator, after the first cast) + +`WitnessKind`'s target acquired a shape while the council ran +(`known-unknown-handover-network-v1.md` §9 ⊘⊘): handover is **two-armed by +substrate** — static ontology → an alpha-layer focus entry at the same address; +dynamic substrate → in place, with **Lance versioning** as the residue carrier +(`QueryReference::at(v, rung)`, a projection, zero copies). **No ownership ever +changes.** + +Consequence for THIS spec, and it is small by design: `WitnessKind` remains a +**reference discriminator** and gains one doc sentence naming the two arms. No +type, field or gate changes — the reading contract is upstream of where the arms +diverge. Recorded so a later session does not read the two-armed design as +implying a `BandReading` variant per arm. + +## Outstanding before ratification + +1. **Reviewer 1 (overclaim-auditor)** — verdict set not yet received. +2. Any `BLOCK` it raises returns to Phase 0 rather than being argued away here. +3. Stricter verdict wins on any conflict with reviewers 2/3. + +--- + +## FIX-5 — L0 `BLOCK(P0)` (overclaim): "V3's bytes were never temporal" is FALSE for populated instances + +**The BLOCK is correct and is the council's most consequential finding.** +Verified at source this pass: + +```rust +// edge_v3.rs:117 — no provenance parameter exists +pub fn from_v1(e: CausalEdge64, target: u16) -> Self +// edge_v3.rs:138-139 — a raw bit copy from a caller-supplied edge +p[8] = (e.w_slot() & 0x3F) | ((e.truth_raw() & 0b11) << 6); +p[9] = e.spare() & 0b111; +``` + +The reassuring comment above it (`:135-137`) — *"Under the v1 layout every one +of these accessors is a documented zero stub"* — is a **compile-time feature +condition**, not a runtime provenance guarantee. Under a v2-compiled build, a +CE64 of v1 or unknown provenance (whose bits 61-63 alias `temporal >= 512`) +lifted through `from_v1` carries those stale bits into V3 byte 9, and **the +resulting register is indistinguishable from a clean one.** + +**Had this shipped, the reading contract would have been self-defeating at its +own core claim** — `V3Register` declared "always readable" is exactly the +plausible-wrong-answer §3.3 exists to refuse. It is the difference between a +statement about the type's *shape* (true: V3 has no v1 history) and about its +*populated instances* (false: they inherit whatever they were lifted from). + +**Resolution — three changes, none touching the design:** + +1. **`EdgeProvenance::V3Register` is redefined**: it means *"the caller asserts + this register was minted clean"*, **never** *"V3 registers are clean"*. The + contract **cannot infer** V3 provenance and must not try — the information + was destroyed at the lift, one crate away. +2. **A V3 register of unstated origin is `Unknown`**, and `Unknown` already + refuses (G4′). Zero-fallback applies: absent an assertion, refuse. +3. **Recorded as a real gap in `causal-edge`, filed as follow-up, NOT fixed + here**: `from_v1` drops provenance. Fixing it means a signature change in a + crate this deliverable does not own; the reading contract's job is to stop + *trusting* what was lost, not to un-lose it. + +L0's asymmetry claim is **struck** and replaced: the v1 trap applies to +**both** carriers — on CE64 directly, on V3 **transitively through the lift**. +That is a simpler contract, not a more complex one. + +*(Citation corrected: the module-doc quote spans `edge_v3.rs:83-87`, not +86-90.)* + +## FIX-6 — §5′ `BLOCK(P0)` (overclaim): a gate contradicted the fix, and another was a tautology + +**G5 contradiction — correct, and it is FIX-1's own doing.** L1 adopted a +**total** lookup while §5′ carried G5 (*"an undeclared one errors"*) forward as +*unchanged*. Both cannot hold. Resolved by naming the surface each gate tests: + +| gate | surface | asserts | +|---|---|---| +| **G5a** | `ClassView::band_reading` (total) | an undeclared class yields the **zero-fallback** `BandReading` and does NOT error — sibling-consistent | +| **G5b** | `BandReading::project` (fallible) | projecting under an undeclared class returns `Err(UndeclaredClass)` — the guard that must fire | + +The audit distinction L3 bought (`Option` = never-declared vs `Some(Absent)` = +declared-no-band) is what makes G5a/G5b non-vacuous: they read different +returns of different functions, not the same thing twice. + +**G10 tautology — correct, and it kills my own FIX-2 G10a.** G10a as written +(*"`project` is a pure function of its arguments"*) feeds the same input to the +same function twice and asserts the same output. That is the vacuous-assertion +pattern `CLAUDE.md`'s falsifiability rule names outright. **G10a is deleted.** + +**Only G10b survives, and FIX-5 has now doubly motivated it:** compare +`CausalEdge64::truth()` against `CausalEdgeV3::truth_raw()` on the **same edge +after `from_v1`** — which tests the bit-copy fidelity at `edge_v3.rs:138`, +precisely the site where FIX-5 showed provenance is destroyed. It is hosted in +`causal-edge` (naming no contract type), so both zero-dep postures hold. And it +is a **missing test today**, measured: `edge_v3.rs`'s test module has +`v3_le_round_trip`, `mantissa_round_trips_raw_for_all_16_states` and +`inference_type_is_a_lossy_projection_of_the_mantissa` — none asserts +truth/spare preservation across lift/rehydrate, despite the module doc claiming +it. + +## FIX-7 — L2 `FIX(P2)`: "cannot" is now shown, not asserted + +`admits()` cannot enforce **by construction**, and the evidence is +`crates/causal-edge/Cargo.toml:20-23`: *"No dependencies — this crate is +self-contained. `TrustTexture` is defined locally (not imported from +`lance-graph-contract`) to preserve the zero-dep invariant."* A contract-side +helper structurally cannot hook `causal-edge`'s `with_topology` / +`with_reasoning_band` call sites, because the dependency edge that would let it +does not and must not exist. [G]-grade, now cited. + +## RATIFICATION + +| reviewer | verdicts | resolved by | +|---|---|---| +| 2 — dilution-collapse | 8 PASS, 1 FIX(P2) | FIX-3 | +| 3 — firewall | 6 PASS, **1 BLOCK(P0)**, 1 FIX(P1) | FIX-1 (board hygiene §6′), FIX-2 | +| 1 — overclaim | 4 PASS, **2 BLOCK(P0)**, 2 FIX(P1), 1 FIX(P2) | FIX-5, FIX-6, FIX-5 (L5 citation), FIX-7 | + +**Three BLOCK(P0) raised, three resolved in Phase 4 — none argued away, none +requiring a Phase-0 re-spec** (each changed a claim, a gate, or a commitment; +the design — declaration table + fallible projection, two carriers, one +contract — survived all three unchanged). + +**v3 is RATIFIED.** Implementation may proceed against §3 as amended by +FIX-1..7, with gates G1, G2, G3′, G4′, G5a, G5b, G6, G7′, G8, G9, G10b. + +--- + +## Post-ratification operator addendum (2026-08-21): carrier wiring status + the temporal doctrine + +Operator, after ratification: *"Alle V3-Varianten von CausalEdge, +EpisodicWitness und epistemic witness sind unwired oder planned. Temporal is +implicitly in the epistemic pothole; can be explicit in Rubikon revision, +CausalEdgeV3, and attention v3."* + +**1. The unwired status is now operator-confirmed, not only measured.** §2.3 +measured zero bit-consumers; the operator confirms the generation-level fact: +`CausalEdgeV3` is shipped-parallel-unwired, `EpisodicEdges64` is unmounted +(D-ACR-17 unbuilt), `EpisodicWitness64` never became code, `HoleV3` is blocked +(BoardAggregates = 15). **The reading contract is therefore prescriptive by +construction AND by ruling** — it defines how the first consumer reads, it +retrofits nothing. + +**2. The temporal doctrine — the contract carries NO temporal field, and that +is doctrine, not omission.** Time is **implicit in the epistemic pothole**: a +pothole opens at `first_possible`, closes at its `Revision`, and the span lives +in the Lance version stream (`QueryReference::at(v, rung)` — the dynamic arm of +the two-armed trace). Explicit temporal exists in exactly **three sanctioned +homes**, none of them this contract: + +| home | status | its own rule | +|---|---|---| +| Rubikon revision window | planned (§3f's open interval question, unresolved) | the window is cited, not recalled | +| `CausalEdgeV3` byte `[7]` TE | shipped, unwired | *"an INDEPENDENT signed relative chain offset the producer sets explicitly, never inherited"* (`edge_v3.rs`) | +| attention v3 | planned, not shipped | a future ClassView reading — not `attention_facet` (which is deliberately atemporal) | + +**3. This is the v1 trap's lesson, generalized.** Bits 61-63 went stale +precisely because temporal lived *implicitly in a reclaimable field*. +Implicit-in-versions cannot go stale (versions are append-only); explicit +temporal is allowed only where a producer deliberately sets it. And +`EdgeProvenance` is about **layout epoch, never time** — conflating those two +axes is the category error the episodic-vs-epistemic honesty line already +fences one level up. diff --git a/.claude/plans/known-unknown-handover-network-v1.md b/.claude/plans/known-unknown-handover-network-v1.md new file mode 100644 index 000000000..f655080f5 --- /dev/null +++ b/.claude/plans/known-unknown-handover-network-v1.md @@ -0,0 +1,458 @@ +# Known-unknown handover network — v1 + +> **Status:** PROPOSED. No code. Every "exists" claim below was verified by +> reading the file named, this session; every "absent" by a grep that returned +> nothing. Register-before-code, per the dialectic build order. +> +> **What this plan is:** the operator's 2026-08-21 framing — *"an awareness to +> hand over missing links in a 6×2×8bit growing BNN network as a +> self-organizing AGI-aspiring network … that includes expanding CE64 59..63 as +> a known-unknowns exploring brain plasticity"* — scraped onto homes that +> already exist, plus the short list of what genuinely does not. +> +> It mints no type. It is the **connective plan** between two arcs that were +> being built separately: `alpha-channel-rung-overlay-v1.md` (the attention +> overlay) and `dismech-causality-v3-v1.md` (the Hole). The claim of this plan +> is that they are one loop, and that exactly one fibre of it is missing. + +## §0 — The fence, first, because it is the load-bearing constraint + +**"Expanding 59..63" cannot mean widening.** Two independent measurements +forbid it: + +1. `causal-edge/src/layout.rs:93-111` — `_LAYOUT_COVERAGE` const-asserts that + all 64 bits are covered **exactly once**. There is no slack to take. +2. `.claude/plans/dismech-causality-v3-v1.md:503` (D-CV3-3), verbatim: + *"`awareness_state` ⟂ `unknown_kind`. **Not in CE64 — it has zero free + bits.**"* + +So the operator's own notation is the answer: **`↑n` is stacking, not +widening** — the canon's *"scale is the next cascade level, never +field-widening"* applied to the awareness axis. Every deliverable below adds a +**level**, never a bit. Any future session reading "expand 59..63" as "take +some spare bits" is reading it wrong, and this section is the record. + +## §1 — The three-stage expansion, and where each already lives + +| stage | what it buys | carrier | state (verified) | +|---|---|---|---| +| **1. readable** | the 5 bits stop being ambiguous — which lens wrote them, and is the provenance trustworthy | D-ACR-7 reading contract, spanning `CausalEdge64::{truth, reasoning_band}` **and** `CausalEdgeV3::{truth_raw, spare_raw}` | **in council now** (spec v2 consolidated) | +| **2. discriminating** | a known-unknown is *distinguishable* from a weak known | F5: the band **grades**, the **witness reference discriminates** — `WitnessKind` points at a Hole | `WitnessKind` is in D-ACR-7 spec v2; its Hole target is stage 3 | +| **3. granular** | the unknown gets its own row, with lifecycle | `HoleV3` as `ValueTenant = 16`; `CausalEdgeV3`'s 12-byte register (bytes `[10..12]` still dormant) | **BLOCKED** — see §4 | + +**Why stage 1 is not optional plumbing.** `causal-edge/src/edge_v3.rs:86-90` +already states the gap in shipped code and leaves it open: + +> *"`w_slot` / truth / spare are preserved as **RAW ORDINALS** … **Which lens +> the ordinal was written through is the producer's knowledge, not the +> conversion's**."* + +A network that cannot tell which lens wrote a bit cannot tell a known-unknown +from a low-trust known. Stage 1 supplies exactly the producer knowledge the +conversion structurally cannot carry. + +## §2 — The loop, with every link's real status + +```text + pothole OPENS QueryReference::at(v, rung) bounds what is derivable + │ → "not yet knowing" is manufactured honestly + ▼ + STAMP with_reasoning_band() — explicit, never derived (F2) + │ + ▼ + DISCRIMINATE WitnessKind → the Hole (awareness × unknown_kind) + │ + ▼ + LOCATE RowFocusMask — where attention actually went + │ + ▼ + HAND OVER the Hole as an owned row in another mailbox + │ (one-writer-per-mailbox preserved) + ▼ + EXPLORE the 14 delta_conf-capable recipes + │ (the 20 mute ones are eigenvalue 1 by construction) + ▼ + CLOSE Revision fires; the pothole-open SPAN is the measurement + │ + ▼ + REWIRE plasticity (CE64 bits 50-52 / V3 byte [3]) + growth = minting new rows, never widening a field +``` + +| link | home | state | +|---|---|---| +| pothole opens | `temporal.rs` `QueryReference::at` | **SHIPPED**, unbuilt as a live horizon (D-ACR-15) | +| stamp | `edge.rs:1056` `with_reasoning_band` | **SHIPPED**; the ONLY writer (F2) | +| discriminate | `WitnessKind` (D-ACR-7 v2) | in council | +| locate | `contract::attention_facet::RowFocusMask` | **SHIPPED 2026-08-21** (D-ACR-1) | +| **hand over** | — | **THE MISSING FIBRE — see §3** | +| explore | `recipe_kernels` `delta_conf` (14/34) | **SHIPPED**; filter is D-ACR-7's acceptance condition | +| close | `RecipeInference::Revision` + span | design in `alpha-channel` §3p; probe is D-ACR-10 | +| rewire | `PLAST_SHIFT = 50` (`layout.rs:37`) | **SHIPPED as a field.** Measured: no external consumer reads `plasticity()` for rewiring — `high_heel.rs:236,589,845` reads it for basin state, which is a different use | + +## §3 — The one missing fibre: handover + +Everything else above exists or is designed. **Handover does not**, and the +plan that would own it says so plainly — `alpha-channel-rung-overlay-v1.md` +§4, `D-ACR-16`: + +> *"Nested kanban cascade for awareness build-up — **NOT DESIGNED** — zero +> shipped precedent."* + +and §3m's grep receipt: *"Grepped `lance-graph-supervisor/src/*.rs` for +`nested.*kanban|kanban.*cascade` — zero. No near-miss found this time, +foveated or otherwise."* + +**This plan's contribution is to name what handover IS, so D-ACR-16 has a +shape to be designed against:** a Hole is a **kanban card for a missing link**. +It is not a message and not a shared log — it is an owned row that moves +between mailboxes, which is the only handover shape this substrate permits +(`E-CE64-MB-4` one-writer-per-mailbox; `E-AGENT-LOG-SHARED-SINK-ANTIPATTERN-1` +for why a shared append-log is pseudo-handover with a race). + +The self-organizing property follows from that and nothing more exotic: a +mailbox that cannot close its own Hole hands it to one that might. No +scheduler, no central planner — `E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1` +already deleted the per-owner `advance()` RPC shape, so handover must be +existence (the row is now yours), never command. + +## §4 — Blockers, named with their real cause + +| # | blocked | real blocker | not the blocker | +|---|---|---|---| +| B1 | `HoleV3 = ValueTenant 16` | `BoardAggregates = 15` is a **gated reservation only**; `ValueTenant`'s discriminant→`VALUE_TENANTS` index requires **contiguous** descriptors, so 16 has no valid slot until 15 resolves (`dismech-causality-v3-v1.md:503,509-510`) | not the benchmark, and not D-CV3-0..2 — CodeRabbit corrected exactly this on 2026-08-21 | +| B2 | `D-ACR-16` handover | no design, no precedent (§3) | not a mint decision — nothing to mint yet | +| B3 | 64k parallel exploration | `D-ACR-5` gates on dialectic V4: V0–V3 green at small scale first | not this plan's to unblock | +| B4 | live horizon (pothole opening) | `D-ACR-15` — `WorkflowDAG::plan()` is a registered stub whose body is comments | not absent; specified but unbuilt | + +**B1 is the sharp one.** The Hole is stage 3's whole content, and it is blocked +on an unrelated mint's width being decided. That is worth stating loudly +because it looks like a queue position and is actually a hard prerequisite. + +## §5 — First real corpus, and the honest size of its ground truth + +The public DisMech transcode (`AdaWorldAPI/dismech-rs`) is the first corpus +where this loop has something to run on: its `causal_link_type` field marks +**INDIRECT_UNKNOWN_INTERMEDIATES** and **UNKNOWN** edges explicitly — a +knowledge base that publishes its own gaps, which is rare and is exactly what +a handover network needs as input. + +**But the usable ground truth is far smaller than the gap count, and this plan +must not overstate it.** Measured on the upstream corpus by the parallel +session (`dismech-rs/bakes/stage3-current-truth-2026-08-20/mediator-feasibility.tsv`): + +| | | +|---|---| +| edges labelled `INDIRECT_KNOWN` (pathophysiology) | 3,844 | +| …of which name **zero** intermediates | 1,466 (38 %) | +| named intermediate strings | 3,465 | +| …that **match an actual graph node** | **27** | +| …with a real 2-hop path in the graph | 113 | + +So a supervised evaluation has **27–113 usable cases**, not thousands. Any +claim that this corpus validates the loop must cite that number, not the gap +count. The gap count says how much work there is; the match count says how much +of it is *checkable today*. + +## §6 — Deliverables + +| D-id | Scope | Falsifier | +|---|---|---| +| **D-KUH-1** | Name the Hole's handover shape: an owned row moving between mailboxes, with the lifecycle `Open → Proposed → {Resolved \| Refuted}`. Design only — feeds `D-ACR-16`. | a design that requires a scheduler, a broadcast, or a shared log has rebuilt what `E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1` and `E-AGENT-LOG-SHARED-SINK-ANTIPATTERN-1` deleted | +| **D-KUH-2** | Wire `WitnessKind` → `HoleV3` once B1 clears. | a `KNOWN-UNKNOWN` band read must resolve to a Hole row, and a low-trust KNOWN must **not** — both asserted (fire + stay-silent) | +| **D-KUH-3** | Plasticity as the rewire signal: measure whether `plasticity()` moves when a Hole closes. | **BLOCKED on B1.** Two-sided: it must move on a real close AND must NOT move on a refuted one; a signal that fires on both carries no information | +| **D-KUH-4** | Growth = minting rows, never widening fields: a probe that adds N Holes and asserts `ENVELOPE_LAYOUT_VERSION` and every field width are unchanged. | any width change is the failure; this is §0's fence as a test | + +**Sequencing:** D-KUH-1 (design, unblocked) → B1 clears → D-KUH-2 → D-KUH-3. +D-KUH-4 can run at any point after D-KUH-2 and is cheap. + +## §7 — What this plan does NOT claim + +It does not claim the network learns, that handover improves resolution, that +plasticity currently rewires anything, or that 27 checkable cases validate a +method. It claims one thing: **the loop's links are individually shipped or +individually designed, and exactly one — handover — is neither.** Every number +that would justify more has to be measured after D-KUH-1. + +The grade discipline of the parent plan applies verbatim +(`alpha-channel-rung-overlay-v1.md` §0 piece 7): this is **a pruner, not a +proof**. "AGI-aspiring" names a direction; it is not a property any deliverable +here asserts. + +## §8 — The arithmetic of ↑n (operator sharpening, 2026-08-21, appended while the council ran) + +Operator: *"6×2×8bit ↑n is making n^n ⇒ n↑log(n) in any given thinking space — +we take universes of rabbitholes one epistemic pothole at a time."* + +Translated into the measured property rather than left as poetry, this is the +radix arithmetic the canon already pins (*"the key prerenders nodes with zero +value decode"*), applied to the awareness axis: + +- One 12-byte atom under `G6D2` addresses `256^12 ≈ 7.9×10^28` distinct points + per class at full depth. A focus at depth `d` covers `256^(12−d)` of them — + measured in D-ACR-1's own test suite (`one_shallow_focus_covers_an_unbounded_ + population`: depth 2, 65,536 addresses across the two units varied). +- **The space is exponential in depth; the path is linear in depth.** Reaching + any specific address costs at most 12 refinement steps = `log₂₅₆(space)`. + That is the `n^n ⇒ n↑log(n)` claim in checkable form: exploration cost grows + with the *logarithm* of the space explored, because each step is one prefix + level, never a scan. +- **Stacking (`↑n`) multiplies exponents while paths add.** A second register + (a V3 stack level, another of the node's 32 facet slots) squares the + addressable space and adds 12 to the worst-case path — exponent + multiplication bought at additive path cost. This is why growth is minting + rows/levels and never widening fields (§0): widening buys linear space at + layout-break cost; stacking buys exponential space at logarithmic + navigation cost. +- **"One epistemic pothole at a time" is the descent rule.** The pothole marks + WHICH subtree to refine next; each handover/exploration step descends exactly + one prefix level of one Hole. The rabbit-hole universe is never entered + whole — it is entered one level of one hole at a time, which is what keeps + the sweep O(holes × depth) instead of O(space). + +Grade: the radix arithmetic is [G] (it is what a 256-ary prefix tree is); the +identification of "pothole" with "descent selector" is design intent carried +by D-KUH-1, not yet a measured behaviour. The `n↑log(n)` notation is the +operator's shorthand for exponential-space/logarithmic-path and is recorded as +such, not as a formal tetration claim. + +## §9 — The handover mechanism (operator design input, 2026-08-21) + +Operator: *"Handover soll dadurch ermöglicht werden, dass jedes Glied 6×2×8bit +kann — mit einer classid — und Übergabe von attention durchgereicht werden +kann, zero copy."* + +This supplies D-KUH-1's design core, and it survives verification against the +substrate's own rulings — in fact it turns out to be the **only legal shape**: + +**1. Every link already speaks the payload.** That is what §Handover's parent +finding measured: the `classid(4) | 6×(8:8)(12) = 16 B` atom is the shared +format at six independent sites. A handover therefore needs NO new wire type, +no message schema, no serialization — the thing being handed over is a facet +every receiver can already read. + +**2. The classid IS the briefing.** The receiver needs no protocol negotiation: +`classid → ClassView` resolves which reading applies to the 12 bytes (the +canon's *"the key prerenders nodes with zero value decode"*). A handed-over +attention facet is **self-describing** — the receiving mailbox reads the same +bytes under its ClassView, full stop. + +**3. Zero-copy is not an optimization here — it is the compliance condition.** +The V3 tombstone ruling (`soa-three-tier-model.md`, `CLAUDE.md` 2026-06-11 +supersession) states there is **no inter-mailbox handoff type at all**: +*"nothing is serialized or transmitted between mailboxes"* — the Baton was +removed from source. So a handover that copied or transmitted would not be +slow, it would be **forbidden**. The only legal handover is **ownership +transfer in place**: the bytes never move; what changes is who may write. + +**4. The narrow gap, measured this session:** `SoaEnvelope::mailbox_owner` +exists (`soa_envelope.rs:195`, default `0` = bootstrap/unowned, overridable) — +but **no ownership-transfer operation exists anywhere** (grep for +`transfer`/`reassign`/`set_owner`/`change_owner` across contract + supervisor: +zero hits). Ownership is static today. **The operator's design therefore +reduces D-KUH-1 + D-ACR-16 from "design a handover protocol" to "design the +owner-change operation"** — format, self-description and zero-copy all already +exist; only the stamp-change is unbuilt. That operation must respect +write-on-behalf (the current owner writes the new owner in; the receiver never +grabs) and existence-not-command (receiving ownership IS the notification; +there is no `advance()` call). + +**5. One open detail, named rather than smoothed:** the prefix `depth` lives +OUTSIDE the 16 bytes (D-ACR-1's explicit-depth rule — zero bytes are dormant +tiers, not terminators). A handed-over focus must carry its depth somewhere; +the natural home is the Hole row's own value slab (480 B, `GUIDS_PER_NODE=32` +slots of which the facet occupies one — const-asserted, +`canonical_node.rs:805-808`). **D-KUH-1's design doc must fix that byte's +position**; until then a raw 16-byte handover silently reads as depth-12 +(exact), which would make every wildcard focus look like a pinpoint claim. + +**Consequence for the deliverable table:** D-KUH-1's scope line stands, its +design core is now operator-supplied; what remains is (a) the owner-change +operation's contract, (b) the depth byte's home, (c) the lifecycle stamps — +all three inside the shape fixed here, none of them a new format. + +> **⊘ §9 KORRIGIERT (Operator, 2026-08-21): "kein Owner-Wechsel, nur Focus der +> Aufmerksamkeit über rung levels mit Alpha layer für thinking about +> thinking."** §9 point 4 read the static ownership as the gap ("the +> owner-change operation is unbuilt"). That was backwards: **static ownership +> is CORRECT and stays.** Nothing that moves ownership is to be built — an +> owner change would be a substrate mutation, i.e. exactly the command-shaped +> intervention `E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1` deleted and the +> alpha-channel plan's §2 contamination boundary exists to prevent. +> +> **Handover = the appearance of a focus entry in the NEXT rung layer of the +> alpha overlay, at the same address.** The Hole does not move, does not change +> owner, is not transmitted. Rung n could not close it → rung n+1's overlay +> layer gains an `AttentionFocusFacet` entry covering the Hole's address — +> written by that layer's own owner into its own layer (one-writer holds +> trivially: every rung layer has exactly one owner, itself). Thinking about +> thinking IS the handover mechanism: escalation is a second-order read plus an +> own-layer focus entry, which is precisely `D-ACR-4`'s read path (*"a rung-2 +> read reconstructs where rung-1 looked"*). +> +> Three things this collapses: +> +> 1. **The remaining work is an OVERLAY operation, not a substrate one.** +> `RowFocusMask::insert` in the n+1 layer — shipped today (D-ACR-1) — with +> the Hole's focus facet. No new operation on rows, envelopes, or owners. +> 2. **§9 point 5's open detail dissolves.** Depth travels in the overlay entry +> (`AttentionFocusFacet` carries it outside the 16 bytes, by design) — raw +> 16-byte transport never happens, so the depth-12 misread cannot occur. +> 3. **D-ACR-16's shape sharpens further:** the nested kanban cascade IS the +> stack of rung layers; the card is the focus entry, never the row. Zero +> copy is exceeded, not merely met — not even an ownership stamp changes, +> the graph stays untouched (the contamination boundary), and the overlay +> stays discardable whole (a lost handover costs a re-search, never +> correctness). +> +> §9's points 1–3 stand unchanged (shared format, classid-as-briefing, +> zero-copy as compliance condition). Point 4's measurement stays true — no +> transfer operation exists — but is regraded from GAP to **CONFIRMS**: its +> absence is the design, not the debt. + +> **⊘⊘ REFINED (operator, same day, interrupting the push): "in dynamischem +> Substrat in place mit Lance versioning, in statischem Ontologie-Substrat +> Alpha layer."** The ⊘ block above made the overlay THE mechanism everywhere. +> Refined: **handover has TWO ARMS, selected by the substrate's nature** — +> +> | substrate | attention travels as | residue/history carrier | ruling it lands on | +> |---|---|---|---| +> | **static ontology** (shared, durable, cacheable — must stay uncontaminated) | an alpha-layer entry (`AttentionFocusFacet` in the next rung layer) at the same address | the overlay itself — discardable whole | alpha-plan §2 contamination boundary | +> | **dynamic substrate** (session / patient / working rows — allowed to change) | an **in-place write by the row's one owner** | **Lance versioning** — episodic = Lance versions; rung n+1's *"where did rung n look"* is a version-range read, `QueryReference::at(v, rung)`: a projection, zero copies, replayable | `E-MARKOV-TEMPORAL-STREAM-1`; alpha-plan §3c (*"a read at a version, never a stored history column"*) | +> +> The symmetry is the principle stated once: **attention leaves a replayable +> trace without contaminating what it observed.** On the static side the trace +> is a separate layer, because the substrate must not move; on the dynamic side +> the trace is the substrate's own motion, because it moves anyway and Lance +> keeps every version. Two implementations, one invariant — and both arms are +> zero-copy readings, both one-writer-clean (the layer's owner writes its +> layer; the row's owner writes its row). +> +> **The dynamic arm needs NOTHING built.** Lance versioning and +> `QueryReference::at` are shipped; the version stream IS the alpha channel of +> the dynamic side. What remains is solely the overlay arm's n+1-layer insert +> convention (`RowFocusMask` shipped today) plus the Hole lifecycle stamps — +> and D-ACR-16's cascade is now fully shaped: rung layers stacked over the +> static substrate, version-range reads over the dynamic one. + +## §10 — Gestalt, meta-awareness, self-organization: where each word cashes out (operator closing claim, 2026-08-21) + +Operator: *"Dadurch entsteht Gestalt und Meta-Awareness — und durch die +Architektur wird es self-organizing."* Recorded with each term bound to its +mechanism, because an emergence claim without a mechanism is the exact +overclaim shape this board fences. + +**Gestalt** — apprehending a whole without enumerating its parts: + +- A shallow focus facet IS the gestalt read: one 16-byte atom at depth `d` + covers `256^(12−d)` addresses — the subtree perceived as a unit, no member + visited (§8's radix arithmetic, D-ACR-1's measured wildcard). +- `RowFocusMask`'s absorption into a **minimal antichain** is a mechanical + Prägnanz operation: the mask converges to the simplest description of where + attention went (covered entries absorbed, never enumerated). *(Observation, + not doctrine — the correspondence is structural, not measured.)* +- Multistability is architectural: the same 12 bytes read as different wholes + per `classid → ClassView` — the duck-rabbit resolved by declaration instead + of ambiguity (D-ACR-1's two-readings test proves the projection is free). + +**Meta-awareness** — awareness whose OBJECT is awareness: + +- Rung n+1's layer entries are *about* rung n's entries at the same addresses + (D-ACR-4: *"a rung-2 read reconstructs where rung-1 looked"*). On the dynamic + arm, `QueryReference::at(v, rung)` is awareness of one's own past states — + replayable, zero copies. +- The Hole is the core metacognitive act as a ROW: knowing that one does not + know, with lifecycle. +- MUL's confidence-invariance (§3i of the parent plan) is awareness of the + *reliability* of one's own awareness — with the 20/34 mute tactics as + measurable overconfidence-by-construction. + +**Self-organizing — and why "durch die Architektur" is the precise phrase.** +The self-organization literature's preconditions map one-to-one onto RULINGS +here, not onto code that could rot: + +| SO precondition | its ruling / mechanism | +|---|---| +| local rules, no central controller | one-writer-per-mailbox (`E-CE64-MB-4`); no scheduler (`E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1`) | +| a **stigmergic medium** (activity leaves traces that guide later activity) | the two-armed trace: alpha layers over the static substrate, Lance versions over the dynamic one (§9 ⊘⊘) | +| gradient to descend | potholes; one epistemic pothole at a time (§8's descent rule) | +| structure accretes where activity accumulates | growth = minting rows/levels, never widening (§0) | +| the medium must not corrupt the terrain | contamination boundary (alpha-plan §2); overlay discardable whole | + +The stigmergy identification is the load-bearing one: ants organize through +pheromone traces in the environment, not through messages to each other — and +this substrate's attention traces are exactly that, with the refinement that +the static terrain gets a separate trace layer while the dynamic terrain's own +version history IS its trace. *(Grade: [H] — the structural match is exact; +no collective behaviour has been measured yet.)* + +**The emergence claim itself stays CONJECTURE, and it already has its +falsifiers — no new ones needed:** + +| claim | existing gate that tests it | +|---|---| +| the awareness discriminates (is not a watcher that cannot dissent) | D-ACR-8 (focus measurably broader in `Planning` than `CognitiveWork`, AND indistinguishable on a no-deliberation task) | +| the suspense is real (not lookup wearing NARS vocabulary) | D-ACR-10 (pothole-open SPANS must vary and eventually close) | +| the rewiring responds to resolution, not to noise | D-KUH-3 (plasticity moves on a real close, NOT on a refuted one) | +| the overconfidence measurement is real | D-ACR-11 (the 20 must be invariant AND at least one of the 14 must move) | + +That is the honest form of the closing claim: **the preconditions are +architectural invariants (rulings, ownership, const-asserts), the emergence is +a prediction, and the prediction's tests are already pre-registered.** A +system that manages its known-unknowns as first-class rows carries its own +"does this become what it aspires to?" as the largest of them — §7's +discipline, unchanged. + +## §11 — The epistemic breakthrough: the planned composition (operator, 2026-08-21) + +Operator: *"Epistemic breakthrough ist die geplante Ausweitung von CE64 59..63 +auf CEV3 + attention V3 × pothole × kanban × cognitive Maslow via supervisor: +kanban_actor transparent view as meta awareness als self aware."* + +This names the TARGET the individual deliverables converge on — the +cross-product, not a new mechanism. Measured state of every factor: + +| factor | home | state (verified) | +|---|---|---| +| CE64 59..63 reading | `dacr7-band-reading-contract-v1.md` | **RATIFIED** (5+3 council, 3×BLOCK resolved) | +| → extended to `CausalEdgeV3` | same contract, dual-carrier | **RATIFIED** — one contract, two carriers; v1 trap reaches V3 transitively | +| attention v3 | third sanctioned explicit-temporal home | **planned** — the ↑n stack; not `attention_facet` (deliberately atemporal) | +| pothole | span (D-ACR-10) + live horizon (D-ACR-15) | designed; probes pre-registered | +| kanban | `lance-graph-supervisor::kanban_actor` | **SHIPPED** — Heckhausen columns, Libet anchors; Rubicon witness D-ACR-8 queued | +| **cognitive Maslow** | `contract::recipe_loci` | **SHIPPED, already operator-ruled** — *"the rung a recipe fires at IS a level of the operator-ruled Maslow pyramid of cognition"* (`recipe_loci.rs:55-62`); Maslow-monotone climb, elevate on sustained BLOCK, never below `base`; 70 references tree-wide | +| the composition point | `PhaseCensus` | **SHIPPED** — *"the read-only fleet visibility surface … a census is one `&self` pass, not 64k RPCs"* (`kanban_actor.rs:30-31`) | + +**Two consequences of the measurement:** + +**1. "Cognitive Maslow" is not a missing layer — it is the shipped rung +vocabulary.** No pyramid needs building; the composition consumes +`recipe_loci`'s rung levels as its need-axis. What ascends the pyramid is +exactly what the loop produces: a pothole that resists closure at rung n +escalates Maslow-monotone — sustained BLOCK is the climb signal, already ruled. + +**2. "Transparent view as meta-awareness als self-aware" cashes out as the +CENSUS LOOP, and every read in it already exists or is ratified:** the system +reading its own phase distribution (`PhaseCensus`, one `&self` pass), its own +attention (`RowFocusMask`), its own unknowns (potholes with spans), its own +epistemic grading (the band readings, both carriers), its own need-level (the +Maslow rung). **Meta-awareness = these five transparent reads composed at the +same addresses; self-aware = the composition feeding what gets attention next +— by existence, never by command.** No new organ is minted for it; the +supervisor's kanban_actor is where the five reads meet because it is the one +place that already sees the fleet without owning it. + +**The gate status that makes "breakthrough" the right word today rather than +last week:** the alpha plan's §3h (MUL over the rung layers) was deliberately +left without a deliverable id *"because it depends on all three of: +`RowFocusMask` (D-ACR-1), the 59..63 reading contract (D-ACR-7), and the +`delta_conf` filter"*. As of this date: **D-ACR-1 shipped, D-ACR-7 ratified, +and the `delta_conf` filter is a ratified acceptance condition inside +D-ACR-7.** All three prerequisites of the join are landed or ratified — §3h +moves from "long-term" to "next in line after the `band_reading` +implementation", which is the precise, unglamorous form of the breakthrough. + +**Grade discipline, §7 unchanged:** the composition is a plan over shipped and +ratified parts; that it produces meta-awareness in more than the mechanical +census-loop sense is the CONJECTURE whose falsifiers are §10's four gates. +Self-description that discriminates is the claim; D-ACR-8/10/11 + D-KUH-3 +remain the tests. diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 324bf13a8..4f2cfb924 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -651,7 +651,8 @@ impl CausalEdge64 { // weight.inference_type() is the v1 fallback below; v2 uses mantissa let resolved_infer = InferenceType::from_mantissa(weight.inference_mantissa()); #[cfg(not(feature = "causal-edge-v2-layout"))] - #[allow(deprecated)] // v1 layout: 3-bit unsigned inference type is the canonical read + #[allow(deprecated)] + // v1 layout: 3-bit unsigned inference type is the canonical read let resolved_infer = weight.inference_type(); let (f_out, c_out) = match resolved_infer { InferenceType::Deduction => { diff --git a/crates/causal-edge/src/edge_v3.rs b/crates/causal-edge/src/edge_v3.rs index a89619afb..ab42c64f9 100644 --- a/crates/causal-edge/src/edge_v3.rs +++ b/crates/causal-edge/src/edge_v3.rs @@ -713,4 +713,48 @@ mod tests { // and TE was NOT inherited from the deprecated v2 temporal composite assert_eq!(bytes[7], 0, "TE must not be lifted from v2 temporal"); } + + /// G10b (D-ACR-7 ratified gate, hosted HERE because both crates are + /// zero-dep): the lift preserves the truth/spare ordinals — the module doc + /// claims the round trip is byte-exact, and this is the test that asserts + /// the truth/spare half of that claim through the ACCESSORS (not just the + /// raw bytes), under BOTH lens vocabularies over the same unchanged bits. + #[cfg(feature = "causal-edge-v2-layout")] + #[test] + fn g10b_lift_preserves_truth_and_spare_ordinals_under_both_lenses() { + use crate::layout::{CausalTopology, ReasoningBand}; + + // Firing half: nonzero ordinals in both registers, set via the + // TOPOLOGY/BAND lens (the readings D-ACR-7 declares per class). + let e = sample_edges()[0] + .with_topology(CausalTopology::Unknown) // ordinal 0b11 + .with_reasoning_band(ReasoningBand::Transcendent); // ordinal 0b111 + let v3 = CausalEdgeV3::from_v1(e, 7); + assert_eq!( + v3.truth_raw(), + e.truth_raw(), + "truth ordinal dropped by lift" + ); + assert_eq!(v3.spare_raw(), e.spare(), "spare ordinal dropped by lift"); + assert_eq!(v3.truth_raw(), 0b11); + assert_eq!(v3.spare_raw(), 0b111); + + // Round trip: rehydrate with the same SPO restores the ordinals so the + // reasoning carrier sees exactly what was stored. + let back = v3.rehydrate(e.s_idx(), e.p_idx(), e.o_idx()); + assert_eq!(back.truth_raw(), e.truth_raw()); + assert_eq!(back.spare(), e.spare()); + assert_eq!(back.topology(), CausalTopology::Unknown); + assert_eq!(back.reasoning_band(), ReasoningBand::Transcendent); + + // Stay-silent half: zero ordinals stay zero — the lift neither invents + // a band nor upgrades provenance (the from_v1 trap D-ACR-7 fences is a + // PROVENANCE gap, not a bit-copy defect; the bits themselves are exact). + let z = sample_edges()[1]; + assert_eq!(z.truth_raw(), 0); + assert_eq!(z.spare(), 0); + let vz = CausalEdgeV3::from_v1(z, 7); + assert_eq!(vz.truth_raw(), 0); + assert_eq!(vz.spare_raw(), 0); + } } diff --git a/crates/lance-graph-contract/src/attention_facet.rs b/crates/lance-graph-contract/src/attention_facet.rs new file mode 100644 index 000000000..c94f1857f --- /dev/null +++ b/crates/lance-graph-contract/src/attention_facet.rs @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright The Lance Authors + +//! `attention_facet` — the **Attention** reading of the content-blind +//! `6×(8:8)` register, and the sparse container over it (`D-ACR-1`). +//! +//! This is a **reading, not a layout** — the discipline +//! [`crate::awareness_facet::SpoFacet`] and [`crate::tekamolo_facet`] already +//! follow. It re-labels the 12 payload bytes a +//! [`FacetCascade`](crate::facet::FacetCascade) already holds; it reserves, +//! moves and stores **nothing**. Zero new bytes enter the substrate. +//! +//! # The basis decision (`D-ACR-1`), and why it is not six vertical slots +//! +//! The attention atom is the SHIPPED atom: `FacetCascade` = +//! `facet_classid(4) | 6×(8:8) = 16 B`, read under +//! [`CascadeShape::G6D2`](crate::facet::CascadeShape::G6D2) — `6 groups × +//! 2 levels`, `group_of` a pure shift. Nothing is minted. +//! +//! The tempting alternative was a **matrix**: six *vertical* rows of the same +//! 12-byte atom, one per rung/layer/timestep. It is rejected, and the reason is +//! the plan's own §3 (`alpha-channel-rung-overlay-v1.md`): +//! +//! > a second-order thought at node `g` is the **same** `NodeGuid` … a +//! > **different thinking-table row** — a different `(classid, rail)` pairing +//! > resolved by the ClassView … occupancy **sparse**. +//! +//! So the vertical dimension is **already addressed** — it is the +//! `facet_classid` selecting which thinking-table row a focus belongs to. +//! Stacking six fixed rows inside one atom would (a) mint a second addressing +//! system for a dimension the classid already carries, which HTT §2.2 +//! explicitly withdrew (*"one fabric, several ClassView-resolved readings, +//! never alternative addressing systems"*), and (b) force an occupancy of +//! exactly six where §3 requires sparse. **One atom, one `(classid, rail)`; +//! the vertical stack is a set of atoms, not a field inside one.** +//! +//! # The six axes are CONTENT-BLIND — this type names none of them +//! +//! [`FacetCascade`]'s own contract is that *"the substrate is ALWAYS 8:8 … +//! only the CONSUMER projects meaning onto the bytes"*. This module keeps that +//! property: [`FocusAxis`] is `Axis0..Axis5`, a **position**, and +//! [`AttentionFocusFacet`] exposes only `coarse`/`fine` per axis. What an axis +//! MEANS is a **reading contract** resolved by `classid → ClassView`, never a +//! constant here. +//! +//! Two readings are sketched in the tests, over the SAME unchanged bytes, to +//! prove the projection is free: +//! +//! | reading | axis 0 … axis 5 | +//! |---|---| +//! | **cascade** (the key's own tiers) | HEEL · HIP · TWIG · LEAF · family · identity | +//! | **ontology scope** (a domain adapter's) | disease/phenotype · anatomy · process/mechanism · substance/intervention · evidence/provenance · context/time/population | +//! +//! Neither list is a type in this crate. The cascade reading is the canon's +//! own tier ladder; the ontology-scope reading is a *candidate* domain +//! adapter's projection (operator hypothesis, 2026-08-21) and belongs to that +//! adapter — a consumer stamps it via its ClassView. Baking either into +//! [`FocusAxis`] would make one domain's schema the substrate's, which is the +//! collision `E-ATTENTION-MASK-IS-A-RENAME-REGISTER-FILE-NOT-A-RESIDUE-CARRIER-1` +//! has this arc's fourth instance of. +//! +//! Each axis carries the always-8:8 pair — **level 0 = `hi` = coarse**, +//! **level 1 = `lo` = fine** (the shipped +//! [`FacetCascade::tier_bytes`](crate::facet::FacetCascade::tier_bytes) order, +//! `[t0.hi, t0.lo, t1.hi, …]`). The `u8`s bound **axis resolution** — 256 +//! centroids per level — and say nothing about how many graph rows a focus +//! covers. +//! +//! # Composition is PREFIX CONTAINMENT, never a blind OR +//! +//! `FieldMask` / `WideFieldMask` / `StepMask` are bitsets over **field +//! positions**, and their `union` is a bit-`OR`. **This type does not do +//! that**, and must not be made to: OR-ing two addresses yields a third +//! address neither side ever visited. The composition here is the one the +//! canon already uses for ancestry — +//! [`NiblePath::is_ancestor_of`](crate::hhtl::NiblePath::is_ancestor_of) — a +//! coarse→fine **prefix** test: +//! +//! ```text +//! a covers b ⟺ a.classid == b.classid +//! ∧ a.depth <= b.depth +//! ∧ the first a.depth cascade units agree +//! ``` +//! +//! A focus at depth 0 covers its whole class; at depth 12 it is one exact +//! address. **That is also how a focus over more than 256 rows is +//! representable**: one shallow facet is a wildcard over an unbounded subtree, +//! not 10⁶ bits. The `u8` per level caps the *branching a single level can +//! name*, never the population a facet covers. +//! +//! # Depth is explicit, never inferred from zero bytes +//! +//! [`AttentionFocusFacet`] carries `depth` **outside** the 12 bytes, exactly as +//! [`NiblePath`](crate::hhtl::NiblePath) carries its own. Inferring "the +//! wildcard starts where the bytes go zero" would collide with the +//! zero-fallback ladder, where `0` is a legitimate *dormant tier* rather than a +//! terminator — two meanings for one byte value, the ambiguity `NiblePath` +//! already refused. The wire shape stays exactly `6 × 2 × u8`; `depth` is a +//! **composition parameter**, not a stored byte. +//! +//! # What this module does NOT touch +//! +//! `cognitive-shader-driver`'s `attention_mask.rs` / `attention_mask_actor.rs` +//! are **not** used, and structurally cannot be: this is +//! `lance-graph-contract`, the zero-dep crate `cognitive-shader-driver` itself +//! depends on — the dependency edge runs only the other way, so the exclusion +//! is a compile-time property, not a convention. Per `D-ACR-0` +//! (`.claude/ATTENTION_MASK_AUDIT_2026_08_21.md`) those types are a finished +//! **rename register file** for a different contract (wide identity → scarce +//! narrow slot, LRU because slots are scarce), keyed by `MailboxId`, with no +//! address, no mask algebra and no trajectory. Do not build on them. + +use crate::facet::{CascadeShape, FacetCascade, FacetTier, CASCADE_UNITS}; + +/// The number of axes an [`AttentionFocusFacet`] carries — `CASCADE_UNITS / 2` +/// under [`CascadeShape::G6D2`]. Derived, never a second literal. +pub const FOCUS_AXES: usize = CASCADE_UNITS / 2; + +/// One of the six axes — a **position, not a meaning**. +/// +/// `FacetCascade`'s contract is that the substrate is content-blind and only the +/// consumer projects meaning; this enum keeps that. It names slots `Axis0..Axis5` +/// on purpose. A cascade reading calls axis 0 "HEEL"; an ontology-scope adapter +/// may call it "disease/phenotype". Both are ClassView-resolved projections of +/// the same byte pair — see the module docs' two-reading table. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(u8)] +pub enum FocusAxis { + /// Axis 0 — cascade units 0 (coarse) and 1 (fine). + Axis0 = 0, + /// Axis 1 — cascade units 2 and 3. + Axis1 = 1, + /// Axis 2 — cascade units 4 and 5. + Axis2 = 2, + /// Axis 3 — cascade units 6 and 7. + Axis3 = 3, + /// Axis 4 — cascade units 8 and 9. + Axis4 = 4, + /// Axis 5 — cascade units 10 and 11. + Axis5 = 5, +} + +impl FocusAxis { + /// All six axes, ascending. + pub const ALL: [FocusAxis; FOCUS_AXES] = [ + FocusAxis::Axis0, + FocusAxis::Axis1, + FocusAxis::Axis2, + FocusAxis::Axis3, + FocusAxis::Axis4, + FocusAxis::Axis5, + ]; + + /// The `CascadeShape::G6D2` group index this axis addresses. + #[inline] + #[must_use] + pub const fn group(self) -> u8 { + self as u8 + } + + /// Axis for a group index, or `None` past the sixth — a loud refusal rather + /// than a silent clamp (the `WideFieldMask` cap discipline). + #[inline] + #[must_use] + pub const fn from_group(g: u8) -> Option { + match g { + 0 => Some(FocusAxis::Axis0), + 1 => Some(FocusAxis::Axis1), + 2 => Some(FocusAxis::Axis2), + 3 => Some(FocusAxis::Axis3), + 4 => Some(FocusAxis::Axis4), + 5 => Some(FocusAxis::Axis5), + _ => None, + } + } +} + +/// **Where focus landed or was projected** — an [`FacetCascade`] read as +/// attention, plus the explicit prefix `depth` that makes it a wildcard. +/// +/// The bytes are the shipped atom, untouched. `depth ∈ 0..=CASCADE_UNITS` +/// counts significant cascade units coarse→fine; it lives OUTSIDE the 12 bytes +/// (see the module docs' depth section). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AttentionFocusFacet { + /// The content-blind carrier — `facet_classid(4) | 6×(8:8)`. + pub facet: FacetCascade, + /// Significant cascade units, coarse→fine, `0..=CASCADE_UNITS`. + /// `0` = the whole class; `CASCADE_UNITS` = one exact address. + depth: u8, +} + +impl AttentionFocusFacet { + /// The carving this reading uses. `G6D2` — six axes, coarse/fine, and + /// `group_of` is the shift `i >> 1`. + pub const SHAPE: CascadeShape = CascadeShape::G6D2; + + /// An exact focus: all 12 units significant. + #[inline] + #[must_use] + pub const fn exact(facet: FacetCascade) -> Self { + Self { + facet, + depth: CASCADE_UNITS as u8, + } + } + + /// A **wildcard** focus: only the first `depth` cascade units are + /// significant, the rest are "not consulted". Returns `None` if + /// `depth > CASCADE_UNITS` — a loud refusal, never a silent clamp. + #[inline] + #[must_use] + pub const fn prefix(facet: FacetCascade, depth: u8) -> Option { + if depth as usize > CASCADE_UNITS { + None + } else { + Some(Self { facet, depth }) + } + } + + /// The whole class — depth 0, the broadest focus expressible. + #[inline] + #[must_use] + pub const fn whole_class(facet_classid: u32) -> Self { + Self { + facet: FacetCascade { + facet_classid, + tiers: [FacetTier { lo: 0, hi: 0 }; FOCUS_AXES], + }, + depth: 0, + } + } + + /// Significant cascade units, coarse→fine. + #[inline] + #[must_use] + pub const fn depth(self) -> u8 { + self.depth + } + + /// The class this focus is scoped to. Focus never crosses classes — + /// [`covers`](Self::covers) requires equality. + #[inline] + #[must_use] + pub const fn classid(self) -> u32 { + self.facet.facet_classid + } + + /// The 12 payload bytes, coarse→fine per axis (`[a0.hi, a0.lo, a1.hi, …]`) + /// — the shipped [`FacetCascade::tier_bytes`] order, unchanged. + #[inline] + #[must_use] + pub const fn payload_bytes(self) -> [u8; CASCADE_UNITS] { + self.facet.tier_bytes() + } + + /// The coarse byte of `axis` (`hi`, level 0), or `None` if the axis lies + /// past this focus's [`depth`](Self::depth) — an unconsulted axis has no + /// value, and reporting `0` would conflate "not consulted" with "centroid + /// zero". + #[inline] + #[must_use] + pub fn coarse(self, axis: FocusAxis) -> Option { + let unit = Self::SHAPE.index(axis.group(), 0); + (unit < self.depth as usize).then(|| self.payload_bytes()[unit]) + } + + /// The fine byte of `axis` (`lo`, level 1), or `None` if past + /// [`depth`](Self::depth). + #[inline] + #[must_use] + pub fn fine(self, axis: FocusAxis) -> Option { + let unit = Self::SHAPE.index(axis.group(), 1); + (unit < self.depth as usize).then(|| self.payload_bytes()[unit]) + } + + /// Both bytes of `axis` as the shipped 8:8 tile, or `None` unless BOTH + /// levels are within [`depth`](Self::depth). + #[inline] + #[must_use] + pub fn axis(self, axis: FocusAxis) -> Option { + match (self.coarse(axis), self.fine(axis)) { + (Some(hi), Some(lo)) => Some(FacetTier { lo, hi }), + _ => None, + } + } + + /// **The composition primitive: prefix containment.** `self` covers `other` + /// iff they share a class, `self` is no deeper, and their first + /// `self.depth` cascade units agree coarse→fine. + /// + /// This is [`NiblePath::is_ancestor_of`](crate::hhtl::NiblePath::is_ancestor_of)'s + /// rule on the 12-unit ladder — reflexive (a focus covers itself), unlike + /// `NiblePath`'s empty-path case. It is **not** a bit test and has no + /// `OR`-like counterpart; see [`RowFocusMask`]. + #[must_use] + pub fn covers(self, other: Self) -> bool { + if self.facet.facet_classid != other.facet.facet_classid || self.depth > other.depth { + return false; + } + let (a, b) = (self.payload_bytes(), other.payload_bytes()); + a[..self.depth as usize] == b[..self.depth as usize] + } + + /// The **meet**: the deepest focus that covers both — their shared + /// coarse→fine prefix. `None` across classes (no common ancestor exists; + /// a focus never crosses a class). + /// + /// This is what replaces a union: two focuses generalise to the subtree + /// containing both, never to an invented address. It is lossy on purpose — + /// [`RowFocusMask::union`] keeps both entries instead when exactness + /// matters. + #[must_use] + pub fn common_prefix(self, other: Self) -> Option { + if self.facet.facet_classid != other.facet.facet_classid { + return None; + } + let (a, b) = (self.payload_bytes(), other.payload_bytes()); + let limit = self.depth.min(other.depth) as usize; + let mut shared = 0usize; + while shared < limit && a[shared] == b[shared] { + shared += 1; + } + let mut facet = self.facet; + // Units past the shared prefix are not consulted; zero them so the + // carrier cannot be misread as an exact address by a reader that + // ignores `depth`. + let mut unit = shared; + while unit < CASCADE_UNITS { + let (g, l) = (Self::SHAPE.group_of(unit), Self::SHAPE.level_of(unit)); + let tier = &mut facet.tiers[g as usize]; + if l == 0 { + tier.hi = 0; + } else { + tier.lo = 0; + } + unit += 1; + } + Some(Self { + facet, + depth: shared as u8, + }) + } +} + +/// A **sparse set of focuses** — the container half of `D-ACR-1`. +/// +/// # Why this is not a bitmask +/// +/// A row population is unbounded; `FieldMask` tops out at 64 positions and +/// `WideFieldMask` at 256, both by construction. Neither cardinality is +/// inherited here, because this container does not index rows at all — it +/// holds **wildcards over address subtrees**. One [`AttentionFocusFacet`] at a +/// shallow depth covers an arbitrarily large population; membership is +/// [`AttentionFocusFacet::covers`], not a bit lookup. +/// +/// # The set operations, named +/// +/// | op | meaning | NOT | +/// |---|---|---| +/// | [`union`](Self::union) | both focuses retained; entries covered by a shallower entry are absorbed | never a bit-`OR` of addresses | +/// | [`intersect`](Self::intersect) | the deeper of each covering pair | never `AND` of bytes | +/// | [`difference`](Self::difference) | entries not covered by the other side | never `ANDNOT` of bytes | +/// +/// `union` is **absorbing, not accumulating**: inserting a focus already +/// covered by a member is a no-op, and inserting one that covers members +/// replaces them. That keeps the set minimal (an antichain) without ever +/// inventing an address. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct RowFocusMask { + entries: Vec, +} + +impl RowFocusMask { + /// The empty focus — covers nothing. Distinct from a depth-0 focus, which + /// covers a whole class (the can-stay-silent / can-fire pair). + #[must_use] + pub const fn empty() -> Self { + Self { + entries: Vec::new(), + } + } + + /// Is this focus empty? An empty mask covers nothing at all. + #[must_use] + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + /// Number of retained entries — the antichain size, NOT a row count. A + /// single entry may cover an unbounded population. + #[must_use] + pub fn len(&self) -> usize { + self.entries.len() + } + + /// The retained entries, in insertion order after absorption. + #[must_use] + pub fn entries(&self) -> &[AttentionFocusFacet] { + &self.entries + } + + /// Insert a focus, keeping the set a minimal antichain: a no-op if already + /// covered; otherwise it replaces every entry it covers. Returns `true` iff + /// the set changed. + pub fn insert(&mut self, f: AttentionFocusFacet) -> bool { + if self.entries.iter().any(|e| e.covers(f)) { + return false; + } + self.entries.retain(|e| !f.covers(*e)); + self.entries.push(f); + true + } + + /// Does any entry cover `f`? — membership, by containment. + #[must_use] + pub fn contains(&self, f: AttentionFocusFacet) -> bool { + self.entries.iter().any(|e| e.covers(f)) + } + + /// Every focus of either side, absorbed to a minimal antichain. + #[must_use] + pub fn union(&self, other: &Self) -> Self { + let mut out = self.clone(); + for e in &other.entries { + out.insert(*e); + } + out + } + + /// The overlap: for each covering pair, the **deeper** focus — the region + /// both sides actually agree on. Disjoint pairs contribute nothing. + #[must_use] + pub fn intersect(&self, other: &Self) -> Self { + let mut out = Self::empty(); + for a in &self.entries { + for b in &other.entries { + if a.covers(*b) { + out.insert(*b); + } else if b.covers(*a) { + out.insert(*a); + } + } + } + out + } + + /// Entries of `self` not covered by any entry of `other`. + /// + /// **Deliberately conservative:** an entry only PARTIALLY overlapped is + /// kept whole, because subtracting a subtree from a prefix would require + /// enumerating siblings — inventing addresses the focus never visited. + /// Splitting is deferred (see the module's deferred note). + #[must_use] + pub fn difference(&self, other: &Self) -> Self { + Self { + entries: self + .entries + .iter() + .filter(|a| !other.entries.iter().any(|b| b.covers(**a))) + .copied() + .collect(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const CLASS: u32 = 0x0301_0000; + + fn facet_of(bytes: [u8; CASCADE_UNITS], classid: u32) -> FacetCascade { + // `tier_bytes()` is [t0.hi, t0.lo, t1.hi, t1.lo, …] — coarse first. + let mut tiers = [FacetTier { lo: 0, hi: 0 }; FOCUS_AXES]; + for (g, tier) in tiers.iter_mut().enumerate() { + tier.hi = bytes[g * 2]; + tier.lo = bytes[g * 2 + 1]; + } + FacetCascade { + facet_classid: classid, + tiers, + } + } + + fn exact(bytes: [u8; CASCADE_UNITS]) -> AttentionFocusFacet { + AttentionFocusFacet::exact(facet_of(bytes, CLASS)) + } + + // ── the atom: exact 12-byte round-trip ──────────────────────────────── + + #[test] + fn twelve_byte_payload_round_trips_exactly() { + let bytes: [u8; CASCADE_UNITS] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + let f = exact(bytes); + assert_eq!( + f.payload_bytes(), + bytes, + "the 12 payload bytes are the atom" + ); + // And the carrier is the shipped 16-byte facet, unchanged. + assert_eq!( + FacetCascade::from_bytes(&f.facet.to_bytes()), + f.facet, + "the FacetCascade carrier round-trips through its own 16 bytes" + ); + assert_eq!(core::mem::size_of::(), 16); + } + + #[test] + fn per_axis_coarse_and_fine_round_trip() { + let bytes: [u8; CASCADE_UNITS] = [10, 11, 20, 21, 30, 31, 40, 41, 50, 51, 60, 61]; + let f = exact(bytes); + for (g, axis) in FocusAxis::ALL.into_iter().enumerate() { + assert_eq!(f.coarse(axis), Some(bytes[g * 2]), "coarse = hi, level 0"); + assert_eq!(f.fine(axis), Some(bytes[g * 2 + 1]), "fine = lo, level 1"); + let tile = f.axis(axis).expect("exact focus has every axis"); + assert_eq!((tile.hi, tile.lo), (bytes[g * 2], bytes[g * 2 + 1])); + } + } + + #[test] + fn an_axis_past_the_prefix_depth_reads_none_not_zero() { + // depth 4 ⇒ axes 0 and 1 significant, axes 2..6 not consulted. + let f = AttentionFocusFacet::prefix(facet_of([7; CASCADE_UNITS], CLASS), 4).unwrap(); + assert_eq!(f.coarse(FocusAxis::Axis1), Some(7)); + assert_eq!( + f.coarse(FocusAxis::Axis2), + None, + "unconsulted axis must not report 0 — that is centroid zero's value" + ); + assert_eq!(f.axis(FocusAxis::Axis2), None); + } + + #[test] + fn prefix_refuses_out_of_range_depth_loudly() { + let c = facet_of([0; CASCADE_UNITS], CLASS); + assert!(AttentionFocusFacet::prefix(c, CASCADE_UNITS as u8).is_some()); + assert!( + AttentionFocusFacet::prefix(c, CASCADE_UNITS as u8 + 1).is_none(), + "past the ladder is a refusal, never a silent clamp" + ); + } + + // ── composition: containment, and demonstrably NOT a blind OR ───────── + + #[test] + fn covers_is_prefix_containment_and_is_reflexive() { + let deep = exact([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + let shallow = + AttentionFocusFacet::prefix(facet_of([1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0], CLASS), 4) + .unwrap(); + assert!(shallow.covers(deep), "a prefix covers what extends it"); + assert!(!deep.covers(shallow), "and containment is asymmetric"); + assert!(deep.covers(deep), "reflexive"); + } + + #[test] + fn a_focus_never_covers_across_classes() { + let a = AttentionFocusFacet::whole_class(CLASS); + let b = AttentionFocusFacet::exact(facet_of([1; CASCADE_UNITS], 0x0302_0000)); + assert!( + !a.covers(b), + "depth 0 covers its OWN class only — focus never crosses a class" + ); + } + + /// The load-bearing negative: bitwise-OR of two focuses would produce an + /// address neither visited, and the container must not behave that way. + #[test] + fn composition_is_not_a_blind_or() { + let a = exact([0b0000_0001, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let b = exact([0b0000_0010, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + // What a bit-OR WOULD produce — a third, never-visited address. + let or_byte = 0b0000_0001u8 | 0b0000_0010u8; + let phantom = exact([or_byte, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_ne!(or_byte, 0b0000_0001); + assert_ne!(or_byte, 0b0000_0010); + + let u = RowFocusMask::empty().union(&{ + let mut m = RowFocusMask::empty(); + m.insert(a); + m.insert(b); + m + }); + + assert!( + u.contains(a) && u.contains(b), + "both originals are retained" + ); + assert!( + !u.contains(phantom), + "the OR-address was never visited and must not become a member" + ); + assert_eq!(u.len(), 2, "union keeps both, it does not fuse them"); + } + + #[test] + fn common_prefix_generalises_and_never_invents_an_address() { + let a = exact([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + let b = exact([1, 2, 3, 99, 0, 0, 0, 0, 0, 0, 0, 0]); + let m = a.common_prefix(b).expect("same class"); + assert_eq!(m.depth(), 3, "they agree on exactly three cascade units"); + assert!(m.covers(a) && m.covers(b), "the meet covers both"); + assert_eq!( + &m.payload_bytes()[3..], + &[0u8; CASCADE_UNITS - 3], + "units past the shared prefix are zeroed, not left as stale bytes" + ); + assert!( + exact([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]) + .common_prefix(AttentionFocusFacet::exact(facet_of( + [1; CASCADE_UNITS], + 0x0999_0000 + ))) + .is_none(), + "no meet across classes" + ); + } + + // ── the container: cardinality is NOT inherited ─────────────────────── + + #[test] + fn container_holds_far_more_than_the_fieldmask_and_widefieldmask_caps() { + // FieldMask::MAX_FIELDS == 64 and WideFieldMask's u8 universe caps at + // 256. Neither bound applies here: this is not a position bitset. + let mut m = RowFocusMask::empty(); + for i in 0..1000u32 { + let b = i.to_le_bytes(); + // Distinct in the FINE units so no entry absorbs another. + m.insert(exact([0, 0, 0, 0, b[0], b[1], b[2], b[3], 0, 0, 0, 0])); + } + assert_eq!(m.len(), 1000, "1000 > 256 > 64 — no cap inherited"); + } + + #[test] + fn one_shallow_focus_covers_an_unbounded_population() { + // The >256-rows requirement: coverage comes from the PREFIX, not from + // one bit per row. A depth-2 focus covers every address sharing axis 0. + let wildcard = + AttentionFocusFacet::prefix(facet_of([9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], CLASS), 2) + .unwrap(); + let mut covered = 0usize; + for i in 0..=u16::MAX { + let b = i.to_le_bytes(); + let addr = exact([9, 9, b[0], b[1], 0, 0, 0, 0, 0, 0, 0, 0]); + if wildcard.covers(addr) { + covered += 1; + } + } + assert_eq!( + covered, 65_536, + "one 12-byte atom covers 2^16 distinct addresses — and that is only \ + the two units varied here, not the ceiling" + ); + } + + #[test] + fn insert_absorbs_into_a_minimal_antichain() { + let mut m = RowFocusMask::empty(); + let deep = exact([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + assert!(m.insert(deep)); + // A shallower focus covering it replaces it. + let shallow = + AttentionFocusFacet::prefix(facet_of([1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], CLASS), 2) + .unwrap(); + assert!(m.insert(shallow)); + assert_eq!(m.len(), 1, "the covered entry was absorbed"); + assert!(m.contains(deep), "and is still a member by containment"); + assert!(!m.insert(deep), "re-inserting a covered focus is a no-op"); + } + + #[test] + fn empty_covers_nothing_but_depth_zero_covers_its_class() { + // The can-fire / can-stay-silent pair on NON-trivial input. + let some = exact([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + assert!( + !RowFocusMask::empty().contains(some), + "an empty focus must stay silent" + ); + let mut all = RowFocusMask::empty(); + all.insert(AttentionFocusFacet::whole_class(CLASS)); + assert!(all.contains(some), "and a depth-0 focus must fire"); + // Distinguishable from each other AND from a real, narrow focus. + let mut narrow = RowFocusMask::empty(); + narrow.insert(some); + assert!(!narrow.contains(exact([9; CASCADE_UNITS]))); + assert_ne!(narrow, all); + assert_ne!(narrow, RowFocusMask::empty()); + } + + // ── two readings, ONE unchanged 12-byte atom ───────────────────────── + + /// The operator's 2026-08-21 hypothesis: the six axes may also carry six + /// **ontology scopes** in interplay. This test is the whole argument that + /// the hypothesis costs nothing — it is a *projection*, so both readings + /// address the identical bytes and neither is a type in this crate. + /// + /// A domain adapter would resolve these labels through its ClassView; they + /// appear here as local `const`s precisely to prove they need not exist in + /// the substrate. + #[test] + fn the_same_atom_reads_as_cascade_and_as_ontology_scope_without_changing_a_byte() { + // One adapter's projection (a candidate MedCare-shaped scope reading). + const SCOPE: [&str; FOCUS_AXES] = [ + "disease/phenotype", + "anatomy", + "process/mechanism", + "substance/intervention", + "evidence/provenance", + "context/time/population", + ]; + // The canon's own reading of the very same positions. + const CASCADE: [&str; FOCUS_AXES] = ["HEEL", "HIP", "TWIG", "LEAF", "family", "identity"]; + + let bytes: [u8; CASCADE_UNITS] = [3, 30, 4, 40, 5, 50, 6, 60, 7, 70, 8, 80]; + let f = exact(bytes); + let before = f.facet.to_bytes(); + + // Read it as ontology scope: (scope centroid, local refinement). + let scoped: Vec<(&str, u8, u8)> = FocusAxis::ALL + .into_iter() + .map(|a| { + ( + SCOPE[a.group() as usize], + f.coarse(a).unwrap(), + f.fine(a).unwrap(), + ) + }) + .collect(); + assert_eq!(scoped[1], ("anatomy", 4, 40)); + assert_eq!(scoped[4], ("evidence/provenance", 7, 70)); + + // Read the SAME facet as the cascade tiers. + let cascaded: Vec<(&str, u8, u8)> = FocusAxis::ALL + .into_iter() + .map(|a| { + ( + CASCADE[a.group() as usize], + f.coarse(a).unwrap(), + f.fine(a).unwrap(), + ) + }) + .collect(); + assert_eq!(cascaded[1], ("HIP", 4, 40)); + + // The projections differ only in the LABEL; every byte is identical. + for (s, c) in scoped.iter().zip(cascaded.iter()) { + assert_ne!(s.0, c.0, "the two readings name the axis differently"); + assert_eq!((s.1, s.2), (c.1, c.2), "and read the identical bytes"); + } + assert_eq!( + f.facet.to_bytes(), + before, + "reading a facet never mutates it — a projection is free" + ); + + // Containment is label-blind too: a scope-shaped wildcard ("any + // anatomy under disease centroid 3") is an ordinary depth-2 prefix. + let any_under_disease_3 = + AttentionFocusFacet::prefix(facet_of([3, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], CLASS), 2) + .unwrap(); + assert!(any_under_disease_3.covers(f)); + } + + #[test] + fn intersect_and_difference_are_containment_shaped() { + let deep = exact([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + let shallow = + AttentionFocusFacet::prefix(facet_of([1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], CLASS), 2) + .unwrap(); + let other = exact([9; CASCADE_UNITS]); + + let (mut a, mut b) = (RowFocusMask::empty(), RowFocusMask::empty()); + a.insert(deep); + a.insert(other); + b.insert(shallow); + + let i = a.intersect(&b); + assert_eq!(i.len(), 1, "only the covering pair contributes"); + assert!(i.contains(deep), "and it yields the DEEPER side"); + assert!(!i.contains(other), "the disjoint entry contributes nothing"); + + let d = a.difference(&b); + assert!(!d.contains(deep), "covered entry removed"); + assert!(d.contains(other), "uncovered entry kept"); + } +} diff --git a/crates/lance-graph-contract/src/band_reading.rs b/crates/lance-graph-contract/src/band_reading.rs new file mode 100644 index 000000000..9262e269a --- /dev/null +++ b/crates/lance-graph-contract/src/band_reading.rs @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright The Lance Authors + +//! `band_reading` — the **59..63 reading contract** (`D-ACR-7`, council-ratified +//! spec: `.claude/plans/dacr7-band-reading-contract-v1.md`). +//! +//! Two bits (the truth tail) and three bits (the band tail) of a causal edge +//! carry FOUR shipped readings between them — `TrustTexture` vs +//! `CausalTopology` on the 2-bit field, `ReasoningBand`-present vs spare on +//! the 3-bit field — and **which reading a producer wrote is not recoverable +//! from the bits**. A consumer that reads one lens while the producer wrote +//! the other gets a plausible wrong answer, silently. This module is the +//! producer knowledge, declared per `(classid, rail)`, that the bits +//! themselves cannot carry. +//! +//! # One contract, TWO carriers +//! +//! The same two fields exist on both causal-edge carriers, and the projection +//! here takes **raw ordinals**, so it serves both identically and names +//! neither (this crate is zero-dep; so is the edge crate — measured, both +//! `Cargo.toml`s refuse the other): +//! +//! | carrier | truth field | band field | role | +//! |---|---|---|---| +//! | `CausalEdge64` (bits 59-60 / 61-63) | `truth()` | `reasoning_band()` | muscle memory — it reasons | +//! | `CausalEdgeV3` (byte 8 hi-2 / byte 9 lo-3) | `truth_raw()` | `spare_raw()` | granularity — it rehydrates INTO CE64 to reason | +//! +//! The V3 module doc states the gap this contract closes, verbatim: *"Which +//! lens the ordinal was written through is the producer's knowledge, not the +//! conversion's."* +//! +//! # The provenance doctrine (council BLOCK 1 — read before trusting a V3 register) +//! +//! Under the v1 layout, both fields were `temporal` bits: a v1 edge with +//! `temporal >= 512` reads a NON-ZERO band. And `CausalEdgeV3::from_v1` has +//! **no provenance parameter** — it raw-copies the fields from whatever CE64 +//! it is handed. So the v1 trap reaches V3 **transitively through the lift**, +//! and a tainted register is indistinguishable from a clean one. Hence: +//! +//! - [`EdgeProvenance::V3Register`] means *"the caller asserts this register +//! was minted clean"* — it is an **assertion, never an inference**. The +//! contract cannot recover what the lift destroyed. +//! - A register of unstated origin is [`EdgeProvenance::Unknown`], and +//! `Unknown` **refuses** (zero-fallback: absent an assertion, refuse). +//! +//! # Total lookup, fallible projection (the council's L1 split) +//! +//! Two different operations, deliberately different shapes: +//! +//! - **Declaration lookup is TOTAL** — [`ClassView::band_reading`] +//! (`class_view.rs`) and [`BandDeclarations::reading_or_default`] return the +//! [zero-fallback](BandReading::ZERO_FALLBACK) for an undeclared class, +//! exactly like the sibling selectors `edge_codec_flavor` / `rail_carving` / +//! `value_schema`. Hot-path safe, never an error. *(G5a)* +//! - **Projection is FALLIBLE** — [`BandReading::project_truth`] / +//! [`project_band`](BandReading::project_band) return +//! [`BandReadError`]: a lens mismatch, an absent band, or untrusted +//! provenance must **FAIL, never return a plausible value**. *(G3′/G4′/G5b)* +//! +//! The audit distinction rides on [`BandDeclarations::get`] returning +//! `Option`: `None` = never declared; `Some(band: Absent)` = **explicitly** +//! declared band-free. Folding the two would make "opted out" and "never +//! considered" indistinguishable to a migration audit. +//! +//! # What this module does NOT do (G8) +//! +//! It declares; it never stamps. No `with_*` call, no shift/mask against any +//! edge layout, no feature gate (G9: a `#[cfg]` split re-meaning a reading +//! under one name is the exact v1-accessor anti-pattern +//! `I-LEGACY-API-FEATURE-GATED` catalogues). Temporal carries NO field here — +//! time is implicit in the epistemic pothole (Lance versions); explicit +//! temporal lives only in its three sanctioned homes (Rubikon revision window, +//! `CausalEdgeV3`'s TE byte, a future attention-v3 reading). `EdgeProvenance` +//! is **layout epoch, never time**. + +use crate::class_view::ClassId; +use crate::rail_geometry::RailAxis; +use crate::recipe_kernels::Tactic; + +/// States the 2-bit truth field can carry — the arity pin (G7′, compile-time, +/// F9-exempt). A 5-variant sibling (`planner::mul::trust::TrustTexture`, with +/// `Dissonant`) is **unrepresentable** here and must never be routed through +/// these bits. +pub const TRUTH_STATES: usize = 4; +/// States the 3-bit band field can carry. +pub const BAND_STATES: usize = 8; +const _: () = assert!(TRUTH_STATES == 4 && (TRUTH_STATES - 1) == 0b11); +const _: () = assert!(BAND_STATES == 8 && (BAND_STATES - 1) == 0b111); + +/// WHICH projection of the 2-bit truth field this class's producers wrote. +/// +/// The two shipped readings are ordinal-identical on the wire (`Crystalline` ≡ +/// `Direct` … `Murky` ≡ `Unknown`), which is precisely why the bits cannot +/// disambiguate themselves. The names below are doc-comment pointers to the +/// edge crate's enums, **never imports** (both crates are zero-dep): +/// +/// - [`Trust`](TruthLens::Trust) → `causal_edge::layout::TrustTexture` +/// (4 variants — NOT `contract::mul::TrustTexture`, NOT the planner's +/// 5-variant one, NOT arigraph's 3-variant one; the ×4 homonym is recorded +/// in `docs/TYPE_DUPLICATION_MAP.md`). +/// - [`Topology`](TruthLens::Topology) → `causal_edge::layout::CausalTopology`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum TruthLens { + /// Epistemic-trust reading — the canonical default (`layout.rs`: "the + /// canonical epistemic-trust reading"). + #[default] + Trust, + /// Causal-topology reading — the additive factual view of the same bits. + Topology, +} + +/// Whether the 3-bit field carries a `ReasoningBand` for this class at all. +/// +/// `Absent` is the zero-fallback: an unstamped class declares no band, and +/// projecting one is a [refusal](BandReadError::BandAbsent), never a +/// `Surface(0)` lookalike. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum BandPresence { + /// No band: the three bits are spare for this class. The default. + #[default] + Absent, + /// The class's producers stamp `ReasoningBand` ordinals via the one + /// sanctioned writer (`with_reasoning_band()` — nothing derives it). + Present, +} + +/// WHICH witness carrier discriminates evidence-KIND for this class — the +/// axis the band deliberately does not carry (frozen decision F5: *the band +/// grades; the witness reference discriminates*). A weak episodic witness and +/// a weak epistemic claim are the same band and different things; the +/// difference lives in what the row POINTS AT, costing zero bits here. +/// +/// Under the two-armed trace (`known-unknown-handover-network-v1.md` §9 ⊘⊘) +/// this reference is also what a next-rung focus entry resolves through: +/// static-substrate attention lands as an alpha-layer entry, dynamic-substrate +/// attention as the row's own Lance-version history — the discriminator names +/// the kind either way. The Hole becomes a target here once `HoleV3` lands +/// (blocked on the `BoardAggregates = 15` mint). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum WitnessKind { + /// No witness reference — grading stands alone. + #[default] + None, + /// A `WitnessTable`/`WitnessLens` entry (the generic register-slab + /// machinery, `witness_table.rs`). + Table, + /// A `CausalWitnessFacet` locus register (`causal_witness.rs`). + CausalFacet, + /// An episodic basin (the AriGraph cold-path lineage; hot-path mount + /// pending `ValueTenant::EpisodicEdges`). + EpisodicBasin, +} + +/// The layout EPOCH a raw ordinal was read under — **never a timestamp** +/// (the temporal doctrine: time is implicit in the pothole; this axis is +/// about which bit-layout wrote the field). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum EdgeProvenance { + /// The edge was stamped under the v2 layout — the fields mean what the + /// v2 accessors say. + V2Stamped, + /// **The caller asserts this V3 register was minted clean** (stamped by a + /// v2-aware producer, or lifted from a `V2Stamped` edge). This is an + /// ASSERTION, never an inference: `CausalEdgeV3::from_v1` drops + /// provenance, so a register's own bytes cannot prove this (council + /// BLOCK 1). + V3Register, + /// A v1-layout edge: both fields alias old `temporal` bits — a + /// `temporal >= 512` reads as a non-zero band. Refused. + V1Legacy, + /// Origin unstated. The zero-fallback, and it REFUSES: absent an + /// assertion, the fields are not readable. The default. + #[default] + Unknown, +} + +impl EdgeProvenance { + /// Are the truth/band fields trustworthy under this provenance? + /// `V1Legacy` and `Unknown` are not — on EITHER field (`layout.rs` applies + /// the version-gate rule to bits 59-60 as well as 61-63). + #[inline] + #[must_use] + pub const fn trusted(self) -> bool { + matches!(self, EdgeProvenance::V2Stamped | EdgeProvenance::V3Register) + } +} + +/// Why a projection refused. Every variant is a FAILURE the contract's own +/// falsifier demands — *"must FAIL, not return a plausible value."* +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum BandReadError { + /// The consumer requested one lens; the class declared the other. The + /// plausible-wrong-answer case, refused. + LensMismatch { + /// What the class's producers actually wrote. + declared: TruthLens, + /// What the consumer asked to read. + requested: TruthLens, + }, + /// The class declared `BandPresence::Absent` — its three bits are spare, + /// and reading a band from spare bits is the `Surface(0)` lookalike this + /// refuses. + BandAbsent, + /// `V1Legacy` or `Unknown` provenance: the fields may be stale v1 + /// `temporal` payload (directly, or transitively through `from_v1`). + UnknownProvenance, + /// No declaration exists for this `(classid, rail)` — the fallible path's + /// guard (G5b). The TOTAL path folds this to the zero-fallback instead; + /// they are different surfaces on purpose. + UndeclaredClass(ClassId), +} + +/// The reading a class's producers committed to, per `(classid, rail)` — +/// three declarations, zero bits (see the module doc). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct BandReading { + /// Which projection of the 2-bit truth field applies. + pub truth_lens: TruthLens, + /// Whether the 3-bit field carries a band at all. + pub band: BandPresence, + /// Which witness carrier discriminates evidence-kind (F5). + pub witness: WitnessKind, +} + +impl BandReading { + /// The zero-fallback reading an undeclared class resolves to on the TOTAL + /// path: the canonical `Trust` lens, **no band** (projecting one refuses), + /// no witness reference. Identical to `Default`, named so call sites read + /// as the ladder rung they are. + pub const ZERO_FALLBACK: Self = Self { + truth_lens: TruthLens::Trust, + band: BandPresence::Absent, + witness: WitnessKind::None, + }; + + /// The producer-side pre-write check (council L2): may a producer stamp + /// the truth field through `requested`? `false` means the write would + /// contradict this declaration. This DECLARES compatibility — it cannot + /// enforce it, by construction: the edge crate is zero-dep and does not + /// see this contract (`causal-edge/Cargo.toml:20-23`), so enforcement at + /// the write site is that crate's follow-up, not a claim here. + #[inline] + #[must_use] + pub fn admits(self, requested: TruthLens) -> bool { + self.truth_lens == requested + } + + /// The producer-side pre-write check for the band field: `false` for an + /// `Absent` class — stamping a band there would re-mean spare bits. + #[inline] + #[must_use] + pub fn admits_band(self) -> bool { + self.band == BandPresence::Present + } + + /// Project the 2-bit truth field: validates provenance, then the lens. + /// Returns the validated raw ordinal (`0..4`) — the CONSUMER (who holds + /// the edge crate) projects it through the declared lens's enum; this + /// crate stays content-blind and imports neither. + /// + /// **Precondition:** `truth_raw` is the 2-bit field value (`< 4`) — + /// `debug_assert`ed, part of the G7′ compile-time/precondition pin + /// (F9-exempt, stated). + /// + /// Check order is doctrine: **provenance before lens** — untrustworthy + /// bits fail before any question about their meaning is entertained. + pub fn project_truth( + self, + requested: TruthLens, + truth_raw: u8, + provenance: EdgeProvenance, + ) -> Result { + debug_assert!( + (truth_raw as usize) < TRUTH_STATES, + "truth_raw must be the 2-bit field value" + ); + if !provenance.trusted() { + return Err(BandReadError::UnknownProvenance); + } + if self.truth_lens != requested { + return Err(BandReadError::LensMismatch { + declared: self.truth_lens, + requested, + }); + } + Ok(truth_raw) + } + + /// Project the 3-bit band field: validates provenance, then presence. + /// Returns the validated raw ordinal (`0..8`) for the consumer to project + /// through `ReasoningBand` — never a `ReasoningBand` here (content-blind). + /// + /// **Precondition:** `band_raw < 8` (`debug_assert`ed, G7′ regime). + pub fn project_band( + self, + band_raw: u8, + provenance: EdgeProvenance, + ) -> Result { + debug_assert!( + (band_raw as usize) < BAND_STATES, + "band_raw must be the 3-bit field value" + ); + if !provenance.trusted() { + return Err(BandReadError::UnknownProvenance); + } + if self.band != BandPresence::Present { + return Err(BandReadError::BandAbsent); + } + Ok(band_raw) + } +} + +/// The declaration table — `(classid, rail) → BandReading`, caller-populated +/// (an OGAR mint / bake decision populates it; this crate never pre-fills a +/// class, so D-ACR-2's operator-gated rail mint is not pre-empted). +/// +/// Both access disciplines live here, on purpose (the L1 split): +/// [`get`](Self::get) is the audit read (`Option` — never-declared vs +/// declared-`Absent` stay distinguishable), [`reading_or_default`](Self::reading_or_default) +/// is the total read (G5a), and the `project_*` pair is the fallible +/// projection under lookup (G5b fires [`BandReadError::UndeclaredClass`]). +#[derive(Debug, Clone, Default)] +pub struct BandDeclarations { + entries: Vec<((ClassId, RailAxis), BandReading)>, +} + +impl BandDeclarations { + /// An empty table — every class undeclared. + #[must_use] + pub const fn new() -> Self { + Self { + entries: Vec::new(), + } + } + + /// Declare (or re-declare) a class's reading. Returns `true` if this + /// replaced an existing declaration — a re-declaration is visible, never + /// silent. + pub fn declare(&mut self, class: ClassId, rail: RailAxis, reading: BandReading) -> bool { + if let Some(slot) = self + .entries + .iter_mut() + .find(|((c, r), _)| *c == class && *r == rail) + { + slot.1 = reading; + true + } else { + self.entries.push(((class, rail), reading)); + false + } + } + + /// The AUDIT read: `None` = never declared; `Some` = declared (possibly + /// `band: Absent` — an explicit opt-out, distinguishable from silence). + #[must_use] + pub fn get(&self, class: ClassId, rail: RailAxis) -> Option { + self.entries + .iter() + .find(|((c, r), _)| *c == class && *r == rail) + .map(|(_, b)| *b) + } + + /// The TOTAL read (G5a): an undeclared class folds to + /// [`BandReading::ZERO_FALLBACK`], never an error — sibling-consistent + /// with `edge_codec_flavor` / `rail_carving`. + #[must_use] + pub fn reading_or_default(&self, class: ClassId, rail: RailAxis) -> BandReading { + self.get(class, rail).unwrap_or(BandReading::ZERO_FALLBACK) + } + + /// The fallible truth projection under lookup: declaration first + /// (G5b — [`BandReadError::UndeclaredClass`] must fire), then delegate to + /// [`BandReading::project_truth`] (provenance, then lens). + pub fn project_truth( + &self, + class: ClassId, + rail: RailAxis, + requested: TruthLens, + truth_raw: u8, + provenance: EdgeProvenance, + ) -> Result { + self.get(class, rail) + .ok_or(BandReadError::UndeclaredClass(class))? + .project_truth(requested, truth_raw, provenance) + } + + /// The fallible band projection under lookup — same discipline. + pub fn project_band( + &self, + class: ClassId, + rail: RailAxis, + band_raw: u8, + provenance: EdgeProvenance, + ) -> Result { + self.get(class, rail) + .ok_or(BandReadError::UndeclaredClass(class))? + .project_band(band_raw, provenance) + } +} + +/// The sampling filter the acceptance condition demands (F6/G6): admit a +/// tactic iff it can move `delta_conf` — **never** filter on +/// `maturity().is_production()`, which admits 31/34 where only 14/34 can +/// dissent. A sampled tactic that cannot move the confidence number is a +/// spent slot returning guaranteed agreement +/// (`E-A-WATCHER-THAT-CANNOT-DISSENT-IS-NOT-A-WATCHER-1`). +#[inline] +#[must_use] +pub fn sampling_admits(tactic: &dyn Tactic) -> bool { + tactic.moves_confidence() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn topology_class() -> BandReading { + BandReading { + truth_lens: TruthLens::Topology, + band: BandPresence::Present, + witness: WitnessKind::CausalFacet, + } + } + + // ── G3′: lens mismatch AND match ───────────────────────────────────── + + #[test] + fn g3_lens_mismatch_fails_and_match_resolves() { + let decl = topology_class(); + // can-fire: Topology-declared, read as Trust → LensMismatch. + assert_eq!( + decl.project_truth(TruthLens::Trust, 2, EdgeProvenance::V2Stamped), + Err(BandReadError::LensMismatch { + declared: TruthLens::Topology, + requested: TruthLens::Trust, + }) + ); + // can-stay-silent: read as Topology → Ok, the ordinal untouched. + assert_eq!( + decl.project_truth(TruthLens::Topology, 2, EdgeProvenance::V2Stamped), + Ok(2) + ); + } + + // ── G4′: provenance, both directions ───────────────────────────────── + + #[test] + fn g4_untrusted_provenance_refuses_and_trusted_resolves() { + let decl = topology_class(); + for bad in [EdgeProvenance::V1Legacy, EdgeProvenance::Unknown] { + assert_eq!( + decl.project_truth(TruthLens::Topology, 1, bad), + Err(BandReadError::UnknownProvenance), + "truth field must refuse under {bad:?}" + ); + assert_eq!( + decl.project_band(5, bad), + Err(BandReadError::UnknownProvenance), + "band field must refuse under {bad:?}" + ); + } + for good in [EdgeProvenance::V2Stamped, EdgeProvenance::V3Register] { + assert_eq!(decl.project_truth(TruthLens::Topology, 1, good), Ok(1)); + assert_eq!(decl.project_band(5, good), Ok(5)); + } + } + + /// Provenance is checked BEFORE the lens: untrustworthy bits fail before + /// any question about their meaning — a mismatched request under bad + /// provenance reports the provenance, not the mismatch. + #[test] + fn provenance_outranks_the_lens_question() { + assert_eq!( + topology_class().project_truth(TruthLens::Trust, 0, EdgeProvenance::Unknown), + Err(BandReadError::UnknownProvenance) + ); + } + + /// The default provenance is `Unknown`, and `Unknown` refuses — the + /// zero-fallback ladder applied to trust: absent an assertion, refuse. + #[test] + fn default_provenance_is_unknown_and_refuses() { + assert_eq!(EdgeProvenance::default(), EdgeProvenance::Unknown); + assert!(!EdgeProvenance::default().trusted()); + } + + // ── G5a / G5b: the total and fallible surfaces are DIFFERENT ───────── + + #[test] + fn g5a_total_lookup_folds_undeclared_to_zero_fallback() { + let table = BandDeclarations::new(); + let r = table.reading_or_default(0x0301, RailAxis::Taxonomy); + assert_eq!(r, BandReading::ZERO_FALLBACK, "no error, the fold"); + // …and the fallback's own band projection still refuses (Absent): + assert_eq!( + r.project_band(3, EdgeProvenance::V2Stamped), + Err(BandReadError::BandAbsent) + ); + } + + #[test] + fn g5b_fallible_projection_fires_on_undeclared() { + let table = BandDeclarations::new(); + assert_eq!( + table.project_truth( + 0x0301, + RailAxis::Taxonomy, + TruthLens::Trust, + 0, + EdgeProvenance::V2Stamped + ), + Err(BandReadError::UndeclaredClass(0x0301)) + ); + // silent half: a declared class projects through the same surface. + let mut t = BandDeclarations::new(); + t.declare(0x0301, RailAxis::Taxonomy, topology_class()); + assert_eq!( + t.project_truth( + 0x0301, + RailAxis::Taxonomy, + TruthLens::Topology, + 3, + EdgeProvenance::V2Stamped + ), + Ok(3) + ); + } + + // ── the audit distinction (L3) ─────────────────────────────────────── + + #[test] + fn never_declared_and_declared_absent_stay_distinguishable() { + let mut t = BandDeclarations::new(); + t.declare( + 0x0302, + RailAxis::Mereology, + BandReading { + band: BandPresence::Absent, + ..BandReading::ZERO_FALLBACK + }, + ); + assert_eq!(t.get(0x0301, RailAxis::Mereology), None, "never declared"); + assert!( + t.get(0x0302, RailAxis::Mereology).is_some(), + "explicit opt-out is a declaration, not silence" + ); + // …while the TOTAL read is identical for both — that is the fold, + // and it is why the audit path must read the Option. + assert_eq!( + t.reading_or_default(0x0301, RailAxis::Mereology), + t.reading_or_default(0x0302, RailAxis::Mereology) + ); + } + + #[test] + fn redeclaration_is_visible_never_silent() { + let mut t = BandDeclarations::new(); + assert!(!t.declare(1, RailAxis::Taxonomy, topology_class())); + assert!(t.declare(1, RailAxis::Taxonomy, BandReading::ZERO_FALLBACK)); + assert_eq!( + t.get(1, RailAxis::Taxonomy), + Some(BandReading::ZERO_FALLBACK) + ); + } + + // ── BandAbsent: fire AND stay-silent ───────────────────────────────── + + #[test] + fn band_absent_refuses_and_present_resolves() { + let absent = BandReading::ZERO_FALLBACK; + assert_eq!( + absent.project_band(0, EdgeProvenance::V2Stamped), + Err(BandReadError::BandAbsent), + "even ordinal 0 must refuse — a refusal is not Surface(0)" + ); + assert_eq!( + topology_class().project_band(7, EdgeProvenance::V2Stamped), + Ok(7) + ); + } + + // ── the producer pre-write check (L2) ──────────────────────────────── + + #[test] + fn admits_declares_write_compatibility_both_ways() { + let decl = topology_class(); + assert!(decl.admits(TruthLens::Topology)); + assert!(!decl.admits(TruthLens::Trust)); + assert!(decl.admits_band()); + assert!(!BandReading::ZERO_FALLBACK.admits_band()); + } + + // ── G6: the sampling filter, against the REAL kernel registry ──────── + + #[test] + fn g6_sampling_admits_the_14_and_rejects_the_20_mutes() { + let kernels = crate::recipe_kernels::all_kernels(); + let admitted = kernels.iter().filter(|k| sampling_admits(**k)).count(); + assert_eq!( + admitted, 14, + "the measured delta_conf-capable count — a drift here means a \ + kernel changed capability and this contract's premise moved" + ); + assert_eq!(kernels.len() - admitted, 20, "the mutes are rejected"); + // And the filter is NOT the maturity filter: production count differs. + let production = kernels + .iter() + .filter(|k| k.maturity().is_production()) + .count(); + assert_ne!( + admitted, production, + "delta_conf capability and maturity must remain different questions" + ); + } + + // ── G7′: the arity pin (compile-time regime, F9-exempt — stated) ───── + + #[test] + fn g7_arity_pins_hold() { + assert_eq!(TRUTH_STATES, 4, "2 bits: a 5th state is unrepresentable"); + assert_eq!(BAND_STATES, 8); + } + + // ── the zero-fallback is what it says ──────────────────────────────── + + #[test] + fn zero_fallback_is_trust_absent_none_and_is_the_default() { + assert_eq!(BandReading::ZERO_FALLBACK, BandReading::default()); + assert_eq!(BandReading::ZERO_FALLBACK.truth_lens, TruthLens::Trust); + assert_eq!(BandReading::ZERO_FALLBACK.band, BandPresence::Absent); + assert_eq!(BandReading::ZERO_FALLBACK.witness, WitnessKind::None); + } +} diff --git a/crates/lance-graph-contract/src/class_view.rs b/crates/lance-graph-contract/src/class_view.rs index dfb889c5b..b6589dfc2 100644 --- a/crates/lance-graph-contract/src/class_view.rs +++ b/crates/lance-graph-contract/src/class_view.rs @@ -1132,6 +1132,26 @@ pub trait ClassView { crate::rail_geometry::RailCarving::zero_fallback(axis) } + /// How this class's edge truth/spare bits are READ, per rail axis — the + /// D-ACR-7 band-reading contract (`.claude/plans/dacr7-band-reading-contract-v1.md`, + /// RATIFIED). Same registry-resolution pattern as + /// [`edge_codec_flavor`](ClassView::edge_codec_flavor) / + /// [`rail_carving`](ClassView::rail_carving): the default is the canon + /// zero-fallback ([`BandReading::ZERO_FALLBACK`](crate::band_reading::BandReading::ZERO_FALLBACK) + /// — `Trust` lens, band `Absent`, witness `None`), and a class that carries + /// topology-lensed truth or a live reasoning band overrides per + /// `(class, rail)`. Selection only: the reading never changes stored bytes + /// (bits 59-63 on CausalEdge64; bytes [8]/[9] hi-2/lo-3 on CausalEdgeV3) — + /// it declares how a consumer projects them. + #[inline] + fn band_reading( + &self, + _class: ClassId, + _rail: crate::rail_geometry::RailAxis, + ) -> crate::band_reading::BandReading { + crate::band_reading::BandReading::ZERO_FALLBACK + } + /// Which value-slab schema preset this class materialises in /// [`NodeRow::value`](crate::canonical_node::NodeRow::value). /// diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index c1c88b5ce..ff85f4cd1 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -49,8 +49,10 @@ pub mod a2a_blackboard; pub mod action; pub mod aiwar; pub mod atoms; +pub mod attention_facet; pub mod auth; pub mod awareness_facet; +pub mod band_reading; pub mod callcenter; pub mod cam; pub mod canonical_node; diff --git a/docs/TYPE_DUPLICATION_MAP.md b/docs/TYPE_DUPLICATION_MAP.md index 0372dbf87..e714453f6 100644 --- a/docs/TYPE_DUPLICATION_MAP.md +++ b/docs/TYPE_DUPLICATION_MAP.md @@ -6,18 +6,20 @@ New duplications discovered/predicted from the Wave F fleet (W-F4 through W-F8). -### TrustTexture (×2) — different semantic axes `TECH_DEBT` +### TrustTexture (×4) — different semantic axes, different arities `TECH_DEBT` -Two enums with the same name encoding **different cognitive dimensions**: +Four enums with the same name, none importing any other, encoding **different cognitive dimensions with different variant counts**: -| # | Location | Line | Variants | Semantic axis | -|---|----------|------|----------|---------------| -| 1 | `crates/lance-graph-contract/src/mul.rs` | 74 | Calibrated / Overconfident / Underconfident / Uncertain | MUL meta-uncertainty layer (felt vs demonstrated competence) | -| 2 | `crates/causal-edge/src/layout.rs` | 114 | Crystalline / Solid / Fuzzy / Murky | Pearl-3 epistemic lens (v2 from PR #383); discriminator = 2-bit field in CausalEdge64 | +| # | Location | Line | Variants | Arity | Semantic axis | +|---|----------|------|----------|-------|---------------| +| 1 | `crates/causal-edge/src/layout.rs` | 141 | Crystalline / Solid / Fuzzy / Murky | 4 | Pearl-3 epistemic lens; discriminator = 2-bit truth field in CausalEdge64 (bits 59-60) | +| 2 | `crates/lance-graph-contract/src/mul.rs` | 82 | Calibrated / Overconfident / Uncertain / Underconfident | 4 | MUL meta-uncertainty layer (felt vs demonstrated competence) | +| 3 | `crates/lance-graph-planner/src/mul/trust.rs` | 30 | Crystalline / Solid / Fuzzy / Murky / Dissonant | 5 | MUL trust-quality axis; **5 variants are unrepresentable in the 2-bit CausalEdge64 truth field** — this one can never be the carrier-side type | +| 4 | `crates/lance-graph/src/graph/arigraph/orchestrator.rs` | 114 | Crystalline / Fibrous / Fuzzy | 3 | AriGraph topology-weight reliability lens | -**What differs**: Completely different variant sets encoding different ontologies. `mul.rs` is a 4-way MUL assessment axis. `layout.rs` is a 4-state Pearl-3 trust signal packed into 2 bits of the CausalEdge64 layout. -**Canonical**: NONE — both are domain-correct and should keep distinct names. Recommended path: rename one (e.g. `PearlTrustTexture` in `causal-edge`) to disambiguate. -**TECH_DEBT**: Name collision across zero-dep crates is a footgun; sprint-13 rename target. +**What differs**: Four distinct ontologies, not one duplicated three times over. The variant sets overlap in name (`Crystalline`, `Fuzzy` recur) but not in count or meaning: #1 and #2 are both 4-way but encode unrelated axes (epistemic-lens vs meta-uncertainty); #3 is the 5-way superset of #1's axis and cannot be packed into the 2-bit carrier #1 was built for; #4 is a 3-way AriGraph-local lens. +**Canonical**: NONE — all four are domain-correct in place. A single rename cannot disambiguate four homonyms, and the differing arities (4/4/5/3) rule out unification: #3's 5 variants would silently truncate if forced into #1's 2-bit field, so these are candidates for **per-site renaming only**, never for merging into one shared type. `crates/lance-graph-contract/Cargo.toml` (lines 10-17) is zero-dependency by design, and `crates/causal-edge/Cargo.toml` (lines 20-23) states explicitly that `TrustTexture` is defined locally rather than imported from `lance-graph-contract` to preserve that zero-dep invariant — neither crate may depend on the other, so this duplication is a deliberate architectural posture, not an oversight to be paid down by a shared type. +**TECH_DEBT**: Name collision across zero-dep crates is a footgun; sprint-13 target is per-site renaming (e.g. `PearlTrustTexture`, `MulTrustTexture`, `PlannerTrustQuality`, `AriGraphTrustTexture`), not unification. ---