diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 6796f26e7..cd22096a1 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,6 +1,25 @@ +## ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS — P7a certifications with no canonical EpistemicState5 code (2026-10-07) + +**Status:** RESOLVED 2026-10-07 (same day, before merge) by the operator's Cartesian V1: `EpistemicState5 = Topology2 × Certification3` has a code for every P7a contract under every topology (`CausalCandidate` = 16..19, `Direct × Supports` = 12); validity is only `certification < 6`. The hand-assigned #1370 codebook that lacked these cells is gone. **Was:** OPEN — operator decision. **Basis:** TEST-PINNED in `contract::epistemic_state5` (`the_layout_is_the_two_by_three_product`, `causes_is_meaningful_under_every_topology`), `relational_certification_probe` (every contract stamps under every topology) and `epistemic_reading_conflict_probe` (census 24 agree / 0 disagree / 8 reserved). The bullets below are HISTORICAL: they describe the superseded ten-code draft, not the current contract. + +- Codebook V1 (the ten #1370 codes) has no conjunction for: `CausalCandidate` under any grounding (no fact for "ordered" / "explains"); `Contributes` (= `SUPPORTS`) under `Direct`; `Associated` under an indirect grounding; `Causes` / `Related` under `Unknown` grounding; `Causes` under an unknown intermediate (the last is arguably correct to refuse). +- Consequence, measured: P7a's two observational fixtures certify `CausalCandidate`, so the certification is computed but cannot be stamped; it refuses instead of writing 61..63 (no code invented). +- **What closes it:** a V2 codebook generation that declares the missing conjunctions (which codes, and whether a new fact is minted for `CausalCandidate`) — a codebook design decision, not a migration step. Adding codes is a new generation, never an edit of V1. + +## ISS-LEGACY-59-63-READINGS-REMAIN — historical half-field readings still live in probes (2026-10-07) + +**Status:** OPEN (tracking), narrowed 2026-10-07 by D-EPI-LEGACY-DEPROJECT-0 (`entries/2026-10-07-epi-legacy-deprojection.md`): the kanban hinge migrated (its band was a non-interference witness, now a canonical coordinate), `EdgeRole::band` removed, the V3 lift reads 59..63 jointly. No production path reads or writes 59..63 through a legacy lens. Remaining: the four-plane and `reasoning_band_probe` records, and the compatibility surfaces. **Basis:** VERIFIED-IN-CODE; inventory `entries/2026-10-07-epi-mig-inventory.md`. + +- PROBE-ONLY legacy writers of the historical `ReasoningBand` on the certification bits, kept with `#![allow(deprecated)]` because no mapping onto `Certification3` exists (mapping `Causal` → `Supports`/`Causes` would be invention): `cognitive-shader-driver/examples/{reasoning_band_probe, probe_revision_kanban_hinge}.rs`, `lance-graph-planner/examples/probe_four_plane_causal_medium.rs`. (2026-10-07 Cartesian revision: `entropy_topology_probe` migrated — it writes only topology; `with_topology` is the canonical topology-factor writer and no longer deprecated.) +- Compatibility surfaces kept: `contract::band_reading` (regraded legacy), the deprecated `TrustTexture` / historical-band / untyped writers in `causal-edge`, the legacy lens readers (`truth`, `reasoning_band`), `dismech_counterfactual::EdgeRole::band` (beside the canonical `epistemic_raw5`). +- `CausalEdge64::temporal()` (deprecated) still composes bits 52..63 incl. 59..63 and `Network::evidence_trail` sorts by it — documented meaningless ordering, untouched. +- **What closes it:** removing each once no caller needs it; the probes are records, so they may stay as legacy indefinitely. + ## ISS-EPISTEMIC-READINGS-DISAGREE-ON-BITS-61-63 — the affordance law reads 59..63 without the D-ACR-7 contract and contradicts P7a (2026-10-07) -**Status:** OPEN. **Basis:** TEST-PINNED in `cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs` (D-EPI-CONFLICT-0). +**Status:** RESOLVED 2026-10-07 by operator decision + D-EPI-CANON-0 / D-EPI-MIG-0: bits 59..63 are ONE field, `EpistemicState5`, declared in `contract::epistemic_state5` as the Cartesian `Topology2 × Certification3` (`raw5 = topology | certification << 2`, 24 meaningful / 8 reserved; declared per `(classid, rail, generation)`, projection refuses). The affordance law measures only a projected state; P7a stamps by identity packing and reads through the projection. The probe's pins were replaced by agreement tests (census 24 agree / 0 disagree / 8 reserved), not re-pinned. (An earlier same-day draft used the ten #1370 codes and measured 11 / 0 / 13; it was replaced before merge.) No residue: `ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS` is resolved. + +**Was:** OPEN. **Basis:** TEST-PINNED in `cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs` (D-EPI-CONFLICT-0). - `contract::band_reading` (D-ACR-7) is the unified 59..63 reading contract: per `(classid, rail)` it declares the truth lens (59..60) and band presence (61..63), and projection refuses an undeclared class, an absent band and untrusted provenance. It declares the two fields separately; it has no joint 5-bit lens. - P7a (D-GSO-7a) goes through it: class `0x0902` declared `BandPresence::Present`, bits 61..63 read through `project_band` as `0 Open … 5 Causes`, 6..7 refuse. @@ -23,7 +42,7 @@ ## ISS-NO-EVIDENCE-WRITER-FOR-EPISTEMIC-STATE — accumulated evidence never reaches what the next cycle may do (2026-10-06) -**Status:** OPEN. **Basis:** TEST-PINNED in `cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs` (D-CE64-TIME-0). +**Status:** OPEN (2026-10-07 note: since D-EPI-MIG-0 a candidate writer must write a DECLARED canonical state jointly — #1379's revision writer and P7a both do; P7a can now feed the law for `Open`/`Associated`/`Related`/`Causes` under `Direct` without the #1378 disagreement. The production decision stays open.) **Basis:** TEST-PINNED in `cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs` (D-CE64-TIME-0). - `CausalEdge64::learn` (the shipped fold) revises F/C and plasticity only; it never writes bits 59..63. The #1370 eligibility law reads only bits 59..63 and Pearl. So F/C accumulation, however long it survives, cannot change `EligibleRecipes`. The probe declares the missing step locally (code 3 ↔ 7); no production writer of bits 59..63 from evidence exists (the band writers live in probes only), and `CausalEdge64::learn` itself has no production caller. - Cross-cycle source identity is not carried: the `SupportLedger` lives one cycle, and the register holds no source set, so two different sources seen in two different cycles never count as two (`sources_split_across_cycles_do_not_combine`). Carrying it would need state the register does not have. diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 43ed000ac..7dac38ee0 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,31 @@ +## 2026-10-07 — D-EPI-CANON-0 / D-EPI-MIG-0: CE64 bits 59..63 = `EpistemicState5` (branch `claude/epistemicstate5-ce64-bits-3oe0dq`, unmerged) + +### Current Contract Inventory — net delta (`epistemic_state5.rs`, `class_view.rs`, `causal-edge`) +- New zero-dep module `lance_graph_contract::epistemic_state5`: the + Cartesian `EpistemicState5 = Topology2 × Certification3` + (`raw5 = topology | certification << 2`; 24 meaningful, 8 reserved): + `Topology2`, `Certification3` (P7a codes), `fact::*` (no `OBSERVED`; + `TOPOLOGY_UNKNOWN`, `CAUSAL_CANDIDATE` added), `TOPOLOGY_FACTS`, + `CERTIFICATION_FACTS`, `facts_v1`, derived `COMPILED_FACTS_V1`, `Epi5Gen`, + `EpistemicState5` (`new`, `decode`, `raw`, `topology`, `certification`, + `facts`, `asserts`, `with_topology`, `with_certification`), `Epi5Reading` + (`project`), `Epi5ReadError`, `Epi5Declarations::{declare, get, + project_state5}`. +- D-EPI-POP-0: `Population = u32` (bit `r` = the state whose raw5 is `r`), + `facts_population(required) -> Population` (`const fn`; the codes whose + facts include every required fact; reserved codes never appear; conjunction + is intersection), `EpistemicState5::bit()`. Transient — never stored. +- D-EPI-LEGACY-DEPROJECT-0: `lance_graph_planner::dismech_counterfactual::EdgeRole::band` (historical `ReasoningBand`) REMOVED — the canonical field is `epistemic_raw5`; `CausalEdgeV3` lifts bits 59..63 through `epistemic_raw5()` (byte layout unchanged). +- `ClassView::epistemic_reading(class, rail) -> Option` (provided, + default `None` = refuse). +- `band_reading` regraded: legacy compatibility surface, no longer owns 59..63. +- `causal-edge`: `layout::{EPISTEMIC_SHIFT, EPISTEMIC_MASK, BITS5_MASK}`, + `CausalEdge64::{epistemic_raw5, with_epistemic_raw5}` (v1: zero / no-op), + `CausalEdgeV3::epistemic_raw5`; `with_topology` is the canonical topology- + factor writer; `with_truth`/`set_truth`/`with_routing` (TrustTexture), + `with_reasoning_band` (historical band) and `with_spare`/`set_spare` + (untyped) are `#[deprecated]`. + ## 2026-10-07 — Cypher autopsy + experimental Cypher→Quack seam (branch `ccr-a86d1f2f-015t11`, PR #1376, unmerged) ### Current Contract Inventory — UNCHANGED (no contract type added; one crate added) diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 9e6ac2397..bb013bfcc 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -44,7 +44,13 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **D-CE64-NEXTSTATE-0** | EpistemicState5 as a sparse next-state mutation of the CE64 register | In PR | `cognitive-shader-driver/examples/ce64_nextstate_probe.rs`: N1 only bits 59..63 move, all 32 codes on an edge with every field set; N2 written in cycle k through `MailboxSoA::write_row`, read in k+1 through `MailboxSoaView`; N3 the mailbox does not double-buffer (a same-cycle read sees the write), and next-cycle authority is a read discipline: start-of-cycle copy plus `last_write_cycle == cycle` treated as pending; N4 canonical LE round trip (bits 59..63 in byte 7, bits 3..7); N5 a fresh mailbox from the persisted word continues identically; N6 the shipped `with_spare` + `with_truth` compile to `bzhi; shl; or`, identical to the masked insert (LLVM folded the two), so no 5-bit writer is added. Production change: `CausalEdge64::to_le_bytes` / `from_le_bytes` (mirroring `EpisodicEdges64`) and the register contract on the type. 6 tests, 7 disable runs red | | **D-ANCESTRY-K1-0** | StreamDto / PerturbationDto / BusDto / p64 / shader-driver ancestry vs code | Shipped (this PR) | `entries/2026-10-06-streamdto-circuit-ancestry.md`: v3 docs first, then code (git history is shallow at #1327). The k → k+1 loop was intended (`EmitMode::Persist`, `persist_cycle`, `mailbox-thoughtspace` read shim, W4a cast pairing) and left open: the driver owned its mailbox read-only. Path B | | **D-STREAMDTO-0** | A real `StreamDto` through the shipped path to next-cycle #1370 eligibility | In PR | `cognitive-shader-driver/examples/streamdto_circuit_probe.rs`: `StreamDto` → the new `ingest_codebook_indices_soa` (straight into the SoA, cycle-gated `write_row`, encoding shared with the superseded BindSpace arm) → `dispatch` k → `persisted_row` gets `emitted_edges[0]` via the new `ShaderDriver::mailbox_mut` → `tick` → `dispatch` k+1 reads it and changes → `measure`. The edge steers k+1 only through `s_idx / 4`; the shader writes code 0, so eligibility is unchanged by the circuit (`0x20`, same as an unwritten word; code 7 gives `0x24`). Timestamp ≠ cycle; restart from LE bytes reproduces k+1. CI step added (feature-gated targets incl. `w2_differential` were never run). 6 tests + 1 lib test, 9 disable runs red | -| **D-EPI-CONFLICT-0** | Pin the disagreement between the D-ACR-7-declared P7a band and the affordance law on CE64 bits 59..63 | In PR | `cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs`: contract-projected `Causes` reads as affordance code 20 `Related` (topology 0) or refuses (1..3), `COUNTERFACTUAL_PROBE` never eligible; `CAUSES` codes 1 and 17 sit on P7a `Open` / `CausalCandidate`; codes 25, 30 on P7a reserved bands; census over 24 cells = 3 agree, 5 disagree, 16 refuse; the affordance law measures where the contract refuses (undeclared / band `Absent`). Both readings run from shared code (`shared/affordance_law.rs`, and P7a's reading moved unchanged into `shared/certification_reading.rs`, used by `relational_certification_probe` too), never copied. Pins of current state, to become agreement tests when the issue closes (`ISS-EPISTEMIC-READINGS-DISAGREE-ON-BITS-61-63`); 5 tests, 6 disable runs red (3 on the affordance codebook, 3 on P7a's: `Contributes` entailment, a reserved code admitted, `Causes` recoded) | +| **D-EPI-MIG-INVENTORY** | Every reader/writer of CE64 bits 59..63 classified before migration | Shipped (this PR) | `entries/2026-10-07-epi-mig-inventory.md` | +| **D-EPI-CANON-0** | Canonical joint `EpistemicState5` reading of bits 59..63 in the contract | In PR | `contract::epistemic_state5`: Cartesian `Topology2 × Certification3`, `raw5 = t \| c << 2`, 24 meaningful / 8 reserved; per-factor facts; declarations + `project_state5`; `CausalEdge64::{epistemic_raw5, with_epistemic_raw5}`; non-declared-reading writers deprecated | +| **D-EPI-POP-0** | Facts → 32-state population: the missing operator between one edge's coordinate (u5) and a semantic condition over all coordinates (u32) | In PR | `contract::epistemic_state5::{Population, facts_population, EpistemicState5::bit}`: `const fn`, no DSL / query object / planner. Tests pin exact sets (`CAUSES` = {20..23}, `IND_UNKNOWN \| RELATED` = {10,14,18,22}, `IND_KNOWN \| SUPPORTS` = {13,17,21}, `RELATED` = 8..23), operator == state enumeration over all 2048 requirements with no reserved code, conjunction == intersection; 2 disable runs red (any-of semantics; reserved read as fact-less) | +| **D-EPI-LEGACY-DEPROJECT-0** | Deproject the remaining `ReasoningBand` / CE64-`TrustTexture` consumers: each goes to an exact coordinate, a `facts_population(..)`, MUL, or another subsystem (e.g. counterfactual context) — never a 1:1 replacement enum | In PR | `entries/2026-10-07-epi-legacy-deprojection.md`: 6 ReasoningBand caller sites (1 certification question, 1 non-interference witness → both exact coordinates; 2 context/obsolete → compat; 2 refusal witnesses), 0 production CE64 `TrustTexture` consumers, 4 `TrustTexture` types with no bridge; `EdgeRole::band` removed; hinge + V3 lift migrated; 4 code loops → `facts_population` | +| **D-PUZZLE-0** | Sudoku / crossword / chess on the same population algebra under domain laws | Queued | after D-EPI-LEGACY-DEPROJECT-0 | +| **D-EPI-MIG-0** | Migrate readers/writers to the canonical reading; #1378 → conformance | In PR | `entries/2026-10-07-epi-mig-0.md`: affordance law (per-factor compile) + P7a (identity packing) + 6 probes + dismech + V3 lift; conformance census 24/0/8 (was 3/5/16); 3 historical-band probes cannot migrate without invention | +| **D-EPI-CONFLICT-0** | (superseded by D-EPI-MIG-0: probe converted to conformance) Pin the disagreement between the D-ACR-7-declared P7a band and the affordance law on CE64 bits 59..63 | Superseded | **Current:** the same probe is the conformance test, census 24 agree / 0 disagree / 8 reserved under the Cartesian V1 (D-EPI-MIG-0). **HISTORICAL — the pre-Cartesian pins below no longer describe the code:** `cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs`: contract-projected `Causes` reads as affordance code 20 `Related` (topology 0) or refuses (1..3), `COUNTERFACTUAL_PROBE` never eligible; `CAUSES` codes 1 and 17 sit on P7a `Open` / `CausalCandidate`; codes 25, 30 on P7a reserved bands; census over 24 cells = 3 agree, 5 disagree, 16 refuse; the affordance law measures where the contract refuses (undeclared / band `Absent`). Both readings run from shared code (`shared/affordance_law.rs`, and P7a's reading moved unchanged into `shared/certification_reading.rs`, used by `relational_certification_probe` too), never copied. Pins of current state, to become agreement tests when the issue closes (`ISS-EPISTEMIC-READINGS-DISAGREE-ON-BITS-61-63`); 5 tests, 6 disable runs red (3 on the affordance codebook, 3 on P7a's: `Contributes` entailment, a reserved code admitted, `Causes` recoded) | | **D-MBX-CUTOVER-0** | ShaderDriver on MailboxSoA without BindSpace | In PR | `entries/2026-10-06-shaderdriver-bindspace-cutover.md`: every ShaderDriver → BindSpace edge classified (production state / shared resource / fallback / test oracle / fossil / dead). In `run()` the only mailbox-mode BindSpace read was the ontology; outside it, metrics and the builder read the singleton and `backing()` silently fell back for any mailbox not under id 0. Cutover: optional BindSpace, explicit mailbox selection with no fallback, ontology on the driver, metrics from the selected substrate; no temporal change. `tests/mailbox_cutover.rs` 8 tests, 8 disable runs red | | **D-GSO-8** | P8: seal boundary | Shipped (#1363) | `lance-graph-planner/examples/seal_boundary_probe.rs`: internal events change only working rows; a declared `Boundary` event freezes the changed rows with the shipped `DetachedCycleBatch::freeze` into a local ledger (no `WalSink`); an unchanged boundary writes nothing; replaying the persisted events from each prior seal reproduces the next seal exactly (frame, landings, image, hash); wrong base, dropped event and swapped order each change the seal; tie limit handled by plan option 1: the key is the event's own durable unique `seq`, a tied batch is refused, and a test shows on the real `freeze` that tied keys make the seal arrival-dependent; 6 tests, 5 disable runs red. Follow-up: `apply` refuses a `seq` not greater than the last (a reuse on one row or across seals escaped `first_tie`), `resume_after` restores the top persisted key; 7 tests, 2 more disable runs red | diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index 3e96d2df3..2f1bff70e 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -264,7 +264,7 @@ this arc's code, and a lint sweep in a doctrine + one-module PR would bury the d ## TD-SPARE-SHIFT-NAME-IS-STALE-1 (2026-09-10) — OPEN, doc-only -**`crates/causal-edge/src/layout.rs:67-77` still names bits 61-63 `SPARE_SHIFT` and +**`crates/causal-edge/src/layout.rs` (the `SPARE_SHIFT` const) still names bits 61-63 `SPARE_SHIFT` and doc-comments them *"Spare: 3-bit reserved for sprint-12+ … Candidates: Rubicon-commit marker, Markov-decay quantum, I-NOISE-FLOOR-JIRAK threshold"* — while the same file's `ReasoningBand` (`:353-373`) has occupied those bits since `bbab3541` (introduced as diff --git a/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md b/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md new file mode 100644 index 000000000..eb1870ac0 --- /dev/null +++ b/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md @@ -0,0 +1,218 @@ +# 2026-10-07 — D-EPI-POP-0 / D-EPI-LEGACY-DEPROJECT-0: populations, and what the old 59..63 readers were asking + +Builds on #1381 (head `47bd36b`, unmerged): `EpistemicState5 = Topology2 × +Certification3`, `raw5 = topology | certification << 2`, 24 meaningful / 8 +reserved. That layout is given here, not revisited. + +The rule of this wave: the historical enums are lenses, not dimensions. For +every reader and writer the question is what the caller was asking, and the +answer goes to the subsystem that owns it. No ordinal translation +(`ReasoningBand → Certification3`, CE64 `TrustTexture → Topology2`) is +used anywhere. + +## 1. D-EPI-POP-0 — the population operator + +`lance_graph_contract::epistemic_state5`: + +- `Population = u32`: bit `r` is the state whose raw5 is `r`. +- `const fn facts_population(required: Facts) -> Population`: bit `r` is set + iff code `r` is meaningful and its facts include every required fact. + Derived from `COMPILED_FACTS_V1` (which is derived from the two factor + tables), so there is no second semantic table. +- `EpistemicState5::bit()`: one state's bit. + +One edge carries a coordinate (`u5`); one semantic condition is a population +over all coordinates (`u32`, transient, never stored on an edge). Many edges: +population filter, then fold. + +Pinned (exact sets, not only sizes): `CAUSES` = {20,21,22,23}; +`RELATED` = 8..23 (16); `IND_UNKNOWN | RELATED` = {10,14,18,22}; +`IND_KNOWN | SUPPORTS` = {13,17,21}; `TOPOLOGY_UNKNOWN | CAUSES` = {23}; +`DIRECT | ASSOCIATED` = {4,8,12,16,20}; `required = 0` = the 24 meaningful +states; `DIRECT | INDIRECT` = ∅. Defining falsifier, exhaustive: for every +requirement over the declared facts and every `raw in 0..32`, +`bit(raw) ∈ population ⇔ facts_v1(raw)` asserts the requirement (reserved ⇒ +false). Conjunction is intersection. A topology factor update holding the +certification moves a state only between topology-conditioned populations, +and vice versa. Disable runs: any-of semantics, reserved read as fact-less — +both red. + +## 2. Occurrence census (repository-wide, `47bd36b`) + +Finders: `ReasoningBand`, `reasoning_band`, `with_reasoning_band`, +`TrustTexture`, `truth`/`truth_raw`/`with_truth`/`set_truth`/`with_routing`, +`topology`/`with_topology`, `spare`/`with_spare`/`set_spare`, +`TRUTH_*`/`SPARE_*`/`EPISTEMIC_*`, `epistemic_raw5`/`with_epistemic_raw5`, +`state[`, `COMPILED_FACTS_V1[`, `facts_v1(`, `decode`, `asserts(`, and the +#1370 codes 3/7/17/25/30 in epistemic contexts. + +### 2a. Readers and writers of bits 59..63 through a legacy lens + +| path | symbol | R/W | kind | type | semantic question | class | +|---|---|---|---|---|---|---| +| `lance-graph-planner/src/dismech_counterfactual.rs` | `EdgeRole::band` | R | production | `ReasoningBand` | "does the load-bearing edge EXPLAIN the chain or merely RELATE to it" (module doc) — a certification question, answerable only under the cut edge's class declaration | EXACT-COORDINATE via `epistemic_raw5` (already present); `band` REMOVED | +| same, tests | `reasoning_band()` | R | test | `ReasoningBand` | that `EdgeRole` reports the cut edge's own bits | EXACT-COORDINATE: pin raw5 + decoded factors | +| `cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs` | `with_reasoning_band(Causal)`, F8 | W+R | probe | `ReasoningBand` | non-interference: does a nontrivial value in 59..63 survive the control loop untouched? The value's meaning is never used | EXACT-COORDINATE: a meaningful nontrivial state, pinned by raw5 + factors | +| `lance-graph-planner/examples/probe_four_plane_causal_medium.rs` | `with_reasoning_band(Causal)`, FP3, band_before | W+R | probe | `ReasoningBand` | its thesis assigns 61..63 a fourth plane, "THROUGH WHAT lens?" — a reasoning lens; the checks are non-interference of that plane | EXECUTION-CONTEXT (the lens belongs with `RungLevel` / thinking style, not CE64) → COMPAT: migrating rewrites the probe's thesis | +| `cognitive-shader-driver/examples/reasoning_band_probe.rs` | D-GSO-7 ladder | W+R | probe | `ReasoningBand` + `band_reading` projector | earned rungs: `Association` ← corroborated observation, `Causal` ← one passed intervention, `Counterfactual` ← removal attack | superseded by P7a (whose `Causes` needs ≥ 2 intervention-backed sources, and which states removal is not the causal path); `Counterfactual` rung = COUNTERFACTUAL (`dismech_counterfactual`); probe kept as shipped → COMPAT (obsolete) | +| `cognitive-shader-driver/examples/relational_certification_probe.rs` | `Meta`/`Transcendent` `to_bits_3() << 2` | W | probe | `ReasoningBand` | do the historical ordinals land on reserved certifications and refuse? | REFUSE witness (keep) | +| `cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs` | `with_reasoning_band(Causal)` | W | probe | `ReasoningBand` | a class declared under the historical reading is not consumed as canonical | REFUSE witness (keep) | +| same | `split()` (`topology()` + `spare()`) | R | probe | `CausalTopology`, untyped spare | old lenses vs canonical projection (the 24/0/8 conformance) | COMPAT comparison (keep) | +| `cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs` | `spare()`, `truth_raw()` | R | probe | untyped | the joint writer equals the two halves | COMPAT comparison (keep) | +| `cognitive-shader-driver/examples/ce64_nextstate_probe.rs` | `via_two_writers` (`with_spare`, `with_truth`) | W | probe | CE64 `TrustTexture` | the joint writer equals the deprecated two-writer path | COMPAT comparison (keep) | +| `causal-edge/src/edge_v3.rs` | V3 lift `truth_raw()` / `spare()` | R | production | untyped | copy bits 59..63 into V3 bytes 8/9 | EXACT-COORDINATE (physical): read through `epistemic_raw5()`, bit-identical; V3 byte layout unchanged | +| `causal-edge/src/edge.rs` | `with_routing` doc | — | doc | CE64 `TrustTexture` | claims `MailboxSoA::dispatch_cycle()` stamps routing — no such function exists | doc corrected | +| `causal-edge` `layout.rs` / `edge.rs` / `lib.rs` / `v2_layout_tests.rs` / `edge_v3.rs` tests | definitions, re-exports, own tests | — | compatibility | both | the lens surface itself | COMPAT | +| `lance-graph-contract/src/band_reading.rs`, `class_view.rs::band_reading` | `BandDeclarations::project_band` | R | compatibility | `ReasoningBand` raw | the historical declared projector; only consumer is `reasoning_band_probe`; no `ClassView` overrides `band_reading` | COMPAT | +| `lance-graph-ogar/src/recipe_vocab.rs`, `probe_stamp_morton_cascade.rs`, `arm-discovery/src/translator.rs`, `epistemic_state5.rs` docs | prose | — | docs | — | — | FALSE-POSITIVE (doc mentions) | + +### 2b. Homonyms (no bits 59..63) + +`Triplet::with_truth` (arigraph, osint), `nars_engine::set_truth`, +`CognitiveAtom::with_truth`, the deepnsm V3-facet example's `set_truth(f, c)`, +`RungLevel` (shares four `ReasoningBand` names at different ordinals), +`verb_lexicon::epistemic_reading` — all NARS truth or unrelated: +FALSE-POSITIVE. + +## 3. `TrustTexture` collision map + +| type | variants | repr / storage | writers / readers | purpose | +|---|---|---|---|---| +| `causal_edge::layout::TrustTexture` | Crystalline, Solid, Fuzzy, Murky | `repr(u8)`, the historical lens over bits 59..60 | no production writer or reader; `causal-edge` tests, `ce64_nextstate_probe`'s comparison writer | historical gate texture (its doc: Proceed / Proceed / Sandbox / Compass) | +| `lance_graph_contract::mul::TrustTexture` | Calibrated, Overconfident, Uncertain, Underconfident | `repr(u8)`, MUL outputs and i4 batch slices | `mul`, `collapse_gate`, `kanban`, `action`, `sensorium`, `exploration`, `deepnsm-v2`, supervisor cycle driver | felt vs demonstrated competence | +| `lance_graph_planner::mul::trust::TrustTexture` | Crystalline, Solid, Fuzzy, Murky, Dissonant | 5 buckets over an f32 trust score | planner `mul::gate`, `thinking::style` | trust-score quantizer | +| `lance_graph::graph::arigraph::orchestrator::TrustTexture` | Crystalline, Fibrous, Fuzzy | serde enum | orchestrator | source / environment reliability factor | + +Bridge search (`as u8`, `transmute`, `From`/`TryFrom`/`Into`, `from_bits_*` / +`to_bits_*` on a texture, match-and-repack, `with_truth`, `truth_raw`): none +between any two of the four, and none from any of them onto `Topology2`. The +only ordinal casts are `causal-edge`'s tests of its own discriminants. The +CE64 lens shares four names with the planner type, which has five buckets: an +ordinal cast would not even be total. OQ-MCAL-1 (which MUL vocabulary is +canonical) is unresolved. No bridge exists, and none was added. + +## 4. ReasoningBand callers — semantic intent + +| historical use | intent found | home | +|---|---|---| +| dismech `EdgeRole::band` | "explains vs relates" | certification coordinate of `epistemic_raw5`, under the cut edge's declaration | +| hinge `Causal` | a nontrivial witness value | any meaningful state; the coordinate is pinned, its meaning unused | +| four-plane `Causal` lens | the reasoning lens a hypothesis is read at | execution context (`RungLevel` / style), not CE64 — COMPAT | +| D-GSO-7 `Association` / `Causal` | earned by observation / one intervention | P7a obligations (different policy: P7a `Causes` ≥ 2 sources) — no name mapping | +| D-GSO-7 `Counterfactual` | survived a removal attack | counterfactual machinery (`dismech_counterfactual`) | +| `Meta` / `Transcendent` (P7a) | none — refusal witnesses | reserved certifications refuse | +| `Relation`, `Perspective` | no caller | — | + +## 5. CE64 TrustTexture callers — semantic intent + +No production reader or writer. The V3 lift copies raw bits (physical, no +lens) and now reads them jointly. The nextstate comparison writer is kept. +`band_reading`'s `Trust` lens refuses under the canonical reading. Topology +queries already read `CausalTopology` / `Topology2`; no `TrustTexture` caller +meant topology, MUL or conflict. + +## 6. Stale raw5 / code assertions + +The model failure (writer migrated 7 → 4, expectation still `state[7]`) was +fixed in `350e364`. This census re-checked every `state[N]`, `EPI_LAW[N]`, +`with_epistemic_raw5(N)`, `write_code`, `CODE_*` and `decode` literal +against `topology | certification << 2` and its comment: all agree. The #1370 +codes surviving in comments are provenance notes ("was code 7"). No new stale +assertion. + +## 7. Code-oriented checks turned into population queries + +- `dismech_counterfactual` test: "explains vs relates" is membership of the + cut edge's code in `facts_population(fact::CAUSES)`; fixtures + `IndirectKnown × Causes` (21) and `IndirectKnown × Related` (9), each pinned + by raw5, decoded factors and the role's topology. +- `epistemic_reading_conflict_probe::a_certified_causes_reads_as_causes_under_every_topology`: + `20 + t.ordinal()` → each stamp pinned to its coordinate, and the four + stamped codes together equal `facts_population(CAUSES)`. +- `revision_epistemic_writer_probe` (`for start in (0..32).filter(EPI_LAW is_some)`): + → the members of `facts_population(0)`. +- `affordance_measurement_probe::reserved_codes_refuse_…` + (`filter(EPI_LAW is_none)`): → the complement `!facts_population(0)`. + +The remaining raw literals are coordinates by intent (a fixture's concrete +state, or a test about code arithmetic itself), each pinned with its +decoded factors where it drives behaviour. + +## 8. What remains, and what cannot move + +- Canonical: `EpistemicState5`, `Topology2`, `Certification3`, `Facts`, + `facts_population`, the affordance law. +- Independent: MUL (both vocabularies), witness / provenance, revision / + contradiction, `dismech_counterfactual`, Pearl `CausalMask`, NARS F/C, + inference mantissa, `RungLevel`. +- Compatibility only: `causal_edge::layout::{TrustTexture, ReasoningBand}`, + their deprecated writers and plain readers, `band_reading`'s projector. + Production consumers after this wave: **none**. Load-bearing only as (a) + the V1-feature stubs, (b) the old-vs-new comparison and refusal witnesses, + (c) the two historical probes. They could be deprecated harder (readers + too) or moved behind a `legacy-59-63` feature once those probes are + retired; deletion is not proposed — `band_reading` is the declared + projector for data stamped under the historical reading, and no replay + inventory of such data exists yet. +- Cannot migrate without invention: `probe_four_plane_causal_medium` (its + thesis puts a reasoning lens on 61..63) and `reasoning_band_probe` (its + rungs are a different policy from P7a's). Both stay as records. +- No population helper beyond `facts_population` / `bit`: no query repeats + three times. + +## 9. Counts + +ReasoningBand caller sites (bits 59..63, outside `causal-edge`'s own +definitions and tests): 6. Each has exactly one primary class: + +| site | was really | primary class | +|---|---|---| +| `dismech EdgeRole::band` | a certification question (explains vs relates) | EXACT-COORDINATE (field removed; test asks `facts_population(CAUSES)`) | +| kanban hinge witness | a non-interference witness | EXACT-COORDINATE (`IndirectUnknown × Causes` = 22) | +| four-plane probe | a reasoning lens | EXECUTION-CONTEXT → COMPAT | +| `reasoning_band_probe` | D-GSO-7 rungs (superseded by P7a; its `Counterfactual` rung is counterfactual evidence) | obsolete → COMPAT | +| P7a `Meta`/`Transcendent` | refusal witness | REFUSE | +| conflict probe legacy class | refusal witness | REFUSE | + +So of the six, 1 was a certification query, 1 was a value whose meaning was +never read, 2 were context or obsolete, and 2 are refusal witnesses. +Removed: 1 field (`EdgeRole::band`). + +CE64 `TrustTexture` callers: 0 production readers or writers. Topology +queries: 0 (they already used `CausalTopology` / `Topology2`). MUL / +calibration: 0. Other subsystems: 0. Physical copy now joint: 1 (the V3 +lift). Compatibility only: the nextstate two-writer comparison, the +`band_reading` `Trust` lens, `causal-edge`'s own tests. + +Raw-code checks that became population queries: 4 call sites (`dismech` +test, conflict F3, revision `every_write_is_a_declared_joint_code`, +affordance reserved codes). Production callers of `facts_population`: 0. +It is a contract operator awaiting its first runtime fold. + +Raw5 literals remaining: coordinates by intent only (fixtures and +code-arithmetic tests), each pinned to its decoded factors where it drives +behaviour. + +Ordinal bridges between unrelated `TrustTexture` types: none, before and +after. + +Legacy APIs still load-bearing: no production path reads or writes 59..63 +through a legacy lens. What remains carries the `v1`-feature stubs, the +comparison and refusal witnesses, and two historical probes. + +## 10. Disable runs (deprojection) + +- `EdgeRole` reports a constant code instead of the cut edge's → the + `dismech` coordinate test fails. +- The V3 lift swaps the two halves → 3 `edge_v3` tests fail (joint-code + preservation, full field parity, tail placement). +- The P7a stamp ignores its topology → conflict F3 fails at the coordinate + pin. + +Gates: `causal-edge` 81 (v2) / 39 (v1); `lance-graph-contract` +`epistemic_state5` 12; planner `dismech` 24 and `probe_four_plane` builds; +`cognitive-shader-driver --examples` 26 suites, 0 failures, clippy clean. +Clippy `-D warnings` still fails on lints this branch does not touch +(`causal-edge` `edge.rs` / `tables.rs` / `module_inception`, planner +`nested_bands.rs`, `nars_engine.rs`, `probe_nxg_hist_1`, all `chunks_exact` +or older). diff --git a/.claude/board/entries/2026-10-07-epi-mig-0.md b/.claude/board/entries/2026-10-07-epi-mig-0.md new file mode 100644 index 000000000..07e4f0e8a --- /dev/null +++ b/.claude/board/entries/2026-10-07-epi-mig-0.md @@ -0,0 +1,64 @@ +# 2026-10-07 — D-EPI-CANON-0 / D-EPI-MIG-0: CE64 bits 59..63 are `EpistemicState5 = Topology2 × Certification3` + +Operator decision (revised the same day, before merge): bits 59..63 are ONE +declared 5-bit field, the dense PRODUCT of the two coordinates already on the +wire — `raw5 = topology | certification << 2`, `CausalTopology` ordinals × +P7a certification codes, 24 meaningful states, 8 reserved. Layout decides +coordinates; the affordance law decides what may happen next +(`CausalEdge64 × RecipeLaw[64] → EligibleRecipes: u64`, transient). +`OBSERVED` is not a coordinate: observation is evidence/provenance. The +first draft's adoption of #1370's hand-assigned ten-code codebook is +replaced, not versioned (V1 never merged). Inventory + re-run: +`entries/2026-10-07-epi-mig-inventory.md`. + +## CURRENT-CONTRACT + +- `lance_graph_contract::epistemic_state5`: `Topology2`, `Certification3`, + `TOPOLOGY_FACTS` / `CERTIFICATION_FACTS` (facts = union; disjoint, + const-asserted), `facts_v1`, derived `COMPILED_FACTS_V1`, `EpistemicState5` + (`new`, `decode` — validity is `certification < 6` —, `raw`, factor + updates `with_topology` / `with_certification`), `Epi5Reading::project`, + `Epi5Declarations::project_state5` (refuses undeclared class, untrusted + provenance, generation mismatch, reserved certification), + `ClassView::epistemic_reading` (default `None`). +- `causal-edge`: `epistemic_raw5` / `with_epistemic_raw5`, `EPISTEMIC_MASK`; + `with_topology` = canonical topology-factor writer; `with_truth` / + `set_truth` / `with_routing` / `with_reasoning_band` / `with_spare` / + `set_spare` `#[deprecated]` because they write under a reading other than + the declared one; V3 lift restores the field jointly. +- `band_reading`: legacy compatibility surface. Topology lens + P7a band = + identity with the product; trust lens / historical band classes refuse. + +## MEASURED (each disable-verified red on `6c395c2` unless noted) + +| F | where | disable → red | +|---|---|---| +| F1 | causal-edge joint round trip; contract `the_layout_is_the_two_by_three_product` | reserved 6 accepted → 3 contract + 3 probe tests | +| F2 | probe `a_legacy_reading_class_is_not_consumed_as_canonical`; causal-edge `the_topology_writer_is_a_factor_update_and_the_band_writer_is_not_canonical`; contract `factor_updates_move_only_their_coordinate` | classless `measure_declared` → 2 probe tests | +| F3 | probe `a_certified_causes_reads_as_causes_under_every_topology` (20..23) | — | +| F4 | reserved certifications 24..31 refuse (contract + probe + affordance probe) | as F1 | +| F5 | probe `the_state_survives_restart_from_le_bytes`; causal-edge LE + V3 lift | — | +| F6 | probe `the_derived_table_equals_the_rules` (factor-compiled table == per-code rule evaluation, V1 + V2); affordance `f9` 0 allocations over 192 edges | topology-only table → 2 probe tests | +| F7 | probe `one_factor_moves_only_its_recipes` (10↔9 hydrate↔mechanism, 9↔13 causal identification, 22/23 frontier, generic topology/certification isolation) | swapped topology facts → 2 contract + 1 probe | +| F8 | probe `projections_never_change_the_edge` | — | +| F10 | probe census `(24, 0, 8)` — canonical == `topology()` lens + P7a decode over all 32 codes (was 3 / 5 / 16) | reserved 6 accepted → red | +| chain | contract `certification_entailment_is_the_measured_chain` | `Causes` without `CAUSAL_CANDIDATE` → 2 contract tests | + +## MIGRATED + +`shared/affordance_law.rs` (per-factor compile), `shared/certification_reading.rs` +(P7a, identity packing), `affordance_measurement`, `ce64_cycle_survival`, +`ce64_nextstate`, `revision_epistemic_writer` (#1379: `Direct × Open → +Direct × Associated`, i.e. 0 → 4), `streamdto_circuit`, `entropy_topology`, +`epistemic_reading_conflict` (#1378 → conformance), `relational_certification`, +`dismech_counterfactual` (`EdgeRole.epistemic_raw5`), `edge_v3` lift. + +## OPEN + +- Cannot migrate without semantic invention (historical `ReasoningBand` on + the certification bits): `reasoning_band_probe`, `probe_revision_kanban_hinge`, + `probe_four_plane_causal_medium`, `EdgeRole::band`. Tracked in + `ISS-LEGACY-59-63-READINGS-REMAIN`. +- P7a's `MODEL_GROUNDING = Direct` is a producer declaration in the probe. +- No production class declares the canonical reading yet (no OGAR mint). +- "Observed vs asserted", if ever needed, belongs to witness/provenance. diff --git a/.claude/board/entries/2026-10-07-epi-mig-inventory.md b/.claude/board/entries/2026-10-07-epi-mig-inventory.md new file mode 100644 index 000000000..11d4460e5 --- /dev/null +++ b/.claude/board/entries/2026-10-07-epi-mig-inventory.md @@ -0,0 +1,177 @@ +# 2026-10-07 — D-EPI-MIG-INVENTORY: every reader and writer of CE64 bits 59..63 + +Operator decision (2026-10-07): bits 59..63 are ONE declared field, +`EpistemicState5` — a dense 5-bit codebook over valid epistemic +conjunctions. `contract::band_reading`'s split reading (59..60 truth/topology +lens, 61..63 band) and the P7a certification band survive only as +compatibility projections / translations. This entry is the inventory taken +on `main` @ `1c91fa4` (after #1380) BEFORE any semantic change. + +Method: `grep` located candidates for `truth`/`truth_raw`/`with_truth`/ +`set_truth`/`topology`/`with_topology`/`reasoning_band`/`with_reasoning_band`/ +`spare`/`with_spare`/`set_spare`/`with_routing`/`temporal()`/`TRUTH_*`/ +`SPARE_*`/`>> 59`/`raw5`/`EPI_LAW`/`EpistemicState5`/`band_reading`/ +`ReasoningBand`/`CausalTopology`/`TrustTexture` across `crates/`; every hit +was then opened and read. False positives (same method name, other type) +are listed so they are not re-checked. + +Actions: MIGRATE / PROJECT / WRAP / REFUSE / REMOVE / PROBE-ONLY. + +## Physical layer (`causal-edge`) + +| path | r/w | kind | interpretation | joint? | action | +|---|---|---|---|---|---| +| `edge.rs` `truth()/truth_raw()/topology()` | R | prod API | 2-bit lens (Trust / Topology) | half | PROJECT — legacy lens, raw half kept as physical accessor | +| `edge.rs` `spare()/reasoning_band()` | R | prod API | 3-bit lens (spare / historical band) | half | PROJECT — legacy lens | +| `edge.rs` `with_truth/set_truth/with_topology/with_routing` | W | prod API | writes 59..60 only | half | WRAP — `#[deprecated]` → joint writer | +| `edge.rs` `with_spare/set_spare/with_reasoning_band` | W | prod API | writes 61..63 only | half | WRAP — `#[deprecated]` → joint writer | +| `edge.rs` (none) | — | — | no joint accessor exists | — | MIGRATE — add `epistemic_raw5` / `with_epistemic_raw5` | +| `edge.rs` `pack()` v2 / `pack_v2()` | W | prod | zeroes 59..63 | joint | PROJECT — code 0 = canonical `Open`, no change | +| `edge.rs` `compose()` → `pack()` | W | prod | result 59..63 = 0 | joint | PROJECT — composition certifies nothing: `Open` | +| `edge.rs` `temporal()` / `network.rs` `evidence_trail` sort | R | prod (deprecated) | bits 52..63 composite incl. 59..63 | joint-as-noise | REFUSE semantically — already deprecated; ordering is documented meaningless; listed as remaining | +| `edge.rs` `set_temporal()` v1 | W | v1 compat | v1 temporal | — | v2 no-op, unchanged | +| `edge_v3.rs` `from_v1` p[8]/p[9] | R | prod lift | raw copy of both halves | joint (two copies) | MIGRATE — copy `epistemic_raw5` jointly; provenance rule unchanged | +| `edge_v3.rs` `to_ce64` `set_truth`+`set_spare` | W | prod lift | raw copy back | joint (two writes) | MIGRATE — `with_epistemic_raw5` | +| `edge_v3.rs` tests | R/W | test | raw round trip | — | keep (allow deprecated) | +| `v2_layout_tests.rs` | R/W | test | field isolation of the halves | half | keep (allow deprecated) — physical layout tests | + +## Contract (`lance-graph-contract`) + +| path | r/w | kind | interpretation | class/rail-aware | provenance-gated | action | +|---|---|---|---|---|---|---| +| `band_reading.rs` | R (declares) | prod contract | split: `TruthLens` 59..60 + `BandPresence` 61..63 | yes | yes | WRAP — regraded legacy/transitional; no longer owns 59..63; `EdgeProvenance` reused unchanged | +| `class_view.rs` `band_reading()` | R | prod contract | default `ZERO_FALLBACK` | yes | — | WRAP — doc points at the canonical reading | +| (absent) | — | — | no joint 5-bit reading | — | — | MIGRATE — new `epistemic_state5` module | + +## Production consumers + +| path | r/w | interpretation | gated? | can map to State5? | action | +|---|---|---|---|---|---| +| `lance-graph-planner/src/dismech_counterfactual.rs:251` `CounterfactualRole{topology, band}` | R | legacy lens pair of the cut edge, no provenance | no | the edge's raw5 yes; meaning needs a declaration the call site does not have | MIGRATE — carry `epistemic_raw5`; `topology`/`band` kept as legacy projections (doc); test fixture to joint writer | +| `lance-graph-arm-discovery/src/translator.rs` doc | — | prose: "61-63 have been ReasoningBand" | — | — | docs: corrected | +| `lance-graph-planner/src/cache/nars_engine.rs` `set_truth` | — | **false positive** (`SpoHead`) | — | — | none | +| `lance-graph-cognitive/src/search/cognitive.rs` `with_truth` | — | **false positive** (`CognitiveAtom`) | — | — | none | +| `deepnsm/examples/causal_edge_v3_facet.rs` `set_truth(f,c)` | — | **false positive** (V3 facet truth pair, not CE64 59..63) | — | — | none | +| `lance-graph-contract/src/mul.rs`, supervisor `probe_ignition` `TrustTexture` | — | **false positive** (`contract::mul::TrustTexture`) | — | — | none | +| Mailbox / shader (`cognitive-shader-driver/src`, supervisor src, `lance-graph/src`) | — | no reader or writer of 59..63 found | — | — | none (streamdto probe pins "the shader writes no epistemic state") | + +## Probes (`cognitive-shader-driver/examples`, `lance-graph-planner/examples`) + +| path | r/w | interpretation | joint? | action | +|---|---|---|---|---| +| `shared/affordance_law.rs` `EPI_LAW`, `raw5`, `measure` | R | joint raw5, classless, own codebook | joint | MIGRATE — codebook = contract `CODEBOOK_V1`; `measure` projects through a declaration (class, rail, generation, provenance) | +| `affordance_measurement_probe.rs` `stamp` (`with_spare`+`with_truth`) | W | joint via two halves | joint | MIGRATE — joint writer | +| `ce64_cycle_survival_probe.rs` `stamp` | W | same | joint | MIGRATE | +| `ce64_nextstate_probe.rs` `stamp` + raw mask write | W | same; raw mask is the measured instruction shape | joint | MIGRATE `stamp`; raw mask pinned equal to the canonical writer (PROBE-ONLY) | +| `revision_epistemic_writer_probe.rs` (#1379) `stamp` | W | same | joint | MIGRATE | +| `streamdto_circuit_probe.rs` | R + raw W | raw5 + mask poke for a fixture | joint | MIGRATE reads via shared law (no stamp change needed) | +| `shared/certification_reading.rs` (P7a) `stamp` / `read` | W/R | **61..63 only**, P7a contract via `band_reading::project_band` | half | MIGRATE — stamp = legacy translation (grounding × contract) → canonical code → joint write; read = `project_state5` → certification projection | +| `relational_certification_probe.rs` (P7a) | W/R | via shared; raw `with_reasoning_band(6,7,Meta)` fixtures | half | MIGRATE — fixtures declare `Direct` grounding; Contributes/CausalCandidate under Direct have no canonical code → REFUSE (listed open) | +| `epistemic_reading_conflict_probe.rs` (#1378) | R | both readings | — | MIGRATE → conformance probe | +| `reasoning_band_probe.rs` (D-GSO-7, #1360) | W/R | historical `ReasoningBand` ladder on 61..63 | half | PROBE-ONLY — historical reading; canonical projection REFUSES it (no declared translation); listed remaining | +| `probe_revision_kanban_hinge.rs` | W/R | historical topology + `ReasoningBand::Causal` fixture | half | PROBE-ONLY — listed remaining | +| `entropy_topology_probe.rs` | R | topology lens via `band_reading::project_truth` | half | PROBE-ONLY — listed remaining | +| `lance-graph-planner/examples/probe_four_plane_causal_medium.rs` | W/R | historical topology + band | half | PROBE-ONLY — listed remaining | +| `witness_angle_probe.rs`, `spog_witness_probe.rs` | — | `EdgeProvenance` only | — | none | + +## Why historical readings are REFUSED, not translated + +The historical `ReasoningBand` (`Surface, Association, Relation, Causal, +Counterfactual, Perspective, Meta, Transcendent`) names reasoning LEVELS, and +P7a (#1369) already showed they are not the certification contract (old +`Meta`/`Transcendent` sit numerically above `Causes`). The `TrustTexture` +lens on 59..60 is an epistemic texture, not grounding. Neither producer +declared conjunction semantics, so no translation table is written for them: +canonical projection of their bits refuses (`UndeclaredCode` or, for a class +declared only under the historical reading, `UndeclaredClass`). + +The only legacy translation declared is P7a certification × legacy +`CausalTopology` grounding, whose obligations were measured in #1369. + +## Re-run under the Cartesian V1 layout (operator revision, same day) + +The operator replaced the #1370 hand-assigned codebook with the product +`EpistemicState5 = Topology2 × Certification3`, `raw5 = topology | +certification << 2` (`CausalTopology` ordinals × P7a certification codes; 24 +meaningful, 8 reserved). Every site above was re-classified against it on +`6c395c2`: + +| site | before (dense #1370) | after (Cartesian) | +|---|---|---| +| `edge.rs` `topology()` / `with_topology` | PROJECT / WRAP (deprecated) | **canonical topology coordinate** — `with_topology` un-deprecated (factor update moves only 59..60) | +| `edge.rs` `spare()` | PROJECT | raw certification coordinate (`epistemic_raw5 >> 2`) | +| `edge.rs` `truth()` / `with_truth` / `set_truth` / `with_routing` | WRAP | WRAP (deprecated): `TrustTexture` reading of the topology bits, no mapping | +| `edge.rs` `reasoning_band()` / `with_reasoning_band` | WRAP | WRAP (deprecated): historical reasoning levels on the certification bits, no mapping | +| `edge.rs` `with_spare` / `set_spare` | WRAP | WRAP (deprecated): untyped | +| `band_reading` | WRAP | WRAP: a `Topology` lens + P7a band declaration IS the product (identity); `Trust` lens / historical band classes have no canonical declaration and refuse | +| `shared/certification_reading.rs` (P7a) | MIGRATE via translation table (13 of 24 cells refused) | **MIGRATE by identity packing** — all 6 contracts × 4 topologies stamp | +| `shared/affordance_law.rs` | MIGRATE | MIGRATE: compiled per factor; per-code table derived | +| `affordance_measurement` / `ce64_cycle_survival` / `ce64_nextstate` / `revision_epistemic_writer` / `streamdto` | MIGRATE | MIGRATE + fixture remap (3/7 → 0/4; 25/5/17 → 10/9/21; 7 → 4) | +| `entropy_topology_probe` | PROBE-ONLY | **MIGRATE** — writes only topology over `Open`; reads `state.topology()` through `project_state5` | +| `dismech_counterfactual::EdgeRole` | MIGRATE (`epistemic_raw5`) | same; `topology` regraded to the raw topology coordinate; `band` stays legacy | +| `reasoning_band_probe`, `probe_revision_kanban_hinge`, `probe_four_plane_causal_medium` | PROBE-ONLY | **PROBE-ONLY — cannot migrate without semantic invention** (see below) | +| `temporal()` / `evidence_trail` | REFUSE | unchanged | + +### Callsites that cannot be migrated without semantic invention + +1. `cognitive-shader-driver/examples/reasoning_band_probe.rs` — earns and + lowers the historical `ReasoningBand` ladder (`Surface … Transcendent`) on + 61..63. Those are reasoning levels; there is no declared mapping onto + `Open … Causes`. The first three names look similar (`Surface ~ Open`, + `Association ~ Associated`, `Relation ~ Related`), but adopting that would + be a mapping nobody declared, and from `Causal` (3) on the two + vocabularies diverge. +2. `cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs` — writes + `IndirectUnknown` (canonical as-is) + `ReasoningBand::Causal`. Raw code + `2 | 3 << 2 = 14` decodes as `IndirectUnknown × Supports`; reading it so + would invent a support claim. +3. `lance-graph-planner/examples/probe_four_plane_causal_medium.rs` — same + shape (`IndirectUnknown`/`IndirectKnown` + `ReasoningBand::Causal`). +4. `dismech_counterfactual::EdgeRole::band` — the historical lens over the + certification bits; kept as a compatibility field beside the canonical + `epistemic_raw5`, never mapped. + +No `TrustTexture` writer exists in the tree (`with_routing`/`with_truth`/ +`set_truth` have no non-test caller), so the trust-lens case is +declaration-only. + +## Census after the Codex finding on #1381 (three axes) + +Codex found a stale *consumer*: `streamdto_circuit_probe` S4 wrote code 4 but +expected `t.state[7]`. Codes 4 (`Direct × Associated`) and 7 (`Unknown × +Associated`) grant the same recipes there, so the test passed on the wrong +topology. Fixed: it expects `state[4]` and pins `raw5 == 4`, `Topology2::Direct` +and `Certification3::Associated`. Disable run (write 7): red. + +**A — storage touches (bits 59..63).** Outside `causal-edge`, three probes +hand-rolled the joint field from `TRUTH_MASK | SPARE_MASK` / `TRUTH_SHIFT`. +MIGRATED: `streamdto_circuit_probe` → `with_epistemic_raw5`; +`revision_epistemic_writer_probe` → `EPISTEMIC_MASK`; `ce64_nextstate_probe` → +`EPISTEMIC_MASK` / `EPISTEMIC_SHIFT` (its masked write stays as the +independent oracle of the joint writer). KEEP: the split reads in +`epistemic_reading_conflict_probe` and `revision_epistemic_writer_probe`, plus +`via_two_writers`, which exist to compare the old reading against the +canonical one. LEGACY: unchanged from the re-run above. + +**B — raw5 semantics.** Every literal code (`state[N]`, `EPI_LAW[N]`, +`write_code`, `with_epistemic_raw5(N)`, `CODE_*`) was decoded as +`topology | certification << 2` against its comment; all agree after the S4 +fix. One more stale site: `streamdto_circuit_probe`'s `main` table printed +the old #1370 sample codes `[0,1,3,5,7,12,17,20,25,30]`. 25 and 30 are now +reserved, so it printed `0x0` where the law refuses. MIGRATED: it prints the 24 +meaningful states by coordinate. + +**C — name collisions.** `TrustTexture` has four definitions: the CE64 lens +(`causal_edge::layout`), `contract::mul` (Calibrated/Overconfident/…), +`planner::mul::trust` (Crystalline/Solid/Fuzzy/Murky/Dissonant; it shares four +names with the CE64 lens) and `arigraph::orchestrator`. No `From`/`TryFrom` +impl, ordinal cast or `from_bits_2` bridges any of them to the CE64 lens or to +`Topology2`. MUL's i4 batch writes only into its own `[TrustTexture]` slices. +FALSE-POSITIVE: MUL stays out of the migration. The CE64 TrustTexture writers +are deprecated and have no callers outside `causal-edge`'s tests, so nothing +can re-couple them silently. + +**Rule for new tests.** A test that asserts an eligibility derived from a code +also pins the code itself (`epistemic_raw5`, `topology()`, `certification()`). +Distinct states can grant identical recipes, so an eligibility match alone +does not identify the state. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 7fa76d5c8..9eb491c98 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,13 +25,16 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -241 entries, 2026-08-06 .. 2026-10-07. +246 entries, 2026-08-06 .. 2026-10-07. | date | entry id | finding | file | |---|---|---|---| | 2026-10-07 | `tinker-janus-fold-harvest` | TinkerPop bulk = K (GroupReduce), ONE_BULK = S, GValue pinning = bundle invalidation; JanusGraph slice = OrderedLaneWitness→Range (30–38×); no new V4 op | [2026-10-07-tinker-janus-fold-harvest.md](2026-10-07-tinker-janus-fold-harvest.md) | | 2026-10-07 | `selector-seq-aware-coverage` | | [2026-10-07-selector-seq-aware-coverage.md](2026-10-07-selector-seq-aware-coverage.md) | | 2026-10-07 | `gated-gather-bit-schedule` | Production gather is 3.8× slow from a per-row branch; bit-gated visitation wins or ties at every density; ordinal vectors never win (R1 holds) | [2026-10-07-gated-gather-bit-schedule.md](2026-10-07-gated-gather-bit-schedule.md) | +| 2026-10-07 | `D-EPI-MIG-INVENTORY` | | [2026-10-07-epi-mig-inventory.md](2026-10-07-epi-mig-inventory.md) | +| 2026-10-07 | `D-EPI-CANON-0` | | [2026-10-07-epi-mig-0.md](2026-10-07-epi-mig-0.md) | +| 2026-10-07 | `D-EPI-POP-0` | | [2026-10-07-epi-legacy-deprojection.md](2026-10-07-epi-legacy-deprojection.md) | | 2026-10-07 | `D-CTX-7` | | [2026-10-07-ctx7-revision-writer-for-epistemic-state.md](2026-10-07-ctx7-revision-writer-for-epistemic-state.md) | | 2026-10-07 | `bind-bundle-gated-gather` | One bound query, five routes, identical answers; the semijoin is never gated and dominates; a gated mask gather beats a selection vector (R1 survives) | [2026-10-07-bind-bundle-gated-gather.md](2026-10-07-bind-bundle-gated-gather.md) | | 2026-10-07 | `aperture16-u64-word-schedule` | The u16 view of the 64K mask is free but 1.3–2.3× slower per layout as a schedule than u64 words; extent × mask compose; the exact next-frontier mask beats a u16 target histogram; no new carrier or opcode | [2026-10-07-aperture16-u64-word-schedule.md](2026-10-07-aperture16-u64-word-schedule.md) | diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 7d9084f09..9af601d3c 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -1015,6 +1015,22 @@ impl CausalEdge64 { ReasoningBand::from_bits_3(((self.0 >> SPARE_SHIFT) & BITS3_MASK) as u8) } + /// Bits 59..63 as ONE 5-bit `EpistemicState5` code (`0..=31`). + /// + /// This is the canonical physical read of the field; `truth_raw()` / + /// `topology()` / `spare()` / `reasoning_band()` are legacy projections of + /// its halves. The code is semantically opaque without a declared reading: + /// project it through `lance_graph_contract::epistemic_state5`, which also + /// enforces provenance (a v1 edge with `temporal >= 128` reads non-zero + /// here). + /// + /// Equal to `(spare() << 2) | truth_raw()` by construction. + #[inline(always)] + pub fn epistemic_raw5(self) -> u8 { + use crate::layout::{BITS5_MASK, EPISTEMIC_SHIFT}; + ((self.0 >> EPISTEMIC_SHIFT) & BITS5_MASK) as u8 + } + // ── Builder-Shape Setters (functional update, returns Self) ───────────── /// Return new edge with W slot set to `w` (0..=63). @@ -1025,7 +1041,28 @@ impl CausalEdge64 { Self((self.0 & !W_MASK) | (((w as u64) & BITS6_MASK) << W_SHIFT)) } + /// Return new edge with bits 59..63 set to the 5-bit `EpistemicState5` + /// code `code` (`0..=31`). The ONE canonical writer of the field: all five + /// bits move together, nothing else moves. + /// + /// Callers pass a code obtained from a declared reading + /// (`lance_graph_contract::epistemic_state5::EpistemicState5::raw`) — this + /// crate is zero-dep and cannot check the codebook itself. + #[inline] + pub fn with_epistemic_raw5(self, code: u8) -> Self { + use crate::layout::{BITS5_MASK, EPISTEMIC_MASK, EPISTEMIC_SHIFT}; + debug_assert!( + code <= 31, + "EpistemicState5 code must fit 5 bits, got {code}" + ); + Self((self.0 & !EPISTEMIC_MASK) | ((code as u64 & BITS5_MASK) << EPISTEMIC_SHIFT)) + } + /// Return new edge with truth-band lens set. + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_truth(self, t: crate::layout::TrustTexture) -> Self { use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; @@ -1034,6 +1071,12 @@ impl CausalEdge64 { /// Return new edge with the causal-topology lens set (bits 59-60). /// + /// D-EPI-CANON-0: this is the canonical TOPOLOGY-FACTOR writer of + /// `EpistemicState5` (`raw5 = topology | certification << 2`); it moves + /// only bits 59..60 and holds the certification, a legal product-space + /// transition. Not deprecated, unlike `with_truth` (the `TrustTexture` + /// reading of the same bits). + /// /// Consuming builder, register-style (`edge.with_topology(t)`), matching /// `with_truth`/`with_spare`. Shares storage with `truth`/`with_truth` /// — the last writer of either view wins, by construction, since it is @@ -1058,6 +1101,10 @@ impl CausalEdge64 { } /// Return new edge with spare bits set (0..=7, 3-bit field). + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_spare(self, s: u8) -> Self { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; @@ -1087,6 +1134,10 @@ impl CausalEdge64 { /// writes a band today, so this is a trap for the first producer, not a /// live defect. /// the orthogonality notes, and the no-auto-derivation guarantee. + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_reasoning_band(self, band: crate::layout::ReasoningBand) -> Self { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; @@ -1095,10 +1146,16 @@ impl CausalEdge64 { /// Set W-slot and truth-band in one mask-and-or operation (hot-path emit). /// - /// Used by `MailboxSoA::dispatch_cycle()` when stamping routing onto emissions. + /// This note used to name `MailboxSoA::dispatch_cycle()` as its caller; no + /// such function exists in-tree, and nothing outside this crate's tests + /// calls `with_routing`. /// NOTE: No `g` parameter — G-slot is absent in v2 layout (L-3: redundant via /// palette family-prefix + SoA partition + witness corpus root). /// Composable: `edge.with_routing(12, TrustTexture::Solid).with_inference_mantissa(-1)`. + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_routing(self, w: u8, t: crate::layout::TrustTexture) -> Self { use crate::layout::{BITS2_MASK, BITS6_MASK, TRUTH_MASK, TRUTH_SHIFT, W_MASK, W_SHIFT}; @@ -1119,6 +1176,10 @@ impl CausalEdge64 { } /// Set truth-band lens in-place. + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn set_truth(&mut self, t: crate::layout::TrustTexture) { use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; @@ -1135,6 +1196,10 @@ impl CausalEdge64 { } /// Set spare bits in-place (0..=7, 3-bit field). + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn set_spare(&mut self, s: u8) { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; @@ -1189,10 +1254,25 @@ impl CausalEdge64 { pub fn reasoning_band(self) -> crate::layout::ReasoningBand { crate::layout::ReasoningBand::Surface } + /// V1 stub: bits 59..63 are temporal under v1; the joint field is a + /// documented zero (matches `truth_raw()` / `spare()`). + #[inline(always)] + pub fn epistemic_raw5(self) -> u8 { + 0 + } + /// V1 stub: no-op (same rule as every v2 writer under v1). + #[inline] + pub fn with_epistemic_raw5(self, _code: u8) -> Self { + self + } #[inline] pub fn with_w_slot(self, _w: u8) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_truth(self, _t: crate::layout::TrustTexture) -> Self { self @@ -1205,24 +1285,44 @@ impl CausalEdge64 { pub fn with_inference_mantissa(self, _m: i8) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_spare(self, _s: u8) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_reasoning_band(self, _band: crate::layout::ReasoningBand) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn with_routing(self, _w: u8, _t: crate::layout::TrustTexture) -> Self { self } #[inline] pub fn set_w_slot(&mut self, _w: u8) {} + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn set_truth(&mut self, _t: crate::layout::TrustTexture) {} #[inline] pub fn set_inference_mantissa(&mut self, _m: i8) {} + #[deprecated( + since = "0.2.0", + note = "writes bits 59..63 under a reading that is not the declared EpistemicState5 one (TrustTexture, historical ReasoningBand, or untyped spare); its bits would then be consumed as canonical without a declaration. Write a state from `lance_graph_contract::epistemic_state5` with `with_epistemic_raw5`, or the topology factor with `with_topology`" + )] #[inline] pub fn set_spare(&mut self, _s: u8) {} } @@ -1246,6 +1346,9 @@ impl std::fmt::Debug for CausalEdge64 { } #[cfg(test)] +// The half-field writers are deprecated (D-EPI-CANON-0); these tests +// pin their physical layout, so they keep calling them. +#[allow(deprecated)] mod tests { use super::*; diff --git a/crates/causal-edge/src/edge_v3.rs b/crates/causal-edge/src/edge_v3.rs index 68187ddb2..6cce816bc 100644 --- a/crates/causal-edge/src/edge_v3.rs +++ b/crates/causal-edge/src/edge_v3.rs @@ -140,8 +140,12 @@ impl CausalEdgeV3 { // ordinals, no lens interpretation (see the module doc). Under the v1 // layout every one of these accessors is a documented zero stub, so this // writes zeros and the round trip stays exact there too. - p[8] = (e.w_slot() & 0x3F) | ((e.truth_raw() & 0b11) << 6); - p[9] = e.spare() & 0b111; + // Bits 59..63 are read as ONE field (`epistemic_raw5`, D-EPI-CANON-0) + // and split only by the V3 byte layout: its low 2 bits (topology) + // into [8], its high 3 (certification) into [9]. + let raw5 = e.epistemic_raw5(); + p[8] = (e.w_slot() & 0x3F) | ((raw5 & 0b11) << 6); + p[9] = (raw5 >> 2) & 0b111; Self { payload: p } } @@ -248,6 +252,16 @@ impl CausalEdgeV3 { self.payload[9] & 0b111 } + /// The preserved CE64 bits 59..63 as ONE 5-bit `EpistemicState5` code — + /// `(spare_raw << 2) | truth_raw`, the same value + /// [`CausalEdge64::epistemic_raw5`] reads. Raw and opaque: the meaning is + /// the declared reading's (`lance_graph_contract::epistemic_state5`), and + /// the register's provenance is the caller's assertion (see the module + /// doc on `from_v1`). + pub fn epistemic_raw5(&self) -> u8 { + (self.spare_raw() << 2) | self.truth_raw() + } + /// Rebuild a [`CausalEdge64`] for reasoning by supplying the SPO resolved /// from the target node's CAM-PQ facet. The conclusion of `syllogize` /// depends only on SPO + freq/conf + causal_mask — but this restores the @@ -277,17 +291,9 @@ impl CausalEdgeV3 { ); edge.set_inference_mantissa(mantissa); edge.set_w_slot(self.w_slot()); - // `set_truth` has no raw-u8 form, so the 2-bit ordinal goes through - // `TrustTexture`. That is SAFE where `InferenceType` was not, and the - // difference is the whole point: `from_bits_2`/`to_bits_2` is a total - // BIJECTION on 0..=3 (four ordinals, four variants, discriminants - // 0,1,2,3), whereas `InferenceType` maps 16 mantissa states onto 8 - // variants and cannot be injective. Pinned by - // `trust_texture_bits_2_is_a_bijection_unlike_inference_type` — if a - // variant is ever added or reordered, that test fails before this does. - edge.set_truth(crate::layout::TrustTexture::from_bits_2(self.truth_raw())); - edge.set_spare(self.spare_raw()); - edge + // Bits 59..63 are one field: restore it jointly (D-EPI-CANON-0), so + // the lift can never write half of an EpistemicState5 code. + edge.with_epistemic_raw5(self.epistemic_raw5()) } /// The Lokal target node reference — the node whose 6×256² CAM-PQ facet IS @@ -341,6 +347,9 @@ impl CausalEdgeV3 { } #[cfg(test)] +// The half-field writers are deprecated (D-EPI-CANON-0); these tests +// pin their physical layout, so they keep calling them. +#[allow(deprecated)] mod tests { use super::*; use crate::edge::{CausalEdge64, InferenceType}; @@ -871,3 +880,23 @@ mod tests { ); } } + +#[cfg(test)] +#[cfg(feature = "causal-edge-v2-layout")] +mod epistemic_lift_tests { + use super::CausalEdgeV3; + use crate::edge::CausalEdge64; + + /// The V3 lift carries bits 59..63 as one field, both directions. + #[test] + fn the_lift_preserves_the_joint_epistemic_code() { + for code in 0u8..32 { + let e = CausalEdge64::ZERO.with_epistemic_raw5(code); + let v3 = CausalEdgeV3::from_v1(e, 7); + assert_eq!(v3.epistemic_raw5(), code); + let back = v3.rehydrate(e.s_idx(), e.p_idx(), e.o_idx()); + assert_eq!(back.epistemic_raw5(), code); + assert_eq!(back.0, e.0, "rehydrate is bit-identical"); + } + } +} diff --git a/crates/causal-edge/src/layout.rs b/crates/causal-edge/src/layout.rs index c06e528c8..743937826 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -54,6 +54,12 @@ pub const V1_TEMPORAL_SHIFT: u32 = 52; pub const W_SHIFT: u32 = 53; /// Truth-band lens: 2-bit TrustTexture ordinal (bits 59-60). +/// +/// ⊘ D-EPI-CANON-0 (2026-10-07): bits 59..63 are ONE field, the 5-bit +/// `EpistemicState5` code ([`EPISTEMIC_SHIFT`]), `raw5 = topology | +/// certification << 2`. These two bits are its TOPOLOGY coordinate, read with +/// the [`CausalTopology`] ordinals. The `TrustTexture` reading of the same +/// bits is legacy: no mapping into the canonical state exists for it. /// 0 = Crystalline. Per cognitive-substrate-convergence-v1.md L-7. /// /// Same two bits also carry an ADDITIVE factual view, [`CausalTopology`] @@ -65,6 +71,12 @@ pub const W_SHIFT: u32 = 53; pub const TRUTH_SHIFT: u32 = 59; /// Spare: 3-bit reserved for sprint-12+ (bits 61-63). +/// +/// ⊘ D-EPI-CANON-0 (2026-10-07): the CERTIFICATION coordinate of the joint +/// `EpistemicState5` field ([`EPISTEMIC_SHIFT`]): `0 Open, 1 Associated, +/// 2 Related, 3 Supports, 4 CausalCandidate, 5 Causes, 6/7 reserved` (the P7a +/// codes). The historical [`ReasoningBand`] view below names reasoning levels, +/// not certifications; it is legacy, with no mapping into the canonical state. /// Candidates: Rubicon-commit marker, Markov-decay quantum, I-NOISE-FLOOR-JIRAK threshold. /// /// Same three bits also carry an ADDITIVE quantized-projection view, @@ -87,6 +99,21 @@ pub const W_MASK: u64 = BITS6_MASK << W_SHIFT; pub const TRUTH_MASK: u64 = BITS2_MASK << TRUTH_SHIFT; pub const SPARE_MASK: u64 = BITS3_MASK << SPARE_SHIFT; +// ── The joint epistemic field (bits 59..63) ────────────────────────────────── +/// Bits 59..63 are ONE field: the 5-bit `EpistemicState5` code (operator +/// decision 2026-10-07, `D-EPI-CANON-0`), the product `raw5 = topology | +/// certification << 2` of the 2-bit [`TRUTH_SHIFT`] topology and the 3-bit +/// [`SPARE_SHIFT`] certification coordinates. +/// What a code MEANS is declared per `(classid, rail, generation)` in +/// `lance_graph_contract::epistemic_state5`; this crate only carries bits. +pub const EPISTEMIC_SHIFT: u32 = TRUTH_SHIFT; +/// 5-bit mask (`0..=31`). +pub const BITS5_MASK: u64 = 0x1F; +/// Bits 59..63: exactly `TRUTH_MASK | SPARE_MASK`. +pub const EPISTEMIC_MASK: u64 = BITS5_MASK << EPISTEMIC_SHIFT; +const _: () = assert!(EPISTEMIC_MASK == TRUTH_MASK | SPARE_MASK); +const _: () = assert!(SPARE_SHIFT == EPISTEMIC_SHIFT + 2); + // ── Compile-time layout coverage assertion ──────────────────────────────────── /// Const-assert: all 64 bits covered exactly once. /// 8+8+8+8+8+3+3+4+3+6+2+3 = 64. diff --git a/crates/causal-edge/src/v2_layout_tests.rs b/crates/causal-edge/src/v2_layout_tests.rs index 2aa4fd662..4536609cf 100644 --- a/crates/causal-edge/src/v2_layout_tests.rs +++ b/crates/causal-edge/src/v2_layout_tests.rs @@ -17,6 +17,9 @@ #[cfg(test)] #[cfg(feature = "causal-edge-v2-layout")] +// The half-field writers are deprecated (D-EPI-CANON-0); these tests +// pin their physical layout, so they keep calling them. +#[allow(deprecated)] mod v2_layout_tests { use crate::edge::{CausalEdge64, InferenceType}; use crate::layout::{CausalTopology, ReasoningBand, TrustTexture}; @@ -949,3 +952,72 @@ mod v2_layout_tests { } } } + +/// D-EPI-CANON-0: bits 59..63 are ONE field. The joint accessors are the +/// canonical physical read and write; the halves are its projections. +#[cfg(test)] +#[cfg(feature = "causal-edge-v2-layout")] +#[allow(deprecated)] +mod epistemic_field_tests { + use crate::edge::CausalEdge64; + use crate::layout::{EPISTEMIC_MASK, SPARE_MASK, TRUTH_MASK}; + + fn busy() -> CausalEdge64 { + // Every bit outside 59..63 set, so a leak in either direction shows. + CausalEdge64(!EPISTEMIC_MASK) + } + + /// F1 (physical half): every code round-trips through the joint pair. + #[test] + fn every_code_round_trips_and_touches_only_59_63() { + for code in 0u8..32 { + let e = busy().with_epistemic_raw5(code); + assert_eq!(e.epistemic_raw5(), code); + assert_eq!(e.0 & !EPISTEMIC_MASK, busy().0, "code {code} leaked"); + // The halves are projections of the same field. + assert_eq!(e.epistemic_raw5(), (e.spare() << 2) | e.truth_raw()); + } + } + + /// The joint writer overwrites the WHOLE field: writing a code over a + /// different one leaves no stale half behind. + #[test] + fn joint_write_replaces_both_halves() { + let e = CausalEdge64(EPISTEMIC_MASK).with_epistemic_raw5(0b00101); + assert_eq!(e.0 & TRUTH_MASK, 0b01u64 << 59); + assert_eq!(e.0 & SPARE_MASK, 0b001u64 << 61); + } + + /// F2 (physical half), Cartesian layout: the topology writer is a FACTOR + /// update — it moves bits 59..60 only and holds the certification. The + /// historical band writer puts a reasoning level (`Causal` = 3) where the + /// canonical reading sees certification 3 (`Supports`): a plausible claim + /// nobody made. That is why it is deprecated; the bits cannot refuse it, + /// only the class declaration can (contract crate). + #[test] + fn the_topology_writer_is_a_factor_update_and_the_band_writer_is_not_canonical() { + let s = CausalEdge64::ZERO.with_epistemic_raw5(5 << 2); // Direct (topology 0) × Causes + let moved = s.with_topology(crate::layout::CausalTopology::IndirectUnknownIntermediates); + assert_eq!( + moved.epistemic_raw5(), + (5 << 2) | 2, + "IndirectUnknown × Causes" + ); + assert_eq!(moved.epistemic_raw5() >> 2, s.epistemic_raw5() >> 2); + assert_eq!((moved.0 ^ s.0) & !TRUTH_MASK, 0); + let historical = + CausalEdge64::ZERO.with_reasoning_band(crate::layout::ReasoningBand::Causal); + assert_eq!(historical.epistemic_raw5() >> 2, 3, "lands on Supports"); + assert_eq!((historical.0 ^ CausalEdge64::ZERO.0) & !SPARE_MASK, 0); + } + + /// F5 (physical half): the field survives the LE image. + #[test] + fn the_field_survives_the_le_image() { + for code in 0u8..32 { + let e = busy().with_epistemic_raw5(code); + let back = CausalEdge64::from_le_bytes(e.to_le_bytes()); + assert_eq!(back.epistemic_raw5(), code); + } + } +} diff --git a/crates/cognitive-shader-driver/examples/README.md b/crates/cognitive-shader-driver/examples/README.md index fdb239b66..5aa434747 100644 --- a/crates/cognitive-shader-driver/examples/README.md +++ b/crates/cognitive-shader-driver/examples/README.md @@ -179,6 +179,11 @@ cargo run -p cognitive-shader-driver --example reasoning_band_probe ## relational_certification_probe.rs +> **D-EPI-MIG-0 (2026-10-07):** P7a's codes are the certification +> coordinate of the canonical Cartesian `EpistemicState5`; a certification is +> stamped by identity packing `(MODEL_GROUNDING = Direct, code)` into bits +> 59..63. Every contract stamps; 6 and 7 are reserved and refuse. + D-GSO-7a (P7a). Replaces the meaning of the band rungs from `reasoning_band_probe` (#1360, which stays as shipped). Under a reading declared per class, bits 61..63 hold the strongest relational statement the sealed model may assert: @@ -301,6 +306,13 @@ cargo run -p cognitive-shader-driver --features with-engine,mailbox-thoughtspace ## epistemic_reading_conflict_probe.rs +> **D-EPI-MIG-0 (2026-10-07):** converted to the conformance probe of the +> canonical Cartesian `EpistemicState5 = Topology2 × Certification3` +> (`raw5 = topology | certification << 2`): canonical == topology lens + +> P7a decode over all 32 codes (24 agree / 0 disagree / 8 reserved), plus +> F3/F5–F8 and the IndirectUnknown/Unknown × Causes frontier. +> The text below describes the #1378 state it replaced. + D-EPI-CONFLICT-0. Pins that the affordance law (`shared/affordance_law.rs`) reads bits 59..63 as its own 5-bit code without the D-ACR-7 contract (`band_reading`), and so contradicts the P7a band a class declares through diff --git a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs index 724e5a81a..d9c0959e8 100644 --- a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs +++ b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs @@ -12,13 +12,17 @@ //! //! # The readings the law declares (law generation V1 / V2) //! -//! - **EpistemicState5.** Bits 59..63 are read as ONE 5-bit code, -//! `raw5 = spare() << 2 | truth_raw()`, through the existing accessors. The -//! law maps a code to a set of semantic facts (`DIRECT`, `INDIRECT`, -//! `INTERMEDIATE_*`, `OBSERVED`, `ASSOCIATED`, `RELATED`, `SUPPORTS`, -//! `CAUSES`). Only ten codes are valid; the rest refuse. The codes are -//! deliberately not ordered by strength: `raw_a > raw_b` means nothing, and -//! "or higher" is written into each code's fact set by the law. +//! - **EpistemicState5.** Bits 59..63 are read as ONE 5-bit code. ⊘ +//! D-EPI-MIG-0: the code is the canonical Cartesian product +//! `Topology2 × Certification3`, `raw5 = topology | certification << 2` +//! (`contract::epistemic_state5`); its facts are the union of the two +//! factors' facts (`DIRECT`, `INDIRECT`, `INTERMEDIATE_*`, +//! `TOPOLOGY_UNKNOWN`; `ASSOCIATED`, `RELATED`, `SUPPORTS`, +//! `CAUSAL_CANDIDATE`, `CAUSES`). 24 codes are meaningful; 24..31 (reserved +//! certifications) refuse. `OBSERVED` is no longer a fact: observation is +//! evidence, not a coordinate. (#1370's hand-assigned ten-code codebook is +//! gone.) Raw code order is still not epistemic order: topology is not +//! ordered, so `raw_a > raw_b` across topologies means nothing. //! Bits 59..63 are read only under an asserted v2-stamped or V3-register //! provenance (the `band_reading` rule): on a v1 row they are old //! `temporal` bits, and v1 or unknown provenance refuses. @@ -46,8 +50,9 @@ //! stays the job of the certification obligations (D-GSO-7a); nothing here //! writes the edge. //! -//! The rules are compiled at build time into `[u64; 32]` (per code) and -//! `[u64; 8]` (per Pearl projection). At run time the facts disappear: +//! The rules are compiled at build time per factor (`[u64; 4]` topology, +//! `[u64; 6]` certification), the per-code `[u64; 32]` is DERIVED as their AND, +//! plus `[u64; 8]` per Pearl projection. At run time the facts disappear: //! `eligible = STATE[raw5] & PEARL[pearl3]`. //! //! # Layers kept apart @@ -59,11 +64,9 @@ //! //! # Not decided here //! -//! - The codebook, the fact vocabulary and the recipe set are probe pins. -//! - Bits 59..63 have shipped split readings (`truth`/`topology` on 59..60, -//! `ReasoningBand` on 61..63, read separately by `band_reading`). A joint -//! 5-bit reading needs its own declaration there before it can be relied on; -//! this probe declares it locally. +//! - The recipe set is a probe pin. ⊘ D-EPI-MIG-0: the state layout and fact +//! vocabulary are now the contract's (`epistemic_state5`), declared per +//! class; this probe's edges belong to `AFF_CLASS`. //! - Dynamic gates, Shannon preference and executing a recipe are out of scope. //! //! Run: `cargo run -p cognitive-shader-driver --example affordance_measurement_probe` @@ -115,17 +118,20 @@ fn counting(f: impl FnOnce() -> T) -> (T, usize) { mod affordance_law; use affordance_law::*; -const FACT_NAMES: [&str; 10] = [ +/// Fact names by bit position (bit 5, formerly `OBSERVED`, is unused). +const FACT_NAMES: [&str; 12] = [ "DIRECT", "INDIRECT", "INTERMEDIATE_PRESENT", "INTERMEDIATE_UNKNOWN", "INTERMEDIATE_KNOWN", - "OBSERVED", + "-", "ASSOCIATED", "RELATED", "SUPPORTS", "CAUSES", + "TOPOLOGY_UNKNOWN", + "CAUSAL_CANDIDATE", ]; const RECIPE_NAMES: [&str; 7] = [ @@ -201,11 +207,10 @@ fn explain_stamped(law: LawGen, edge: CausalEdge64, recipe: u8) -> Verdict { explain(law, edge, EdgeProvenance::V2Stamped, recipe) } -/// Stamp an EpistemicState5 code through the shipped writers. +/// Stamp a raw 5-bit code through the canonical joint writer. Raw on +/// purpose: fixtures also stamp undeclared codes to prove they refuse. fn with_code(edge: CausalEdge64, code: u8) -> CausalEdge64 { - use causal_edge::layout::TrustTexture; - edge.with_spare(code >> 2) - .with_truth(TrustTexture::from_bits_2(code & 0b11)) + edge.with_epistemic_raw5(code) } /// An edge with every irrelevant field non-zero. @@ -225,7 +230,7 @@ fn busy_edge(pearl: CausalMask) -> CausalEdge64 { } fn fact_names(mask: u32) -> String { - (0..10) + (0..12) .filter(|i| mask & (1 << i) != 0) .map(|i| FACT_NAMES[i]) .collect::>() @@ -251,8 +256,8 @@ fn main() { ); } let unreadable = (0u8..32).filter(|c| EPI_LAW[*c as usize].is_none()).count(); - println!(" {unreadable} codes refuse (not declared by the law)"); - let edge = with_code(busy_edge(CausalMask::SO), 25); + println!(" {unreadable} codes refuse (reserved certifications 6, 7)"); + let edge = with_code(busy_edge(CausalMask::SO), 10); let (e, allocs) = counting(|| stamped(LawGen::V1, edge)); let eligible = e.unwrap_or(0); println!( @@ -273,7 +278,7 @@ fn main() { " preference {cheap:#04x} -> {preferred:#04x}, first recipe {:?} (COUNTERFACTUAL_PROBE stays illegal)", select(preferred).map(|r| RECIPE_NAMES[r as usize]) ); - let causes = with_code(busy_edge(CausalMask::SPO), 17); + let causes = with_code(busy_edge(CausalMask::SPO), 21); println!( "Causes code under V1 {:#04x}, under V2 {:#04x} (V2 forbids STRATIFY once CAUSES holds)", stamped(LawGen::V1, causes).unwrap(), @@ -325,15 +330,15 @@ mod tests { /// F2: changing a relevant factor flips the affected bit. #[test] fn f2_a_relevant_factor_flips_the_affected_bit() { - let unknown = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SO), 25)).unwrap(); - let known = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SO), 5)).unwrap(); + let unknown = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SO), 10)).unwrap(); + let known = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SO), 9)).unwrap(); assert_ne!(unknown & 1 << HYDRATE_INTERMEDIATE, 0); assert_eq!(unknown & 1 << MECHANISM_FOLD, 0); assert_eq!(known & 1 << HYDRATE_INTERMEDIATE, 0); assert_ne!(known & 1 << MECHANISM_FOLD, 0); - let causes_so = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SO), 17)).unwrap(); - let causes_spo = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SPO), 17)).unwrap(); + let causes_so = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SO), 21)).unwrap(); + let causes_spo = stamped(LawGen::V1, with_code(busy_edge(CausalMask::SPO), 21)).unwrap(); assert_eq!(causes_so & 1 << COUNTERFACTUAL_PROBE, 0); assert_ne!(causes_spo & 1 << COUNTERFACTUAL_PROBE, 0); assert_eq!( @@ -469,25 +474,31 @@ mod tests { } } - /// The raw code is not a strength: code 1 (`Causes`) carries strictly more - /// facts than code 20 (`Related`), so comparing codes would admit wrongly. + /// The raw code is not a strength: topology is not ordered, so a larger + /// code can lack what a smaller one licenses. `MECHANISM_FOLD` is legal at + /// 5 (`IndirectKnown × Associated`) and refused at 7 (`Unknown × + /// Associated`), although 7 > 5. Within one topology the certification + /// chain IS cumulative (20 `Causes` ⊇ 8 `Related`). #[test] fn raw_code_order_is_not_epistemic_order() { - let (causes, related) = (EPI_LAW[1].unwrap(), EPI_LAW[20].unwrap()); + let (causes, related) = (EPI_LAW[20].unwrap(), EPI_LAW[8].unwrap()); assert_eq!(causes & related, related); assert_ne!(causes, related); - // `ROBUSTNESS_TEST` is legal at code 1 (Causes) and refused at code 12 - // (Associated only), although 12 > 1. let at = |c: u8| stamped(LawGen::V1, with_code(busy_edge(CausalMask::SPO), c)).unwrap(); - assert_ne!(at(1) & 1 << ROBUSTNESS_TEST, 0); - assert_eq!(at(12) & 1 << ROBUSTNESS_TEST, 0); + assert_ne!(at(5) & 1 << MECHANISM_FOLD, 0); + assert_eq!(at(7) & 1 << MECHANISM_FOLD, 0); } - /// Undeclared codes refuse instead of reading as a weak state, including a - /// code produced by a writer that touches only half of bits 59..63. + /// Reserved codes refuse instead of reading as a weak state. A topology + /// write is a FACTOR update under the Cartesian layout: from 21 + /// (`IndirectKnown × Causes`) it yields 22 (`IndirectUnknown × Causes`), a + /// meaningful state, and only the topology recipes move. #[test] - fn undeclared_codes_refuse_including_half_field_writes() { - for code in (0u8..32).filter(|c| EPI_LAW[*c as usize].is_none()) { + fn reserved_codes_refuse_and_a_topology_write_is_a_factor_update() { + let reserved = !lance_graph_contract::epistemic_state5::facts_population(0); + assert_eq!(reserved, 0xFF00_0000, "reserved = certifications 6 and 7"); + for code in (0u8..32).filter(|c| reserved >> c & 1 == 1) { + assert!(EPI_LAW[code as usize].is_none()); let e = with_code(busy_edge(CausalMask::SPO), code); assert_eq!(stamped(LawGen::V1, e), Err(Refusal::Undeclared(code))); assert_eq!( @@ -495,23 +506,26 @@ mod tests { Verdict::Unreadable ); } - // 17 = 0b100_01; rewriting only bits 59..60 through the shipped - // topology writer leaves bits 61..63 alone and yields 0b100_10 = 18. - let causes = with_code(busy_edge(CausalMask::SPO), 17); - let half = causes.with_topology(causal_edge::layout::CausalTopology::from_bits_2(0b10)); - assert_eq!(raw5(half), 18); - assert_eq!(stamped(LawGen::V1, half), Err(Refusal::Undeclared(18))); + let causes = with_code(busy_edge(CausalMask::SPO), 21); + let moved = causes.with_topology(causal_edge::layout::CausalTopology::from_bits_2(0b10)); + assert_eq!(raw5(moved), 22); + let (a, b) = ( + stamped(LawGen::V1, causes).unwrap(), + stamped(LawGen::V1, moved).unwrap(), + ); + assert_eq!(a ^ b, 1 << HYDRATE_INTERMEDIATE | 1 << MECHANISM_FOLD); } /// Bits 59..63 are only read under an asserted v2/V3 provenance. A v1 row - /// whose old `temporal` was 128 has bit 59 set, which would read as code 1 - /// (`Causes`) and make `COUNTERFACTUAL_PROBE` eligible; it refuses instead. + /// whose old `temporal` was 2560 has bits 61 and 63 set, which would read + /// as code 20 (`Direct × Causes`) and make `COUNTERFACTUAL_PROBE` + /// eligible; it refuses instead. #[test] fn v1_and_unknown_provenance_refuse() { - let v1_row = CausalEdge64((128u64 << 52) | (CausalMask::SPO as u64) << 40); + let v1_row = CausalEdge64((2560u64 << 52) | (CausalMask::SPO as u64) << 40); assert_eq!( raw5(v1_row), - 1, + 20, "the old temporal bit lands on a declared code" ); for prov in [EdgeProvenance::V1Legacy, EdgeProvenance::Unknown] { @@ -535,8 +549,8 @@ mod tests { /// projection. The output is a `u64`; there is no other object. #[test] fn f9_the_measurement_allocates_nothing() { - let edges: [CausalEdge64; 80] = { - let mut a = [CausalEdge64::ZERO; 80]; + let edges: [CausalEdge64; 192] = { + let mut a = [CausalEdge64::ZERO; 192]; let mut i = 0; for code in valid_codes() { for p in ALL_PEARL { @@ -544,7 +558,7 @@ mod tests { i += 1; } } - assert_eq!(i, 80); + assert_eq!(i, 192); a }; let (acc, allocs) = counting(|| { diff --git a/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs b/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs index 0ab6fbadb..eff3d3d2c 100644 --- a/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs @@ -36,8 +36,8 @@ //! //! | from | to | when (after the cycle's folds) | //! |---|---|---| -//! | 3 `DIRECT·OBSERVED` | 7 `+ASSOCIATED` | ≥ 2 distinct `DirectlyObserved` sources this cycle, `c ≥ 200`, `f ≥ 200` | -//! | 7 | 3 | `f < 180` (contradiction revised the carried evidence down) | +//! | 0 `Direct × Open` | 4 `Direct × Associated` | ≥ 2 distinct `DirectlyObserved` sources this cycle, `c ≥ 200`, `f ≥ 200` | +//! | 4 | 0 | `f < 180` (contradiction revised the carried evidence down) | //! //! Accumulation and certification stay separate: any number of folds from //! one source moves F/C but never the code. Thresholds are policy pins. @@ -61,7 +61,6 @@ mod affordance_law; use affordance_law::{measure, raw5, LawGen}; use causal_edge::edge::CausalEdge64; -use causal_edge::layout::TrustTexture; use causal_edge::pearl::CausalMask; use causal_edge::PlasticityState; use cognitive_shader_driver::mailbox_soa::{MailboxSoA, WriteCell, WriteOutcome}; @@ -76,8 +75,11 @@ use lance_graph_contract::soa_view::MailboxSoaView; const ROW: usize = 0; /// EpistemicState5 codes of the AFF-0 law used here. -const CODE_OBSERVED: u8 = 3; -const CODE_ASSOCIATED: u8 = 7; +/// `Direct × Open` (was code 3 `Direct × Observed` under the #1370 +/// probe-local codebook; observation is evidence, not a coordinate). +const CODE_OPEN: u8 = 0; +/// `Direct × Associated` (was code 7). +const CODE_ASSOCIATED: u8 = 4; /// Policy pins of the probe-declared transition. const C_MIN: u8 = 200; @@ -106,10 +108,9 @@ const fn fold(source: u64, f: u8) -> Fold { } } -/// Stamp an EpistemicState5 code through the shipped writers (bits 59..63). +/// Write a declared V1 EpistemicState5 code (bits 59..63, jointly). fn with_code(edge: CausalEdge64, code: u8) -> CausalEdge64 { - edge.with_spare(code >> 2) - .with_truth(TrustTexture::from_bits_2(code & 0b11)) + affordance_law::write_code(edge, code) } /// The tracked relation as an observation carrying `(f, c)`. S/P/O match the @@ -118,11 +119,11 @@ fn observation(f: u8, c: u8) -> CausalEdge64 { CausalEdge64::pack_v2(1, 2, 3, f, c, CausalMask::SO, 0, PlasticityState::ALL_HOT) } -/// The register before any evidence: code `OBSERVED`, no confidence. +/// The register before any evidence: code `Direct × Open`, no confidence. fn initial() -> CausalEdge64 { with_code( CausalEdge64::pack_v2(1, 2, 3, 128, 0, CausalMask::SO, 0, PlasticityState::ALL_HOT), - CODE_OBSERVED, + CODE_OPEN, ) } @@ -142,12 +143,10 @@ fn cycle(register: CausalEdge64, folds: &[Fold]) -> CausalEdge64 { } let sources = ledger.distinct_sources_for(SupportBasis::DirectlyObserved); let code = match raw5(reg) { - CODE_OBSERVED - if sources >= 2 && reg.confidence_u8() >= C_MIN && reg.frequency_u8() >= F_HI => - { + CODE_OPEN if sources >= 2 && reg.confidence_u8() >= C_MIN && reg.frequency_u8() >= F_HI => { CODE_ASSOCIATED } - CODE_ASSOCIATED if reg.frequency_u8() < F_LO => CODE_OBSERVED, + CODE_ASSOCIATED if reg.frequency_u8() < F_LO => CODE_OPEN, other => other, }; with_code(reg, code) @@ -259,10 +258,10 @@ mod tests { assert_eq!(elig, vec![OBS, OBS, OBS | STRAT, OBS]); // Cycle 0 moved F/C without moving the code. assert!(regs[1].confidence_u8() > regs[0].confidence_u8()); - assert_eq!(raw5(regs[1]), CODE_OBSERVED); + assert_eq!(raw5(regs[1]), CODE_OPEN); // Cycle 1 certified association; cycle 2 revised it away. assert_eq!(raw5(regs[2]), CODE_ASSOCIATED); - assert_eq!(raw5(regs[3]), CODE_OBSERVED); + assert_eq!(raw5(regs[3]), CODE_OPEN); assert!(regs[3].frequency_u8() < regs[2].frequency_u8()); } @@ -361,7 +360,7 @@ mod tests { let many: Vec = (0..1000).map(|_| fold(1, 230)).collect(); let reg = cycle(initial(), &many); assert!(reg.confidence_u8() >= C_MIN && reg.frequency_u8() >= F_HI); - assert_eq!(raw5(reg), CODE_OBSERVED); + assert_eq!(raw5(reg), CODE_OPEN); assert_eq!(eligible(reg), OBS); } diff --git a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs index 5848d49e6..69cf806e7 100644 --- a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs @@ -35,7 +35,10 @@ //! `bzhi rax, rdi, 59; shl rsi, 59; or rax, rsi; ret`, i.e. clear bits //! 59..63 and insert. `via_mask` compiled to the identical body and LLVM //! folded the two into one symbol. The shipped writers do not force a -//! decode/repack, so this probe adds no production writer for the 5-bit +//! decode/repack. ⊘ D-EPI-CANON-0 (2026-10-07) DID add the joint writer +//! `with_epistemic_raw5` — not for codegen, but so no producer writes half the +//! field; N6 pins it equal to `via_mask`. Original note: this probe adds no +//! production writer for the 5-bit //! field: it would be a fourth name over bits that already have four lenses //! (`truth`, `topology`, `spare`, `reasoning_band`) and would not change the //! machine code. @@ -48,24 +51,26 @@ mod affordance_law; use affordance_law::{measure, raw5, LawGen}; use causal_edge::edge::CausalEdge64; -use causal_edge::layout::{TrustTexture, SPARE_MASK, TRUTH_MASK, TRUTH_SHIFT}; +use causal_edge::layout::{TrustTexture, EPISTEMIC_MASK, EPISTEMIC_SHIFT}; use causal_edge::pearl::CausalMask; use causal_edge::PlasticityState; use cognitive_shader_driver::mailbox_soa::{MailboxSoA, WriteCell, WriteOutcome}; use lance_graph_contract::band_reading::EdgeProvenance; use lance_graph_contract::soa_view::MailboxSoaView; -/// Bits 59..63. -const EPISTEMIC_MASK: u64 = TRUTH_MASK | SPARE_MASK; - /// The register row. const ROW: usize = 0; -const CODE_OBSERVED: u8 = 3; -const CODE_ASSOCIATED: u8 = 7; +/// `Direct × Open` (was code 3 `Direct × Observed` under the #1370 +/// probe-local codebook; observation is evidence, not a coordinate). +const CODE_OPEN: u8 = 0; +/// `Direct × Associated` (was code 7). +const CODE_ASSOCIATED: u8 = 4; -/// The existing field-update path: two shipped writers, one per sub-field. +/// The historical field-update path: two half writers, one per sub-field. +/// Deprecated since D-EPI-CANON-0; kept as the measured comparison arm. #[inline(never)] +#[allow(deprecated)] fn via_two_writers(edge: CausalEdge64, code: u8) -> CausalEdge64 { edge.with_spare(code >> 2) .with_truth(TrustTexture::from_bits_2(code & 0b11)) @@ -74,7 +79,7 @@ fn via_two_writers(edge: CausalEdge64, code: u8) -> CausalEdge64 { /// The minimal sparse update: one mask and one insert on the `u64`. #[inline(never)] fn via_mask(edge: CausalEdge64, code: u8) -> CausalEdge64 { - CausalEdge64((edge.0 & !EPISTEMIC_MASK) | ((u64::from(code) & 0x1F) << TRUTH_SHIFT)) + CausalEdge64((edge.0 & !EPISTEMIC_MASK) | ((u64::from(code) & 0x1F) << EPISTEMIC_SHIFT)) } /// An edge with every field non-zero, including bits 59..63. @@ -91,8 +96,7 @@ fn busy() -> CausalEdge64 { ) .with_inference_mantissa(-6) .with_w_slot(41) - .with_spare(0b101) - .with_truth(TrustTexture::from_bits_2(0b10)) + .with_epistemic_raw5((0b101 << 2) | 0b10) } /// The register read through the production view; `None` outside the @@ -142,7 +146,7 @@ fn restore(bytes: [u8; 8]) -> CausalEdge64 { } fn main() { - let start = via_mask(busy(), CODE_OBSERVED); + let start = via_mask(busy(), CODE_OPEN); let mut mb = mailbox(start); let now = committed(&mb).expect("seed committed"); let next = via_mask(now, CODE_ASSOCIATED); @@ -170,10 +174,7 @@ fn main() { assert_eq!(restore(persist(k1)), k1); // Both update paths, kept out of the optimiser's reach so their code can // be compared (`--emit asm`). - let (e, c) = ( - std::hint::black_box(k1), - std::hint::black_box(CODE_OBSERVED), - ); + let (e, c) = (std::hint::black_box(k1), std::hint::black_box(CODE_OPEN)); println!( " two writers == mask: {}", via_two_writers(e, c) == via_mask(e, c) @@ -208,7 +209,7 @@ mod tests { /// N2: written in cycle k through the real seam, read in cycle k+1. #[test] fn n2_the_next_cycle_reads_the_certified_state() { - let mut mb = mailbox(via_mask(busy(), CODE_OBSERVED)); + let mut mb = mailbox(via_mask(busy(), CODE_OPEN)); let now = committed(&mb).unwrap(); assert_eq!(eligible(now), OBS); let next = via_mask(now, CODE_ASSOCIATED); @@ -228,7 +229,7 @@ mod tests { /// copy it read at cycle start. #[test] fn n3_same_cycle_authority_is_a_read_discipline() { - let mut mb = mailbox(via_mask(busy(), CODE_OBSERVED)); + let mut mb = mailbox(via_mask(busy(), CODE_OPEN)); let at_start = committed(&mb).unwrap(); let eligible_k = eligible(at_start); write(&mut mb, via_mask(at_start, CODE_ASSOCIATED)); @@ -260,7 +261,10 @@ mod tests { let bytes = persist(CausalEdge64(w)); assert_eq!(restore(bytes).0, w, "{w:016x}"); assert_eq!(bytes[0], (w & 0xFF) as u8, "byte 0 is the low byte"); - assert_eq!(bytes[7] >> 3, ((w & EPISTEMIC_MASK) >> TRUTH_SHIFT) as u8); + assert_eq!( + bytes[7] >> 3, + ((w & EPISTEMIC_MASK) >> EPISTEMIC_SHIFT) as u8 + ); } } @@ -268,7 +272,7 @@ mod tests { /// exactly like the original; no evidence is replayed. #[test] fn n5_restart_from_the_persisted_word() { - let mut mb = mailbox(via_mask(busy(), CODE_OBSERVED)); + let mut mb = mailbox(via_mask(busy(), CODE_OPEN)); let now = committed(&mb).unwrap(); write(&mut mb, via_mask(now, CODE_ASSOCIATED)); mb.tick(); @@ -299,10 +303,14 @@ mod tests { "{:016x} {code}", e.0 ); + // The canonical joint writer (D-EPI-CANON-0) is the same + // masked insert. + assert_eq!(e.with_epistemic_raw5(code), via_mask(e, code)); } } - // Every declared code of the #1370 law is reachable this way. + // Every meaningful code of the Cartesian V1 product is reachable + // this way (4 topologies × 6 certifications). let declared = (0u8..32).filter(|c| EPI_LAW[*c as usize].is_some()).count(); - assert_eq!(declared, 10); + assert_eq!(declared, 24); } } diff --git a/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs b/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs index bb37a3d4d..e9d705b91 100644 --- a/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs +++ b/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs @@ -18,9 +18,10 @@ //! What it reuses, and what it adds: //! //! - **Bits 59..60:** `CausalEdge64::with_topology` / `truth_raw`, unchanged. -//! - **Reading contract:** `band_reading::BandDeclarations::project_truth` -//! (declared lens per `(classid, rail)`, asserted provenance). Projection is -//! fallible; a refusal produces no cell. +//! - **Reading contract:** `epistemic_state5::Epi5Declarations::project_state5` +//! (declared reading per `(classid, rail)`, asserted provenance). Projection +//! is fallible; a refusal produces no cell. (Was `band_reading`'s topology +//! lens before D-EPI-MIG-0.) //! - **Cell:** `settlement::SettlementSignals::cell`. Closure × competence //! decide; entropy and eigenvalue concentration are carried but do not. //! - **New (the D-ECG-2 census):** the share of a basin's edges whose @@ -40,13 +41,21 @@ //! //! Run: `cargo run -p cognitive-shader-driver --example entropy_topology_probe` //! Tests: `cargo test -p cognitive-shader-driver --example entropy_topology_probe` +//! +//! D-EPI-MIG-0 (2026-10-07): migrated to the canonical Cartesian reading of +//! bits 59..63. The fixtures write only the topology coordinate (certification +//! `Open`), so each edge is the state `topology × Open`, projected through +//! `contract::epistemic_state5::Epi5Declarations::project_state5`; the census +//! reads `state.topology()`. Nothing is reinterpreted: `CausalTopology` and +//! `Topology2` are the same ordinals. use causal_edge::edge::CausalEdge64; use causal_edge::layout::CausalTopology; -use lance_graph_contract::band_reading::{ - BandDeclarations, BandReadError, BandReading, EdgeProvenance, TruthLens, -}; +use lance_graph_contract::band_reading::EdgeProvenance; use lance_graph_contract::class_view::ClassId; +use lance_graph_contract::epistemic_state5::{ + Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, Topology2, +}; use lance_graph_contract::rail_geometry::RailAxis; use lance_graph_contract::settlement::{SettlementCell, SettlementScope, SettlementSignals}; @@ -88,20 +97,20 @@ impl Census { /// Count each edge's projected topology. Refuses as a whole if any edge /// cannot be projected under the declaration and provenance. -fn census(decl: &BandDeclarations, basin: &Basin) -> Result { +fn census(decl: &Epi5Declarations, basin: &Basin) -> Result { let mut c = Census::default(); for &(_, edge) in &basin.edges { - let raw = decl.project_truth( + let state = decl.project_state5( CLASS, RAIL, - TruthLens::Topology, - edge.truth_raw(), + Epi5Gen::V1, + edge.epistemic_raw5(), basin.provenance, )?; - match CausalTopology::from_bits_2(raw) { - CausalTopology::Direct | CausalTopology::IndirectKnownIntermediates => c.known += 1, - CausalTopology::IndirectUnknownIntermediates => c.projected += 1, - CausalTopology::Unknown => c.hole += 1, + match state.topology() { + Topology2::Direct | Topology2::IndirectKnown => c.known += 1, + Topology2::IndirectUnknown => c.projected += 1, + Topology2::Unknown => c.hole += 1, } } Ok(c) @@ -147,7 +156,7 @@ fn route(cell: SettlementCell) -> Route { } /// Census → signals. An error when the census refuses; no cell is produced. -fn signals(decl: &BandDeclarations, basin: &Basin) -> Result { +fn signals(decl: &Epi5Declarations, basin: &Basin) -> Result { let c = census(decl, basin)?; Ok(SettlementSignals { scope: SettlementScope { @@ -164,14 +173,13 @@ fn signals(decl: &BandDeclarations, basin: &Basin) -> Result BandDeclarations { - let mut d = BandDeclarations::new(); +fn declarations() -> Epi5Declarations { + let mut d = Epi5Declarations::new(); d.declare( CLASS, RAIL, - BandReading { - truth_lens: TruthLens::Topology, - ..BandReading::ZERO_FALLBACK + Epi5Reading { + generation: Epi5Gen::V1, }, ); d @@ -313,8 +321,8 @@ mod tests { } /// F-ECG-2. FAILS IF: a cell emerges from bits that cannot be read: unknown - /// or v1 provenance, a class declared under the trust lens, or a class - /// that was never declared. + /// or v1 provenance, a class with no canonical declaration (one declared + /// only under the legacy trust lens has none), or a reserved certification. #[test] fn unreadable_ground_produces_no_cell() { let decl = declarations(); @@ -323,20 +331,21 @@ mod tests { b.provenance = p; assert_eq!( signals(&decl, &b).unwrap_err(), - BandReadError::UnknownProvenance + Epi5ReadError::UnknownProvenance(p) ); } - let mut trust = BandDeclarations::new(); - trust.declare(CLASS, RAIL, BandReading::ZERO_FALLBACK); - assert!(matches!( - signals(&trust, &direct_dominant(0.9)), - Err(BandReadError::LensMismatch { .. }) - )); + assert_eq!( + signals(&Epi5Declarations::new(), &direct_dominant(0.9)).unwrap_err(), + Epi5ReadError::UndeclaredClass(CLASS) + ); + // A reserved certification over a readable topology refuses. + let mut reserved = direct_dominant(0.9); + reserved.edges[0].1 = reserved.edges[0].1.with_epistemic_raw5(6 << 2); assert_eq!( - signals(&BandDeclarations::new(), &direct_dominant(0.9)).unwrap_err(), - BandReadError::UndeclaredClass(CLASS) + signals(&decl, &reserved).unwrap_err(), + Epi5ReadError::UndeclaredCode(6 << 2) ); let mut asserted = direct_dominant(0.9); diff --git a/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs b/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs index 66dd46548..488d93df0 100644 --- a/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs +++ b/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs @@ -1,61 +1,33 @@ -//! D-EPI-CONFLICT-0: the affordance law reads `CausalEdge64` bits 59..63 -//! without the unified 59..63 contract, and disagrees with the reading a -//! class declares through it. This probe pins the disagreement; it does not -//! resolve it. +//! D-EPI-CONFLICT-0 → D-EPI-MIG-0: the conformance probe of the canonical +//! Cartesian `EpistemicState5` reading of `CausalEdge64` bits 59..63. //! -//! Issue: `ISS-EPISTEMIC-READINGS-DISAGREE-ON-BITS-61-63` in -//! `.claude/board/ISSUES.md`. +//! # What it pinned (#1378, kept as history) //! -//! # The unified contract +//! P7a (#1369) wrote its certification into bits 61..63 and read it through +//! `band_reading::project_band`; the affordance law (#1370) read bits 59..63 +//! as one code under a hand-assigned, deliberately unordered codebook. A +//! P7a-certified `Causes` over topology 0 was raw code 20, which that codebook +//! read as `Direct × Related`; 3 of 8 shared cells agreed. //! -//! `lance_graph_contract::band_reading` (D-ACR-7) is the one 59..63 reading -//! contract: per `(classid, rail)` a class declares which lens its producers -//! wrote into bits 59..60 (`TruthLens`) and whether bits 61..63 carry a band -//! (`BandPresence`). Projection is fallible: undeclared class, band absent and -//! untrusted provenance refuse. It declares two split fields; it has no joint -//! 5-bit lens. +//! # What holds now (operator decision 2026-10-07) //! -//! # The two readings -//! -//! - **P7a** (`relational_certification_probe.rs`, D-GSO-7a, #1369) writes a -//! relational contract into bits 61..63 through `with_reasoning_band`: -//! `0 Open, 1 Associated, 2 Related, 3 Contributes, 4 CausalCandidate, -//! 5 Causes`, 6 and 7 refuse. Comparison is ordinal on 0..=5 (`entails`). -//! It reads the band through `BandDeclarations::project_band` with the class -//! declared `BandPresence::Present`: it goes through the contract. -//! - **The affordance law** (`shared/affordance_law.rs`, D-GSO-AFF-0, #1370; -//! read by four probes) reads bits 59..63 as ONE 5-bit EpistemicState5 code, -//! `raw5 = spare() << 2 | truth_raw()`. Bits 61..63 are `spare()`, the same -//! three bits P7a writes, so `raw5 >> 2` IS the P7a code. The law's codebook -//! is deliberately not ordered and gives those bits other meanings. It takes -//! no class and never consults the contract: it checks provenance only. -//! -//! The affordance probe says so itself ("a joint 5-bit reading needs its own -//! declaration there before it can be relied on; this probe declares it -//! locally"). That declaration does not exist, and -//! `ISS-NO-EVIDENCE-WRITER-FOR-EPISTEMIC-STATE` cannot be closed with P7a as -//! the writer until the two agree. -//! -//! # What is pinned +//! `EpistemicState5 = Topology2 × Certification3`, `raw5 = topology | +//! certification << 2`, with the ordinals already on the wire +//! (`CausalTopology` and P7a's codes). The #1370 codebook was the only thing +//! out of place; it is gone. So the canonical projection and the two old +//! split readings now agree by construction, and this probe proves it: //! //! | test | pin | //! |---|---| -//! | `the_affordance_law_reads_bits_the_contract_refuses` | an undeclared class and a band-`Absent` class refuse under the contract; the affordance law measures both | -//! | `a_certified_causes_never_reads_as_causes` | the contract projects `Causes` (5); the affordance law reads `Related` (topology 0) or refuses (1..3); `COUNTERFACTUAL_PROBE` is never eligible | -//! | `the_affordance_causes_codes_are_not_p7a_causes` | the two codes that assert `CAUSES` sit on P7a `Open` and `CausalCandidate` | -//! | `affordance_codes_occupy_p7a_reserved_bands` | two declared codes sit on P7a's refused bands 6 and 7 | -//! | `the_readings_agree_on_three_of_eight_shared_cells` | exhaustive count over every P7a contract × topology | -//! -//! Both readings are exercised from shared code, never copied: -//! `shared/affordance_law.rs` and `shared/certification_reading.rs` (P7a's -//! classes, declarations, contract table, `entails` and `stamp`). Each test is -//! a pin of the current state: it FAILS when either codebook changes. A reconciliation should turn these into agreement tests and close -//! the issue, not edit the numbers to match. -//! -//! The agreement test compares only the three facts that share a name with a -//! P7a contract (`ASSOCIATED`, `RELATED`, `CAUSES`). P7a's `Contributes` and -//! `CausalCandidate` have no same-named fact, and `SUPPORTS` is not assumed to -//! mean `Contributes`. +//! | `the_canonical_reading_is_the_two_old_lenses_read_together` | over every topology × 3-bit certification: canonical == `topology()` lens + P7a decode — 24 agree, 0 disagree, 8 reserved refuse | +//! | `undeclared_classes_refuse` | the law never measures a class without a canonical declaration | +//! | `a_certified_causes_reads_as_causes_under_every_topology` | F3: 20..23, `CAUSES` asserted, `COUNTERFACTUAL_PROBE` eligible | +//! | `the_1378_fixture_now_agrees` | P7a `Causes` over topology 0 is code 20 = `Direct × Causes` to both readers | +//! | `the_derived_table_equals_the_rules` | the factor-compiled table equals per-code rule evaluation (F6) | +//! | `one_factor_moves_only_its_recipes` | F7 + the frontier: 10↔9, 9↔13, 22, 23 | +//! | `a_legacy_reading_class_is_not_consumed_as_canonical` | F2: a historical-band class refuses even though its bits decode | +//! | `the_state_survives_restart_from_le_bytes` | F5 | +//! | `projections_never_change_the_edge` | F8 | //! //! Run: `cargo run -p cognitive-shader-driver --example epistemic_reading_conflict_probe` //! Tests: `cargo test -p cognitive-shader-driver --example epistemic_reading_conflict_probe` @@ -65,36 +37,27 @@ mod affordance_law; #[path = "shared/certification_reading.rs"] mod certification_reading; -use affordance_law::{raw5, ASSOCIATED, CAUSES, EPI_LAW, RELATED}; +use affordance_law::raw5; use causal_edge::edge::CausalEdge64; -use causal_edge::layout::TrustTexture; use causal_edge::pearl::CausalMask; use causal_edge::PlasticityState; -use certification_reading::{declarations, read, Contract, CERT_CLASS, RAIL}; -use lance_graph_contract::band_reading::{BandReadError, BandReading, EdgeProvenance}; -use lance_graph_contract::class_view::ClassId; - -/// A class declared band-free: its bits 61..63 are spare. -const SPARE_CLASS: ClassId = 0x0903; -/// A class never declared. -const UNDECLARED_CLASS: ClassId = 0x0904; - -/// P7a's contract as P7a's own reader returns it. -fn p7a_reads(edge: CausalEdge64) -> Result { - read(&declarations(), CERT_CLASS, edge, EdgeProvenance::V2Stamped) -} +use certification_reading::{Contract, CERT_CLASS, RAIL}; +use lance_graph_contract::band_reading::EdgeProvenance; +use lance_graph_contract::epistemic_state5::{ + Epi5Declarations, Epi5Gen, Epi5Reading, EpistemicState5, Topology2, +}; -/// The band as the unified contract projects it for `class`: P7a's -/// declarations plus one band-free class. -fn contract_band(class: ClassId, edge: CausalEdge64) -> Result { - let mut d = declarations(); - d.declare(SPARE_CLASS, RAIL, BandReading::ZERO_FALLBACK); - d.project_band( - class, +/// The declarations both readers share: P7a's class, declared canonical. +fn decl() -> Epi5Declarations { + let mut d = Epi5Declarations::new(); + d.declare( + CERT_CLASS, RAIL, - edge.reasoning_band().to_bits_3(), - EdgeProvenance::V2Stamped, - ) + Epi5Reading { + generation: Epi5Gen::V1, + }, + ); + d } /// An edge with all three Pearl planes, so the plane rule never hides a @@ -112,44 +75,43 @@ fn base() -> CausalEdge64 { ) } -/// What P7a writes (its own `stamp`) plus a topology in bits 59..60. -fn stamp(contract: Contract, topology: u8) -> CausalEdge64 { - certification_reading::stamp(base(), contract).with_truth(TrustTexture::from_bits_2(topology)) +/// The canonical projection of an edge of P7a's class. +fn canonical(edge: CausalEdge64) -> Option { + decl() + .project_state5( + CERT_CLASS, + RAIL, + Epi5Gen::V1, + raw5(edge), + EdgeProvenance::V2Stamped, + ) + .ok() } -/// The facts P7a's contract entails (its own `entails` table), in the -/// affordance vocabulary, for the three facts that share a name with a -/// contract. -fn p7a_entails(contract: Contract) -> u32 { - let mut f = 0; - if contract.entails(Contract::Associated) { - f |= ASSOCIATED; - } - if contract.entails(Contract::Related) { - f |= RELATED; - } - if contract.entails(Contract::Causes) { - f |= CAUSES; - } - f +/// The two OLD split readings, independent of the contract: the edge crate's +/// `CausalTopology` lens on 59..60 and P7a's own decode of 61..63. +fn split(edge: CausalEdge64) -> Option<(u8, Contract)> { + let topology = edge.topology().to_bits_2(); + Contract::from_code(edge.spare()) + .ok() + .map(|c| (topology, c)) } -const SHARED: u32 = ASSOCIATED | RELATED | CAUSES; - -/// The affordance reading of an edge: its facts, or `None` if it refuses. -fn affordance_facts(edge: CausalEdge64) -> Option { - EPI_LAW[raw5(edge) as usize] -} - -/// (agree, disagree, refused) over every P7a contract × topology. +/// (agree, disagree, refused) over every topology × 3-bit certification. fn census() -> (usize, usize, usize) { let (mut agree, mut disagree, mut refused) = (0, 0, 0); - for c in Contract::ALL { - for t in 0..4u8 { - match affordance_facts(stamp(c, t)) { - None => refused += 1, - Some(f) if f & SHARED == p7a_entails(c) => agree += 1, - Some(_) => disagree += 1, + for t in 0..4u8 { + for c in 0..8u8 { + let e = base().with_epistemic_raw5(t | c << 2); + match (canonical(e), split(e)) { + (Some(s), Some((ot, oc))) + if s.topology().ordinal() == ot + && Contract::from_certification(s.certification()) == oc => + { + agree += 1 + } + (None, None) => refused += 1, + _ => disagree += 1, } } } @@ -157,130 +119,237 @@ fn census() -> (usize, usize, usize) { } fn main() { - println!( - "D-EPI-CONFLICT-0: P7a contract (bits 61..63) x topology (59..60) under the affordance law" - ); - for c in Contract::ALL { - for t in 0..4u8 { - let e = stamp(c, t); - let shown = match affordance_facts(e) { - None => "refuses".to_string(), - Some(f) => format!( - "facts {:#05x}{}", - f, - if f & SHARED == p7a_entails(c) { - "" - } else { - " <- disagrees" - } - ), - }; - let declared = p7a_reads(e).expect("P7a reads its own stamp"); - println!( - " P7a {:<16} topo {t} raw5 {:>2} {shown}", - format!("{declared:?}"), - raw5(e) - ); - } + println!("D-EPI-MIG-0: EpistemicState5 = Topology2 × Certification3 (raw5 = t | c << 2)"); + for t in Topology2::ALL { + let row: Vec = Contract::ALL + .iter() + .map(|c| format!("{:>2}", t.ordinal() | c.code() << 2)) + .collect(); + println!(" {t:<16?} {}", row.join(" ")); } let (a, d, r) = census(); - println!("agree {a}, disagree {d}, refused {r} of 24 cells"); - let e = stamp(Contract::Causes, 0); println!( - "same edge under the contract: spare class {:?}, undeclared class {:?}; the affordance law takes no class", - contract_band(SPARE_CLASS, e), - contract_band(UNDECLARED_CLASS, e) + "agree {a}, disagree {d}, reserved/refused {r} of 32 (was 3 / 5 / 16 shared cells in #1378)" ); } #[cfg(test)] mod tests { use super::*; - use affordance_law::{measure, LawGen, Refusal, COUNTERFACTUAL_PROBE}; + use affordance_law::{ + measure_declared, LawGen, Refusal, CAUSAL_IDENTIFICATION, COUNTERFACTUAL_PROBE, EPI_LAW, + HYDRATE_INTERMEDIATE, MECHANISM_FOLD, + }; + use causal_edge::layout::ReasoningBand; + use certification_reading::{read, Refusal as P7aRefusal}; + use lance_graph_contract::class_view::ClassId; + use lance_graph_contract::epistemic_state5::fact::{CAUSES, CERTIFICATION_MASK, TOPOLOGY_MASK}; + use lance_graph_contract::epistemic_state5::Epi5ReadError; + + /// A class never declared under the canonical reading. + const UNDECLARED_CLASS: ClassId = 0x0904; + + fn eligible(class: ClassId, edge: CausalEdge64) -> Result { + measure_declared( + LawGen::V1, + &decl(), + class, + RAIL, + edge, + EdgeProvenance::V2Stamped, + ) + } - /// The affordance measurement. It takes no class: there is nothing to - /// look up a declaration with. - fn eligible(edge: CausalEdge64) -> Result { - measure(LawGen::V1, edge, EdgeProvenance::V2Stamped) + fn at(code: u8) -> u64 { + eligible(CERT_CLASS, base().with_epistemic_raw5(code)).unwrap() } - /// FAILS IF: the affordance law starts refusing what the contract refuses, - /// i.e. it begins to consult the declaration. + fn bits(recipes: &[u8]) -> u64 { + recipes.iter().fold(0, |m, r| m | 1 << r) + } + + /// F10: the canonical reading IS the two old lenses read together. #[test] - fn the_affordance_law_reads_bits_the_contract_refuses() { - let e = stamp(Contract::Causes, 0); + fn the_canonical_reading_is_the_two_old_lenses_read_together() { + assert_eq!(census(), (24, 0, 8)); + } + + /// FAILS IF: the law measures a class with no canonical declaration. + #[test] + fn undeclared_classes_refuse() { + let e = base().with_epistemic_raw5(20); assert_eq!( - contract_band(UNDECLARED_CLASS, e), - Err(BandReadError::UndeclaredClass(UNDECLARED_CLASS)) + eligible(UNDECLARED_CLASS, e), + Err(Refusal::Reading(Epi5ReadError::UndeclaredClass( + UNDECLARED_CLASS + ))) ); + assert!(eligible(CERT_CLASS, e).is_ok()); + } + + /// F3: a certified `Causes` is `Causes` under every topology. + #[test] + fn a_certified_causes_reads_as_causes_under_every_topology() { + use lance_graph_contract::epistemic_state5::{facts_population, Certification3}; + let mut stamped = 0u32; + for t in Topology2::ALL { + let e = certification_reading::stamp(base(), Contract::Causes, t); + let want = EpistemicState5::new(Epi5Gen::V1, t, Certification3::Causes); + assert_eq!(raw5(e), want.raw()); + assert_eq!(canonical(e), Some(want)); + stamped |= 1 << raw5(e); + assert_eq!( + read(&decl(), CERT_CLASS, e, EdgeProvenance::V2Stamped), + Ok(Contract::Causes) + ); + assert!(canonical(e).unwrap().asserts(CAUSES)); + assert_ne!(at(raw5(e)) & 1 << COUNTERFACTUAL_PROBE, 0); + } + // The four stamps are exactly the Causes column, not a codebook walk. + assert_eq!(stamped, facts_population(CAUSES)); + } + + /// The #1378 fixture (P7a band 5 over topology 0) now means the same to + /// both readers: `Direct × Causes`. + #[test] + fn the_1378_fixture_now_agrees() { + let old = base().with_epistemic_raw5(5 << 2); + assert_eq!(raw5(old), 20); + let s = canonical(old).unwrap(); + assert_eq!(s.topology(), Topology2::Direct); + assert_eq!(split(old), Some((0, Contract::Causes))); assert_eq!( - contract_band(SPARE_CLASS, e), - Err(BandReadError::BandAbsent) + Contract::from_certification(s.certification()), + Contract::Causes ); - // Same edge, any class: the affordance law measures it. - assert!(eligible(e).is_ok()); - // Silence twin: the contract does admit the band on P7a's class. - assert_eq!(contract_band(CERT_CLASS, e), Ok(Contract::Causes.code())); } - /// FAILS IF: a P7a-certified `Causes` starts reading as `CAUSES` under the - /// affordance law, or `COUNTERFACTUAL_PROBE` becomes eligible on it. + /// F6: the factor-compiled per-code table equals evaluating the rules on + /// each code's facts directly, for both law generations. + #[test] + fn the_derived_table_equals_the_rules() { + for law in [LawGen::V1, LawGen::V2] { + let rules = law.rules(); + for code in 0u8..32 { + let want = EPI_LAW[code as usize].map(|f| { + (0..64).fold(0u64, |m, r| { + let rl = rules[r]; + if rl.active && f & rl.requires == rl.requires && f & rl.forbids == 0 { + m | 1 << r + } else { + m + } + }) + }); + match want { + Some(w) => assert_eq!(law.tables().state[code as usize], w, "code {code}"), + None => assert!(code >= 24), + } + } + } + } + + /// F7 + the operator's frontier cases: one coordinate moves only the + /// recipes that read it. #[test] - fn a_certified_causes_never_reads_as_causes() { - // Topology 0: code 20, read as Related. - let e = stamp(Contract::Causes, 0); - assert_eq!(raw5(e), 20); - assert_eq!(p7a_reads(e), Ok(Contract::Causes), "P7a reads Causes"); - let facts = affordance_facts(e).expect("code 20 is declared"); - assert_eq!(facts & CAUSES, 0, "P7a Causes carries no CAUSES fact"); - assert_eq!(facts & SHARED, ASSOCIATED | RELATED); - let ok = eligible(e).expect("code 20 is measurable"); - assert_eq!(ok & (1 << COUNTERFACTUAL_PROBE), 0); - // Anti-vacuity: the plane rule does not hide the recipe; a code that - // does carry CAUSES makes it eligible on the same edge. - let with_causes = stamp(Contract::Open, 1); - assert_ne!( - eligible(with_causes).unwrap() & (1 << COUNTERFACTUAL_PROBE), - 0 + fn one_factor_moves_only_its_recipes() { + // IndirectUnknown × Related (10) vs IndirectKnown × Related (9): + // exactly hydrate ↔ mechanism fold. + assert_eq!( + at(10) ^ at(9), + bits(&[HYDRATE_INTERMEDIATE, MECHANISM_FOLD]) ); + // Related → Supports under IndirectKnown (9 → 13): exactly causal + // identification. + assert_eq!(at(9) ^ at(13), bits(&[CAUSAL_IDENTIFICATION])); + // IndirectUnknown × Causes (22): certified, mechanism open — may + // hydrate, may not be "identified" again. + assert_ne!(at(22) & 1 << HYDRATE_INTERMEDIATE, 0); + assert_eq!(at(22) & 1 << CAUSAL_IDENTIFICATION, 0); + assert_ne!(at(22) & 1 << COUNTERFACTUAL_PROBE, 0); + // Unknown × Causes (23): causality known, topology unresolved. + assert_eq!(at(23) & bits(&[HYDRATE_INTERMEDIATE, MECHANISM_FOLD]), 0); + assert_ne!(at(23) & 1 << COUNTERFACTUAL_PROBE, 0); - // Topologies 1..3: codes 21..23, not declared by the affordance law. - for t in 1..4u8 { - let e = stamp(Contract::Causes, t); - assert_eq!(p7a_reads(e), Ok(Contract::Causes)); - assert_eq!(eligible(e), Err(Refusal::Undeclared(20 + t))); + // Generally: recipes whose rules never mention a topology fact are + // unchanged by any topology move, and likewise for certification. + let rules = LawGen::V1.rules(); + let reads = |mask: u32| -> u64 { + (0..64).fold(0, |m, r| { + let rl = rules[r]; + if rl.active && (rl.requires | rl.forbids) & mask != 0 { + m | 1 << r + } else { + m + } + }) + }; + let (topo_recipes, cert_recipes) = (reads(TOPOLOGY_MASK), reads(CERTIFICATION_MASK)); + for c in 0..6u8 { + for (t1, t2) in [(0u8, 1u8), (1, 2), (2, 3), (0, 3)] { + assert_eq!((at(t1 | c << 2) ^ at(t2 | c << 2)) & !topo_recipes, 0); + } + } + for t in 0..4u8 { + for c in 0..5u8 { + assert_eq!((at(t | c << 2) ^ at(t | (c + 1) << 2)) & !cert_recipes, 0); + } } } - /// FAILS IF: the codes that assert `CAUSES` move off P7a `Open` (code 1) - /// and `CausalCandidate` (code 17). + /// F2: what must disappear is consuming legacy-reading bits as canonical. + /// The historical `ReasoningBand::Causal` writer puts 3 into 61..63, which + /// decodes as `Supports`; a class that declared that historical reading + /// has no canonical declaration, so it refuses instead of being read as a + /// support claim nobody made. #[test] - fn the_affordance_causes_codes_are_not_p7a_causes() { - let causes: Vec = (0..32u8) - .filter(|&c| EPI_LAW[c as usize].is_some_and(|f| f & CAUSES != 0)) - .collect(); - assert_eq!(causes, vec![1, 17]); - let contracts: Vec<_> = causes.iter().map(|c| Contract::from_code(c >> 2)).collect(); + fn a_legacy_reading_class_is_not_consumed_as_canonical() { + #[allow(deprecated)] + let e = base().with_reasoning_band(ReasoningBand::Causal); + assert_eq!(raw5(e) >> 2, 3, "the bits decode as Supports"); + const HISTORICAL_CLASS: ClassId = 0x0901; // P7a's LEGACY_CLASS assert_eq!( - contracts, - vec![Ok(Contract::Open), Ok(Contract::CausalCandidate)] + eligible(HISTORICAL_CLASS, e), + Err(Refusal::Reading(Epi5ReadError::UndeclaredClass( + HISTORICAL_CLASS + ))) + ); + assert_eq!( + read(&decl(), HISTORICAL_CLASS, e, EdgeProvenance::V2Stamped), + Err(P7aRefusal::NotCertificationClass) ); } - /// FAILS IF: no declared affordance code sits on a band P7a refuses, or a - /// different set does. + /// F5: same edge + class/rail + generation + provenance → same state + /// after a restart from the LE image. #[test] - fn affordance_codes_occupy_p7a_reserved_bands() { - let reserved: Vec = (0..32u8) - .filter(|&c| EPI_LAW[c as usize].is_some() && Contract::from_code(c >> 2).is_err()) - .collect(); - assert_eq!(reserved, vec![25, 30]); + fn the_state_survives_restart_from_le_bytes() { + for code in 0u8..32 { + let e = base().with_epistemic_raw5(code); + let restarted = CausalEdge64::from_le_bytes(e.to_le_bytes()); + assert_eq!(canonical(e), canonical(restarted)); + assert_eq!(canonical(e).is_some(), code < 24); + } } - /// FAILS IF: the agreement census over all 24 P7a contract × topology - /// cells changes. + /// F8: reading and projecting never write the edge; a factor transition + /// exists only as a value until written jointly. #[test] - fn the_readings_agree_on_three_of_eight_shared_cells() { - assert_eq!(census(), (3, 5, 16)); + fn projections_never_change_the_edge() { + let e = base().with_epistemic_raw5(9); + let before = e.0; + let s = canonical(e).unwrap(); + let _ = ( + split(e), + at(raw5(e)), + read(&decl(), CERT_CLASS, e, EdgeProvenance::V2Stamped), + ); + let moved = s.with_topology(Topology2::IndirectUnknown); + assert_eq!(e.0, before, "a projection wrote the edge"); + assert_eq!(moved.raw(), 10); + assert_eq!( + e.with_epistemic_raw5(moved.raw()).0 ^ before, + (9u64 ^ 10) << 59 + ); } } diff --git a/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs b/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs index 69e7ed9d9..98a2dffb1 100644 --- a/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs +++ b/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs @@ -311,11 +311,21 @@ //! control) + G17 (membrane monotonicity, vicious). //! //! Run: `cargo run -p cognitive-shader-driver --example probe_revision_kanban_hinge` +//! +//! D-EPI-LEGACY-DEPROJECT-0 (2026-10-07): the CE64 witness asks one thing — +//! does a nontrivial value in bits 59..63 survive the control loop untouched? +//! It never reads what the value means. It used to write the historical +//! `ReasoningBand::Causal`; it now writes a canonical `EpistemicState5` +//! (`IndirectUnknown × Causes`, raw 22) and pins the coordinate. The state is +//! a witness, not a claim: any meaningful state with both factors non-zero +//! would do. (The earlier note that this probe could not migrate answered a +//! different question — what `Causal` maps to — which the probe never asks.) use causal_edge::edge::CausalEdge64; -use causal_edge::layout::{CausalTopology, ReasoningBand}; +use causal_edge::layout::CausalTopology; use cognitive_shader_driver::mailbox_soa::MailboxSoA; use lance_graph_contract::cognitive_shader::{RungElevator, RungLevel}; +use lance_graph_contract::epistemic_state5::{Certification3, Epi5Gen, EpistemicState5, Topology2}; use lance_graph_contract::escalation::{ fanout_width, rung_delta, CollapseHint, CouncilVerdict, InnerCouncil, }; @@ -1068,19 +1078,27 @@ fn main() { let mut gates: Vec<(&str, bool, String)> = Vec::new(); // The live CE64 non-interference witness: a genuinely nontrivial edge, - // both lenses verified to read back BEFORE the run (F7/F8 pre-flight). - let edge = CausalEdge64::ZERO - .with_topology(CausalTopology::IndirectUnknownIntermediates) - .with_reasoning_band(ReasoningBand::Causal); + // its coordinate verified to read back BEFORE the run (F7/F8 pre-flight). + let witness = EpistemicState5::new( + Epi5Gen::V1, + Topology2::IndirectUnknown, + Certification3::Causes, + ); + let edge = CausalEdge64::ZERO.with_epistemic_raw5(witness.raw()); assert_eq!( - edge.topology(), - CausalTopology::IndirectUnknownIntermediates, - "pre-flight: topology lens must read back" + edge.epistemic_raw5(), + 22, + "pre-flight: the witness coordinate" ); assert_eq!( - edge.reasoning_band(), - ReasoningBand::Causal, - "pre-flight: reasoning-band lens must read back" + EpistemicState5::decode(Epi5Gen::V1, edge.epistemic_raw5()), + Ok(witness), + "pre-flight: the coordinate must decode back" + ); + assert_eq!( + edge.topology(), + CausalTopology::IndirectUnknownIntermediates, + "pre-flight: topology factor must read back" ); let edge_raw_before = edge.0; @@ -1401,8 +1419,9 @@ fn main() { format!("raw 0x{:016X} identical before/after", edge.0), )); gates.push(( - "F8 ReasoningBand bits 61..63 untouched by the whole control loop", - edge.0 == edge_raw_before && edge.reasoning_band() == ReasoningBand::Causal, + "F8 EpistemicState5 bits 59..63 untouched by the whole control loop", + edge.0 == edge_raw_before + && EpistemicState5::decode(Epi5Gen::V1, edge.epistemic_raw5()) == Ok(witness), "a live semantic object passed through and was left alone".into(), )); let edge_direct = CausalEdge64::ZERO.with_topology(CausalTopology::Direct); diff --git a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs index be90349ae..ea591b281 100644 --- a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs +++ b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs @@ -74,6 +74,25 @@ //! //! Run: `cargo run -p cognitive-shader-driver --example reasoning_band_probe` //! Tests: `cargo test -p cognitive-shader-driver --example reasoning_band_probe` +//! +//! D-EPI-MIG-0 (2026-10-07): PROBE-ONLY legacy. Under the canonical +//! Cartesian reading (`EpistemicState5 = Topology2 × Certification3`) the +//! topology half this probe writes is canonical as-is, but the 61..63 half is +//! written through the historical `ReasoningBand` names (reasoning levels: +//! `Surface … Transcendent`), which have no declared mapping onto the +//! certification coordinate. Mapping `Causal` to `Supports` or `Causes` would +//! be semantic invention, so the probe is not migrated; it keeps the +//! deprecated band writer as a record of that reading. No production code may +//! follow it. +//! +//! D-EPI-LEGACY-DEPROJECT-0 (2026-10-07): superseded by P7a +//! (`relational_certification_probe`), which owns the certification rungs +//! with different obligations (P7a `Causes` needs ≥ 2 intervention-backed +//! sources; this probe's `Causal` needs one). The `Counterfactual` rung — +//! survived a removal attack — is counterfactual evidence and belongs to +//! `lance_graph_planner::dismech_counterfactual`; P7a states that removal is +//! not the causal path. Kept as shipped (#1360). +#![allow(deprecated)] use causal_edge::edge::CausalEdge64; use causal_edge::layout::ReasoningBand; diff --git a/crates/cognitive-shader-driver/examples/relational_certification_probe.rs b/crates/cognitive-shader-driver/examples/relational_certification_probe.rs index 2a88906eb..3158b4c0c 100644 --- a/crates/cognitive-shader-driver/examples/relational_certification_probe.rs +++ b/crates/cognitive-shader-driver/examples/relational_certification_probe.rs @@ -47,12 +47,14 @@ //! removing A from a unit with B leaves Y unchanged, while the population //! trial still certifies `Causes`. //! -//! # Reused, unchanged +//! # Carrier (D-EPI-MIG-0) //! -//! `CausalEdge64` bits 61..63 via `with_reasoning_band` / `reasoning_band`, -//! read through `band_reading::BandDeclarations::project_band`; the -//! `ReasoningBand` enum is used only as the 3-bit carrier, its historical names -//! carry no meaning here. Provenance is `causal_audit::SupportLedger` / +//! P7a's codes are the certification coordinate of the canonical Cartesian +//! `EpistemicState5` (`raw5 = topology | certification << 2`, bits 59..63). +//! A certification is stamped by identity packing `(MODEL_GROUNDING, code)` +//! with `with_epistemic_raw5` and read back through +//! `Epi5Declarations::project_state5`. All six contracts are meaningful under +//! every topology; 6 and 7 are reserved and refuse. Provenance is `causal_audit::SupportLedger` / //! `SupportReceipt` / `SupportBasis` / `EvidenceSourceId`. Seals are //! `scheduler::DatasetVersion`. //! @@ -431,7 +433,9 @@ fn main() { simpson_trial.build(), ), ]; - println!("relational certification over bits 61..63 (declared reading)"); + println!( + "relational certification -> canonical EpistemicState5 (bits 59..63), grounding {MODEL_GROUNDING:?}" + ); let codebook: Vec = Contract::ALL .iter() .map(|c| format!("{}={c:?}", c.code())) @@ -439,10 +443,13 @@ fn main() { println!(" codebook: {} (6, 7 reserved)", codebook.join(", ")); for (name, m) in &fixtures { let c = m.certify(); - let edge = stamp(CausalEdge64::ZERO, c); - let back = read(&decl, CERT_CLASS, edge, EdgeProvenance::V2Stamped); + let e = stamp(CausalEdge64::ZERO, c, MODEL_GROUNDING); + let back = ( + e.epistemic_raw5(), + read(&decl, CERT_CLASS, e, EdgeProvenance::V2Stamped), + ); println!( - " {name:<40} -> {c:?} (code {}, marginal association {:?}, read back {back:?})", + " {name:<40} -> {c:?} (P7a code {}, marginal association {:?}, canonical {back:?})", c.code(), m.associated_marginal() ); @@ -453,7 +460,8 @@ fn main() { t.associated(), t.contributes() ); - let meta = CausalEdge64::ZERO.with_reasoning_band(ReasoningBand::Meta); + // The historical `Meta` band ordinal in the high half, as a joint code. + let meta = CausalEdge64::ZERO.with_epistemic_raw5(ReasoningBand::Meta.to_bits_3() << 2); println!( " historical Meta bits asked to satisfy Causes: {:?}", satisfies(&decl, CERT_CLASS, meta, Contract::Causes) @@ -532,7 +540,8 @@ fn monotonicity_census() -> Census { #[cfg(test)] mod tests { use super::*; - use causal_edge::layout::SPARE_MASK; + use causal_edge::layout::EPISTEMIC_MASK; + use lance_graph_contract::epistemic_state5::Epi5ReadError; /// P7a.1 + P7a.8: the reading is declared per class; reserved codes and /// historically-declared classes refuse; the bits alone decide nothing. @@ -540,29 +549,33 @@ mod tests { fn the_reading_is_declared_and_reserved_codes_refuse() { let decl = declarations(); for c in Contract::ALL { - let edge = stamp(CausalEdge64::ZERO, c); + let edge = stamp(CausalEdge64::ZERO, c, MODEL_GROUNDING); + assert_eq!(edge.epistemic_raw5(), c.code() << 2, "identity packing"); assert_eq!( read(&decl, CERT_CLASS, edge, EdgeProvenance::V2Stamped), Ok(c) ); - // Same bits, historical reading: refused. + // Same bits, a class without a canonical declaration: refused. assert_eq!( read(&decl, LEGACY_CLASS, edge, EdgeProvenance::V2Stamped), Err(Refusal::NotCertificationClass) ); } + // P7a's reserved bands 6, 7 in the high half: not canonical codes. for raw in [6u8, 7] { - let edge = CausalEdge64::ZERO.with_reasoning_band(ReasoningBand::from_bits_3(raw)); + let edge = CausalEdge64::ZERO.with_epistemic_raw5(raw << 2); assert_eq!( read(&decl, CERT_CLASS, edge, EdgeProvenance::V2Stamped), - Err(Refusal::Reserved(raw)) + Err(Refusal::Reading(Epi5ReadError::UndeclaredCode(raw << 2))) ); } - let edge = stamp(CausalEdge64::ZERO, Contract::Causes); - assert!(matches!( + let edge = stamp(CausalEdge64::ZERO, Contract::Causes, MODEL_GROUNDING); + assert_eq!( read(&decl, CERT_CLASS, edge, EdgeProvenance::Unknown), - Err(Refusal::Band(_)) - )); + Err(Refusal::Reading(Epi5ReadError::UnknownProvenance( + EdgeProvenance::Unknown + ))) + ); } /// The `>=` reading is legal on the six contracts and only there. The old @@ -577,7 +590,7 @@ mod tests { } let decl = declarations(); for historical in [ReasoningBand::Meta, ReasoningBand::Transcendent] { - let edge = CausalEdge64::ZERO.with_reasoning_band(historical); + let edge = CausalEdge64::ZERO.with_epistemic_raw5(historical.to_bits_3() << 2); assert!(historical.to_bits_3() > Contract::Causes.code()); assert!(satisfies(&decl, CERT_CLASS, edge, Contract::Causes).is_err()); assert!(satisfies(&decl, CERT_CLASS, edge, Contract::Open).is_err()); @@ -717,12 +730,12 @@ mod tests { let certified = m.certify(); for mantissa in -8i8..=7 { let edge = CausalEdge64::ZERO.with_inference_mantissa(mantissa); - let stamped = stamp(edge, certified); + let stamped = stamp(edge, certified, MODEL_GROUNDING); assert_eq!(stamped.inference_mantissa(), mantissa); assert_eq!( - (stamped.0 ^ edge.0) & !SPARE_MASK, + (stamped.0 ^ edge.0) & !EPISTEMIC_MASK, 0, - "only bits 61..63 move" + "only bits 59..63 move" ); assert_eq!( read(&decl, CERT_CLASS, stamped, EdgeProvenance::V2Stamped), @@ -881,7 +894,7 @@ mod tests { let run = || -> Vec { seals .iter() - .map(|m| stamp(CausalEdge64::ZERO, m.certify()).0) + .map(|m| stamp(CausalEdge64::ZERO, m.certify(), MODEL_GROUNDING).0) .collect() }; assert_eq!(run(), run()); diff --git a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs index 2f2f95e1b..04944d01c 100644 --- a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs +++ b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs @@ -31,8 +31,8 @@ //! //! | from | to | when, in this cycle | //! |---|---|---| -//! | 3 `OBSERVED` | 7 `ASSOCIATED` | ≥ 2 `Differs` encounters the revision admitted (`IncreaseEligible`), each a new root | -//! | 7 | 3 | the revision returned `ContradictionPreserved` on the claim (a complete `Same` check) | +//! | 0 `Direct × Open` | 4 `Direct × Associated` | ≥ 2 `Differs` encounters the revision admitted (`IncreaseEligible`), each a new root | +//! | 4 | 0 | the revision returned `ContradictionPreserved` on the claim (a complete `Same` check) | //! //! # One field, one code //! @@ -79,7 +79,6 @@ use affordance_law::{measure, raw5, LawGen}; use bgz_tensor::fisher_z::FisherZTable; use boundary::strongest_boundary_where; use causal_edge::edge::CausalEdge64; -use causal_edge::layout::TrustTexture; use causal_edge::pearl::CausalMask; use causal_edge::PlasticityState; use cognitive_shader_driver::mailbox_soa::{MailboxSoA, WriteCell, WriteOutcome}; @@ -97,8 +96,11 @@ use virtual_surfel::{neighbor, Tile, PIXELS}; /// The row of the mailbox that holds the register. const ROW: usize = 0; /// EpistemicState5 codes of the #1370 law used here. -const CODE_OBSERVED: u8 = 3; -const CODE_ASSOCIATED: u8 = 7; +/// `Direct × Open` (was code 3 `Direct × Observed` under the #1370 +/// probe-local codebook; observation is evidence, not a coordinate). +const CODE_OPEN: u8 = 0; +/// `Direct × Associated` (was code 7). +const CODE_ASSOCIATED: u8 = 4; /// The claim "P is a material boundary": bit 0 of the claim mask. const CLAIM: u64 = 1; @@ -276,8 +278,8 @@ fn settle( } } let code = match raw5(register) { - CODE_OBSERVED if earned_support >= gen.sources_to_promote() => CODE_ASSOCIATED, - CODE_ASSOCIATED if contradicted => CODE_OBSERVED, + CODE_OPEN if earned_support >= gen.sources_to_promote() => CODE_ASSOCIATED, + CODE_ASSOCIATED if contradicted => CODE_OPEN, other => other, }; with_code(register, code) @@ -286,13 +288,14 @@ fn settle( // ── the register ─────────────────────────────────────────────────────────── -/// Stamp an EpistemicState5 code through the shipped writers (bits 59..63). +/// Write a declared V1 EpistemicState5 code (bits 59..63, jointly). The +/// transition only ever lands on declared codes, so `write_code` never fires +/// its expect. fn with_code(edge: CausalEdge64, code: u8) -> CausalEdge64 { - edge.with_spare(code >> 2) - .with_truth(TrustTexture::from_bits_2(code & 0b11)) + affordance_law::write_code(edge, code) } -/// The register before any evidence: every field set, code `OBSERVED`. +/// The register before any evidence: every field set, code `Direct × Open`. fn initial() -> CausalEdge64 { with_code( CausalEdge64::pack_v2( @@ -305,7 +308,7 @@ fn initial() -> CausalEdge64 { 0, PlasticityState::ALL_HOT, ), - CODE_OBSERVED, + CODE_OPEN, ) } @@ -452,9 +455,9 @@ fn main() { #[cfg(test)] mod tests { - use super::affordance_law::{EPI_LAW, OBSERVE_FOLD, STRATIFY}; + use super::affordance_law::{OBSERVE_FOLD, STRATIFY}; use super::*; - use causal_edge::layout::{SPARE_MASK, TRUTH_MASK}; + use causal_edge::layout::EPISTEMIC_MASK; const OBS: u64 = 1 << OBSERVE_FOLD; const STRAT: u64 = 1 << STRATIFY; @@ -498,12 +501,12 @@ mod tests { ], ); let codes: Vec = regs.iter().map(|r| raw5(*r)).collect(); - assert_eq!(codes, [3, 3, 3, 7, 3]); + assert_eq!(codes, [0, 0, 0, 4, 0]); assert_eq!(elig, [OBS, OBS, OBS, OBS | STRAT, OBS]); } /// FAILS IF: the rendered surface moves the code. Every inner pixel's - /// witness, repeated ten times, is presented while the code is OBSERVED + /// witness, repeated ten times, is presented while the code is `Direct × Open` /// and again while it is ASSOCIATED: neither promotes nor demotes. #[test] fn rendered_witnesses_never_move_the_code() { @@ -513,7 +516,7 @@ mod tests { let once = render_encounters(&tile, &law); assert_eq!(once.len(), 36); let many: Vec = once.iter().cycle().take(360).copied().collect(); - for start in [CODE_OBSERVED, CODE_ASSOCIATED] { + for start in [CODE_OPEN, CODE_ASSOCIATED] { let reg = with_code(initial(), start); let next = settle(TransitionGen::V1, &tile, tracked(), reg, &many); assert_eq!(raw5(next), start); @@ -562,7 +565,7 @@ mod tests { ); assert_eq!( raw5(settle(TransitionGen::V1, &one, p, initial(), &check)), - CODE_OBSERVED + CODE_OPEN ); let two = island(&[(8, 8), (8, 7)]); assert_eq!( @@ -577,7 +580,7 @@ mod tests { ); assert_eq!( raw5(settle(TransitionGen::V2, &two, p, initial(), &check)), - CODE_OBSERVED + CODE_OPEN ); } @@ -590,7 +593,7 @@ mod tests { let tile = split(a, b); let same = [Encounter::Observe(E); 50]; let next = settle(TransitionGen::V1, &tile, tracked(), initial(), &same); - assert_eq!(raw5(next), CODE_OBSERVED); + assert_eq!(raw5(next), CODE_OPEN); let two = [Encounter::Observe(E), Encounter::Observe(SE)]; let next = settle(TransitionGen::V1, &tile, tracked(), initial(), &two); assert_eq!(raw5(next), CODE_ASSOCIATED); @@ -606,7 +609,7 @@ mod tests { assert_eq!(observe(&tile, tracked(), N), Reading::Same); let enc = [Encounter::Observe(N), Encounter::Observe(E)]; let next = settle(TransitionGen::V1, &tile, tracked(), initial(), &enc); - assert_eq!(raw5(next), CODE_OBSERVED); + assert_eq!(raw5(next), CODE_OPEN); } /// Pins the open half of the issue: the horizon dies with its cycle, so @@ -629,7 +632,7 @@ mod tests { }, ], ); - assert!(regs.iter().all(|r| raw5(*r) == CODE_OBSERVED)); + assert!(regs.iter().all(|r| raw5(*r) == CODE_OPEN)); } /// FAILS IF: the transition is not a declared, versioned law. The same @@ -641,7 +644,7 @@ mod tests { let two = [Encounter::Observe(E), Encounter::Observe(NE)]; let v1 = settle(TransitionGen::V1, &tile, tracked(), initial(), &two); let v2 = settle(TransitionGen::V2, &tile, tracked(), initial(), &two); - assert_eq!((raw5(v1), raw5(v2)), (CODE_ASSOCIATED, CODE_OBSERVED)); + assert_eq!((raw5(v1), raw5(v2)), (CODE_ASSOCIATED, CODE_OPEN)); let three = [ Encounter::Observe(E), Encounter::Observe(NE), @@ -673,7 +676,7 @@ mod tests { initial(), &[Encounter::Observe(E), Encounter::Observe(NE)], ); - assert_eq!((up.0 ^ initial().0) & !(SPARE_MASK | TRUTH_MASK), 0); + assert_eq!((up.0 ^ initial().0) & !EPISTEMIC_MASK, 0); assert_ne!(up, initial()); let kept = settle( TransitionGen::V1, @@ -733,7 +736,9 @@ mod tests { &[Encounter::CompleteCheck], &[Encounter::Render(tracked())], ]; - for start in (0..32u8).filter(|c| EPI_LAW[usize::from(*c)].is_some()) { + let meaningful = lance_graph_contract::epistemic_state5::facts_population(0); + assert_eq!(meaningful.count_ones(), 24); + for start in (0..32u8).filter(|c| meaningful >> c & 1 == 1) { for t in [&tile, &flat] { for enc in sets { let next = settle( @@ -744,7 +749,7 @@ mod tests { enc, ); assert!( - EPI_LAW[usize::from(raw5(next))].is_some(), + meaningful >> raw5(next) & 1 == 1, "{start} -> {}", raw5(next) ); diff --git a/crates/cognitive-shader-driver/examples/shared/affordance_law.rs b/crates/cognitive-shader-driver/examples/shared/affordance_law.rs index 938ade46f..dbe477046 100644 --- a/crates/cognitive-shader-driver/examples/shared/affordance_law.rs +++ b/crates/cognitive-shader-driver/examples/shared/affordance_law.rs @@ -3,48 +3,76 @@ //! and `ce64_cycle_survival_probe` (which measures eligibility across cycles //! with the identical law instead of a copy). //! -//! Moved here unchanged. Each including example uses a subset, hence the +//! D-EPI-MIG-0: the state is the contract's Cartesian `EpistemicState5` +//! (`Topology2 × Certification3`, `raw5 = topology | certification << 2`). +//! The recipe rules are unchanged; they now compile PER FACTOR (topology +//! table × certification table), and the per-code `state` table is derived +//! from the two. Measurement projects bits 59..63 through a class +//! declaration (`measure_declared`; `measure` uses the probes' constant +//! `AFF_READING`). Each including example uses a subset, hence the //! `dead_code` allowance. #![allow(dead_code)] use causal_edge::edge::CausalEdge64; use lance_graph_contract::band_reading::EdgeProvenance; +use lance_graph_contract::class_view::ClassId; +use lance_graph_contract::epistemic_state5::{ + Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, EpistemicState5, CERTIFICATION_FACTS, + COMPILED_FACTS_V1, TOPOLOGY_FACTS, +}; +use lance_graph_contract::rail_geometry::RailAxis; -// ── facts (the EpistemicState5 reading) ──────────────────────────────────── - -pub const DIRECT: u32 = 1 << 0; -pub const INDIRECT: u32 = 1 << 1; -pub const INTERMEDIATE_PRESENT: u32 = 1 << 2; -pub const INTERMEDIATE_UNKNOWN: u32 = 1 << 3; -pub const INTERMEDIATE_KNOWN: u32 = 1 << 4; -pub const OBSERVED: u32 = 1 << 5; -pub const ASSOCIATED: u32 = 1 << 6; -pub const RELATED: u32 = 1 << 7; -pub const SUPPORTS: u32 = 1 << 8; -pub const CAUSES: u32 = 1 << 9; - -pub const IND_UNKNOWN: u32 = INDIRECT | INTERMEDIATE_PRESENT | INTERMEDIATE_UNKNOWN; -pub const IND_KNOWN: u32 = INDIRECT | INTERMEDIATE_PRESENT | INTERMEDIATE_KNOWN; -pub const UP_TO_RELATED: u32 = ASSOCIATED | RELATED; -pub const UP_TO_SUPPORTS: u32 = UP_TO_RELATED | SUPPORTS; - -/// EpistemicState5 law: code → facts. Ten valid codes, deliberately not ordered -/// by strength (`Causes` sits at 1, `Open` at 0, `Related` at 20, …). -pub const EPI_LAW: [Option; 32] = { - let mut t = [None; 32]; - t[0] = Some(0); // Open - t[3] = Some(DIRECT | OBSERVED); - t[7] = Some(DIRECT | OBSERVED | ASSOCIATED); - t[12] = Some(DIRECT | ASSOCIATED); - t[20] = Some(DIRECT | UP_TO_RELATED); - t[25] = Some(IND_UNKNOWN | UP_TO_RELATED); - t[5] = Some(IND_KNOWN | UP_TO_RELATED); - t[30] = Some(IND_KNOWN | UP_TO_SUPPORTS); - t[1] = Some(DIRECT | UP_TO_SUPPORTS | CAUSES); - t[17] = Some(IND_KNOWN | UP_TO_SUPPORTS | CAUSES); - t +// ── facts (the canonical EpistemicState5 reading) ────────────────────────── +// +// D-EPI-CANON-0: the fact vocabulary and the codebook are the contract's +// (`lance_graph_contract::epistemic_state5`), not this probe's. Re-exported so +// the probes that name them keep compiling against the ONE source. + +#[allow(unused_imports)] // each including probe uses a subset +pub use lance_graph_contract::epistemic_state5::fact::{ + ASSOCIATED, CAUSES, DIRECT, INDIRECT, IND_KNOWN, IND_UNKNOWN, INTERMEDIATE_KNOWN, + INTERMEDIATE_PRESENT, INTERMEDIATE_UNKNOWN, RELATED, SUPPORTS, UP_TO_RELATED, UP_TO_SUPPORTS, }; +/// Per-code facts: the contract's DERIVED `COMPILED_FACTS_V1` (the union of +/// the two factors' facts). Kept under its #1370 name for the probes that +/// read it; it is no longer a hand-assigned codebook. +pub const EPI_LAW: [Option; 32] = COMPILED_FACTS_V1; + +/// The class the affordance probes' edges belong to, declared under the +/// canonical reading. Measurement is per class: an undeclared class refuses. +pub const AFF_CLASS: ClassId = 0x0905; +pub const AFF_RAIL: RailAxis = RailAxis::Taxonomy; + +/// `AFF_CLASS`'s declaration: bits 59..63 read as `EpistemicState5` V1. +pub const AFF_READING: Epi5Reading = Epi5Reading { + generation: Epi5Gen::V1, +}; + +/// The probes' declaration table (a builder for callers that want one). +pub fn declarations() -> Epi5Declarations { + let mut d = Epi5Declarations::new(); + d.declare(AFF_CLASS, AFF_RAIL, AFF_READING); + d +} + +/// Write a declared state onto an edge: all of bits 59..63, nothing else. +pub fn write_state(edge: CausalEdge64, state: EpistemicState5) -> CausalEdge64 { + edge.with_epistemic_raw5(state.raw()) +} + +/// `Direct × Open`: the resident state before evidence (#1379's former code 3, +/// "observed", now Open — observation is evidence, not a coordinate). +pub const CODE_DIRECT_OPEN: u8 = 0; +/// `Direct × Associated` (#1379's former code 7). +pub const CODE_DIRECT_ASSOCIATED: u8 = 4; + +/// Write a V1 code that must be meaningful (fixture convenience). +pub fn write_code(edge: CausalEdge64, code: u8) -> CausalEdge64 { + let state = EpistemicState5::decode(Epi5Gen::V1, code).expect("a declared V1 code"); + write_state(edge, state) +} + // ── the recipe law ────────────────────────────────────────────────────────── /// One recipe's static obligations. `pearl` names the planes that must @@ -104,29 +132,55 @@ pub enum LawGen { pub const LAW_V1: [Rule; 64] = law(0); pub const LAW_V2: [Rule; 64] = law(CAUSES); -/// Compiled static affordances for one law generation. +/// Compiled static affordances for one law generation, compiled PER FACTOR. +/// Topology and certification facts are disjoint, so a rule holds on a state +/// iff its topology part holds on the topology and its certification part +/// on the certification: `state[t | c << 2] = topology[t] & certification[c]`. pub struct Tables { - /// Per EpistemicState5 code: recipes whose fact obligations hold. Invalid - /// codes have no entry here; they refuse before lookup. + /// Per topology ordinal: recipes whose topology obligations hold. + pub topology: [u64; 4], + /// Per certification code: recipes whose certification obligations hold. + pub certification: [u64; 6], + /// Per raw code, DERIVED from the two above (hot-path artifact). Reserved + /// codes stay 0; they refuse before lookup. pub state: [u64; 32], /// Per Pearl projection: recipes whose plane obligations hold. pub pearl: [u64; 8], } +/// Recipes whose obligations, restricted to `mask`, hold on `facts`. +const fn holds(law: &[Rule; 64], facts: u32, mask: u32) -> u64 { + let mut out = 0u64; + let mut r = 0; + while r < 64 { + let rl = law[r]; + let req = rl.requires & mask; + if rl.active && facts & req == req && facts & rl.forbids & mask == 0 { + out |= 1 << r; + } + r += 1; + } + out +} + pub const fn compile(law: &[Rule; 64]) -> Tables { + use lance_graph_contract::epistemic_state5::fact::{CERTIFICATION_MASK, TOPOLOGY_MASK}; + let mut topology = [0u64; 4]; + let mut t = 0; + while t < 4 { + topology[t] = holds(law, TOPOLOGY_FACTS[t], TOPOLOGY_MASK); + t += 1; + } + let mut certification = [0u64; 6]; + let mut c = 0; + while c < 6 { + certification[c] = holds(law, CERTIFICATION_FACTS[c], CERTIFICATION_MASK); + c += 1; + } let mut state = [0u64; 32]; let mut code = 0; - while code < 32 { - if let Some(facts) = EPI_LAW[code] { - let mut r = 0; - while r < 64 { - let rl = law[r]; - if rl.active && facts & rl.requires == rl.requires && facts & rl.forbids == 0 { - state[code] |= 1 << r; - } - r += 1; - } - } + while code < 24 { + state[code] = topology[code & 0b11] & certification[code >> 2]; code += 1; } let mut pearl = [0u64; 8]; @@ -142,13 +196,22 @@ pub const fn compile(law: &[Rule; 64]) -> Tables { } p += 1; } - Tables { state, pearl } + Tables { + topology, + certification, + state, + pearl, + } } pub static TABLES_V1: Tables = compile(&LAW_V1); pub static TABLES_V2: Tables = compile(&LAW_V2); impl LawGen { + /// The codebook generation this law is compiled against. + pub fn generation(self) -> Epi5Gen { + Epi5Gen::V1 + } pub fn tables(self) -> &'static Tables { match self { LawGen::V1 => &TABLES_V1, @@ -170,39 +233,64 @@ impl LawGen { pub enum Refusal { /// The caller did not assert that bits 59..63 were written under the v2 /// layout. On a v1 row they are old `temporal` bits: `temporal = 128` - /// sets bit 59 and would read as code 1 (`Causes`). Same rule as - /// `band_reading`: v1 and unknown provenance refuse. + /// sets bit 59 and would read as code 1 (`Causes`). Provenance(EdgeProvenance), - /// Bits 59..63 do not form a code this law declares. + /// Bits 59..63 do not form a code the declared generation declares. Undeclared(u8), + /// The class has no canonical declaration, or declares another + /// generation than the law was compiled against. + Reading(Epi5ReadError), +} + +impl From for Refusal { + fn from(e: Epi5ReadError) -> Self { + match e { + Epi5ReadError::UnknownProvenance(p) => Refusal::Provenance(p), + Epi5ReadError::UndeclaredCode(c) => Refusal::Undeclared(c), + other => Refusal::Reading(other), + } + } } /// Only an asserted v2-stamped edge or a clean V3 register is readable. pub fn admitted(provenance: EdgeProvenance) -> bool { - matches!( - provenance, - EdgeProvenance::V2Stamped | EdgeProvenance::V3Register - ) + provenance.trusted() } -/// Bits 59..63 as one code, through the shipped accessors. +/// Bits 59..63 as one code, through the canonical joint accessor. pub fn raw5(edge: CausalEdge64) -> u8 { - (edge.spare() << 2) | edge.truth_raw() + edge.epistemic_raw5() +} + +/// The core: a projected state × the edge's Pearl planes → eligible recipes. +/// Two lookups and one AND. +pub fn measure_state(law: LawGen, state: EpistemicState5, pearl: u8) -> u64 { + let t = law.tables(); + t.state[state.raw() as usize] & t.pearl[pearl as usize & 0b111] +} + +/// `CausalEdge64 × RecipeLaw → EligibleRecipes` for an edge of `class`: +/// the canonical projection first (declaration, provenance, generation, +/// code), then the compiled tables. +pub fn measure_declared( + law: LawGen, + decl: &Epi5Declarations, + class: ClassId, + rail: RailAxis, + edge: CausalEdge64, + provenance: EdgeProvenance, +) -> Result { + let state = decl.project_state5(class, rail, law.generation(), raw5(edge), provenance)?; + Ok(measure_state(law, state, edge.causal_mask() as u8)) } -/// `CausalEdge64 × RecipeLaw → EligibleRecipes`. Two lookups and one AND. +/// `measure_declared` for an edge of the probes' declared `AFF_CLASS`. pub fn measure( law: LawGen, edge: CausalEdge64, provenance: EdgeProvenance, ) -> Result { - if !admitted(provenance) { - return Err(Refusal::Provenance(provenance)); - } - let code = raw5(edge); - if EPI_LAW[code as usize].is_none() { - return Err(Refusal::Undeclared(code)); - } - let t = law.tables(); - Ok(t.state[code as usize] & t.pearl[edge.causal_mask() as usize]) + // The class's declaration is a constant: no table, no allocation (F9). + let state = AFF_READING.project(law.generation(), raw5(edge), provenance)?; + Ok(measure_state(law, state, edge.causal_mask() as u8)) } diff --git a/crates/cognitive-shader-driver/examples/shared/certification_reading.rs b/crates/cognitive-shader-driver/examples/shared/certification_reading.rs index 42b6c1f53..4fbd13ac3 100644 --- a/crates/cognitive-shader-driver/examples/shared/certification_reading.rs +++ b/crates/cognitive-shader-driver/examples/shared/certification_reading.rs @@ -3,28 +3,41 @@ //! `epistemic_reading_conflict_probe` (which pins its disagreement with the //! affordance law against this implementation, not a copy). //! -//! Moved here unchanged. Each including example uses a subset, hence the +//! D-EPI-MIG-0: P7a's codes ARE the certification coordinate of the +//! canonical Cartesian `EpistemicState5` (`raw5 = topology | certification +//! << 2`). Stamping is identity packing of `(declared topology, contract)` +//! into bits 59..63 — no translation table; reading projects the canonical +//! state through the class declaration and takes its certification. +//! +//! Originally moved here unchanged. Each including example uses a subset, hence the //! `dead_code` allowance. #![allow(dead_code)] use causal_edge::edge::CausalEdge64; -use causal_edge::layout::ReasoningBand; -use lance_graph_contract::band_reading::{ - BandDeclarations, BandPresence, BandReadError, BandReading, EdgeProvenance, -}; +use lance_graph_contract::band_reading::EdgeProvenance; use lance_graph_contract::class_view::ClassId; +use lance_graph_contract::epistemic_state5::{ + Certification3, Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, EpistemicState5, + Topology2, +}; use lance_graph_contract::rail_geometry::RailAxis; // ── The declared reading ────────────────────────────────────────────────── /// A class whose edges carry the certification reading. pub const CERT_CLASS: ClassId = 0x0902; -/// A class that declares a band under the historical reading. +/// A class that declared a band under the historical reading. It has no +/// canonical declaration. pub const LEGACY_CLASS: ClassId = 0x0901; pub const RAIL: RailAxis = RailAxis::Taxonomy; /// Classes declared under the certification reading. The bits cannot say which /// reading wrote them, so the class does. pub const CERTIFICATION_CLASSES: &[ClassId] = &[CERT_CLASS]; +/// The grounding P7a's sealed models declare: exposure and outcome are +/// measured on the same units with no intermediate in the model, so the +/// certified relation is `Direct`. A producer statement, not an inference +/// from the bits. +pub const MODEL_GROUNDING: Topology2 = Topology2::Direct; /// The certified relational contract. Working names; the obligations in the /// module table are the content. @@ -90,35 +103,47 @@ impl Contract { } } -/// Why a band could not be read. +impl Contract { + /// The canonical certification coordinate (same code). + pub fn certification(self) -> Certification3 { + Certification3::ALL[self.code() as usize] + } + + pub fn from_certification(c: Certification3) -> Self { + Contract::ALL[c.code() as usize] + } +} + +/// Why a certification could not be stamped or read. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Refusal { /// The class is not declared under the certification reading. NotCertificationClass, - /// A reserved code (6 or 7). + /// A reserved legacy code (6 or 7). Reserved(u8), - /// The contract refused (undeclared, band absent, untrusted provenance). - Band(BandReadError), + /// The canonical projection refused (undeclared, untrusted provenance, + /// undeclared code). + Reading(Epi5ReadError), } -pub fn declarations() -> BandDeclarations { - let mut d = BandDeclarations::new(); - for class in [CERT_CLASS, LEGACY_CLASS] { - d.declare( - class, - RAIL, - BandReading { - band: BandPresence::Present, - ..BandReading::ZERO_FALLBACK - }, - ); - } +/// The canonical declarations: `CERT_CLASS` reads bits 59..63 as +/// `EpistemicState5` V1. `LEGACY_CLASS` is deliberately absent. +pub fn declarations() -> Epi5Declarations { + let mut d = Epi5Declarations::new(); + d.declare( + CERT_CLASS, + RAIL, + Epi5Reading { + generation: Epi5Gen::V1, + }, + ); d } -/// Read the certified contract off an edge. +/// Read the certified contract off an edge: canonical projection, then the +/// legacy certification projection of the state. pub fn read( - decl: &BandDeclarations, + decl: &Epi5Declarations, class: ClassId, edge: CausalEdge64, provenance: EdgeProvenance, @@ -126,15 +151,15 @@ pub fn read( if !CERTIFICATION_CLASSES.contains(&class) { return Err(Refusal::NotCertificationClass); } - let raw = decl - .project_band(class, RAIL, edge.reasoning_band().to_bits_3(), provenance) - .map_err(Refusal::Band)?; - Contract::from_code(raw) + let state = decl + .project_state5(class, RAIL, Epi5Gen::V1, edge.epistemic_raw5(), provenance) + .map_err(Refusal::Reading)?; + Ok(Contract::from_certification(state.certification())) } -/// Does the band on the edge license `required`? Reserved codes refuse. +/// Does the edge license `required`? pub fn satisfies( - decl: &BandDeclarations, + decl: &Epi5Declarations, class: ClassId, edge: CausalEdge64, required: Contract, @@ -142,7 +167,10 @@ pub fn satisfies( Ok(read(decl, class, edge, EdgeProvenance::V2Stamped)?.entails(required)) } -/// Write a contract into bits 61..63. Only those bits move. -pub fn stamp(edge: CausalEdge64, contract: Contract) -> CausalEdge64 { - edge.with_reasoning_band(ReasoningBand::from_bits_3(contract.code())) +/// Stamp a certification under a declared topology: identity packing into +/// all of bits 59..63. Nothing else moves. Every contract × topology is a +/// meaningful V1 state. +pub fn stamp(edge: CausalEdge64, contract: Contract, topology: Topology2) -> CausalEdge64 { + let state = EpistemicState5::new(Epi5Gen::V1, topology, contract.certification()); + edge.with_epistemic_raw5(state.raw()) } diff --git a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs index 467ee80ba..7c2618e08 100644 --- a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs +++ b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs @@ -42,7 +42,6 @@ use bgz17::palette::Palette; use bgz17::palette_semiring::PaletteSemiring; use causal_edge::edge::CausalEdge64; #[cfg(test)] -use causal_edge::layout::{SPARE_MASK, TRUTH_MASK, TRUTH_SHIFT}; #[cfg(test)] use cognitive_shader_driver::bindspace::BindSpace; #[cfg(test)] @@ -54,6 +53,7 @@ use cognitive_shader_driver::{ ShaderCrystal, ShaderDispatch, ShaderDriver, StyleSelector, }; use lance_graph_contract::band_reading::EdgeProvenance; +use lance_graph_contract::epistemic_state5::{Certification3, Epi5Gen, EpistemicState5, Topology2}; use thinking_engine::dto::{SourceType, StreamDto}; const MAILBOX: u32 = 0; @@ -225,11 +225,16 @@ fn main() { ); let t = LawGen::V1.tables(); let pearl = t.pearl[edge.causal_mask() as usize]; - for code in [0usize, 1, 3, 5, 7, 12, 17, 20, 25, 30] { - println!( - " code {code:2} under this Pearl: {:#x}", - t.state[code] & pearl - ); + // Every meaningful state by its coordinate; reserved certifications (codes + // 24..31) refuse and are not a row of this table. + for c in Certification3::ALL { + for tp in Topology2::ALL { + let code = EpistemicState5::new(Epi5Gen::V1, tp, c).raw(); + println!( + " code {code:2} {tp:?} x {c:?} under this Pearl: {:#x}", + t.state[code as usize] & pearl + ); + } } } @@ -348,11 +353,10 @@ mod tests { "the circuit moved eligibility" ); - // Code 7 (DIRECT | OBSERVED | ASSOCIATED) into the same row, committed - // by tick. Not code 3: under Pearl S it grants the same 0x20 as code 0 - // (the table `main` prints). - let observed = - CausalEdge64((committed.0 & !(TRUTH_MASK | SPARE_MASK)) | (7u64 << TRUTH_SHIFT)); + // Code 4 (Direct × Associated, the Cartesian V1 reading) into the same + // row, committed by tick. Not code 0: under Pearl S it would grant the + // same 0x20 as the open register (the table `main` prints). + let observed = committed.with_epistemic_raw5(4); let mb = d.mailbox_mut(MAILBOX).unwrap(); let cell = WriteCell { edge: Some(observed), @@ -360,10 +364,18 @@ mod tests { }; assert_eq!(mb.write_row(row, mb.cycle(), &cell), WriteOutcome::Accepted); mb.tick(); + assert_eq!( + raw5(mb.edge(row)), + 4, + "the committed code is not Direct × Associated" + ); + let reread_state = EpistemicState5::decode(Epi5Gen::V1, raw5(mb.edge(row))).unwrap(); + assert_eq!(reread_state.topology(), Topology2::Direct); + assert_eq!(reread_state.certification(), Certification3::Associated); let reread = measure(LawGen::V1, mb.edge(row), EdgeProvenance::V2Stamped); assert_eq!( reread, - Ok(t.state[7] & t.pearl[edge.causal_mask() as usize]) + Ok(t.state[4] & t.pearl[edge.causal_mask() as usize]) ); assert_ne!(reread, open, "the measurement does not read the row"); } diff --git a/crates/lance-graph-arm-discovery/src/translator.rs b/crates/lance-graph-arm-discovery/src/translator.rs index 32775fe9e..294f6314b 100644 --- a/crates/lance-graph-arm-discovery/src/translator.rs +++ b/crates/lance-graph-arm-discovery/src/translator.rs @@ -39,6 +39,9 @@ pub const NARS_PERSONALITY_K: u32 = 1; /// `ReasoningBand` since `9891cca6`, only the `SPARE_SHIFT` name is stale; the other /// paired `confidence_u8` with the i4 mantissa — the i4 mantissa at bits 46-49 is the /// `InferenceType`, provenance/type grammar, not half of the truth value.) +/// (⊘ 2026-10-07, D-EPI-CANON-0: bits 59..63 are now ONE field, the 5-bit +/// `EpistemicState5` code read through `contract::epistemic_state5`; the +/// `CausalTopology` / `ReasoningBand` halves above are legacy projections.) /// /// **This is a substrate value, not a wire DTO.** It carries no `repr(C)`, no schema /// version, and no little-endian codec, so it binds a DEGREE and not a KIND. Per the diff --git a/crates/lance-graph-contract/src/band_reading.rs b/crates/lance-graph-contract/src/band_reading.rs index 9262e269a..2cfc0abbd 100644 --- a/crates/lance-graph-contract/src/band_reading.rs +++ b/crates/lance-graph-contract/src/band_reading.rs @@ -4,6 +4,18 @@ //! `band_reading` — the **59..63 reading contract** (`D-ACR-7`, council-ratified //! spec: `.claude/plans/dacr7-band-reading-contract-v1.md`). //! +//! > **⊘ SUPERSEDED as the owner of bits 59..63 (operator decision 2026-10-07, +//! > `D-EPI-CANON-0`).** Bits 59..63 are ONE field, `EpistemicState5 = +//! > Topology2 × Certification3` (`raw5 = topology | certification << 2`), +//! > read through [`epistemic_state5`](crate::epistemic_state5). The split +//! > reading below is a **legacy compatibility surface**, not a peer authority. +//! > A class that declared `TruthLens::Topology` + the P7a certification band +//! > wrote exactly the canonical product (identity packing); a class that +//! > declared `TruthLens::Trust` or the historical `ReasoningBand` wrote other +//! > meanings, has no canonical declaration, and its bits are never +//! > reinterpreted. [`EdgeProvenance`] stays here unchanged and is shared by +//! > both readings. Kept, not deleted (append-only). +//! //! 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 diff --git a/crates/lance-graph-contract/src/class_view.rs b/crates/lance-graph-contract/src/class_view.rs index 6ef2d5eaa..356017f6a 100644 --- a/crates/lance-graph-contract/src/class_view.rs +++ b/crates/lance-graph-contract/src/class_view.rs @@ -1346,6 +1346,10 @@ pub trait ClassView { /// `(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. + /// + /// **Legacy (D-EPI-CANON-0):** bits 59..63 are owned by + /// [`epistemic_reading`](ClassView::epistemic_reading); this split reading + /// is a compatibility projection only. #[inline] fn band_reading( &self, @@ -1355,6 +1359,21 @@ pub trait ClassView { crate::band_reading::BandReading::ZERO_FALLBACK } + /// The canonical declaration of bits 59..63 for `(class, rail)`: which + /// `EpistemicState5` codebook generation the class's producers write + /// (`D-EPI-CANON-0`). `None` = undeclared, and projection REFUSES — unlike + /// the total [`band_reading`](ClassView::band_reading) lookup, there is no + /// zero-fallback reading for a 5-bit code whose meaning is the declaration. + /// Selection only; never changes stored bytes. + #[inline] + fn epistemic_reading( + &self, + _class: ClassId, + _rail: crate::rail_geometry::RailAxis, + ) -> Option { + None + } + /// Which value-slab schema preset this class materialises in /// [`NodeRow::value`](crate::canonical_node::NodeRow::value). /// diff --git a/crates/lance-graph-contract/src/epistemic_state5.rs b/crates/lance-graph-contract/src/epistemic_state5.rs new file mode 100644 index 000000000..672e034e2 --- /dev/null +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -0,0 +1,859 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright The Lance Authors + +//! `epistemic_state5` — the canonical reading of `CausalEdge64` bits 59..63 +//! (`D-EPI-CANON-0`, operator decision 2026-10-07). +//! +//! # One field, two coordinates +//! +//! Bits 59..63 are ONE 5-bit field, `EpistemicState5`, and it is a dense +//! PRODUCT space, not a hand-assigned codebook: +//! +//! ```text +//! bits 59..60 = Topology2 00 Direct 01 IndirectKnown 10 IndirectUnknown 11 Unknown +//! bits 61..63 = Certification3 0 Open 1 Associated 2 Related 3 Supports 4 CausalCandidate 5 Causes 6,7 reserved +//! +//! raw5 = topology | (certification << 2) 4 × 6 = 24 meaningful states, 8 reserved +//! ``` +//! +//! Both ordinals are the ones already on the wire: `Topology2` is +//! `causal_edge::layout::CausalTopology` ordinal for ordinal, and +//! `Certification3` is the P7a certification contract (#1369) code for code. +//! The two coordinates are orthogonal on purpose: `IndirectUnknown × Causes` +//! (a randomized intervention certified the relation, the mechanism is still +//! open) and `Unknown × Causes` (causality known, topology unresolved) are +//! meaningful states. Whether a *next step* is legal is the affordance law's +//! question, never this layout's — validity is only `certification < 6`. +//! +//! # Layout decides coordinates, law decides affordances +//! +//! A state's [facts](fact) are `TOPOLOGY_FACTS[t] | CERTIFICATION_FACTS[c]`. +//! The affordance law (`CausalEdge64 × RecipeLaw[64] → EligibleRecipes: u64`) +//! compiles from those factors; a `[u64; 32]` table is a hot-path artifact +//! DERIVED from them ([`COMPILED_FACTS_V1`]), never semantic authority. +//! Eligibility is transient measurement; nothing here stores a capability. +//! +//! Observation is NOT a coordinate. That a transition was earned by +//! observation lives in the evidence path (witness, `SupportLedger`, +//! revision input); "observed vs asserted" belongs to witness/provenance, +//! never to one of these 32 cells. +//! +//! # Declared, never inferred +//! +//! The code is opaque without a declaration — **raw ordinal + declared reading +//! = meaning** — so every read goes through `(classid, rail) → Epi5Reading` +//! and [`Epi5Declarations::project_state5`], which refuses an undeclared +//! class, a generation mismatch, untrusted provenance (`V1Legacy` / +//! `Unknown`: on a v1 row these bits are old `temporal`) and a reserved +//! certification. The physical read/write is `CausalEdge64::epistemic_raw5` / +//! `with_epistemic_raw5(state.raw())`; this crate is zero-dep and never touches +//! the edge layout. +//! +//! # Legacy readings (compatibility only) +//! +//! [`band_reading`](crate::band_reading) declared 59..60 and 61..63 as two +//! fields. A producer that declared **topology + P7a certification** wrote +//! exactly this product (identity packing, no translation table). A producer +//! that declared the `TrustTexture` lens on 59..60 or the historical +//! `ReasoningBand` on 61..63 wrote OTHER meanings into the same bits; no +//! explicit mapping exists for either, so its bits are never numerically +//! reinterpreted here — such a class has no `Epi5Reading` and projection +//! refuses. The declaration is the migration firewall. + +use crate::band_reading::EdgeProvenance; +use crate::class_view::ClassId; +use crate::rail_geometry::RailAxis; + +/// Number of raw codes the 5-bit field can carry. +pub const CODES: usize = 32; +/// Meaningful V1 states: 4 topologies × 6 certifications. +pub const MEANINGFUL_STATES: usize = 24; +const _: () = assert!(CODES == 1 << 5 && MEANINGFUL_STATES == 4 * 6); +// Topology and certification facts are disjoint, so recipe eligibility +// compiles per factor (`state = topology & certification`). +const _: () = assert!(fact::TOPOLOGY_MASK & fact::CERTIFICATION_MASK == 0); + +/// A conjunction of epistemic facts. +pub type Facts = u32; + +/// The fact vocabulary. A state's meaning is the union of its two factors' +/// facts; the affordance law states each recipe's obligations in the same +/// vocabulary. +pub mod fact { + use super::Facts; + + /// Topology: the relation is grounded directly (no intermediate). + pub const DIRECT: Facts = 1 << 0; + /// Topology: the relation runs through at least one intermediate. + pub const INDIRECT: Facts = 1 << 1; + /// Topology: an intermediate is part of the claim. + pub const INTERMEDIATE_PRESENT: Facts = 1 << 2; + /// Topology: the intermediate is not known (hydration is open work). + pub const INTERMEDIATE_UNKNOWN: Facts = 1 << 3; + /// Topology: the intermediate is known (a mechanism can be folded/tested). + pub const INTERMEDIATE_KNOWN: Facts = 1 << 4; + /// Topology: not resolved at all. + pub const TOPOLOGY_UNKNOWN: Facts = 1 << 10; + /// Certification: associated in a declared population. + pub const ASSOCIATED: Facts = 1 << 6; + /// Certification: the association survives the robustness mask. + pub const RELATED: Facts = 1 << 7; + /// Certification: supports / contributes in every declared stratum. + pub const SUPPORTS: Facts = 1 << 8; + /// Certification: supports, and exposure precedes outcome. + pub const CAUSAL_CANDIDATE: Facts = 1 << 11; + /// Certification: intervention-certified. + pub const CAUSES: Facts = 1 << 9; + + /// `Indirect × IntermediateUnknown`. + pub const IND_UNKNOWN: Facts = INDIRECT | INTERMEDIATE_PRESENT | INTERMEDIATE_UNKNOWN; + /// `Indirect × IntermediateKnown`. + pub const IND_KNOWN: Facts = INDIRECT | INTERMEDIATE_PRESENT | INTERMEDIATE_KNOWN; + /// `Associated × Related`. + pub const UP_TO_RELATED: Facts = ASSOCIATED | RELATED; + /// `Associated × Related × Supports`. + pub const UP_TO_SUPPORTS: Facts = UP_TO_RELATED | SUPPORTS; + /// Every topology fact. + pub const TOPOLOGY_MASK: Facts = DIRECT | IND_UNKNOWN | IND_KNOWN | TOPOLOGY_UNKNOWN; + /// Every certification fact. + pub const CERTIFICATION_MASK: Facts = UP_TO_SUPPORTS | CAUSAL_CANDIDATE | CAUSES; +} + +/// The topology coordinate (bits 59..60). Ordinals equal +/// `causal_edge::layout::CausalTopology` (doc pointer, never an import). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Topology2 { + /// `00` + Direct, + /// `01` — `IndirectKnownIntermediates`. + IndirectKnown, + /// `10` — `IndirectUnknownIntermediates`. + IndirectUnknown, + /// `11` + Unknown, +} + +impl Topology2 { + /// All four, in ordinal order. + pub const ALL: [Topology2; 4] = [ + Topology2::Direct, + Topology2::IndirectKnown, + Topology2::IndirectUnknown, + Topology2::Unknown, + ]; + + /// The 2-bit ordinal. + #[must_use] + pub const fn ordinal(self) -> u8 { + self as u8 + } + + /// From the 2-bit ordinal; `None` above 3. + #[must_use] + pub const fn from_ordinal(o: u8) -> Option { + match o { + 0 => Some(Topology2::Direct), + 1 => Some(Topology2::IndirectKnown), + 2 => Some(Topology2::IndirectUnknown), + 3 => Some(Topology2::Unknown), + _ => None, + } + } + + /// This coordinate's facts. + #[must_use] + pub const fn facts(self) -> Facts { + TOPOLOGY_FACTS[self as usize] + } +} + +/// The certification coordinate (bits 61..63). Codes equal the P7a +/// certification contract (#1369); 6 and 7 are reserved. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Certification3 { + /// `0` — no obligation met. + Open, + /// `1` + Associated, + /// `2` + Related, + /// `3` — P7a `Contributes`. + Supports, + /// `4` + CausalCandidate, + /// `5` + Causes, +} + +impl Certification3 { + /// All six, in code order. + pub const ALL: [Certification3; 6] = [ + Certification3::Open, + Certification3::Associated, + Certification3::Related, + Certification3::Supports, + Certification3::CausalCandidate, + Certification3::Causes, + ]; + + /// The 3-bit code. + #[must_use] + pub const fn code(self) -> u8 { + self as u8 + } + + /// From the 3-bit code; `None` for the reserved 6 and 7 (and above). + #[must_use] + pub const fn from_code(c: u8) -> Option { + match c { + 0 => Some(Certification3::Open), + 1 => Some(Certification3::Associated), + 2 => Some(Certification3::Related), + 3 => Some(Certification3::Supports), + 4 => Some(Certification3::CausalCandidate), + 5 => Some(Certification3::Causes), + _ => None, + } + } + + /// This coordinate's facts (cumulative: each certification asserts every + /// weaker one, the chain P7a measured monotone). + #[must_use] + pub const fn facts(self) -> Facts { + CERTIFICATION_FACTS[self as usize] + } + + /// Does holding `self` license asserting `required`? By facts, never by + /// comparing codes. + #[must_use] + pub const fn entails(self, required: Certification3) -> bool { + self.facts() & required.facts() == required.facts() + } +} + +/// Topology factor → facts. +pub const TOPOLOGY_FACTS: [Facts; 4] = { + use fact::*; + [DIRECT, IND_KNOWN, IND_UNKNOWN, TOPOLOGY_UNKNOWN] +}; + +/// Certification factor → facts. +pub const CERTIFICATION_FACTS: [Facts; 6] = { + use fact::*; + [ + 0, + ASSOCIATED, + UP_TO_RELATED, + UP_TO_SUPPORTS, + UP_TO_SUPPORTS | CAUSAL_CANDIDATE, + UP_TO_SUPPORTS | CAUSAL_CANDIDATE | CAUSES, + ] +}; + +/// The V1 facts of a raw code, from the two factors; `None` if the +/// certification is reserved (or `raw5 > 31`). +#[must_use] +pub const fn facts_v1(raw5: u8) -> Option { + if raw5 as usize >= CODES { + return None; + } + match Certification3::from_code(raw5 >> 2) { + Some(c) => Some(TOPOLOGY_FACTS[(raw5 & 0b11) as usize] | c.facts()), + None => None, + } +} + +/// `facts_v1` over all 32 codes — a DERIVED hot-path table, not authority. +pub const COMPILED_FACTS_V1: [Option; CODES] = { + let mut t = [None; CODES]; + let mut c = 0; + while c < CODES { + t[c] = facts_v1(c as u8); + c += 1; + } + t +}; + +/// A set of raw5 codes as one machine word: bit `r` stands for the state +/// whose raw5 is `r`. The 5-bit field says where ONE edge stands; a +/// population says which part of the whole 32-code space satisfies a +/// condition. Transient — computed, never stored on an edge. +pub type Population = u32; + +/// The V1 codes whose facts include every fact in `required`. +/// +/// Conjunction is intersection: `facts_population(a | b) == +/// facts_population(a) & facts_population(b)`. Reserved codes (24..31) +/// never appear — they have no facts, so they satisfy nothing, not even +/// `required == 0` (whose population is the 24 meaningful states). A +/// requirement no state meets (e.g. `DIRECT | INDIRECT`) yields `0`. +#[must_use] +pub const fn facts_population(required: Facts) -> Population { + let mut population = 0; + let mut raw = 0; + while raw < CODES { + if let Some(facts) = COMPILED_FACTS_V1[raw] { + if facts & required == required { + population |= 1 << raw; + } + } + raw += 1; + } + population +} + +/// A reading generation. V1 is the 2 × 3 product above. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Epi5Gen { + /// `Topology2 × Certification3`. + #[default] + V1, +} + +/// A coordinate pair validated under a generation. Only constructible +/// validated, so holding one means the state is meaningful. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EpistemicState5 { + topology: Topology2, + certification: Certification3, + generation: Epi5Gen, +} + +impl EpistemicState5 { + /// The state at `(topology, certification)`. Every pair is meaningful in V1. + #[must_use] + pub const fn new( + generation: Epi5Gen, + topology: Topology2, + certification: Certification3, + ) -> Self { + Self { + topology, + certification, + generation, + } + } + + /// Decode a raw code: `topology = raw5 & 0b11`, `certification = raw5 >> 2`. + /// Refuses a reserved certification. No provenance or class check: use + /// [`Epi5Declarations::project_state5`] to read an edge. + pub const fn decode(generation: Epi5Gen, raw5: u8) -> Result { + if raw5 as usize >= CODES { + return Err(Epi5ReadError::UndeclaredCode(raw5)); + } + let topology = match Topology2::from_ordinal(raw5 & 0b11) { + Some(t) => t, + None => return Err(Epi5ReadError::UndeclaredCode(raw5)), + }; + match Certification3::from_code(raw5 >> 2) { + Some(c) => Ok(Self::new(generation, topology, c)), + None => Err(Epi5ReadError::UndeclaredCode(raw5)), + } + } + + /// The raw 5-bit code, for `CausalEdge64::with_epistemic_raw5`. + #[must_use] + pub const fn raw(self) -> u8 { + self.topology.ordinal() | (self.certification.code() << 2) + } + + /// The topology coordinate. + #[must_use] + pub const fn topology(self) -> Topology2 { + self.topology + } + + /// The certification coordinate. + #[must_use] + pub const fn certification(self) -> Certification3 { + self.certification + } + + /// The generation the state was validated under. + #[must_use] + pub const fn generation(self) -> Epi5Gen { + self.generation + } + + /// `TOPOLOGY_FACTS[t] | CERTIFICATION_FACTS[c]`. + #[must_use] + pub const fn facts(self) -> Facts { + self.topology.facts() | self.certification.facts() + } + + /// This state's bit in a [`Population`]: `1 << raw()`. + #[must_use] + pub const fn bit(self) -> Population { + 1 << self.raw() + } + + /// Does the state assert every fact in `required`? + #[must_use] + pub const fn asserts(self, required: Facts) -> bool { + self.facts() & required == required + } + + /// Factor update: change the topology, hold the certification. A legal + /// product-space transition; moves only bits 59..60 once written. + #[must_use] + pub const fn with_topology(self, topology: Topology2) -> Self { + Self { topology, ..self } + } + + /// Factor update: change the certification, hold the topology. Moves only + /// bits 61..63 once written. Whether the CHANGE is earned is the evidence + /// path's business; this only keeps the coordinate well-formed. + #[must_use] + pub const fn with_certification(self, certification: Certification3) -> Self { + Self { + certification, + ..self + } + } +} + +/// A class's declaration for bits 59..63. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Epi5Reading { + /// The generation the class's producers write. + pub generation: Epi5Gen, +} + +impl Epi5Reading { + /// Project a raw code under THIS declaration (no table lookup, no + /// allocation): provenance, then generation, then code. + pub const fn project( + self, + requested: Epi5Gen, + raw5: u8, + provenance: EdgeProvenance, + ) -> Result { + if !provenance.trusted() { + return Err(Epi5ReadError::UnknownProvenance(provenance)); + } + if !matches!((self.generation, requested), (Epi5Gen::V1, Epi5Gen::V1)) { + return Err(Epi5ReadError::GenerationMismatch { + declared: self.generation, + requested, + }); + } + EpistemicState5::decode(requested, raw5) + } +} + +/// Why a projection refused. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Epi5ReadError { + /// No declaration for this `(classid, rail)`. + UndeclaredClass(ClassId), + /// The class declares a different generation than the reader understands. + GenerationMismatch { + /// What the class's producers write. + declared: Epi5Gen, + /// What the reader asked for. + requested: Epi5Gen, + }, + /// `V1Legacy` or `Unknown` provenance: the bits may be old `temporal`. + UnknownProvenance(EdgeProvenance), + /// A reserved certification (codes 24..31) or a value above 31. + UndeclaredCode(u8), +} + +/// `(classid, rail) → Epi5Reading`, caller-populated (an OGAR mint / bake +/// decision populates it; this crate never pre-fills a class). +#[derive(Debug, Clone, Default)] +pub struct Epi5Declarations { + entries: Vec<((ClassId, RailAxis), Epi5Reading)>, +} + +impl Epi5Declarations { + /// An empty table. + #[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. + pub fn declare(&mut self, class: ClassId, rail: RailAxis, reading: Epi5Reading) -> 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. + #[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 canonical projection of bits 59..63: declaration, then provenance, + /// generation and code ([`Epi5Reading::project`]). + pub fn project_state5( + &self, + class: ClassId, + rail: RailAxis, + requested: Epi5Gen, + raw5: u8, + provenance: EdgeProvenance, + ) -> Result { + self.get(class, rail) + .ok_or(Epi5ReadError::UndeclaredClass(class))? + .project(requested, raw5, provenance) + } +} + +#[cfg(test)] +mod tests { + use super::fact::*; + use super::*; + use Certification3 as C; + use Topology2 as T; + + const CLASS: ClassId = 0x0902; + const RAIL: RailAxis = RailAxis::Taxonomy; + + fn decl() -> Epi5Declarations { + let mut d = Epi5Declarations::new(); + d.declare(CLASS, RAIL, Epi5Reading::default()); + d + } + + fn code(t: T, c: C) -> u8 { + EpistemicState5::new(Epi5Gen::V1, t, c).raw() + } + + /// The layout is the product: `raw5 = t | c << 2`, 24 meaningful, 8 + /// reserved, and decode inverts it on every code. + #[test] + fn the_layout_is_the_two_by_three_product() { + let mut meaningful = 0; + for raw in 0u8..32 { + match EpistemicState5::decode(Epi5Gen::V1, raw) { + Ok(s) => { + meaningful += 1; + assert_eq!(s.raw(), raw); + assert_eq!(s.topology().ordinal(), raw & 0b11); + assert_eq!(s.certification().code(), raw >> 2); + } + Err(e) => { + assert!(raw >= 24, "only certifications 6, 7 are reserved"); + assert_eq!(e, Epi5ReadError::UndeclaredCode(raw)); + } + } + } + assert_eq!(meaningful, MEANINGFUL_STATES); + } + + /// The operator's example coordinates. + #[test] + fn the_operator_examples_land_where_stated() { + let pins = [ + (T::Direct, C::Open, 0), + (T::Direct, C::Associated, 4), + (T::Direct, C::Related, 8), + (T::Direct, C::Supports, 12), + (T::Direct, C::CausalCandidate, 16), + (T::Direct, C::Causes, 20), + (T::IndirectKnown, C::Related, 9), + (T::IndirectKnown, C::Supports, 13), + (T::IndirectKnown, C::CausalCandidate, 17), + (T::IndirectKnown, C::Causes, 21), + (T::IndirectUnknown, C::Related, 10), + (T::IndirectUnknown, C::Supports, 14), + (T::IndirectUnknown, C::Causes, 22), + ]; + for (t, c, want) in pins { + assert_eq!(code(t, c), want, "{t:?} × {c:?}"); + } + } + + /// Facts are the union of the factors' facts, pinned by hand on a few + /// states so the factor tables cannot drift silently. + #[test] + fn facts_are_the_union_of_the_factors() { + let f = |raw| facts_v1(raw).unwrap(); + assert_eq!(f(10), IND_UNKNOWN | ASSOCIATED | RELATED); + assert_eq!(f(13), IND_KNOWN | UP_TO_SUPPORTS); + assert_eq!(f(4), DIRECT | ASSOCIATED); + assert_eq!( + f(21), + IND_KNOWN | UP_TO_SUPPORTS | CAUSAL_CANDIDATE | CAUSES + ); + for raw in 0u8..24 { + let s = EpistemicState5::decode(Epi5Gen::V1, raw).unwrap(); + assert_eq!(s.facts() & TOPOLOGY_MASK, s.topology().facts()); + assert_eq!(s.facts() & CERTIFICATION_MASK, s.certification().facts()); + assert_eq!(COMPILED_FACTS_V1[raw as usize], Some(s.facts())); + } + assert!(COMPILED_FACTS_V1[24..].iter().all(Option::is_none)); + } + + /// Orthogonality: an intervention-certified relation with an unresolved + /// mechanism or topology is a meaningful state. + #[test] + fn causes_is_meaningful_under_every_topology() { + for t in T::ALL { + let s = EpistemicState5::decode(Epi5Gen::V1, code(t, C::Causes)).unwrap(); + assert!(s.asserts(CAUSES)); + } + assert!(EpistemicState5::decode(Epi5Gen::V1, 22) + .unwrap() + .asserts(IND_UNKNOWN | CAUSES)); + assert!(EpistemicState5::decode(Epi5Gen::V1, 23) + .unwrap() + .asserts(TOPOLOGY_UNKNOWN | CAUSES)); + } + + /// Certification entailment is the cumulative chain (P7a's `>=` on 0..=5). + #[test] + fn certification_entailment_is_the_measured_chain() { + for a in C::ALL { + for b in C::ALL { + assert_eq!(a.entails(b), a.code() >= b.code(), "{a:?} vs {b:?}"); + } + } + } + + /// A factor update moves only its own coordinate. + #[test] + fn factor_updates_move_only_their_coordinate() { + for t in T::ALL { + for c in C::ALL { + let s = EpistemicState5::new(Epi5Gen::V1, t, c); + for t2 in T::ALL { + let m = s.with_topology(t2); + assert_eq!(m.certification(), c); + assert_eq!((m.raw() ^ s.raw()) & !0b11, 0); + } + for c2 in C::ALL { + let m = s.with_certification(c2); + assert_eq!(m.topology(), t); + assert_eq!((m.raw() ^ s.raw()) & 0b11, 0); + } + } + } + } + + #[test] + fn projection_refuses_every_failure_and_admits_the_good_case() { + let d = decl(); + let s = d + .project_state5(CLASS, RAIL, Epi5Gen::V1, 10, EdgeProvenance::V3Register) + .unwrap(); + assert_eq!( + (s.topology(), s.certification()), + (T::IndirectUnknown, C::Related) + ); + assert_eq!( + d.project_state5(0x0999, RAIL, Epi5Gen::V1, 10, EdgeProvenance::V2Stamped), + Err(Epi5ReadError::UndeclaredClass(0x0999)) + ); + for p in [EdgeProvenance::V1Legacy, EdgeProvenance::Unknown] { + assert_eq!( + d.project_state5(CLASS, RAIL, Epi5Gen::V1, 10, p), + Err(Epi5ReadError::UnknownProvenance(p)) + ); + } + for reserved in [24u8, 27, 28, 31] { + assert_eq!( + d.project_state5( + CLASS, + RAIL, + Epi5Gen::V1, + reserved, + EdgeProvenance::V2Stamped + ), + Err(Epi5ReadError::UndeclaredCode(reserved)) + ); + } + assert_eq!( + EpistemicState5::decode(Epi5Gen::V1, 32), + Err(Epi5ReadError::UndeclaredCode(32)) + ); + } + + /// Population of `required` built the other way: enumerate the 24 + /// coordinate pairs and ask each state. + fn population_by_states(required: Facts) -> Population { + let mut p = 0; + for c in C::ALL { + for t in T::ALL { + let s = EpistemicState5::new(Epi5Gen::V1, t, c); + if s.asserts(required) { + p |= s.bit(); + } + } + } + p + } + + /// D-EPI-POP-0: the populations of the stated questions. + #[test] + fn populations_of_the_stated_questions() { + let pop = facts_population; + assert_eq!(pop(CAUSES).count_ones(), 4, "the Causes column"); + assert_eq!( + pop(RELATED).count_ones(), + 16, + "Related-or-stronger x 4 topologies" + ); + assert_eq!(pop(IND_UNKNOWN | RELATED).count_ones(), 4); + assert_eq!(pop(IND_KNOWN | SUPPORTS).count_ones(), 3); + assert_eq!(pop(TOPOLOGY_UNKNOWN | CAUSES), 1 << 23); + // The exact sets, not only their sizes: two different populations of + // equal size must not pass for each other. + let set = |codes: &[u32]| codes.iter().fold(0, |p, c| p | 1 << c); + assert_eq!(pop(CAUSES), set(&[20, 21, 22, 23])); + assert_eq!(pop(IND_UNKNOWN | RELATED), set(&[10, 14, 18, 22])); + assert_eq!(pop(IND_KNOWN | SUPPORTS), set(&[13, 17, 21])); + assert_eq!(pop(RELATED), set(&(8..24).collect::>())); + assert_eq!(pop(DIRECT | ASSOCIATED), set(&[4, 8, 12, 16, 20])); + assert_eq!(pop(TOPOLOGY_UNKNOWN | CAUSES), set(&[23])); + // Two causal claims whose mechanism is not established stay separately + // addressable: unknown topology vs a known-to-exist, unidentified + // intermediate. + let unknown_causes = pop(TOPOLOGY_UNKNOWN | CAUSES); + let unresolved_causes = pop(IND_UNKNOWN | CAUSES); + assert_eq!(unresolved_causes, set(&[22])); + assert_eq!(unknown_causes & unresolved_causes, 0); + assert_eq!( + pop(CAUSES) & pop(IND_UNKNOWN), + EpistemicState5::new(Epi5Gen::V1, T::IndirectUnknown, C::Causes).bit() + ); + // The empty requirement selects every meaningful state and no + // reserved one; an impossible requirement selects nothing. + assert_eq!(pop(0), (1 << MEANINGFUL_STATES) - 1); + assert_eq!(pop(DIRECT | INDIRECT), 0); + } + + /// Over every requirement made of the declared facts, the operator + /// equals the state enumeration and never selects a reserved code. + #[test] + fn population_equals_the_state_enumeration_for_every_requirement() { + let all = TOPOLOGY_MASK | CERTIFICATION_MASK; + let mut sub = all; + let mut checked = 0; + loop { + let p = facts_population(sub); + assert_eq!(p, population_by_states(sub), "requirement {sub:#x}"); + assert_eq!(p >> MEANINGFUL_STATES, 0, "a reserved code appeared"); + // The defining statement, code by code against `facts_v1`. + for raw in 0..CODES as u8 { + let member = p >> raw & 1 == 1; + let asserts = match facts_v1(raw) { + Some(f) => f & sub == sub, + None => false, + }; + assert_eq!(member, asserts, "raw {raw}, requirement {sub:#x}"); + } + checked += 1; + if sub == 0 { + break; + } + sub = (sub - 1) & all; + } + assert_eq!(checked, 1 << all.count_ones()); + } + + /// Conjunction of requirements is intersection of populations. + #[test] + fn conjunction_is_intersection() { + let all = TOPOLOGY_MASK | CERTIFICATION_MASK; + for a in 0..Facts::BITS { + for b in 0..Facts::BITS { + let (fa, fb) = (1 << a & all, 1 << b & all); + assert_eq!( + facts_population(fa | fb), + facts_population(fa) & facts_population(fb) + ); + } + } + } + + /// Every requirement made only of facts in `mask` (every submask). + fn requirements_within(mask: Facts) -> Vec { + let mut out = Vec::new(); + let mut sub = mask; + loop { + out.push(sub); + if sub == 0 { + return out; + } + sub = (sub - 1) & mask; + } + } + + fn member(s: EpistemicState5, required: Facts) -> bool { + facts_population(required) & s.bit() != 0 + } + + /// A topology update holding the certification never changes membership + /// in a certification-only population, and lands exactly where the new + /// topology's states are in topology-only populations. + #[test] + fn a_topology_update_moves_only_topology_conditioned_membership() { + let cert_only = requirements_within(CERTIFICATION_MASK); + let topo_only = requirements_within(TOPOLOGY_MASK); + let mut moved = 0; + for c in C::ALL { + for t in T::ALL { + let s = EpistemicState5::new(Epi5Gen::V1, t, c); + for t2 in T::ALL { + let m = s.with_topology(t2); + for &f in &cert_only { + assert_eq!(member(s, f), member(m, f), "{s:?} -> {t2:?}, {f:#x}"); + } + for &f in &topo_only { + let want = member(EpistemicState5::new(Epi5Gen::V1, t2, C::Open), f); + assert_eq!(member(m, f), want); + moved += usize::from(member(s, f) != member(m, f)); + } + } + } + } + assert!(moved > 0, "no topology-conditioned membership ever moved"); + } + + /// A certification update holding the topology never changes membership + /// in a topology-only population. + #[test] + fn a_certification_update_moves_only_certification_conditioned_membership() { + let cert_only = requirements_within(CERTIFICATION_MASK); + let topo_only = requirements_within(TOPOLOGY_MASK); + let mut moved = 0; + for t in T::ALL { + for c in C::ALL { + let s = EpistemicState5::new(Epi5Gen::V1, t, c); + for c2 in C::ALL { + let m = s.with_certification(c2); + for &f in &topo_only { + assert_eq!(member(s, f), member(m, f), "{s:?} -> {c2:?}, {f:#x}"); + } + for &f in &cert_only { + let want = member(EpistemicState5::new(Epi5Gen::V1, T::Direct, c2), f); + assert_eq!(member(m, f), want); + moved += usize::from(member(s, f) != member(m, f)); + } + } + } + } + assert!( + moved > 0, + "no certification-conditioned membership ever moved" + ); + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index de40891bb..896af55b9 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -55,6 +55,7 @@ pub mod attention_facet; pub mod auth; pub mod awareness_facet; pub mod band_reading; +pub mod epistemic_state5; // D-TEH-1: cross-tenant authorization injection point (moved from thinking-engine, // seven items, zero-dep). Consumed by lance-graph-callcenter's UnifiedBridgeGate. pub mod bridge_gate; diff --git a/crates/lance-graph-planner/examples/probe_four_plane_causal_medium.rs b/crates/lance-graph-planner/examples/probe_four_plane_causal_medium.rs index 0a7bd8c7c..52d9751fc 100644 --- a/crates/lance-graph-planner/examples/probe_four_plane_causal_medium.rs +++ b/crates/lance-graph-planner/examples/probe_four_plane_causal_medium.rs @@ -58,6 +58,24 @@ //! an AoS test object holding the lanes side by side so the gates can //! compare them; it is NOT evidence about the resident SoA layout and must //! never be cited as such. +//! +//! D-EPI-MIG-0 (2026-10-07): PROBE-ONLY legacy. Under the canonical +//! Cartesian reading (`EpistemicState5 = Topology2 × Certification3`) the +//! topology half this probe writes is canonical as-is, but the 61..63 half is +//! written through the historical `ReasoningBand` names (reasoning levels: +//! `Surface … Transcendent`), which have no declared mapping onto the +//! certification coordinate. Mapping `Causal` to `Supports` or `Causes` would +//! be semantic invention, so the probe is not migrated; it keeps the +//! deprecated band writer as a record of that reading. No production code may +//! follow it. +//! +//! D-EPI-LEGACY-DEPROJECT-0 (2026-10-07): what the band meant here is a +//! LENS — "through what reasoning lens is this hypothesis read" — which is +//! execution context (`RungLevel` / thinking style), not a certification, and +//! has no CE64 home. Its checks are non-interference of that plane. Moving +//! them onto `Certification3` would rewrite the probe's four-plane thesis, so +//! it stays a record (see `entries/2026-10-07-epi-legacy-deprojection.md`). +#![allow(deprecated)] use causal_edge::layout::{CausalTopology, ReasoningBand}; use causal_edge::CausalEdge64; diff --git a/crates/lance-graph-planner/src/dismech_counterfactual.rs b/crates/lance-graph-planner/src/dismech_counterfactual.rs index b5f8b55e3..bb009a51b 100644 --- a/crates/lance-graph-planner/src/dismech_counterfactual.rs +++ b/crates/lance-graph-planner/src/dismech_counterfactual.rs @@ -32,11 +32,14 @@ //! # Precision about the answer, not just its polarity //! //! A verdict that collapsed to `bool` would throw away what kind of relation -//! was cut. [`EdgeRole`] carries the cut edge's own `CausalTopology` (bits -//! 59-60) and `ReasoningBand` (bits 61-63) alongside the verdict, so a -//! consumer can tell "the edge that EXPLAINS this chain is load-bearing" from -//! "an edge that merely RELATES TO it is". Same bits, read through their own -//! lenses; nothing is re-derived. +//! was cut. [`EdgeRole`] carries the cut edge's bits 59..63 as one raw +//! `EpistemicState5` code alongside the verdict, so a consumer can tell "the +//! edge that EXPLAINS this chain is load-bearing" from "an edge that merely +//! RELATES TO it is". That is a certification question (`Causes` vs +//! `Related`), and it is answered by projecting the code through the cut +//! edge's class declaration — e.g. membership in +//! `epistemic_state5::facts_population(fact::CAUSES)` — never by a +//! historical lens. Nothing is re-derived here. use causal_edge::edge::InferenceType; use causal_edge::tables::NarsTables; @@ -136,10 +139,17 @@ pub struct EdgeRole { pub factual: Verdict, /// Verdict with the edge cut. pub counterfactual: Verdict, - /// The cut edge's causal topology (bits 59-60, `CausalTopology` lens). + /// The cut edge's bits 59..63 as ONE raw `EpistemicState5` code + /// (D-EPI-CANON-0). Opaque here: its meaning is the declaration of the + /// cut edge's `(classid, rail)`, projected through + /// `lance_graph_contract::epistemic_state5::Epi5Declarations::project_state5` + /// by a caller that knows the class. This is the canonical field. + pub epistemic_raw5: u8, + /// The topology coordinate of `epistemic_raw5` (bits 59..60), read with + /// the `CausalTopology` ordinals — the same ordinals as the canonical + /// `Topology2`. Raw: valid as the canonical topology only once the cut + /// edge's class is known to declare the canonical reading. pub topology: causal_edge::layout::CausalTopology, - /// The cut edge's reasoning band (bits 61-63). - pub band: causal_edge::layout::ReasoningBand, } impl EdgeRole { @@ -248,8 +258,8 @@ pub fn counterfactual_replay( predicate, factual: verdict_at(&factual, bar), counterfactual: verdict_at(&counterfactual, bar), + epistemic_raw5: cut_edge.epistemic_raw5(), topology: cut_edge.topology(), - band: cut_edge.reasoning_band(), }, factual, counterfactual, @@ -526,34 +536,29 @@ mod tests { assert_eq!(*f.last().unwrap() + 1, c[0]); } - /// The verdict carries the cut edge's own relation flavour, so "the edge - /// that EXPLAINS this is load-bearing" stays distinguishable from "an edge - /// that merely RELATES TO it is". Read through the lenses, never - /// re-derived. + /// The verdict carries the cut edge's own epistemic coordinate, so "the + /// edge that EXPLAINS this is load-bearing" stays distinguishable from "an + /// edge that merely RELATES TO it is". Under the canonical V1 reading that + /// is `Causes` vs `Related` at the same topology; the role reports the code + /// and the consumer asks the population, never a historical lens. #[test] - fn the_role_reports_the_cut_edges_own_topology_and_band_not_a_bare_boolean() { - use causal_edge::layout::{CausalTopology, ReasoningBand}; + fn the_role_reports_the_cut_edges_own_coordinate_not_a_bare_boolean() { + use causal_edge::layout::CausalTopology; + use lance_graph_contract::epistemic_state5::{ + fact, facts_population, Certification3, Epi5Gen, EpistemicState5, Topology2, + }; let (tables, t) = fixture(); let tabs = ComposeTables { s: &t[0], p: &t[1], o: &t[2], }; + let at = |c| EpistemicState5::new(Epi5Gen::V1, Topology2::IndirectKnown, c); + let (explains, relates) = (at(Certification3::Causes), at(Certification3::Related)); - // Two chains differing ONLY in the cut edge's lens bits. - let plain = edge_with(250, 250); - let flavoured = plain - .with_topology(CausalTopology::IndirectKnownIntermediates) - .with_reasoning_band(ReasoningBand::Causal); - - for (edge, want_topo, want_band) in [ - (plain, plain.topology(), plain.reasoning_band()), - ( - flavoured, - CausalTopology::IndirectKnownIntermediates, - ReasoningBand::Causal, - ), - ] { + // Two chains differing ONLY in the cut edge's certification. + for (state, raw5, explains_chain) in [(explains, 21, true), (relates, 9, false)] { + let edge = edge_with(250, 250).with_epistemic_raw5(state.raw()); let chain: Vec = vec![ (0x90, edge_with(250, 250)), (0x9D, edge), @@ -573,15 +578,21 @@ mod tests { ) .expect("in range") .expect("fits"); - assert_eq!(cf.role.topology, want_topo); - assert_eq!(cf.role.band, want_band); + // The coordinate itself ... + assert_eq!(cf.role.epistemic_raw5, raw5); + let read = EpistemicState5::decode(Epi5Gen::V1, cf.role.epistemic_raw5).unwrap(); + assert_eq!(read.topology(), Topology2::IndirectKnown); + assert_eq!(read.certification(), state.certification()); + assert_eq!(cf.role.topology, CausalTopology::IndirectKnownIntermediates); + // ... and the question a consumer asks of it. + assert_eq!( + facts_population(fact::CAUSES) & read.bit() != 0, + explains_chain + ); assert_eq!(cf.role.predicate, 0x9D, "perturbs"); } - - // Anti-vacuity: the two flavours must actually DIFFER, or the loop - // above compares a value with itself twice. - assert_ne!(plain.topology(), CausalTopology::IndirectKnownIntermediates); - assert_ne!(plain.reasoning_band(), ReasoningBand::Causal); + // Anti-vacuity: both share RELATED, so only CAUSES tells them apart. + assert!(explains.asserts(fact::RELATED) && relates.asserts(fact::RELATED)); } /// An out-of-range cut is refused at the top level too, not just in