From c7aeddda1702a26217ab0210ca7910e9783a0306 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 10:44:19 +0000 Subject: [PATCH 01/17] D-EPI-MIG-INVENTORY: inventory every reader and writer of CE64 bits 59..63 Wave 1 of the EpistemicState5 migration, taken on main @1c91fa4 before any semantic change. Classifies each site MIGRATE / PROJECT / WRAP / REFUSE / PROBE-ONLY, lists false positives, and states why the historical ReasoningBand and TrustTexture readings are refused rather than translated. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../entries/2026-10-07-epi-mig-inventory.md | 89 +++++++++++++++++++ .claude/board/entries/README.md | 3 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 .claude/board/entries/2026-10-07-epi-mig-inventory.md 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..b520dd2b1 --- /dev/null +++ b/.claude/board/entries/2026-10-07-epi-mig-inventory.md @@ -0,0 +1,89 @@ +# 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. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 7fa76d5c8..ff816a160 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,13 +25,14 @@ 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. +244 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-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) | From 440bcea4c62aec036221dcb3bc5c188c23ea1716 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 10:51:53 +0000 Subject: [PATCH 02/17] D-EPI-CANON-0: canonical joint EpistemicState5 reading of CE64 bits 59..63 contract::epistemic_state5 is the one authoritative codebook (V1 = the ten measured #1370 codes), declared per (classid, rail, generation); projection refuses undeclared class, generation mismatch, untrusted provenance and undeclared codes. The P7a certification band survives only as an explicit legacy translation (grounding x certification -> code) with compatibility writers that resolve a whole state or refuse. ClassView gains epistemic_reading (default None). band_reading is regraded legacy. causal-edge gains the joint physical accessor/writer (epistemic_raw5 / with_epistemic_raw5), the V3 lift restores the field jointly, and the half-field writers are deprecated. Disable runs: an extra translation cell and a grounding-free recertify each turn tests red. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/LATEST_STATE.md | 18 + .claude/board/STATUS_BOARD.md | 3 + crates/causal-edge/src/edge.rs | 103 +++ crates/causal-edge/src/edge_v3.rs | 46 +- crates/causal-edge/src/layout.rs | 14 + crates/causal-edge/src/v2_layout_tests.rs | 63 ++ .../lance-graph-contract/src/band_reading.rs | 11 + crates/lance-graph-contract/src/class_view.rs | 19 + .../src/epistemic_state5.rs | 703 ++++++++++++++++++ crates/lance-graph-contract/src/lib.rs | 1 + 10 files changed, 971 insertions(+), 10 deletions(-) create mode 100644 crates/lance-graph-contract/src/epistemic_state5.rs diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 43ed000ac..4d2dfb827 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,21 @@ +## 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`: `fact::*` + (DIRECT … CAUSES), `Facts`, `CODEBOOK_V1` (the 10 measured #1370 codes), + `code_v1::*`, `Epi5Gen::{V1}` (`codebook()`), `EpistemicState5` + (`decode`, `raw`, `generation`, `facts`, `asserts`), `Epi5Reading`, + `Epi5ReadError`, `Epi5Declarations::{declare, get, project_state5}`, and + `legacy::{LegacyTopology, LegacyCertification, LegacyError, translate, + project_topology, project_certification, recertify, retopologize}`. +- `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_truth`/`set_truth`/`with_topology`/ + `with_routing`/`with_spare`/`set_spare`/`with_reasoning_band` `#[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..745c6bf6a 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -44,6 +44,9 @@ 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-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` (codebook V1, declarations, `project_state5`, legacy translation); `CausalEdge64::{epistemic_raw5, with_epistemic_raw5}`; half writers deprecated | +| **D-EPI-MIG-0** | Migrate readers/writers to the canonical reading; #1378 → conformance | In PR | see `entries/2026-10-07-epi-mig-0.md` | | **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-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/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 7d9084f09..79e44c4be 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 = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_truth(self, t: crate::layout::TrustTexture) -> Self { use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; @@ -1039,6 +1076,10 @@ impl CausalEdge64 { /// — the last writer of either view wins, by construction, since it is /// one 2-bit register read through two lenses. See /// [`crate::layout::CausalTopology`] for the compatibility statement. + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_topology(self, topo: crate::layout::CausalTopology) -> Self { use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; @@ -1058,6 +1099,10 @@ impl CausalEdge64 { } /// Return new edge with spare bits set (0..=7, 3-bit field). + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_spare(self, s: u8) -> Self { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; @@ -1087,6 +1132,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 = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_reasoning_band(self, band: crate::layout::ReasoningBand) -> Self { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; @@ -1099,6 +1148,10 @@ impl CausalEdge64 { /// 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 = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[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 +1172,10 @@ impl CausalEdge64 { } /// Set truth-band lens in-place. + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn set_truth(&mut self, t: crate::layout::TrustTexture) { use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; @@ -1135,6 +1192,10 @@ impl CausalEdge64 { } /// Set spare bits in-place (0..=7, 3-bit field). + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn set_spare(&mut self, s: u8) { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; @@ -1189,14 +1250,33 @@ 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 = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_truth(self, _t: crate::layout::TrustTexture) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_topology(self, _topo: crate::layout::CausalTopology) -> Self { self @@ -1205,24 +1285,44 @@ impl CausalEdge64 { pub fn with_inference_mantissa(self, _m: i8) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_spare(self, _s: u8) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[inline] pub fn with_reasoning_band(self, _band: crate::layout::ReasoningBand) -> Self { self } + #[deprecated( + since = "0.2.0", + note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[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 = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[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 = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + )] #[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..da0f8986e 100644 --- a/crates/causal-edge/src/edge_v3.rs +++ b/crates/causal-edge/src/edge_v3.rs @@ -248,6 +248,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,16 +287,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()); + // Bits 59..63 are one field: restore it jointly (D-EPI-CANON-0), so + // the lift can never write half of an EpistemicState5 code. + let edge = edge.with_epistemic_raw5(self.epistemic_raw5()); edge } @@ -341,6 +344,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 +877,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..438d78158 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -87,6 +87,20 @@ 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 2-bit [`TRUTH_SHIFT`] and 3-bit +/// [`SPARE_SHIFT`] halves are its low and high parts, never two fields. +/// 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..17f2ae760 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,63 @@ 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): a half-field write turns one valid-looking code + /// into ANOTHER valid-looking code. Code 1 with topology half set to 3 + /// becomes code 3; code 12 with band half set to 5 becomes code 20. + /// The bits cannot refuse; only a declared reading can (contract crate). + #[test] + fn a_half_write_silently_produces_a_different_code() { + let causes = CausalEdge64::ZERO.with_epistemic_raw5(1); + let moved = causes.with_truth(crate::layout::TrustTexture::from_bits_2(3)); + assert_eq!(moved.epistemic_raw5(), 3); + let assoc = CausalEdge64::ZERO.with_epistemic_raw5(12); + let moved = assoc.with_reasoning_band(crate::layout::ReasoningBand::from_bits_3(5)); + assert_eq!(moved.epistemic_raw5(), 20); + } + + /// 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/lance-graph-contract/src/band_reading.rs b/crates/lance-graph-contract/src/band_reading.rs index 9262e269a..ae41d0324 100644 --- a/crates/lance-graph-contract/src/band_reading.rs +++ b/crates/lance-graph-contract/src/band_reading.rs @@ -4,6 +4,17 @@ //! `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`, read +//! > through [`epistemic_state5`](crate::epistemic_state5). The split reading +//! > below (59..60 lens + 61..63 band) is a **legacy compatibility surface**: +//! > it may still project the halves of an edge whose class has not migrated, +//! > but it is not a peer authority and new readers must not use it. The P7a +//! > certification band translates through `epistemic_state5::legacy`; the +//! > historical `ReasoningBand` / `TrustTexture` readings have no canonical +//! > translation. [`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..61e916b98 --- /dev/null +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -0,0 +1,703 @@ +// 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, one reading +//! +//! Bits 59..63 are ONE 5-bit field: an `EpistemicState5` code, a dense +//! codebook over valid epistemic conjunctions (grounding × assertion). The +//! code is semantically opaque on its own — **raw ordinal + declared reading +//! = meaning** — so every read goes through a declaration keyed by +//! `(classid, rail)` and a codebook generation: +//! +//! ```text +//! (classid, rail) → Epi5Reading { generation } +//! → project_state5(raw5, provenance) → EpistemicState5 | refusal +//! ``` +//! +//! The physical read is `CausalEdge64::epistemic_raw5()` (equal to +//! `(spare() << 2) | truth_raw()`), the physical write +//! `CausalEdge64::with_epistemic_raw5(state.raw())`. This crate is zero-dep and +//! never touches the edge layout; it takes and returns raw ordinals, like +//! [`band_reading`](crate::band_reading). +//! +//! # The codebook is also the affordance law's input +//! +//! A code's [facts](fact) decide which statistical/microcode recipes are legal +//! on the edge (`CausalEdge64 × RecipeLaw[64] → EligibleRecipes: u64`, the +//! D-GSO-AFF-0 measurement). For example `25 = Indirect × IntermediateUnknown × +//! Related` admits hydration of the intermediate but never a known-intermediate +//! mechanism fold; `30 = Indirect × IntermediateKnown × Supports` admits the +//! mechanism fold and causal identification. Eligibility is transient +//! measurement; nothing here stores a capability. +//! +//! # Supersedes the split reading +//! +//! [`band_reading`](crate::band_reading) declared 59..60 (`TruthLens`) and +//! 61..63 (`BandPresence`) as two fields. That split no longer owns these bits. +//! The P7a certification band (#1369), which wrote 61..63 alone, survives as a +//! [legacy](legacy) translation into this codebook; the historical +//! `ReasoningBand` and `TrustTexture` readings have **no** declared +//! translation and refuse (their producers never declared conjunction +//! semantics, and the bits cannot say which reading wrote them). +//! +//! # Refusals +//! +//! [`Epi5Declarations::project_state5`] refuses, never guesses: undeclared +//! `(classid, rail)`, a generation other than the one requested, untrusted +//! provenance (`V1Legacy` / `Unknown` — on a v1 row these bits are old +//! `temporal`), and a code the generation does not declare. + +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; +const _: () = assert!(CODES == 1 << 5); + +/// A conjunction of epistemic facts — the meaning of one declared code. +pub type Facts = u32; + +/// The fact vocabulary. A code's meaning is a conjunction of these; the +/// affordance law states each recipe's obligations in the same vocabulary. +pub mod fact { + use super::Facts; + + /// The relation is grounded directly (no intermediate). + pub const DIRECT: Facts = 1 << 0; + /// The relation runs through at least one intermediate. + pub const INDIRECT: Facts = 1 << 1; + /// An intermediate is part of the claim. + pub const INTERMEDIATE_PRESENT: Facts = 1 << 2; + /// The intermediate is not known (hydration is the open work). + pub const INTERMEDIATE_UNKNOWN: Facts = 1 << 3; + /// The intermediate is known (a mechanism can be folded/tested). + pub const INTERMEDIATE_KNOWN: Facts = 1 << 4; + /// The relation was observed on units, not only asserted. + pub const OBSERVED: Facts = 1 << 5; + /// Associated in a declared population. + pub const ASSOCIATED: Facts = 1 << 6; + /// Related: the association survives the robustness mask. + pub const RELATED: Facts = 1 << 7; + /// Supports / contributes in every declared stratum. + pub const SUPPORTS: Facts = 1 << 8; + /// Certified causes (intervention-backed). + 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; +} + +/// The V1 codebook: code → facts, `None` = not a code of this generation. +/// +/// Adopted unchanged from the D-GSO-AFF-0 measurement (#1370). Deliberately +/// not ordered by strength (`Causes` sits at 1, `Open` at 0, `Related` at 20): +/// the codebook is dense over conjunctions, never an ordinal scale. +pub const CODEBOOK_V1: [Option; CODES] = { + use fact::*; + let mut t = [None; CODES]; + 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 +}; + +/// Named V1 codes. The facts are the content; the names are pointers. +pub mod code_v1 { + /// No fact asserted. Also what `pack_v2` / `compose` leave behind. + pub const OPEN: u8 = 0; + /// `Direct × Supports × Causes`. + pub const DIRECT_CAUSES: u8 = 1; + /// `Direct × Observed`. + pub const DIRECT_OBSERVED: u8 = 3; + /// `Indirect × IntermediateKnown × Related`. + pub const IND_KNOWN_RELATED: u8 = 5; + /// `Direct × Observed × Associated`. + pub const DIRECT_OBSERVED_ASSOCIATED: u8 = 7; + /// `Direct × Associated`. + pub const DIRECT_ASSOCIATED: u8 = 12; + /// `Indirect × IntermediateKnown × Supports × Causes`. + pub const IND_KNOWN_CAUSES: u8 = 17; + /// `Direct × Related`. + pub const DIRECT_RELATED: u8 = 20; + /// `Indirect × IntermediateUnknown × Related`. + pub const IND_UNKNOWN_RELATED: u8 = 25; + /// `Indirect × IntermediateKnown × Supports`. + pub const IND_KNOWN_SUPPORTS: u8 = 30; +} + +/// A codebook generation. A generation names the whole declared reading; +/// changing any code's facts is a new generation, never an edit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Epi5Gen { + /// [`CODEBOOK_V1`]. + #[default] + V1, +} + +impl Epi5Gen { + /// The generation's codebook — the ONE authoritative code → facts table. + #[must_use] + pub const fn codebook(self) -> &'static [Option; CODES] { + match self { + Epi5Gen::V1 => &CODEBOOK_V1, + } + } +} + +/// A code validated under a generation. Only obtainable through +/// [`EpistemicState5::decode`] / [`Epi5Declarations::project_state5`] / +/// a [legacy] translation, so holding one means the code is declared. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EpistemicState5 { + code: u8, + generation: Epi5Gen, +} + +impl EpistemicState5 { + /// Validate `raw5` against `generation`'s codebook. No provenance or + /// class check: use [`Epi5Declarations::project_state5`] to read an edge. + /// This is the producer-side constructor. + pub const fn decode(generation: Epi5Gen, raw5: u8) -> Result { + if raw5 as usize >= CODES || generation.codebook()[raw5 as usize].is_none() { + return Err(Epi5ReadError::UndeclaredCode(raw5)); + } + Ok(Self { + code: raw5, + generation, + }) + } + + /// The raw 5-bit code, for `CausalEdge64::with_epistemic_raw5`. + #[must_use] + pub const fn raw(self) -> u8 { + self.code + } + + /// The generation the code was validated under. + #[must_use] + pub const fn generation(self) -> Epi5Gen { + self.generation + } + + /// The code's facts. + #[must_use] + pub const fn facts(self) -> Facts { + match self.generation.codebook()[self.code as usize] { + Some(f) => f, + // Unreachable: construction validated the code. + None => 0, + } + } + + /// Does the state assert every fact in `required`? + #[must_use] + pub const fn asserts(self, required: Facts) -> bool { + self.facts() & required == required + } +} + +/// A class's declaration for bits 59..63. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Epi5Reading { + /// The codebook generation the class's producers write. + pub generation: Epi5Gen, +} + +/// 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), + /// The code is not declared by the generation. + 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. Check order: declaration, + /// provenance, generation, code — untrusted bits fail before their + /// meaning is asked. + pub fn project_state5( + &self, + class: ClassId, + rail: RailAxis, + requested: Epi5Gen, + raw5: u8, + provenance: EdgeProvenance, + ) -> Result { + let reading = self + .get(class, rail) + .ok_or(Epi5ReadError::UndeclaredClass(class))?; + if !provenance.trusted() { + return Err(Epi5ReadError::UnknownProvenance(provenance)); + } + if reading.generation != requested { + return Err(Epi5ReadError::GenerationMismatch { + declared: reading.generation, + requested, + }); + } + EpistemicState5::decode(requested, raw5) + } +} + +/// Legacy compatibility: the split readings, as translations INTO and +/// projections OUT OF the canonical codebook. Transitional — new code reads +/// [`Epi5Declarations::project_state5`] and writes a declared state. +/// +/// Only one legacy producer has declared conjunction semantics: P7a +/// certification (#1369) under a legacy `CausalTopology` grounding. Its +/// translation is an explicit table; a cell without a canonical code refuses. +/// A translation never manufactures a fact: `Causes` comes only from a +/// certified `Causes`. +pub mod legacy { + use super::{code_v1, fact, Epi5Gen, EpistemicState5}; + + /// The legacy 2-bit grounding reading. Ordinals mirror + /// `causal_edge::layout::CausalTopology` (doc pointer, never an import). + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum LegacyTopology { + /// `CausalTopology::Direct` (0). + Direct, + /// `CausalTopology::IndirectKnownIntermediates` (1). + IndirectKnown, + /// `CausalTopology::IndirectUnknownIntermediates` (2). + IndirectUnknown, + /// `CausalTopology::Unknown` (3): grounding not known. + Unknown, + } + + impl LegacyTopology { + /// All four. + pub const ALL: [LegacyTopology; 4] = [ + LegacyTopology::Direct, + LegacyTopology::IndirectKnown, + LegacyTopology::IndirectUnknown, + LegacyTopology::Unknown, + ]; + + /// The legacy ordinal. + #[must_use] + pub const fn ordinal(self) -> u8 { + self as u8 + } + + /// From the legacy ordinal (`0..=3`); `None` above. + #[must_use] + pub const fn from_ordinal(o: u8) -> Option { + match o { + 0 => Some(LegacyTopology::Direct), + 1 => Some(LegacyTopology::IndirectKnown), + 2 => Some(LegacyTopology::IndirectUnknown), + 3 => Some(LegacyTopology::Unknown), + _ => None, + } + } + } + + /// The P7a certification contract (#1369) that used to own bits 61..63. + /// Codes 6 and 7 were reserved there. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum LegacyCertification { + /// No obligation met. + Open, + /// Associated in a declared population, ≥ 2 sources. + Associated, + /// Associated and robust under the mask. + Related, + /// Does not lower Y in any stratum, raises it in one (= `SUPPORTS`). + Contributes, + /// `Contributes` plus ordering. No canonical fact yet. + CausalCandidate, + /// Intervention-certified. + Causes, + } + + impl LegacyCertification { + /// All six, in P7a's ordinal order. + pub const ALL: [LegacyCertification; 6] = [ + LegacyCertification::Open, + LegacyCertification::Associated, + LegacyCertification::Related, + LegacyCertification::Contributes, + LegacyCertification::CausalCandidate, + LegacyCertification::Causes, + ]; + + /// P7a's 3-bit code. + #[must_use] + pub const fn code(self) -> u8 { + self as u8 + } + + /// From P7a's code; 6 and 7 are reserved and refuse. + pub const fn from_code(raw: u8) -> Result { + Ok(match raw { + 0 => LegacyCertification::Open, + 1 => LegacyCertification::Associated, + 2 => LegacyCertification::Related, + 3 => LegacyCertification::Contributes, + 4 => LegacyCertification::CausalCandidate, + 5 => LegacyCertification::Causes, + other => return Err(LegacyError::ReservedCertification(other)), + }) + } + } + + /// Why a legacy translation refused. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum LegacyError { + /// The legacy pair has no canonical conjunction in this generation. + NoCanonicalState { + /// The declared grounding. + topology: LegacyTopology, + /// The declared certification. + certification: LegacyCertification, + }, + /// P7a's reserved codes 6 / 7. + ReservedCertification(u8), + /// The state asserts no grounding, so a grounding-relative update + /// has nothing to keep. + NoGrounding, + } + + /// Translate a declared legacy pair into the canonical code. The ONE + /// translation table; every cell not listed refuses. + pub const fn translate( + generation: Epi5Gen, + topology: LegacyTopology, + certification: LegacyCertification, + ) -> Result { + use LegacyCertification as C; + use LegacyTopology as T; + let code = match generation { + Epi5Gen::V1 => match (topology, certification) { + (_, C::Open) => Some(code_v1::OPEN), + (T::Direct, C::Associated) => Some(code_v1::DIRECT_ASSOCIATED), + (T::Direct, C::Related) => Some(code_v1::DIRECT_RELATED), + (T::Direct, C::Causes) => Some(code_v1::DIRECT_CAUSES), + (T::IndirectKnown, C::Related) => Some(code_v1::IND_KNOWN_RELATED), + (T::IndirectKnown, C::Contributes) => Some(code_v1::IND_KNOWN_SUPPORTS), + (T::IndirectKnown, C::Causes) => Some(code_v1::IND_KNOWN_CAUSES), + (T::IndirectUnknown, C::Related) => Some(code_v1::IND_UNKNOWN_RELATED), + _ => None, + }, + }; + match code { + Some(c) => match EpistemicState5::decode(generation, c) { + Ok(s) => Ok(s), + Err(_) => Err(LegacyError::NoCanonicalState { + topology, + certification, + }), + }, + None => Err(LegacyError::NoCanonicalState { + topology, + certification, + }), + } + } + + /// Project a canonical state onto the legacy grounding. `None` if the + /// state asserts none (`Open`). Observed states project to `Direct` + /// (lossy: `Observed` has no legacy spelling). + #[must_use] + pub const fn project_topology(state: EpistemicState5) -> Option { + let f = state.facts(); + if f & fact::DIRECT != 0 { + Some(LegacyTopology::Direct) + } else if f & fact::INTERMEDIATE_KNOWN != 0 { + Some(LegacyTopology::IndirectKnown) + } else if f & fact::INTERMEDIATE_UNKNOWN != 0 { + Some(LegacyTopology::IndirectUnknown) + } else { + None + } + } + + /// Project a canonical state onto the strongest legacy certification it + /// asserts. + #[must_use] + pub const fn project_certification(state: EpistemicState5) -> LegacyCertification { + let f = state.facts(); + if f & fact::CAUSES != 0 { + LegacyCertification::Causes + } else if f & fact::SUPPORTS != 0 { + LegacyCertification::Contributes + } else if f & fact::RELATED != 0 { + LegacyCertification::Related + } else if f & fact::ASSOCIATED != 0 { + LegacyCertification::Associated + } else { + LegacyCertification::Open + } + } + + /// Compatibility writer for a legacy certification update: keep the + /// state's grounding, replace the certification, resolve to a canonical + /// code — or refuse. Never a 3-bit overwrite. + pub const fn recertify( + state: EpistemicState5, + certification: LegacyCertification, + ) -> Result { + match project_topology(state) { + Some(t) => translate(state.generation(), t, certification), + None => match certification { + LegacyCertification::Open => Ok(state), + _ => Err(LegacyError::NoGrounding), + }, + } + } + + /// Compatibility writer for a legacy topology update: keep the state's + /// certification, replace the grounding, resolve — or refuse. Never a + /// 2-bit overwrite. + pub const fn retopologize( + state: EpistemicState5, + topology: LegacyTopology, + ) -> Result { + translate(state.generation(), topology, project_certification(state)) + } +} + +#[cfg(test)] +mod tests { + use super::legacy::*; + use super::*; + + const CLASS: ClassId = 0x0902; + const RAIL: RailAxis = RailAxis::Taxonomy; + + fn decl() -> Epi5Declarations { + let mut d = Epi5Declarations::new(); + d.declare(CLASS, RAIL, Epi5Reading::default()); + d + } + + #[test] + fn v1_declares_exactly_the_ten_measured_codes() { + let declared: Vec = (0..32u8) + .filter(|c| CODEBOOK_V1[*c as usize].is_some()) + .collect(); + assert_eq!(declared, vec![0, 1, 3, 5, 7, 12, 17, 20, 25, 30]); + } + + /// The four operator examples, pinned by facts. + #[test] + fn the_operator_examples_mean_what_they_say() { + use fact::*; + let f = |c: u8| EpistemicState5::decode(Epi5Gen::V1, c).unwrap().facts(); + assert_eq!(f(25), IND_UNKNOWN | ASSOCIATED | RELATED); + assert_eq!(f(30), IND_KNOWN | ASSOCIATED | RELATED | SUPPORTS); + assert_eq!(f(7), DIRECT | OBSERVED | ASSOCIATED); + assert_eq!(f(17), IND_KNOWN | ASSOCIATED | RELATED | SUPPORTS | CAUSES); + } + + /// F1 (contract half): every declared code projects to itself. + #[test] + fn projection_round_trips_every_declared_code() { + let d = decl(); + for c in (0..32u8).filter(|c| CODEBOOK_V1[*c as usize].is_some()) { + let s = d + .project_state5(CLASS, RAIL, Epi5Gen::V1, c, EdgeProvenance::V2Stamped) + .unwrap(); + assert_eq!(s.raw(), c); + assert_eq!(s.generation(), Epi5Gen::V1); + } + } + + #[test] + fn projection_refuses_every_failure_and_admits_the_good_case() { + let d = decl(); + let ok = d.project_state5(CLASS, RAIL, Epi5Gen::V1, 25, EdgeProvenance::V3Register); + assert!(ok.is_ok()); + assert_eq!( + d.project_state5(0x0999, RAIL, Epi5Gen::V1, 25, EdgeProvenance::V2Stamped), + Err(Epi5ReadError::UndeclaredClass(0x0999)) + ); + for p in [EdgeProvenance::V1Legacy, EdgeProvenance::Unknown] { + assert_eq!( + d.project_state5(CLASS, RAIL, Epi5Gen::V1, 25, p), + Err(Epi5ReadError::UnknownProvenance(p)) + ); + } + assert_eq!( + d.project_state5(CLASS, RAIL, Epi5Gen::V1, 2, EdgeProvenance::V2Stamped), + Err(Epi5ReadError::UndeclaredCode(2)) + ); + assert_eq!( + EpistemicState5::decode(Epi5Gen::V1, 32), + Err(Epi5ReadError::UndeclaredCode(32)) + ); + } + + /// Every legacy cell that translates comes back through the legacy + /// projections unchanged (Open carries no grounding). + #[test] + fn legacy_translation_round_trips_through_the_projections() { + let mut mapped = 0; + for t in LegacyTopology::ALL { + for c in LegacyCertification::ALL { + if let Ok(s) = translate(Epi5Gen::V1, t, c) { + mapped += 1; + assert_eq!(project_certification(s), c, "{t:?} {c:?}"); + if c != LegacyCertification::Open { + assert_eq!(project_topology(s), Some(t), "{t:?} {c:?}"); + } + } + } + } + // 4 Open cells + 7 grounded cells. + assert_eq!(mapped, 11); + } + + /// F3: a certified Causes is canonical Causes, never Related. + #[test] + fn legacy_causes_translates_to_a_causes_code() { + for t in [LegacyTopology::Direct, LegacyTopology::IndirectKnown] { + let s = translate(Epi5Gen::V1, t, LegacyCertification::Causes).unwrap(); + assert!(s.asserts(fact::CAUSES)); + assert!(!matches!(s.raw(), code_v1::DIRECT_RELATED)); + } + assert_eq!( + translate( + Epi5Gen::V1, + LegacyTopology::IndirectKnown, + LegacyCertification::Causes + ) + .unwrap() + .raw(), + 17 + ); + } + + /// F4: an undeclared legacy combination refuses. + #[test] + fn undeclared_legacy_combinations_refuse() { + for (t, c) in [ + (LegacyTopology::Unknown, LegacyCertification::Causes), + (LegacyTopology::IndirectUnknown, LegacyCertification::Causes), + (LegacyTopology::Direct, LegacyCertification::Contributes), + (LegacyTopology::Direct, LegacyCertification::CausalCandidate), + ( + LegacyTopology::IndirectKnown, + LegacyCertification::Associated, + ), + ] { + assert_eq!( + translate(Epi5Gen::V1, t, c), + Err(LegacyError::NoCanonicalState { + topology: t, + certification: c + }) + ); + } + assert_eq!( + LegacyCertification::from_code(6), + Err(LegacyError::ReservedCertification(6)) + ); + } + + /// F2 + F9: the compatibility writers resolve a whole state or refuse, + /// where the raw half-writes would land on another valid code (1 → 3, + /// 12 → 20). + #[test] + fn compatibility_writers_never_produce_an_accidental_code() { + let causes = EpistemicState5::decode(Epi5Gen::V1, code_v1::DIRECT_CAUSES).unwrap(); + // Raw: topology half := 3 gives code 3 (Direct × Observed). Wrapper: + assert_eq!( + retopologize(causes, LegacyTopology::Unknown), + Err(LegacyError::NoCanonicalState { + topology: LegacyTopology::Unknown, + certification: LegacyCertification::Causes + }) + ); + let assoc = EpistemicState5::decode(Epi5Gen::V1, code_v1::DIRECT_ASSOCIATED).unwrap(); + // Raw: band half := 5 gives code 20 = Related, a claim nobody made. + // The wrapper with the legacy meaning of 5 (Causes) resolves to 1. + assert_eq!( + recertify(assoc, LegacyCertification::Causes).unwrap().raw(), + code_v1::DIRECT_CAUSES + ); + // A legal grounding move keeps the certification. + let rel = EpistemicState5::decode(Epi5Gen::V1, code_v1::DIRECT_RELATED).unwrap(); + assert_eq!( + retopologize(rel, LegacyTopology::IndirectUnknown) + .unwrap() + .raw(), + code_v1::IND_UNKNOWN_RELATED + ); + // Open has no grounding to keep. + let open = EpistemicState5::decode(Epi5Gen::V1, 0).unwrap(); + assert_eq!( + recertify(open, LegacyCertification::Related), + Err(LegacyError::NoGrounding) + ); + assert_eq!(recertify(open, LegacyCertification::Open), Ok(open)); + } +} 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; From 24bdd49ab1ec1d26c02389b4e2be1097e1ceeba4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 11:00:11 +0000 Subject: [PATCH 03/17] D-EPI-MIG-0: migrate the 59..63 probes to the canonical EpistemicState5 reading - shared/affordance_law: codebook + facts are the contract's; measurement projects through a declaration (measure_declared; measure uses the probes' constant AFF_READING, non-allocating via Epi5Reading::project). - shared/certification_reading (P7a): stamp = legacy translation of (declared grounding, contract) -> canonical code -> joint write, or refuse; read = project_state5 -> certification projection. P7a's observational fixtures certify CausalCandidate, which has no canonical code: they now refuse instead of writing 61..63. - affordance / cycle-survival / nextstate / revision-writer probes write bits 59..63 jointly (with_epistemic_raw5 / write_code). - epistemic_reading_conflict_probe (#1378) becomes the conformance probe: census 11 agree / 0 disagree / 13 refuse (was 3/5/16); both readings agree on every raw code; F3, F5, F6 (pre-migration measure oracle), F7, F8. - reasoning_band / kanban-hinge / entropy-topology probes marked PROBE-ONLY legacy (historical readings, no canonical translation). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../examples/affordance_measurement_probe.rs | 8 +- .../examples/ce64_cycle_survival_probe.rs | 6 +- .../examples/ce64_nextstate_probe.rs | 15 +- .../examples/entropy_topology_probe.rs | 6 + .../epistemic_reading_conflict_probe.rs | 493 +++++++++++------- .../examples/probe_revision_kanban_hinge.rs | 8 + .../examples/reasoning_band_probe.rs | 8 + .../relational_certification_probe.rs | 105 ++-- .../revision_epistemic_writer_probe.rs | 8 +- .../examples/shared/affordance_law.rs | 156 ++++-- .../examples/shared/certification_reading.rs | 104 ++-- .../src/epistemic_state5.rs | 39 +- 12 files changed, 621 insertions(+), 335 deletions(-) diff --git a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs index 724e5a81a..06eeca1ac 100644 --- a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs +++ b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs @@ -201,11 +201,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. @@ -498,6 +497,7 @@ mod tests { // 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); + #[allow(deprecated)] // the deprecated half writer IS what is pinned here 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))); 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..31cf9e4de 100644 --- a/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_cycle_survival_probe.rs @@ -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}; @@ -106,10 +105,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 diff --git a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs index 5848d49e6..8d647c00a 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. @@ -64,8 +67,10 @@ const ROW: usize = 0; const CODE_OBSERVED: u8 = 3; const CODE_ASSOCIATED: u8 = 7; -/// 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)) @@ -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 @@ -299,6 +303,9 @@ 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. diff --git a/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs b/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs index bb37a3d4d..069f2450c 100644 --- a/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs +++ b/crates/cognitive-shader-driver/examples/entropy_topology_probe.rs @@ -40,6 +40,12 @@ //! //! 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): PROBE-ONLY legacy. Reads/writes the +//! historical `CausalTopology` half of CE64 bits 59..63 through +//! `band_reading`; that split reading no longer owns the field +//! (`contract::epistemic_state5` does). Kept as a record of the lens. +#![allow(deprecated)] use causal_edge::edge::CausalEdge64; use causal_edge::layout::CausalTopology; 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..8f352d3ed 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,35 @@ -//! 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 disagreement this probe pinned is +//! resolved by having ONE reading of `CausalEdge64` bits 59..63, and the probe +//! is now the conformance check of that reading. //! -//! 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 alone and read it +//! through `band_reading::project_band`; the affordance law (#1370) read bits +//! 59..63 as one `EpistemicState5` code without any class declaration. A +//! P7a-certified `Causes` with topology 0 was raw code 20, which the law read +//! as `Direct × Related`; codes 21..23 refused; 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 +//! Bits 59..63 are owned by `lance_graph_contract::epistemic_state5`: one +//! codebook (`CODEBOOK_V1`), declared per `(classid, rail, generation)`, +//! projected with refusals. P7a stamps by translating `(grounding, contract)` +//! into a canonical code and writing all five bits; it reads by projecting the +//! state and taking the legacy certification projection. The affordance law +//! measures only a projected state. So both are views of the same state, and: //! //! | 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`. +//! | `undeclared_classes_refuse_under_both_readings` | the law no longer measures a class it has no declaration for | +//! | `a_certified_causes_reads_as_causes` | F3: `Causes` → code 1 (Direct) / 17 (IndirectKnown), carries `CAUSES`, makes `COUNTERFACTUAL_PROBE` eligible | +//! | `every_legacy_cell_translates_in_agreement_or_refuses` | F10: 11 of 24 cells map, 0 disagree, 13 refuse (was 3 / 5 / 16) | +//! | `both_readings_agree_on_every_raw_code` | F10: over all 32 codes the P7a reading and the law agree or both refuse | +//! | `the_historical_split_pattern_is_read_by_its_canonical_meaning` | the old `(band 5, topology 0)` bit pattern is code 20 to both readers alike | +//! | `measurement_is_unchanged_by_the_refactor` | F6: `measure` = the pre-migration body on every code × Pearl × law × provenance | +//! | `one_factor_moves_only_its_recipes` | F7: 25↔5 differ only in hydrate/mechanism; 5↔30 only in causal identification | +//! | `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 +39,26 @@ mod affordance_law; #[path = "shared/certification_reading.rs"] mod certification_reading; -use affordance_law::{raw5, ASSOCIATED, CAUSES, EPI_LAW, RELATED}; +use affordance_law::{raw5, ASSOCIATED, CAUSES, RELATED, SUPPORTS}; 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::{read, stamp, Contract, Refusal as P7aRefusal, CERT_CLASS, RAIL}; +use lance_graph_contract::band_reading::EdgeProvenance; +use lance_graph_contract::epistemic_state5::legacy::LegacyTopology; +use lance_graph_contract::epistemic_state5::{Epi5Declarations, Epi5Gen, Epi5Reading}; -/// 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,14 +76,13 @@ 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)) +/// What P7a reads off an edge of its class. +fn p7a_reads(edge: CausalEdge64) -> Result { + read(&decl(), CERT_CLASS, edge, EdgeProvenance::V2Stamped) } -/// 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. +/// The facts P7a's contract entails, in the affordance vocabulary. +/// `Contributes` is `SUPPORTS` (operator decision: "Contributes / Supports"). fn p7a_entails(contract: Contract) -> u32 { let mut f = 0; if contract.entails(Contract::Associated) { @@ -128,28 +91,42 @@ fn p7a_entails(contract: Contract) -> u32 { if contract.entails(Contract::Related) { f |= RELATED; } + if contract.entails(Contract::Contributes) { + f |= SUPPORTS; + } if contract.entails(Contract::Causes) { f |= CAUSES; } f } -const SHARED: u32 = ASSOCIATED | RELATED | CAUSES; +const SHARED: u32 = ASSOCIATED | RELATED | SUPPORTS | CAUSES; -/// The affordance reading of an edge: its facts, or `None` if it refuses. +/// The affordance facts of an edge of P7a's class, or `None` if refused. fn affordance_facts(edge: CausalEdge64) -> Option { - EPI_LAW[raw5(edge) as usize] + decl() + .project_state5( + CERT_CLASS, + RAIL, + Epi5Gen::V1, + raw5(edge), + EdgeProvenance::V2Stamped, + ) + .ok() + .map(|s| s.facts()) } -/// (agree, disagree, refused) over every P7a contract × topology. +/// (agree, disagree, refused) over every legacy grounding × P7a contract. 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 LegacyTopology::ALL { + for c in Contract::ALL { + match stamp(base(), c, t) { + Err(_) => refused += 1, + Ok(e) => match affordance_facts(e) { + Some(f) if f & SHARED == p7a_entails(c) && p7a_reads(e) == Ok(c) => agree += 1, + _ => disagree += 1, + }, } } } @@ -157,130 +134,276 @@ 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" - } + println!("D-EPI-MIG-0: legacy grounding x P7a contract -> canonical EpistemicState5"); + for t in LegacyTopology::ALL { + for c in Contract::ALL { + match stamp(base(), c, t) { + Ok(e) => println!( + " {t:<16?} {:<16} -> code {:>2} facts {:#05x}", + format!("{c:?}"), + raw5(e), + affordance_facts(e).unwrap_or(0) ), - }; - let declared = p7a_reads(e).expect("P7a reads its own stamp"); - println!( - " P7a {:<16} topo {t} raw5 {:>2} {shown}", - format!("{declared:?}"), - raw5(e) - ); + Err(r) => println!(" {t:<16?} {:<16} -> refuses ({r:?})", format!("{c:?}")), + } } } 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) - ); + println!("agree {a}, disagree {d}, refused {r} of 24 cells (was 3 / 5 / 16 in #1378)"); } #[cfg(test)] mod tests { use super::*; - use affordance_law::{measure, LawGen, Refusal, COUNTERFACTUAL_PROBE}; + use affordance_law::EPI_LAW; + use affordance_law::{ + measure_declared, LawGen, Refusal, CAUSAL_IDENTIFICATION, COUNTERFACTUAL_PROBE, + HYDRATE_INTERMEDIATE, MECHANISM_FOLD, ROBUSTNESS_TEST, STRATIFY, + }; + use lance_graph_contract::class_view::ClassId; + use lance_graph_contract::epistemic_state5::{legacy, Epi5ReadError}; - /// 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) + /// 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, + ) } - /// 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) + } + + /// FAILS IF: the law measures a class with no canonical declaration. #[test] - fn the_affordance_law_reads_bits_the_contract_refuses() { - let e = stamp(Contract::Causes, 0); - assert_eq!( - contract_band(UNDECLARED_CLASS, e), - Err(BandReadError::UndeclaredClass(UNDECLARED_CLASS)) - ); + fn undeclared_classes_refuse_under_both_readings() { + let e = stamp(base(), Contract::Causes, LegacyTopology::Direct).unwrap(); assert_eq!( - contract_band(SPARE_CLASS, e), - Err(BandReadError::BandAbsent) + eligible(UNDECLARED_CLASS, e), + Err(Refusal::Reading(Epi5ReadError::UndeclaredClass( + UNDECLARED_CLASS + ))) ); - // 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())); + // Silence twin: the declared class measures the same edge. + assert!(eligible(CERT_CLASS, e).is_ok()); + } + + /// F3: a certified `Causes` is canonical `Causes`, never `Related`. + #[test] + fn a_certified_causes_reads_as_causes() { + for (t, code) in [ + (LegacyTopology::Direct, 1), + (LegacyTopology::IndirectKnown, 17), + ] { + let e = stamp(base(), Contract::Causes, t).unwrap(); + assert_eq!(raw5(e), code); + assert_eq!(p7a_reads(e), Ok(Contract::Causes)); + let facts = affordance_facts(e).unwrap(); + assert_ne!(facts & CAUSES, 0); + assert_ne!( + eligible(CERT_CLASS, e).unwrap() & 1 << COUNTERFACTUAL_PROBE, + 0 + ); + } + // An unknown or unknown-intermediate grounding cannot carry Causes. + for t in [LegacyTopology::IndirectUnknown, LegacyTopology::Unknown] { + assert!(matches!( + stamp(base(), Contract::Causes, t), + Err(P7aRefusal::NoCanonicalState(_)) + )); + } + } + + /// F10 (legacy direction): every cell either translates into a state both + /// readers agree on, or refuses. No disagreement remains. + #[test] + fn every_legacy_cell_translates_in_agreement_or_refuses() { + assert_eq!(census(), (11, 0, 13)); + } + + /// F10 (bit direction): over every raw code, the P7a reading and the law + /// either agree on the shared facts or both refuse. + #[test] + fn both_readings_agree_on_every_raw_code() { + let mut agreed = 0; + for code in 0u8..32 { + let e = base().with_epistemic_raw5(code); + match (p7a_reads(e), affordance_facts(e)) { + (Ok(c), Some(f)) => { + agreed += 1; + // The certification projection is the strongest claim; + // the facts must entail exactly what it entails. + assert_eq!(f & SHARED, p7a_entails(c), "code {code}"); + } + (Err(P7aRefusal::Reading(_)), None) => {} + other => panic!("code {code}: readers diverge: {other:?}"), + } + } + assert_eq!(agreed, EPI_LAW.iter().filter(|f| f.is_some()).count()); + assert_eq!(agreed, 10); } - /// FAILS IF: a P7a-certified `Causes` starts reading as `CAUSES` under the - /// affordance law, or `COUNTERFACTUAL_PROBE` becomes eligible on it. + /// The bit pattern #1378 was built on (P7a band 5 over topology 0) is + /// code 20. It is no longer what a P7a `Causes` produces, and both readers + /// read it as the same thing: `Direct × Related`. #[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 the_historical_split_pattern_is_read_by_its_canonical_meaning() { + let old = base().with_epistemic_raw5((Contract::Causes.code() << 2) | 0); + assert_eq!(raw5(old), 20); + assert_eq!(p7a_reads(old), Ok(Contract::Related)); + assert_eq!( + affordance_facts(old).unwrap() & SHARED, + ASSOCIATED | RELATED ); + let now = stamp(base(), Contract::Causes, LegacyTopology::Direct).unwrap(); + assert_ne!(raw5(now), raw5(old)); + } - // 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))); + /// The pre-migration `measure` body, verbatim (classless, own codebook + /// lookup), as the regression oracle for F6. + fn pre_migration_measure( + law: LawGen, + edge: CausalEdge64, + provenance: EdgeProvenance, + ) -> Result { + if !matches!( + provenance, + EdgeProvenance::V2Stamped | EdgeProvenance::V3Register + ) { + return Err(Refusal::Provenance(provenance)); + } + let code = (edge.spare() << 2) | edge.truth_raw(); + 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]) } - /// FAILS IF: the codes that assert `CAUSES` move off P7a `Open` (code 1) - /// and `CausalCandidate` (code 17). + /// F6: on a declared class, the projected measurement equals the + /// pre-migration measurement everywhere. #[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 measurement_is_unchanged_by_the_refactor() { + let mut compared = 0; + for law in [LawGen::V1, LawGen::V2] { + for pearl in 0u8..8 { + for code in 0u8..32 { + for prov in [ + EdgeProvenance::V2Stamped, + EdgeProvenance::V3Register, + EdgeProvenance::V1Legacy, + EdgeProvenance::Unknown, + ] { + let e = CausalEdge64::pack_v2( + 1, + 2, + 3, + 9, + 9, + CausalMask::from_bits(pearl), + 0, + PlasticityState::from_bits(0), + ) + .with_epistemic_raw5(code); + let new = measure_declared(law, &decl(), CERT_CLASS, RAIL, e, prov); + assert_eq!(new, pre_migration_measure(law, e, prov), "{code} {prov:?}"); + compared += 1; + } + } + } + } + assert_eq!(compared, 2 * 8 * 32 * 4); + } + + /// F7 + the operator examples: changing one factor changes only the + /// recipes that read that factor. + #[test] + fn one_factor_moves_only_its_recipes() { + let at = |code| eligible(CERT_CLASS, base().with_epistemic_raw5(code)).unwrap(); + let (unknown, known, supports) = (at(25), at(5), at(30)); + // 25 = Indirect × IntermediateUnknown × Related: search, not fold. + assert_eq!( + unknown + & bits(&[ + HYDRATE_INTERMEDIATE, + MECHANISM_FOLD, + STRATIFY, + ROBUSTNESS_TEST + ]), + bits(&[HYDRATE_INTERMEDIATE, STRATIFY, ROBUSTNESS_TEST]) + ); + assert_eq!(unknown & 1 << COUNTERFACTUAL_PROBE, 0); + // Intermediate unknown → known: exactly hydrate ↔ mechanism fold. assert_eq!( - contracts, - vec![Ok(Contract::Open), Ok(Contract::CausalCandidate)] + unknown ^ known, + bits(&[HYDRATE_INTERMEDIATE, MECHANISM_FOLD]) ); + // Related → Supports: exactly causal identification becomes eligible. + assert_eq!(known ^ supports, bits(&[CAUSAL_IDENTIFICATION])); + assert_ne!(supports & 1 << MECHANISM_FOLD, 0); } - /// 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).filter(|c| EPI_LAW[*c as usize].is_some()) { + let e = base().with_epistemic_raw5(code); + let restarted = CausalEdge64::from_le_bytes(e.to_le_bytes()); + let a = decl().project_state5( + CERT_CLASS, + RAIL, + Epi5Gen::V1, + raw5(e), + EdgeProvenance::V2Stamped, + ); + let b = decl().project_state5( + CERT_CLASS, + RAIL, + Epi5Gen::V1, + raw5(restarted), + EdgeProvenance::V2Stamped, + ); + assert_eq!(a, b); + assert_eq!(p7a_reads(e), p7a_reads(restarted)); + } } - /// FAILS IF: the agreement census over all 24 P7a contract × topology - /// cells changes. + /// F8: reading, projecting and computing a compatibility transition never + /// writes the edge; only an explicit joint write does. #[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(5); + let before = e.0; + let s = decl() + .project_state5( + CERT_CLASS, + RAIL, + Epi5Gen::V1, + raw5(e), + EdgeProvenance::V2Stamped, + ) + .unwrap(); + let _ = ( + legacy::project_topology(s), + legacy::project_certification(s), + ); + let _ = p7a_reads(e); + let _ = eligible(CERT_CLASS, e); + let moved = legacy::retopologize(s, LegacyTopology::IndirectUnknown).unwrap(); + assert_eq!(e.0, before, "a projection wrote the edge"); + // The transition exists only as a value until it is written jointly. + assert_eq!(moved.raw(), 25); + assert_eq!( + e.with_epistemic_raw5(moved.raw()).0 ^ before, + (5u64 ^ 25) << 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..dac63c872 100644 --- a/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs +++ b/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs @@ -311,6 +311,14 @@ //! control) + G17 (membrane monotonicity, vicious). //! //! Run: `cargo run -p cognitive-shader-driver --example probe_revision_kanban_hinge` +//! +//! D-EPI-MIG-0 (2026-10-07): PROBE-ONLY legacy. This probe writes the +//! historical `ReasoningBand` / `CausalTopology` half-field readings of +//! CE64 bits 59..63, which have no translation into the canonical +//! `EpistemicState5` reading (`contract::epistemic_state5`) and refuse there. +//! It keeps the deprecated half writers on purpose, as a record of that +//! reading; no production code may follow it. +#![allow(deprecated)] use causal_edge::edge::CausalEdge64; use causal_edge::layout::{CausalTopology, ReasoningBand}; diff --git a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs index be90349ae..f691ced92 100644 --- a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs +++ b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs @@ -74,6 +74,14 @@ //! //! 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. This probe writes the +//! historical `ReasoningBand` / `CausalTopology` half-field readings of +//! CE64 bits 59..63, which have no translation into the canonical +//! `EpistemicState5` reading (`contract::epistemic_state5`) and refuse there. +//! It keeps the deprecated half writers on purpose, as a record of that +//! reading; no production code may follow it. +#![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..77aa27112 100644 --- a/crates/cognitive-shader-driver/examples/relational_certification_probe.rs +++ b/crates/cognitive-shader-driver/examples/relational_certification_probe.rs @@ -47,12 +47,19 @@ //! 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` / +//! The certification no longer owns bits 61..63. It is stamped by translating +//! `(MODEL_GROUNDING, contract)` into the canonical `EpistemicState5` code +//! (`contract::epistemic_state5::legacy::translate`) and writing all of bits +//! 59..63 with `with_epistemic_raw5`; it is read back through +//! `Epi5Declarations::project_state5` and the legacy certification projection. +//! `Open`, `Associated`, `Related` and `Causes` have canonical codes under the +//! declared `Direct` grounding (0, 12, 20, 1); `Contributes` and +//! `CausalCandidate` do not, and stamping them REFUSES. The observational +//! fixtures certify `CausalCandidate`, so their certification is computed but +//! cannot be stored until the codebook declares that conjunction (open point, +//! `entries/2026-10-07-epi-mig-0.md`). Provenance is `causal_audit::SupportLedger` / //! `SupportReceipt` / `SupportBasis` / `EvidenceSourceId`. Seals are //! `scheduler::DatasetVersion`. //! @@ -431,7 +438,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 +448,15 @@ 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 stamped = stamp(CausalEdge64::ZERO, c, MODEL_GROUNDING); + let back = stamped.map(|e| { + ( + 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 +467,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,37 +547,54 @@ 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. #[test] fn the_reading_is_declared_and_reserved_codes_refuse() { let decl = declarations(); + let mut stamped = 0; for c in Contract::ALL { - let edge = stamp(CausalEdge64::ZERO, c); - assert_eq!( - read(&decl, CERT_CLASS, edge, EdgeProvenance::V2Stamped), - Ok(c) - ); - // Same bits, historical reading: refused. - assert_eq!( - read(&decl, LEGACY_CLASS, edge, EdgeProvenance::V2Stamped), - Err(Refusal::NotCertificationClass) - ); + match stamp(CausalEdge64::ZERO, c, MODEL_GROUNDING) { + Ok(edge) => { + stamped += 1; + assert_eq!( + read(&decl, CERT_CLASS, edge, EdgeProvenance::V2Stamped), + Ok(c) + ); + // Same bits, historical reading: refused. + assert_eq!( + read(&decl, LEGACY_CLASS, edge, EdgeProvenance::V2Stamped), + Err(Refusal::NotCertificationClass) + ); + } + Err(Refusal::NoCanonicalState(_)) => { + assert!( + matches!(c, Contract::Contributes | Contract::CausalCandidate), + "{c:?} must have a canonical code under Direct" + ); + } + Err(other) => panic!("{c:?}: {other:?}"), + } } + assert_eq!(stamped, 4, "Open, Associated, Related, Causes"); + // 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).unwrap(); + 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 +609,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 +749,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).expect("Causes is canonical"); 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), @@ -878,14 +910,19 @@ mod tests { b.build() }, ]; - let run = || -> Vec { + let run = || -> Vec> { seals .iter() - .map(|m| stamp(CausalEdge64::ZERO, m.certify()).0) + .map(|m| stamp(CausalEdge64::ZERO, m.certify(), MODEL_GROUNDING).map(|e| e.0)) .collect() }; assert_eq!(run(), run()); - for bits in run() { + // The two observational seals certify CausalCandidate, which has no + // canonical code: refused, deterministically. The trial seal stamps. + let r = run(); + assert!(matches!(r[0], Err(Refusal::NoCanonicalState(_)))); + assert!(matches!(r[1], Err(Refusal::NoCanonicalState(_)))); + for bits in r.into_iter().flatten() { assert!(read( &decl, CERT_CLASS, 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..65c4eb3e2 100644 --- a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs +++ b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs @@ -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}; @@ -286,10 +285,11 @@ 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`. diff --git a/crates/cognitive-shader-driver/examples/shared/affordance_law.rs b/crates/cognitive-shader-driver/examples/shared/affordance_law.rs index 938ade46f..ac041b6d1 100644 --- a/crates/cognitive-shader-driver/examples/shared/affordance_law.rs +++ b/crates/cognitive-shader-driver/examples/shared/affordance_law.rs @@ -3,48 +3,67 @@ //! 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 -//! `dead_code` allowance. +//! D-EPI-MIG-0: the codebook and facts now come from +//! `lance_graph_contract::epistemic_state5`; measurement projects bits 59..63 +//! through a class declaration (`measure_declared`; `measure` uses the +//! probes' declared `AFF_CLASS`). Tables and rules are unchanged. 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, CODEBOOK_V1, +}; +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, OBSERVED, RELATED, SUPPORTS, UP_TO_RELATED, + UP_TO_SUPPORTS, +}; + +/// The codebook the law compiles against: the contract's `CODEBOOK_V1`, +/// aliased (was a probe-local table until D-EPI-CANON-0; the values are +/// identical). +pub const EPI_LAW: [Option; 32] = CODEBOOK_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()) +} + +/// Write a V1 code that must be declared (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 @@ -149,6 +168,10 @@ 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 +193,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`. Two lookups and one AND. +/// `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)) +} + +/// `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..da9d2e205 100644 --- a/crates/cognitive-shader-driver/examples/shared/certification_reading.rs +++ b/crates/cognitive-shader-driver/examples/shared/certification_reading.rs @@ -3,28 +3,45 @@ //! `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 no longer owns bits 61..63. A certification is stamped +//! by translating `(declared grounding, contract)` into the canonical +//! `EpistemicState5` code (`contract::epistemic_state5::legacy::translate`) +//! and writing all of bits 59..63; it is read by projecting the canonical +//! state and taking its legacy certification projection. A cell without a +//! canonical code (any `CausalCandidate`; `Contributes` under `Direct`) +//! REFUSES — no code is invented for it. +//! +//! 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::legacy::{ + project_certification, translate, LegacyCertification, LegacyError, LegacyTopology, +}; +use lance_graph_contract::epistemic_state5::{ + Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, +}; 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: LegacyTopology = LegacyTopology::Direct; /// The certified relational contract. Working names; the obligations in the /// module table are the content. @@ -90,35 +107,49 @@ impl Contract { } } -/// Why a band could not be read. +impl Contract { + /// The contract in the contract crate's legacy vocabulary (same order). + pub fn legacy(self) -> LegacyCertification { + LegacyCertification::ALL[self.code() as usize] + } + + pub fn from_legacy(c: LegacyCertification) -> 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), + /// `(grounding, contract)` has no canonical code in this generation. + NoCanonicalState(LegacyError), } -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 +157,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_legacy(project_certification(state))) } -/// 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 +173,14 @@ 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: translate `(grounding, contract)` to the canonical +/// code and write all of bits 59..63, or refuse. Nothing else moves. +pub fn stamp( + edge: CausalEdge64, + contract: Contract, + grounding: LegacyTopology, +) -> Result { + let state = + translate(Epi5Gen::V1, grounding, contract.legacy()).map_err(Refusal::NoCanonicalState)?; + Ok(edge.with_epistemic_raw5(state.raw())) } diff --git a/crates/lance-graph-contract/src/epistemic_state5.rs b/crates/lance-graph-contract/src/epistemic_state5.rs index 61e916b98..6b2b161aa 100644 --- a/crates/lance-graph-contract/src/epistemic_state5.rs +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -220,6 +220,29 @@ pub struct Epi5Reading { pub generation: Epi5Gen, } +impl Epi5Reading { + /// Project a raw code under THIS declaration (no table lookup, no + /// allocation): provenance, then generation, then code. The class lookup + /// is the caller's — [`Epi5Declarations::project_state5`] is the usual one. + 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 { @@ -290,19 +313,9 @@ impl Epi5Declarations { raw5: u8, provenance: EdgeProvenance, ) -> Result { - let reading = self - .get(class, rail) - .ok_or(Epi5ReadError::UndeclaredClass(class))?; - if !provenance.trusted() { - return Err(Epi5ReadError::UnknownProvenance(provenance)); - } - if reading.generation != requested { - return Err(Epi5ReadError::GenerationMismatch { - declared: reading.generation, - requested, - }); - } - EpistemicState5::decode(requested, raw5) + self.get(class, rail) + .ok_or(Epi5ReadError::UndeclaredClass(class))? + .project(requested, raw5, provenance) } } From 9675825e5804e87fe97061f74920eb0176cf9d99 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 11:05:30 +0000 Subject: [PATCH 04/17] D-EPI-MIG-0: migrate dismech EdgeRole, mark legacy probes, board + docs - dismech_counterfactual::EdgeRole carries the canonical epistemic_raw5; topology/band stay beside it as documented legacy projections; the test fixture writes the field jointly. - probe_four_plane_causal_medium marked PROBE-ONLY legacy. - layout.rs / arm-discovery translator docs: 59..63 is one field. - Board: ISS-EPISTEMIC-READINGS-DISAGREE-ON-BITS-61-63 resolved; new ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS (operator decision) and ISS-LEGACY-59-63-READINGS-REMAIN; entry 2026-10-07-epi-mig-0 with the F1-F10 falsifier map; STATUS_BOARD rows; examples README notes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/ISSUES.md | 23 ++++++- .claude/board/STATUS_BOARD.md | 4 +- .claude/board/entries/2026-10-07-epi-mig-0.md | 63 +++++++++++++++++++ .claude/board/entries/README.md | 3 +- crates/causal-edge/src/layout.rs | 9 +++ .../examples/README.md | 12 ++++ .../src/translator.rs | 3 + .../probe_four_plane_causal_medium.rs | 6 ++ .../src/dismech_counterfactual.rs | 21 +++++-- 9 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 .claude/board/entries/2026-10-07-epi-mig-0.md diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 6796f26e7..acc4b2783 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:** OPEN — operator decision. **Basis:** TEST-PINNED in `contract::epistemic_state5` (`undeclared_legacy_combinations_refuse`), `relational_certification_probe` and `epistemic_reading_conflict_probe` (census 13 refused cells). + +- 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). **Basis:** VERIFIED-IN-CODE; inventory `entries/2026-10-07-epi-mig-inventory.md`. + +- PROBE-ONLY legacy writers/readers of the historical `ReasoningBand` / `CausalTopology` / `TrustTexture` halves, kept with `#![allow(deprecated)]` because their readings have no canonical translation: `cognitive-shader-driver/examples/{reasoning_band_probe, probe_revision_kanban_hinge, entropy_topology_probe}.rs`, `lance-graph-planner/examples/probe_four_plane_causal_medium.rs`. +- Compatibility surfaces kept: `contract::band_reading` (regraded legacy), the deprecated half writers in `causal-edge`, the legacy lens readers (`truth`, `topology`, `spare`, `reasoning_band`), `dismech_counterfactual::EdgeRole::{topology, band}` (now 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` (codebook V1 = the ten #1370 codes, per `(classid, rail, generation)`, projection refuses). The affordance law measures only a projected state; P7a stamps through the legacy translation `(grounding, contract) → code` and reads through the projection. The probe's pins were replaced by agreement tests (census 11 agree / 0 disagree / 13 refuse; both readings agree on all 32 raw codes), not re-pinned. Residue: `ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS`. + +**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/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 745c6bf6a..591701c26 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -46,8 +46,8 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **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-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` (codebook V1, declarations, `project_state5`, legacy translation); `CausalEdge64::{epistemic_raw5, with_epistemic_raw5}`; half writers deprecated | -| **D-EPI-MIG-0** | Migrate readers/writers to the canonical reading; #1378 → conformance | In PR | see `entries/2026-10-07-epi-mig-0.md` | -| **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-0** | Migrate readers/writers to the canonical reading; #1378 → conformance | In PR | `entries/2026-10-07-epi-mig-0.md`: affordance law + P7a + 4 probes + dismech + V3 lift migrated; conformance census 11/0/13 (was 3/5/16); F1–F10 pinned; open: P7a `CausalCandidate` has no V1 code | +| **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 | 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-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/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..9e7b2d950 --- /dev/null +++ b/.claude/board/entries/2026-10-07-epi-mig-0.md @@ -0,0 +1,63 @@ +# 2026-10-07 — D-EPI-CANON-0 / D-EPI-MIG-0: CE64 bits 59..63 are `EpistemicState5` + +Operator decision: bits 59..63 are ONE declared 5-bit `EpistemicState5` code, +a dense codebook over valid epistemic conjunctions whose factorization also +decides the legal recipes (`CausalEdge64 × RecipeLaw[64] → EligibleRecipes: +u64`, transient). The split readings survive only as compatibility. +Inventory taken first: `entries/2026-10-07-epi-mig-inventory.md`. + +## CURRENT-CONTRACT + +- `lance_graph_contract::epistemic_state5`: `CODEBOOK_V1` (the ten #1370 + codes, adopted unchanged; ONE authoritative table), `fact::*`, `Epi5Gen`, + `EpistemicState5` (only constructible validated), `Epi5Reading::project` + (non-allocating), `Epi5Declarations::project_state5` — refuses undeclared + `(classid, rail)`, untrusted provenance, generation mismatch, undeclared + code. `ClassView::epistemic_reading` (default `None` = refuse). +- `legacy::{translate, project_topology, project_certification, recertify, + retopologize}`: the only declared translation is P7a certification × + legacy `CausalTopology` grounding (11 of 24 cells). Compatibility writers + resolve a whole state or refuse; never a 2- or 3-bit overwrite. +- `causal-edge`: `epistemic_raw5` / `with_epistemic_raw5` (joint read/write; + v1 stubs zero / no-op), `EPISTEMIC_MASK`; V3 lift restores the field + jointly; `with_truth` / `set_truth` / `with_topology` / `with_routing` / + `with_spare` / `set_spare` / `with_reasoning_band` `#[deprecated]`. +- `band_reading`: regraded legacy; no longer owns 59..63. + +## MEASURED (falsifiers; each disable-verified red unless noted) + +| F | where | disable that turns it red | +|---|---|---| +| F1 | `causal-edge` `every_code_round_trips_and_touches_only_59_63`; contract `projection_round_trips_every_declared_code` | — (round trips) | +| F2 | `causal-edge` `a_half_write_silently_produces_a_different_code` (1→3, 12→20 via the half writers); contract `compatibility_writers_never_produce_an_accidental_code` | grounding-free `recertify` admits → red | +| F3 | contract `legacy_causes_translates_to_a_causes_code`; probe `a_certified_causes_reads_as_causes` | translate `(Direct, Causes)` → 20 → 3 probe tests red; old split `stamp` → 3 red | +| F4 | contract `undeclared_legacy_combinations_refuse` | extra `(Unknown, Causes)` cell → 3 contract tests red | +| F5 | probe `the_state_survives_restart_from_le_bytes`; causal-edge LE + V3 lift tests | — | +| F6 | probe `measurement_is_unchanged_by_the_refactor` (vs the pre-migration body, 2048 cases); `affordance_measurement_probe` `f9` (0 allocations) | drop the Pearl gate → red; the first draft allocated per call and `f9` caught it | +| F7 | probe `one_factor_moves_only_its_recipes` (25↔5 = hydrate↔mechanism; 5↔30 = causal identification) | code 30 → unknown intermediate → red | +| F8 | probe `projections_never_change_the_edge` | — | +| F9 | contract compatibility-writer test (resolve or refuse) | as F2 | +| F10 | probe census `(11, 0, 13)` (was `(3, 5, 16)`) and `both_readings_agree_on_every_raw_code` (10 agree, 22 both refuse) | old split stamp → red | +| — | probe `undeclared_classes_refuse_under_both_readings` | classless `measure_declared` → red | + +## MIGRATED + +- `shared/affordance_law.rs`, `shared/certification_reading.rs` (P7a), + `affordance_measurement`, `ce64_cycle_survival`, `ce64_nextstate`, + `revision_epistemic_writer` (#1379), `epistemic_reading_conflict` (#1378 → + conformance), `relational_certification` (P7a), `dismech_counterfactual` + (`EdgeRole.epistemic_raw5`, legacy `topology`/`band` kept beside it), + `edge_v3` lift. + +## OPEN + +- `ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS`: no V1 code for any + `CausalCandidate`, nor `Contributes` under `Direct`. P7a's observational + fixtures certify `CausalCandidate` and now refuse to stamp. A V2 generation + is a codebook decision, not taken here. +- `ISS-LEGACY-59-63-READINGS-REMAIN`: four PROBE-ONLY legacy probes, the + compatibility surfaces, `temporal()` ordering. +- P7a's `MODEL_GROUNDING = Direct` is a producer declaration in the probe + (no intermediate in the sealed model), not derived from bits. +- No production class declares the canonical reading yet (no OGAR mint); + every declaration lives in probes. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index ff816a160..241194e83 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,7 +25,7 @@ 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. -244 entries, 2026-08-06 .. 2026-10-07. +245 entries, 2026-08-06 .. 2026-10-07. | date | entry id | finding | file | |---|---|---|---| @@ -33,6 +33,7 @@ exactly one of them, never both. | 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-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/layout.rs b/crates/causal-edge/src/layout.rs index 438d78158..5f9fdcb1e 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -54,6 +54,11 @@ 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`]). This 2-bit lens and the +/// `TrustTexture` / `CausalTopology` readings below are legacy projections +/// of its low half, kept for compatibility, never the authority. /// 0 = Crystalline. Per cognitive-substrate-convergence-v1.md L-7. /// /// Same two bits also carry an ADDITIVE factual view, [`CausalTopology`] @@ -65,6 +70,10 @@ 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 high half of the joint +/// `EpistemicState5` field ([`EPISTEMIC_SHIFT`]); the `ReasoningBand` view +/// below is a legacy projection only. /// Candidates: Rubicon-commit marker, Markov-decay quantum, I-NOISE-FLOOR-JIRAK threshold. /// /// Same three bits also carry an ADDITIVE quantized-projection view, diff --git a/crates/cognitive-shader-driver/examples/README.md b/crates/cognitive-shader-driver/examples/README.md index fdb239b66..b69a275e6 100644 --- a/crates/cognitive-shader-driver/examples/README.md +++ b/crates/cognitive-shader-driver/examples/README.md @@ -179,6 +179,12 @@ cargo run -p cognitive-shader-driver --example reasoning_band_probe ## relational_certification_probe.rs +> **D-EPI-MIG-0 (2026-10-07):** P7a no longer owns bits 61..63. A +> certification is stamped by translating `(MODEL_GROUNDING = Direct, +> contract)` into the canonical `EpistemicState5` code and writing bits +> 59..63 jointly; `Contributes` and `CausalCandidate` have no V1 code and +> refuse (the observational fixtures certify `CausalCandidate`). + 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 +307,12 @@ 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 `EpistemicState5` reading (`contract::epistemic_state5`): legacy +> grounding × P7a contract census 11 agree / 0 disagree / 13 refuse (was +> 3 / 5 / 16), both readings agree on all 32 raw codes, F3/F5–F8/F10 pinned. +> 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/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-planner/examples/probe_four_plane_causal_medium.rs b/crates/lance-graph-planner/examples/probe_four_plane_causal_medium.rs index 0a7bd8c7c..3fcd5f797 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,12 @@ //! 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. Writes the historical +//! `CausalTopology` / `ReasoningBand` halves of CE64 bits 59..63, which the +//! canonical `EpistemicState5` reading (`contract::epistemic_state5`) does +//! not translate. Kept as a record of that reading. +#![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..9bb8bde42 100644 --- a/crates/lance-graph-planner/src/dismech_counterfactual.rs +++ b/crates/lance-graph-planner/src/dismech_counterfactual.rs @@ -136,9 +136,18 @@ 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, + /// LEGACY projection of the low half of `epistemic_raw5` through the + /// historical `CausalTopology` lens. Kept for compatibility; it is not a + /// reading of the field and must not be used to decide anything. pub topology: causal_edge::layout::CausalTopology, - /// The cut edge's reasoning band (bits 61-63). + /// LEGACY projection of the high half through the historical + /// `ReasoningBand` lens. Same caveat. pub band: causal_edge::layout::ReasoningBand, } @@ -248,6 +257,7 @@ 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(), }, @@ -542,9 +552,8 @@ mod tests { // 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); + // Written jointly (D-EPI-CANON-0): topology ordinal 1, band ordinal 3. + let flavoured = plain.with_epistemic_raw5((3 << 2) | 1); for (edge, want_topo, want_band) in [ (plain, plain.topology(), plain.reasoning_band()), @@ -573,6 +582,7 @@ mod tests { ) .expect("in range") .expect("fits"); + assert_eq!(cf.role.epistemic_raw5, edge.epistemic_raw5()); assert_eq!(cf.role.topology, want_topo); assert_eq!(cf.role.band, want_band); assert_eq!(cf.role.predicate, 0x9D, "perturbs"); @@ -582,6 +592,7 @@ mod tests { // above compares a value with itself twice. assert_ne!(plain.topology(), CausalTopology::IndirectKnownIntermediates); assert_ne!(plain.reasoning_band(), ReasoningBand::Causal); + assert_ne!(plain.epistemic_raw5(), flavoured.epistemic_raw5()); } /// An out-of-range cut is refused at the top level too, not just in From 1a21a71ecbfe7144eef24cacc8e14695b9522584 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 11:06:47 +0000 Subject: [PATCH 05/17] Board: anchor the TECH_DEBT layout.rs citation by symbol, not line range The D-EPI-CANON-0 doc note in layout.rs shifted SPARE_SHIFT out of the cited 67-77 window (citation-decay). Replaced the line range with the const name, per the tool's stable-anchor rule. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/TECH_DEBT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6c395c20d596a40fb0c3152b7b51c5077d861033 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 11:41:57 +0000 Subject: [PATCH 06/17] D-EPI-CANON-0 rev: EpistemicState5 = Topology2 x Certification3 (Cartesian V1) Replaces the hand-assigned ten-code #1370 codebook with the dense product the two old fields already formed: raw5 = topology | certification << 2, CausalTopology ordinals x P7a certification codes, 24 meaningful states, 8 reserved (certification 6/7). Validity is only certification < 6; IndirectUnknown x Causes and Unknown x Causes are meaningful (procedure is the affordance law's job). OBSERVED is dropped: observation is evidence, not a coordinate. - contract: Topology2, Certification3, per-factor facts, derived COMPILED_FACTS_V1, factor updates (with_topology/with_certification); the legacy translation table is gone (P7a + topology is identity packing). - causal-edge: with_topology is the canonical topology-factor writer (no longer deprecated); the TrustTexture / historical-band / untyped writers stay deprecated for bypassing the declared reading. - affordance law compiles per factor; state = topology & certification. - probes remapped: #1379/cycle/nextstate 3/7 -> 0/4 (Direct x Open / Direct x Associated), affordance fixtures 25/5/17 -> 10/9/21, streamdto 7 -> 4; P7a stamps every contract; entropy_topology migrated to the canonical projection. - #1378 probe: canonical == topology lens + P7a decode over all 32 codes (24 agree / 0 disagree / 8 reserved), derived table == rules, frontier cases 22/23, legacy-reading class refuses. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/LATEST_STATE.md | 23 +- crates/causal-edge/src/edge.rs | 38 +- crates/causal-edge/src/layout.rs | 20 +- crates/causal-edge/src/v2_layout_tests.rs | 33 +- .../examples/affordance_measurement_probe.rs | 108 ++- .../examples/ce64_cycle_survival_probe.rs | 27 +- .../examples/ce64_nextstate_probe.rs | 25 +- .../examples/entropy_topology_probe.rs | 75 +- .../epistemic_reading_conflict_probe.rs | 430 ++++----- .../examples/probe_revision_kanban_hinge.rs | 15 +- .../examples/reasoning_band_probe.rs | 15 +- .../relational_certification_probe.rs | 78 +- .../revision_epistemic_writer_probe.rs | 37 +- .../examples/shared/affordance_law.rs | 96 +- .../examples/shared/certification_reading.rs | 48 +- .../examples/streamdto_circuit_probe.rs | 8 +- .../lance-graph-contract/src/band_reading.rs | 17 +- .../src/epistemic_state5.rs | 890 ++++++++---------- .../probe_four_plane_causal_medium.rs | 13 +- .../src/dismech_counterfactual.rs | 13 +- 20 files changed, 971 insertions(+), 1038 deletions(-) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 4d2dfb827..d5b150c10 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,20 +1,25 @@ ## 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`: `fact::*` - (DIRECT … CAUSES), `Facts`, `CODEBOOK_V1` (the 10 measured #1370 codes), - `code_v1::*`, `Epi5Gen::{V1}` (`codebook()`), `EpistemicState5` - (`decode`, `raw`, `generation`, `facts`, `asserts`), `Epi5Reading`, - `Epi5ReadError`, `Epi5Declarations::{declare, get, project_state5}`, and - `legacy::{LegacyTopology, LegacyCertification, LegacyError, translate, - project_topology, project_certification, recertify, retopologize}`. +- 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}`. - `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_truth`/`set_truth`/`with_topology`/ - `with_routing`/`with_spare`/`set_spare`/`with_reasoning_band` `#[deprecated]`. + `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) diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 79e44c4be..cdc9c7ba1 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -1061,7 +1061,7 @@ impl CausalEdge64 { /// Return new edge with truth-band lens set. #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1071,15 +1071,17 @@ 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 /// one 2-bit register read through two lenses. See /// [`crate::layout::CausalTopology`] for the compatibility statement. - #[deprecated( - since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" - )] #[inline] pub fn with_topology(self, topo: crate::layout::CausalTopology) -> Self { use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; @@ -1101,7 +1103,7 @@ impl CausalEdge64 { /// Return new edge with spare bits set (0..=7, 3-bit field). #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1134,7 +1136,7 @@ impl CausalEdge64 { /// the orthogonality notes, and the no-auto-derivation guarantee. #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1150,7 +1152,7 @@ impl CausalEdge64 { /// Composable: `edge.with_routing(12, TrustTexture::Solid).with_inference_mantissa(-1)`. #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1174,7 +1176,7 @@ impl CausalEdge64 { /// Set truth-band lens in-place. #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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) { @@ -1194,7 +1196,7 @@ impl CausalEdge64 { /// Set spare bits in-place (0..=7, 3-bit field). #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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) { @@ -1267,16 +1269,12 @@ impl CausalEdge64 { } #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 } - #[deprecated( - since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" - )] #[inline] pub fn with_topology(self, _topo: crate::layout::CausalTopology) -> Self { self @@ -1287,7 +1285,7 @@ impl CausalEdge64 { } #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1295,7 +1293,7 @@ impl CausalEdge64 { } #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1303,7 +1301,7 @@ impl CausalEdge64 { } #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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 { @@ -1313,7 +1311,7 @@ impl CausalEdge64 { pub fn set_w_slot(&mut self, _w: u8) {} #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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) {} @@ -1321,7 +1319,7 @@ impl CausalEdge64 { pub fn set_inference_mantissa(&mut self, _m: i8) {} #[deprecated( since = "0.2.0", - note = "half-field write of the joint EpistemicState5 field (bits 59..63): it can turn one valid code into another by accident. Use `with_epistemic_raw5` with a code projected through `lance_graph_contract::epistemic_state5`" + 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) {} diff --git a/crates/causal-edge/src/layout.rs b/crates/causal-edge/src/layout.rs index 5f9fdcb1e..743937826 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -56,9 +56,10 @@ 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`]). This 2-bit lens and the -/// `TrustTexture` / `CausalTopology` readings below are legacy projections -/// of its low half, kept for compatibility, never the authority. +/// `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`] @@ -71,9 +72,11 @@ pub const TRUTH_SHIFT: u32 = 59; /// Spare: 3-bit reserved for sprint-12+ (bits 61-63). /// -/// ⊘ D-EPI-CANON-0 (2026-10-07): the high half of the joint -/// `EpistemicState5` field ([`EPISTEMIC_SHIFT`]); the `ReasoningBand` view -/// below is a legacy projection only. +/// ⊘ 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, @@ -98,8 +101,9 @@ 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 2-bit [`TRUTH_SHIFT`] and 3-bit -/// [`SPARE_SHIFT`] halves are its low and high parts, never two fields. +/// 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; diff --git a/crates/causal-edge/src/v2_layout_tests.rs b/crates/causal-edge/src/v2_layout_tests.rs index 17f2ae760..7444a72e1 100644 --- a/crates/causal-edge/src/v2_layout_tests.rs +++ b/crates/causal-edge/src/v2_layout_tests.rs @@ -988,18 +988,27 @@ mod epistemic_field_tests { assert_eq!(e.0 & SPARE_MASK, 0b001u64 << 61); } - /// F2 (physical half): a half-field write turns one valid-looking code - /// into ANOTHER valid-looking code. Code 1 with topology half set to 3 - /// becomes code 3; code 12 with band half set to 5 becomes code 20. - /// The bits cannot refuse; only a declared reading can (contract crate). - #[test] - fn a_half_write_silently_produces_a_different_code() { - let causes = CausalEdge64::ZERO.with_epistemic_raw5(1); - let moved = causes.with_truth(crate::layout::TrustTexture::from_bits_2(3)); - assert_eq!(moved.epistemic_raw5(), 3); - let assoc = CausalEdge64::ZERO.with_epistemic_raw5(12); - let moved = assoc.with_reasoning_band(crate::layout::ReasoningBand::from_bits_3(5)); - assert_eq!(moved.epistemic_raw5(), 20); + /// 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) | 0); // Direct × 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. diff --git a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs index 06eeca1ac..18e2856b0 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] = [ @@ -224,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::>() @@ -250,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!( @@ -272,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(), @@ -324,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!( @@ -468,25 +474,29 @@ 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() { + fn reserved_codes_refuse_and_a_topology_write_is_a_factor_update() { for code in (0u8..32).filter(|c| EPI_LAW[*c as usize].is_none()) { + assert!(code >= 24); let e = with_code(busy_edge(CausalMask::SPO), code); assert_eq!(stamped(LawGen::V1, e), Err(Refusal::Undeclared(code))); assert_eq!( @@ -494,24 +504,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); - #[allow(deprecated)] // the deprecated half writer IS what is pinned here - 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 +547,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 +556,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 31cf9e4de..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. @@ -75,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; @@ -116,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, ) } @@ -140,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) @@ -257,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()); } @@ -359,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 8d647c00a..ccddda583 100644 --- a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs @@ -64,8 +64,11 @@ 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 historical field-update path: two half writers, one per sub-field. /// Deprecated since D-EPI-CANON-0; kept as the measured comparison arm. @@ -146,7 +149,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); @@ -174,10 +177,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) @@ -212,7 +212,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); @@ -232,7 +232,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)); @@ -272,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(); @@ -308,8 +308,9 @@ mod tests { 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 069f2450c..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 @@ -41,18 +42,20 @@ //! 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): PROBE-ONLY legacy. Reads/writes the -//! historical `CausalTopology` half of CE64 bits 59..63 through -//! `band_reading`; that split reading no longer owns the field -//! (`contract::epistemic_state5` does). Kept as a record of the lens. -#![allow(deprecated)] +//! 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}; @@ -94,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) @@ -153,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 { @@ -170,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 @@ -319,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(); @@ -329,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 8f352d3ed..2f8b04ae5 100644 --- a/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs +++ b/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs @@ -1,33 +1,31 @@ -//! D-EPI-CONFLICT-0 → D-EPI-MIG-0: the disagreement this probe pinned is -//! resolved by having ONE reading of `CausalEdge64` bits 59..63, and the probe -//! is now the conformance check of that reading. +//! D-EPI-CONFLICT-0 → D-EPI-MIG-0: the conformance probe of the canonical +//! Cartesian `EpistemicState5` reading of `CausalEdge64` bits 59..63. //! //! # What it pinned (#1378, kept as history) //! -//! P7a (#1369) wrote its certification into bits 61..63 alone and read it -//! through `band_reading::project_band`; the affordance law (#1370) read bits -//! 59..63 as one `EpistemicState5` code without any class declaration. A -//! P7a-certified `Causes` with topology 0 was raw code 20, which the law read -//! as `Direct × Related`; codes 21..23 refused; 3 of 8 shared cells agreed. +//! 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. //! //! # What holds now (operator decision 2026-10-07) //! -//! Bits 59..63 are owned by `lance_graph_contract::epistemic_state5`: one -//! codebook (`CODEBOOK_V1`), declared per `(classid, rail, generation)`, -//! projected with refusals. P7a stamps by translating `(grounding, contract)` -//! into a canonical code and writing all five bits; it reads by projecting the -//! state and taking the legacy certification projection. The affordance law -//! measures only a projected state. So both are views of the same state, and: +//! `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 | //! |---|---| -//! | `undeclared_classes_refuse_under_both_readings` | the law no longer measures a class it has no declaration for | -//! | `a_certified_causes_reads_as_causes` | F3: `Causes` → code 1 (Direct) / 17 (IndirectKnown), carries `CAUSES`, makes `COUNTERFACTUAL_PROBE` eligible | -//! | `every_legacy_cell_translates_in_agreement_or_refuses` | F10: 11 of 24 cells map, 0 disagree, 13 refuse (was 3 / 5 / 16) | -//! | `both_readings_agree_on_every_raw_code` | F10: over all 32 codes the P7a reading and the law agree or both refuse | -//! | `the_historical_split_pattern_is_read_by_its_canonical_meaning` | the old `(band 5, topology 0)` bit pattern is code 20 to both readers alike | -//! | `measurement_is_unchanged_by_the_refactor` | F6: `measure` = the pre-migration body on every code × Pearl × law × provenance | -//! | `one_factor_moves_only_its_recipes` | F7: 25↔5 differ only in hydrate/mechanism; 5↔30 only in causal identification | +//! | `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 | //! @@ -39,14 +37,15 @@ mod affordance_law; #[path = "shared/certification_reading.rs"] mod certification_reading; -use affordance_law::{raw5, ASSOCIATED, CAUSES, RELATED, SUPPORTS}; +use affordance_law::raw5; use causal_edge::edge::CausalEdge64; use causal_edge::pearl::CausalMask; use causal_edge::PlasticityState; -use certification_reading::{read, stamp, Contract, Refusal as P7aRefusal, CERT_CLASS, RAIL}; +use certification_reading::{read, Contract, Refusal as P7aRefusal, CERT_CLASS, RAIL}; use lance_graph_contract::band_reading::EdgeProvenance; -use lance_graph_contract::epistemic_state5::legacy::LegacyTopology; -use lance_graph_contract::epistemic_state5::{Epi5Declarations, Epi5Gen, Epi5Reading}; +use lance_graph_contract::epistemic_state5::{ + Epi5Declarations, Epi5Gen, Epi5Reading, EpistemicState5, Topology2, +}; /// The declarations both readers share: P7a's class, declared canonical. fn decl() -> Epi5Declarations { @@ -76,34 +75,8 @@ fn base() -> CausalEdge64 { ) } -/// What P7a reads off an edge of its class. -fn p7a_reads(edge: CausalEdge64) -> Result { - read(&decl(), CERT_CLASS, edge, EdgeProvenance::V2Stamped) -} - -/// The facts P7a's contract entails, in the affordance vocabulary. -/// `Contributes` is `SUPPORTS` (operator decision: "Contributes / Supports"). -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::Contributes) { - f |= SUPPORTS; - } - if contract.entails(Contract::Causes) { - f |= CAUSES; - } - f -} - -const SHARED: u32 = ASSOCIATED | RELATED | SUPPORTS | CAUSES; - -/// The affordance facts of an edge of P7a's class, or `None` if refused. -fn affordance_facts(edge: CausalEdge64) -> Option { +/// The canonical projection of an edge of P7a's class. +fn canonical(edge: CausalEdge64) -> Option { decl() .project_state5( CERT_CLASS, @@ -113,20 +86,32 @@ fn affordance_facts(edge: CausalEdge64) -> Option { EdgeProvenance::V2Stamped, ) .ok() - .map(|s| s.facts()) } -/// (agree, disagree, refused) over every legacy grounding × P7a contract. +/// 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)) +} + +/// (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 t in LegacyTopology::ALL { - for c in Contract::ALL { - match stamp(base(), c, t) { - Err(_) => refused += 1, - Ok(e) => match affordance_facts(e) { - Some(f) if f & SHARED == p7a_entails(c) && p7a_reads(e) == Ok(c) => agree += 1, - _ => 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, } } } @@ -134,34 +119,31 @@ fn census() -> (usize, usize, usize) { } fn main() { - println!("D-EPI-MIG-0: legacy grounding x P7a contract -> canonical EpistemicState5"); - for t in LegacyTopology::ALL { - for c in Contract::ALL { - match stamp(base(), c, t) { - Ok(e) => println!( - " {t:<16?} {:<16} -> code {:>2} facts {:#05x}", - format!("{c:?}"), - raw5(e), - affordance_facts(e).unwrap_or(0) - ), - Err(r) => println!(" {t:<16?} {:<16} -> refuses ({r:?})", format!("{c:?}")), - } - } + 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 (was 3 / 5 / 16 in #1378)"); + println!( + "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::EPI_LAW; use affordance_law::{ - measure_declared, LawGen, Refusal, CAUSAL_IDENTIFICATION, COUNTERFACTUAL_PROBE, - HYDRATE_INTERMEDIATE, MECHANISM_FOLD, ROBUSTNESS_TEST, STRATIFY, + measure_declared, LawGen, Refusal, CAUSAL_IDENTIFICATION, COUNTERFACTUAL_PROBE, EPI_LAW, + HYDRATE_INTERMEDIATE, MECHANISM_FOLD, }; + use causal_edge::layout::ReasoningBand; use lance_graph_contract::class_view::ClassId; - use lance_graph_contract::epistemic_state5::{legacy, Epi5ReadError}; + 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; @@ -177,233 +159,189 @@ mod tests { ) } + fn at(code: u8) -> u64 { + eligible(CERT_CLASS, base().with_epistemic_raw5(code)).unwrap() + } + 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_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_under_both_readings() { - let e = stamp(base(), Contract::Causes, LegacyTopology::Direct).unwrap(); + fn undeclared_classes_refuse() { + let e = base().with_epistemic_raw5(20); assert_eq!( eligible(UNDECLARED_CLASS, e), Err(Refusal::Reading(Epi5ReadError::UndeclaredClass( UNDECLARED_CLASS ))) ); - // Silence twin: the declared class measures the same edge. assert!(eligible(CERT_CLASS, e).is_ok()); } - /// F3: a certified `Causes` is canonical `Causes`, never `Related`. + /// F3: a certified `Causes` is `Causes` under every topology. #[test] - fn a_certified_causes_reads_as_causes() { - for (t, code) in [ - (LegacyTopology::Direct, 1), - (LegacyTopology::IndirectKnown, 17), - ] { - let e = stamp(base(), Contract::Causes, t).unwrap(); - assert_eq!(raw5(e), code); - assert_eq!(p7a_reads(e), Ok(Contract::Causes)); - let facts = affordance_facts(e).unwrap(); - assert_ne!(facts & CAUSES, 0); - assert_ne!( - eligible(CERT_CLASS, e).unwrap() & 1 << COUNTERFACTUAL_PROBE, - 0 + fn a_certified_causes_reads_as_causes_under_every_topology() { + for t in Topology2::ALL { + let e = certification_reading::stamp(base(), Contract::Causes, t); + assert_eq!(raw5(e), 20 + t.ordinal()); + assert_eq!( + read(&decl(), CERT_CLASS, e, EdgeProvenance::V2Stamped), + Ok(Contract::Causes) ); - } - // An unknown or unknown-intermediate grounding cannot carry Causes. - for t in [LegacyTopology::IndirectUnknown, LegacyTopology::Unknown] { - assert!(matches!( - stamp(base(), Contract::Causes, t), - Err(P7aRefusal::NoCanonicalState(_)) - )); + assert!(canonical(e).unwrap().asserts(CAUSES)); + assert_ne!(at(raw5(e)) & 1 << COUNTERFACTUAL_PROBE, 0); } } - /// F10 (legacy direction): every cell either translates into a state both - /// readers agree on, or refuses. No disagreement remains. + /// The #1378 fixture (P7a band 5 over topology 0) now means the same to + /// both readers: `Direct × Causes`. #[test] - fn every_legacy_cell_translates_in_agreement_or_refuses() { - assert_eq!(census(), (11, 0, 13)); + 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::from_certification(s.certification()), + Contract::Causes + ); } - /// F10 (bit direction): over every raw code, the P7a reading and the law - /// either agree on the shared facts or both refuse. + /// F6: the factor-compiled per-code table equals evaluating the rules on + /// each code's facts directly, for both law generations. #[test] - fn both_readings_agree_on_every_raw_code() { - let mut agreed = 0; - for code in 0u8..32 { - let e = base().with_epistemic_raw5(code); - match (p7a_reads(e), affordance_facts(e)) { - (Ok(c), Some(f)) => { - agreed += 1; - // The certification projection is the strongest claim; - // the facts must entail exactly what it entails. - assert_eq!(f & SHARED, p7a_entails(c), "code {code}"); + 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), } - (Err(P7aRefusal::Reading(_)), None) => {} - other => panic!("code {code}: readers diverge: {other:?}"), } } - assert_eq!(agreed, EPI_LAW.iter().filter(|f| f.is_some()).count()); - assert_eq!(agreed, 10); } - /// The bit pattern #1378 was built on (P7a band 5 over topology 0) is - /// code 20. It is no longer what a P7a `Causes` produces, and both readers - /// read it as the same thing: `Direct × Related`. + /// F7 + the operator's frontier cases: one coordinate moves only the + /// recipes that read it. #[test] - fn the_historical_split_pattern_is_read_by_its_canonical_meaning() { - let old = base().with_epistemic_raw5((Contract::Causes.code() << 2) | 0); - assert_eq!(raw5(old), 20); - assert_eq!(p7a_reads(old), Ok(Contract::Related)); + fn one_factor_moves_only_its_recipes() { + // IndirectUnknown × Related (10) vs IndirectKnown × Related (9): + // exactly hydrate ↔ mechanism fold. assert_eq!( - affordance_facts(old).unwrap() & SHARED, - ASSOCIATED | RELATED + at(10) ^ at(9), + bits(&[HYDRATE_INTERMEDIATE, MECHANISM_FOLD]) ); - let now = stamp(base(), Contract::Causes, LegacyTopology::Direct).unwrap(); - assert_ne!(raw5(now), raw5(old)); - } - - /// The pre-migration `measure` body, verbatim (classless, own codebook - /// lookup), as the regression oracle for F6. - fn pre_migration_measure( - law: LawGen, - edge: CausalEdge64, - provenance: EdgeProvenance, - ) -> Result { - if !matches!( - provenance, - EdgeProvenance::V2Stamped | EdgeProvenance::V3Register - ) { - return Err(Refusal::Provenance(provenance)); - } - let code = (edge.spare() << 2) | edge.truth_raw(); - 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]) - } + // 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); - /// F6: on a declared class, the projected measurement equals the - /// pre-migration measurement everywhere. - #[test] - fn measurement_is_unchanged_by_the_refactor() { - let mut compared = 0; - for law in [LawGen::V1, LawGen::V2] { - for pearl in 0u8..8 { - for code in 0u8..32 { - for prov in [ - EdgeProvenance::V2Stamped, - EdgeProvenance::V3Register, - EdgeProvenance::V1Legacy, - EdgeProvenance::Unknown, - ] { - let e = CausalEdge64::pack_v2( - 1, - 2, - 3, - 9, - 9, - CausalMask::from_bits(pearl), - 0, - PlasticityState::from_bits(0), - ) - .with_epistemic_raw5(code); - let new = measure_declared(law, &decl(), CERT_CLASS, RAIL, e, prov); - assert_eq!(new, pre_migration_measure(law, e, prov), "{code} {prov:?}"); - compared += 1; - } + // 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); } } - assert_eq!(compared, 2 * 8 * 32 * 4); } - /// F7 + the operator examples: changing one factor changes only the - /// recipes that read that factor. + /// 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 one_factor_moves_only_its_recipes() { - let at = |code| eligible(CERT_CLASS, base().with_epistemic_raw5(code)).unwrap(); - let (unknown, known, supports) = (at(25), at(5), at(30)); - // 25 = Indirect × IntermediateUnknown × Related: search, not fold. + 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!( - unknown - & bits(&[ - HYDRATE_INTERMEDIATE, - MECHANISM_FOLD, - STRATIFY, - ROBUSTNESS_TEST - ]), - bits(&[HYDRATE_INTERMEDIATE, STRATIFY, ROBUSTNESS_TEST]) + eligible(HISTORICAL_CLASS, e), + Err(Refusal::Reading(Epi5ReadError::UndeclaredClass( + HISTORICAL_CLASS + ))) ); - assert_eq!(unknown & 1 << COUNTERFACTUAL_PROBE, 0); - // Intermediate unknown → known: exactly hydrate ↔ mechanism fold. assert_eq!( - unknown ^ known, - bits(&[HYDRATE_INTERMEDIATE, MECHANISM_FOLD]) + read(&decl(), HISTORICAL_CLASS, e, EdgeProvenance::V2Stamped), + Err(P7aRefusal::NotCertificationClass) ); - // Related → Supports: exactly causal identification becomes eligible. - assert_eq!(known ^ supports, bits(&[CAUSAL_IDENTIFICATION])); - assert_ne!(supports & 1 << MECHANISM_FOLD, 0); } /// F5: same edge + class/rail + generation + provenance → same state /// after a restart from the LE image. #[test] fn the_state_survives_restart_from_le_bytes() { - for code in (0u8..32).filter(|c| EPI_LAW[*c as usize].is_some()) { + for code in 0u8..32 { let e = base().with_epistemic_raw5(code); let restarted = CausalEdge64::from_le_bytes(e.to_le_bytes()); - let a = decl().project_state5( - CERT_CLASS, - RAIL, - Epi5Gen::V1, - raw5(e), - EdgeProvenance::V2Stamped, - ); - let b = decl().project_state5( - CERT_CLASS, - RAIL, - Epi5Gen::V1, - raw5(restarted), - EdgeProvenance::V2Stamped, - ); - assert_eq!(a, b); - assert_eq!(p7a_reads(e), p7a_reads(restarted)); + assert_eq!(canonical(e), canonical(restarted)); + assert_eq!(canonical(e).is_some(), code < 24); } } - /// F8: reading, projecting and computing a compatibility transition never - /// writes the edge; only an explicit joint write does. + /// F8: reading and projecting never write the edge; a factor transition + /// exists only as a value until written jointly. #[test] fn projections_never_change_the_edge() { - let e = base().with_epistemic_raw5(5); + let e = base().with_epistemic_raw5(9); let before = e.0; - let s = decl() - .project_state5( - CERT_CLASS, - RAIL, - Epi5Gen::V1, - raw5(e), - EdgeProvenance::V2Stamped, - ) - .unwrap(); + let s = canonical(e).unwrap(); let _ = ( - legacy::project_topology(s), - legacy::project_certification(s), + split(e), + at(raw5(e)), + read(&decl(), CERT_CLASS, e, EdgeProvenance::V2Stamped), ); - let _ = p7a_reads(e); - let _ = eligible(CERT_CLASS, e); - let moved = legacy::retopologize(s, LegacyTopology::IndirectUnknown).unwrap(); + let moved = s.with_topology(Topology2::IndirectUnknown); assert_eq!(e.0, before, "a projection wrote the edge"); - // The transition exists only as a value until it is written jointly. - assert_eq!(moved.raw(), 25); + assert_eq!(moved.raw(), 10); assert_eq!( e.with_epistemic_raw5(moved.raw()).0 ^ before, - (5u64 ^ 25) << 59 + (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 dac63c872..5332e23d2 100644 --- a/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs +++ b/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs @@ -312,12 +312,15 @@ //! //! Run: `cargo run -p cognitive-shader-driver --example probe_revision_kanban_hinge` //! -//! D-EPI-MIG-0 (2026-10-07): PROBE-ONLY legacy. This probe writes the -//! historical `ReasoningBand` / `CausalTopology` half-field readings of -//! CE64 bits 59..63, which have no translation into the canonical -//! `EpistemicState5` reading (`contract::epistemic_state5`) and refuse there. -//! It keeps the deprecated half writers on purpose, as a record of that -//! reading; no production code may follow it. +//! 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. #![allow(deprecated)] use causal_edge::edge::CausalEdge64; diff --git a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs index f691ced92..80a86799e 100644 --- a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs +++ b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs @@ -75,12 +75,15 @@ //! 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. This probe writes the -//! historical `ReasoningBand` / `CausalTopology` half-field readings of -//! CE64 bits 59..63, which have no translation into the canonical -//! `EpistemicState5` reading (`contract::epistemic_state5`) and refuse there. -//! It keeps the deprecated half writers on purpose, as a record of that -//! reading; no production code may follow it. +//! 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. #![allow(deprecated)] use causal_edge::edge::CausalEdge64; diff --git a/crates/cognitive-shader-driver/examples/relational_certification_probe.rs b/crates/cognitive-shader-driver/examples/relational_certification_probe.rs index 77aa27112..3158b4c0c 100644 --- a/crates/cognitive-shader-driver/examples/relational_certification_probe.rs +++ b/crates/cognitive-shader-driver/examples/relational_certification_probe.rs @@ -49,17 +49,12 @@ //! //! # Carrier (D-EPI-MIG-0) //! -//! The certification no longer owns bits 61..63. It is stamped by translating -//! `(MODEL_GROUNDING, contract)` into the canonical `EpistemicState5` code -//! (`contract::epistemic_state5::legacy::translate`) and writing all of bits -//! 59..63 with `with_epistemic_raw5`; it is read back through -//! `Epi5Declarations::project_state5` and the legacy certification projection. -//! `Open`, `Associated`, `Related` and `Causes` have canonical codes under the -//! declared `Direct` grounding (0, 12, 20, 1); `Contributes` and -//! `CausalCandidate` do not, and stamping them REFUSES. The observational -//! fixtures certify `CausalCandidate`, so their certification is computed but -//! cannot be stored until the codebook declares that conjunction (open point, -//! `entries/2026-10-07-epi-mig-0.md`). 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`. //! @@ -448,13 +443,11 @@ fn main() { println!(" codebook: {} (6, 7 reserved)", codebook.join(", ")); for (name, m) in &fixtures { let c = m.certify(); - let stamped = stamp(CausalEdge64::ZERO, c, MODEL_GROUNDING); - let back = stamped.map(|e| { - ( - e.epistemic_raw5(), - read(&decl, CERT_CLASS, e, 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:?} (P7a code {}, marginal association {:?}, canonical {back:?})", c.code(), @@ -555,31 +548,19 @@ mod tests { #[test] fn the_reading_is_declared_and_reserved_codes_refuse() { let decl = declarations(); - let mut stamped = 0; for c in Contract::ALL { - match stamp(CausalEdge64::ZERO, c, MODEL_GROUNDING) { - Ok(edge) => { - stamped += 1; - assert_eq!( - read(&decl, CERT_CLASS, edge, EdgeProvenance::V2Stamped), - Ok(c) - ); - // Same bits, historical reading: refused. - assert_eq!( - read(&decl, LEGACY_CLASS, edge, EdgeProvenance::V2Stamped), - Err(Refusal::NotCertificationClass) - ); - } - Err(Refusal::NoCanonicalState(_)) => { - assert!( - matches!(c, Contract::Contributes | Contract::CausalCandidate), - "{c:?} must have a canonical code under Direct" - ); - } - Err(other) => panic!("{c:?}: {other:?}"), - } + 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, a class without a canonical declaration: refused. + assert_eq!( + read(&decl, LEGACY_CLASS, edge, EdgeProvenance::V2Stamped), + Err(Refusal::NotCertificationClass) + ); } - assert_eq!(stamped, 4, "Open, Associated, Related, Causes"); // P7a's reserved bands 6, 7 in the high half: not canonical codes. for raw in [6u8, 7] { let edge = CausalEdge64::ZERO.with_epistemic_raw5(raw << 2); @@ -588,7 +569,7 @@ mod tests { Err(Refusal::Reading(Epi5ReadError::UndeclaredCode(raw << 2))) ); } - let edge = stamp(CausalEdge64::ZERO, Contract::Causes, MODEL_GROUNDING).unwrap(); + let edge = stamp(CausalEdge64::ZERO, Contract::Causes, MODEL_GROUNDING); assert_eq!( read(&decl, CERT_CLASS, edge, EdgeProvenance::Unknown), Err(Refusal::Reading(Epi5ReadError::UnknownProvenance( @@ -749,7 +730,7 @@ mod tests { let certified = m.certify(); for mantissa in -8i8..=7 { let edge = CausalEdge64::ZERO.with_inference_mantissa(mantissa); - let stamped = stamp(edge, certified, MODEL_GROUNDING).expect("Causes is canonical"); + let stamped = stamp(edge, certified, MODEL_GROUNDING); assert_eq!(stamped.inference_mantissa(), mantissa); assert_eq!( (stamped.0 ^ edge.0) & !EPISTEMIC_MASK, @@ -910,19 +891,14 @@ mod tests { b.build() }, ]; - let run = || -> Vec> { + let run = || -> Vec { seals .iter() - .map(|m| stamp(CausalEdge64::ZERO, m.certify(), MODEL_GROUNDING).map(|e| e.0)) + .map(|m| stamp(CausalEdge64::ZERO, m.certify(), MODEL_GROUNDING).0) .collect() }; assert_eq!(run(), run()); - // The two observational seals certify CausalCandidate, which has no - // canonical code: refused, deterministically. The trial seal stamps. - let r = run(); - assert!(matches!(r[0], Err(Refusal::NoCanonicalState(_)))); - assert!(matches!(r[1], Err(Refusal::NoCanonicalState(_)))); - for bits in r.into_iter().flatten() { + for bits in run() { assert!(read( &decl, CERT_CLASS, 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 65c4eb3e2..55ae2f579 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 //! @@ -96,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; @@ -275,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) @@ -292,7 +295,7 @@ fn with_code(edge: CausalEdge64, code: u8) -> CausalEdge64 { 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, ) } @@ -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), diff --git a/crates/cognitive-shader-driver/examples/shared/affordance_law.rs b/crates/cognitive-shader-driver/examples/shared/affordance_law.rs index ac041b6d1..dbe477046 100644 --- a/crates/cognitive-shader-driver/examples/shared/affordance_law.rs +++ b/crates/cognitive-shader-driver/examples/shared/affordance_law.rs @@ -3,18 +3,22 @@ //! and `ce64_cycle_survival_probe` (which measures eligibility across cycles //! with the identical law instead of a copy). //! -//! D-EPI-MIG-0: the codebook and facts now come from -//! `lance_graph_contract::epistemic_state5`; measurement projects bits 59..63 -//! through a class declaration (`measure_declared`; `measure` uses the -//! probes' declared `AFF_CLASS`). Tables and rules are unchanged. Each -//! including example uses a subset, hence the `dead_code` allowance. +//! 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, CODEBOOK_V1, + Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, EpistemicState5, CERTIFICATION_FACTS, + COMPILED_FACTS_V1, TOPOLOGY_FACTS, }; use lance_graph_contract::rail_geometry::RailAxis; @@ -27,14 +31,13 @@ use lance_graph_contract::rail_geometry::RailAxis; #[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, OBSERVED, RELATED, SUPPORTS, UP_TO_RELATED, - UP_TO_SUPPORTS, + INTERMEDIATE_PRESENT, INTERMEDIATE_UNKNOWN, RELATED, SUPPORTS, UP_TO_RELATED, UP_TO_SUPPORTS, }; -/// The codebook the law compiles against: the contract's `CODEBOOK_V1`, -/// aliased (was a probe-local table until D-EPI-CANON-0; the values are -/// identical). -pub const EPI_LAW: [Option; 32] = CODEBOOK_V1; +/// 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. @@ -58,7 +61,13 @@ pub fn write_state(edge: CausalEdge64, state: EpistemicState5) -> CausalEdge64 { edge.with_epistemic_raw5(state.raw()) } -/// Write a V1 code that must be declared (fixture convenience). +/// `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) @@ -123,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]; @@ -161,7 +196,12 @@ 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); diff --git a/crates/cognitive-shader-driver/examples/shared/certification_reading.rs b/crates/cognitive-shader-driver/examples/shared/certification_reading.rs index da9d2e205..4fbd13ac3 100644 --- a/crates/cognitive-shader-driver/examples/shared/certification_reading.rs +++ b/crates/cognitive-shader-driver/examples/shared/certification_reading.rs @@ -3,13 +3,11 @@ //! `epistemic_reading_conflict_probe` (which pins its disagreement with the //! affordance law against this implementation, not a copy). //! -//! D-EPI-MIG-0: P7a no longer owns bits 61..63. A certification is stamped -//! by translating `(declared grounding, contract)` into the canonical -//! `EpistemicState5` code (`contract::epistemic_state5::legacy::translate`) -//! and writing all of bits 59..63; it is read by projecting the canonical -//! state and taking its legacy certification projection. A cell without a -//! canonical code (any `CausalCandidate`; `Contributes` under `Direct`) -//! REFUSES — no code is invented for it. +//! 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. @@ -18,11 +16,9 @@ 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::legacy::{ - project_certification, translate, LegacyCertification, LegacyError, LegacyTopology, -}; use lance_graph_contract::epistemic_state5::{ - Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, + Certification3, Epi5Declarations, Epi5Gen, Epi5ReadError, Epi5Reading, EpistemicState5, + Topology2, }; use lance_graph_contract::rail_geometry::RailAxis; @@ -41,7 +37,7 @@ pub const CERTIFICATION_CLASSES: &[ClassId] = &[CERT_CLASS]; /// 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: LegacyTopology = LegacyTopology::Direct; +pub const MODEL_GROUNDING: Topology2 = Topology2::Direct; /// The certified relational contract. Working names; the obligations in the /// module table are the content. @@ -108,12 +104,12 @@ impl Contract { } impl Contract { - /// The contract in the contract crate's legacy vocabulary (same order). - pub fn legacy(self) -> LegacyCertification { - LegacyCertification::ALL[self.code() as usize] + /// The canonical certification coordinate (same code). + pub fn certification(self) -> Certification3 { + Certification3::ALL[self.code() as usize] } - pub fn from_legacy(c: LegacyCertification) -> Self { + pub fn from_certification(c: Certification3) -> Self { Contract::ALL[c.code() as usize] } } @@ -128,8 +124,6 @@ pub enum Refusal { /// The canonical projection refused (undeclared, untrusted provenance, /// undeclared code). Reading(Epi5ReadError), - /// `(grounding, contract)` has no canonical code in this generation. - NoCanonicalState(LegacyError), } /// The canonical declarations: `CERT_CLASS` reads bits 59..63 as @@ -160,7 +154,7 @@ pub fn read( let state = decl .project_state5(class, RAIL, Epi5Gen::V1, edge.epistemic_raw5(), provenance) .map_err(Refusal::Reading)?; - Ok(Contract::from_legacy(project_certification(state))) + Ok(Contract::from_certification(state.certification())) } /// Does the edge license `required`? @@ -173,14 +167,10 @@ pub fn satisfies( Ok(read(decl, class, edge, EdgeProvenance::V2Stamped)?.entails(required)) } -/// Stamp a certification: translate `(grounding, contract)` to the canonical -/// code and write all of bits 59..63, or refuse. Nothing else moves. -pub fn stamp( - edge: CausalEdge64, - contract: Contract, - grounding: LegacyTopology, -) -> Result { - let state = - translate(Epi5Gen::V1, grounding, contract.legacy()).map_err(Refusal::NoCanonicalState)?; - Ok(edge.with_epistemic_raw5(state.raw())) +/// 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..b27f929f2 100644 --- a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs +++ b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs @@ -348,11 +348,11 @@ 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). + // 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 = - CausalEdge64((committed.0 & !(TRUTH_MASK | SPARE_MASK)) | (7u64 << TRUTH_SHIFT)); + CausalEdge64((committed.0 & !(TRUTH_MASK | SPARE_MASK)) | (4u64 << TRUTH_SHIFT)); let mb = d.mailbox_mut(MAILBOX).unwrap(); let cell = WriteCell { edge: Some(observed), diff --git a/crates/lance-graph-contract/src/band_reading.rs b/crates/lance-graph-contract/src/band_reading.rs index ae41d0324..2cfc0abbd 100644 --- a/crates/lance-graph-contract/src/band_reading.rs +++ b/crates/lance-graph-contract/src/band_reading.rs @@ -5,14 +5,15 @@ //! 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`, read -//! > through [`epistemic_state5`](crate::epistemic_state5). The split reading -//! > below (59..60 lens + 61..63 band) is a **legacy compatibility surface**: -//! > it may still project the halves of an edge whose class has not migrated, -//! > but it is not a peer authority and new readers must not use it. The P7a -//! > certification band translates through `epistemic_state5::legacy`; the -//! > historical `ReasoningBand` / `TrustTexture` readings have no canonical -//! > translation. [`EdgeProvenance`] stays here unchanged and is shared by +//! > `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 diff --git a/crates/lance-graph-contract/src/epistemic_state5.rs b/crates/lance-graph-contract/src/epistemic_state5.rs index 6b2b161aa..1b456d824 100644 --- a/crates/lance-graph-contract/src/epistemic_state5.rs +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -4,51 +4,61 @@ //! `epistemic_state5` — the canonical reading of `CausalEdge64` bits 59..63 //! (`D-EPI-CANON-0`, operator decision 2026-10-07). //! -//! # One field, one reading +//! # One field, two coordinates //! -//! Bits 59..63 are ONE 5-bit field: an `EpistemicState5` code, a dense -//! codebook over valid epistemic conjunctions (grounding × assertion). The -//! code is semantically opaque on its own — **raw ordinal + declared reading -//! = meaning** — so every read goes through a declaration keyed by -//! `(classid, rail)` and a codebook generation: +//! Bits 59..63 are ONE 5-bit field, `EpistemicState5`, and it is a dense +//! PRODUCT space, not a hand-assigned codebook: //! //! ```text -//! (classid, rail) → Epi5Reading { generation } -//! → project_state5(raw5, provenance) → EpistemicState5 | refusal +//! 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 //! ``` //! -//! The physical read is `CausalEdge64::epistemic_raw5()` (equal to -//! `(spare() << 2) | truth_raw()`), the physical write -//! `CausalEdge64::with_epistemic_raw5(state.raw())`. This crate is zero-dep and -//! never touches the edge layout; it takes and returns raw ordinals, like -//! [`band_reading`](crate::band_reading). +//! 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 //! -//! # The codebook is also the affordance law's input +//! 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. //! -//! A code's [facts](fact) decide which statistical/microcode recipes are legal -//! on the edge (`CausalEdge64 × RecipeLaw[64] → EligibleRecipes: u64`, the -//! D-GSO-AFF-0 measurement). For example `25 = Indirect × IntermediateUnknown × -//! Related` admits hydration of the intermediate but never a known-intermediate -//! mechanism fold; `30 = Indirect × IntermediateKnown × Supports` admits the -//! mechanism fold and causal identification. 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. //! -//! # Supersedes the split reading +//! # Declared, never inferred //! -//! [`band_reading`](crate::band_reading) declared 59..60 (`TruthLens`) and -//! 61..63 (`BandPresence`) as two fields. That split no longer owns these bits. -//! The P7a certification band (#1369), which wrote 61..63 alone, survives as a -//! [legacy](legacy) translation into this codebook; the historical -//! `ReasoningBand` and `TrustTexture` readings have **no** declared -//! translation and refuse (their producers never declared conjunction -//! semantics, and the bits cannot say which reading wrote them). +//! 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. //! -//! # Refusals +//! # Legacy readings (compatibility only) //! -//! [`Epi5Declarations::project_state5`] refuses, never guesses: undeclared -//! `(classid, rail)`, a generation other than the one requested, untrusted -//! provenance (`V1Legacy` / `Unknown` — on a v1 row these bits are old -//! `temporal`), and a code the generation does not declare. +//! [`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; @@ -56,35 +66,43 @@ use crate::rail_geometry::RailAxis; /// Number of raw codes the 5-bit field can carry. pub const CODES: usize = 32; -const _: () = assert!(CODES == 1 << 5); - -/// A conjunction of epistemic facts — the meaning of one declared code. +/// 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 code's meaning is a conjunction of these; the -/// affordance law states each recipe's obligations in the same vocabulary. +/// 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; - /// The relation is grounded directly (no intermediate). + /// Topology: the relation is grounded directly (no intermediate). pub const DIRECT: Facts = 1 << 0; - /// The relation runs through at least one intermediate. + /// Topology: the relation runs through at least one intermediate. pub const INDIRECT: Facts = 1 << 1; - /// An intermediate is part of the claim. + /// Topology: an intermediate is part of the claim. pub const INTERMEDIATE_PRESENT: Facts = 1 << 2; - /// The intermediate is not known (hydration is the open work). + /// Topology: the intermediate is not known (hydration is open work). pub const INTERMEDIATE_UNKNOWN: Facts = 1 << 3; - /// The intermediate is known (a mechanism can be folded/tested). + /// Topology: the intermediate is known (a mechanism can be folded/tested). pub const INTERMEDIATE_KNOWN: Facts = 1 << 4; - /// The relation was observed on units, not only asserted. - pub const OBSERVED: Facts = 1 << 5; - /// Associated in a declared population. + /// Topology: not resolved at all. + pub const TOPOLOGY_UNKNOWN: Facts = 1 << 10; + /// Certification: associated in a declared population. pub const ASSOCIATED: Facts = 1 << 6; - /// Related: the association survives the robustness mask. + /// Certification: the association survives the robustness mask. pub const RELATED: Facts = 1 << 7; - /// Supports / contributes in every declared stratum. + /// Certification: supports / contributes in every declared stratum. pub const SUPPORTS: Facts = 1 << 8; - /// Certified causes (intervention-backed). + /// Certification: supports, and exposure precedes outcome. + pub const CAUSAL_CANDIDATE: Facts = 1 << 11; + /// Certification: intervention-certified. pub const CAUSES: Facts = 1 << 9; /// `Indirect × IntermediateUnknown`. @@ -95,115 +113,244 @@ pub mod fact { 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, } -/// The V1 codebook: code → facts, `None` = not a code of this generation. -/// -/// Adopted unchanged from the D-GSO-AFF-0 measurement (#1370). Deliberately -/// not ordered by strength (`Causes` sits at 1, `Open` at 0, `Related` at 20): -/// the codebook is dense over conjunctions, never an ordinal scale. -pub const CODEBOOK_V1: [Option; CODES] = { +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::*; - let mut t = [None; CODES]; - 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 + [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, + ] }; -/// Named V1 codes. The facts are the content; the names are pointers. -pub mod code_v1 { - /// No fact asserted. Also what `pack_v2` / `compose` leave behind. - pub const OPEN: u8 = 0; - /// `Direct × Supports × Causes`. - pub const DIRECT_CAUSES: u8 = 1; - /// `Direct × Observed`. - pub const DIRECT_OBSERVED: u8 = 3; - /// `Indirect × IntermediateKnown × Related`. - pub const IND_KNOWN_RELATED: u8 = 5; - /// `Direct × Observed × Associated`. - pub const DIRECT_OBSERVED_ASSOCIATED: u8 = 7; - /// `Direct × Associated`. - pub const DIRECT_ASSOCIATED: u8 = 12; - /// `Indirect × IntermediateKnown × Supports × Causes`. - pub const IND_KNOWN_CAUSES: u8 = 17; - /// `Direct × Related`. - pub const DIRECT_RELATED: u8 = 20; - /// `Indirect × IntermediateUnknown × Related`. - pub const IND_UNKNOWN_RELATED: u8 = 25; - /// `Indirect × IntermediateKnown × Supports`. - pub const IND_KNOWN_SUPPORTS: u8 = 30; +/// 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, + } } -/// A codebook generation. A generation names the whole declared reading; -/// changing any code's facts is a new generation, never an edit. +/// `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 reading generation. V1 is the 2 × 3 product above. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Epi5Gen { - /// [`CODEBOOK_V1`]. + /// `Topology2 × Certification3`. #[default] V1, } -impl Epi5Gen { - /// The generation's codebook — the ONE authoritative code → facts table. - #[must_use] - pub const fn codebook(self) -> &'static [Option; CODES] { - match self { - Epi5Gen::V1 => &CODEBOOK_V1, - } - } -} - -/// A code validated under a generation. Only obtainable through -/// [`EpistemicState5::decode`] / [`Epi5Declarations::project_state5`] / -/// a [legacy] translation, so holding one means the code is declared. +/// 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 { - code: u8, + topology: Topology2, + certification: Certification3, generation: Epi5Gen, } impl EpistemicState5 { - /// Validate `raw5` against `generation`'s codebook. No provenance or - /// class check: use [`Epi5Declarations::project_state5`] to read an edge. - /// This is the producer-side constructor. + /// 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 || generation.codebook()[raw5 as usize].is_none() { + if raw5 as usize >= CODES { return Err(Epi5ReadError::UndeclaredCode(raw5)); } - Ok(Self { - code: raw5, - generation, - }) + 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.code + 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 code was validated under. + /// The generation the state was validated under. #[must_use] pub const fn generation(self) -> Epi5Gen { self.generation } - /// The code's facts. + /// `TOPOLOGY_FACTS[t] | CERTIFICATION_FACTS[c]`. #[must_use] pub const fn facts(self) -> Facts { - match self.generation.codebook()[self.code as usize] { - Some(f) => f, - // Unreachable: construction validated the code. - None => 0, - } + self.topology.facts() | self.certification.facts() } /// Does the state assert every fact in `required`? @@ -211,19 +358,36 @@ impl EpistemicState5 { 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 codebook generation the class's producers write. + /// 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. The class lookup - /// is the caller's — [`Epi5Declarations::project_state5`] is the usual one. + /// allocation): provenance, then generation, then code. pub const fn project( self, requested: Epi5Gen, @@ -257,7 +421,7 @@ pub enum Epi5ReadError { }, /// `V1Legacy` or `Unknown` provenance: the bits may be old `temporal`. UnknownProvenance(EdgeProvenance), - /// The code is not declared by the generation. + /// A reserved certification (codes 24..31) or a value above 31. UndeclaredCode(u8), } @@ -302,9 +466,8 @@ impl Epi5Declarations { .map(|(_, b)| *b) } - /// The canonical projection of bits 59..63. Check order: declaration, - /// provenance, generation, code — untrusted bits fail before their - /// meaning is asked. + /// The canonical projection of bits 59..63: declaration, then provenance, + /// generation and code ([`Epi5Reading::project`]). pub fn project_state5( &self, class: ClassId, @@ -319,229 +482,12 @@ impl Epi5Declarations { } } -/// Legacy compatibility: the split readings, as translations INTO and -/// projections OUT OF the canonical codebook. Transitional — new code reads -/// [`Epi5Declarations::project_state5`] and writes a declared state. -/// -/// Only one legacy producer has declared conjunction semantics: P7a -/// certification (#1369) under a legacy `CausalTopology` grounding. Its -/// translation is an explicit table; a cell without a canonical code refuses. -/// A translation never manufactures a fact: `Causes` comes only from a -/// certified `Causes`. -pub mod legacy { - use super::{code_v1, fact, Epi5Gen, EpistemicState5}; - - /// The legacy 2-bit grounding reading. Ordinals mirror - /// `causal_edge::layout::CausalTopology` (doc pointer, never an import). - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum LegacyTopology { - /// `CausalTopology::Direct` (0). - Direct, - /// `CausalTopology::IndirectKnownIntermediates` (1). - IndirectKnown, - /// `CausalTopology::IndirectUnknownIntermediates` (2). - IndirectUnknown, - /// `CausalTopology::Unknown` (3): grounding not known. - Unknown, - } - - impl LegacyTopology { - /// All four. - pub const ALL: [LegacyTopology; 4] = [ - LegacyTopology::Direct, - LegacyTopology::IndirectKnown, - LegacyTopology::IndirectUnknown, - LegacyTopology::Unknown, - ]; - - /// The legacy ordinal. - #[must_use] - pub const fn ordinal(self) -> u8 { - self as u8 - } - - /// From the legacy ordinal (`0..=3`); `None` above. - #[must_use] - pub const fn from_ordinal(o: u8) -> Option { - match o { - 0 => Some(LegacyTopology::Direct), - 1 => Some(LegacyTopology::IndirectKnown), - 2 => Some(LegacyTopology::IndirectUnknown), - 3 => Some(LegacyTopology::Unknown), - _ => None, - } - } - } - - /// The P7a certification contract (#1369) that used to own bits 61..63. - /// Codes 6 and 7 were reserved there. - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum LegacyCertification { - /// No obligation met. - Open, - /// Associated in a declared population, ≥ 2 sources. - Associated, - /// Associated and robust under the mask. - Related, - /// Does not lower Y in any stratum, raises it in one (= `SUPPORTS`). - Contributes, - /// `Contributes` plus ordering. No canonical fact yet. - CausalCandidate, - /// Intervention-certified. - Causes, - } - - impl LegacyCertification { - /// All six, in P7a's ordinal order. - pub const ALL: [LegacyCertification; 6] = [ - LegacyCertification::Open, - LegacyCertification::Associated, - LegacyCertification::Related, - LegacyCertification::Contributes, - LegacyCertification::CausalCandidate, - LegacyCertification::Causes, - ]; - - /// P7a's 3-bit code. - #[must_use] - pub const fn code(self) -> u8 { - self as u8 - } - - /// From P7a's code; 6 and 7 are reserved and refuse. - pub const fn from_code(raw: u8) -> Result { - Ok(match raw { - 0 => LegacyCertification::Open, - 1 => LegacyCertification::Associated, - 2 => LegacyCertification::Related, - 3 => LegacyCertification::Contributes, - 4 => LegacyCertification::CausalCandidate, - 5 => LegacyCertification::Causes, - other => return Err(LegacyError::ReservedCertification(other)), - }) - } - } - - /// Why a legacy translation refused. - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum LegacyError { - /// The legacy pair has no canonical conjunction in this generation. - NoCanonicalState { - /// The declared grounding. - topology: LegacyTopology, - /// The declared certification. - certification: LegacyCertification, - }, - /// P7a's reserved codes 6 / 7. - ReservedCertification(u8), - /// The state asserts no grounding, so a grounding-relative update - /// has nothing to keep. - NoGrounding, - } - - /// Translate a declared legacy pair into the canonical code. The ONE - /// translation table; every cell not listed refuses. - pub const fn translate( - generation: Epi5Gen, - topology: LegacyTopology, - certification: LegacyCertification, - ) -> Result { - use LegacyCertification as C; - use LegacyTopology as T; - let code = match generation { - Epi5Gen::V1 => match (topology, certification) { - (_, C::Open) => Some(code_v1::OPEN), - (T::Direct, C::Associated) => Some(code_v1::DIRECT_ASSOCIATED), - (T::Direct, C::Related) => Some(code_v1::DIRECT_RELATED), - (T::Direct, C::Causes) => Some(code_v1::DIRECT_CAUSES), - (T::IndirectKnown, C::Related) => Some(code_v1::IND_KNOWN_RELATED), - (T::IndirectKnown, C::Contributes) => Some(code_v1::IND_KNOWN_SUPPORTS), - (T::IndirectKnown, C::Causes) => Some(code_v1::IND_KNOWN_CAUSES), - (T::IndirectUnknown, C::Related) => Some(code_v1::IND_UNKNOWN_RELATED), - _ => None, - }, - }; - match code { - Some(c) => match EpistemicState5::decode(generation, c) { - Ok(s) => Ok(s), - Err(_) => Err(LegacyError::NoCanonicalState { - topology, - certification, - }), - }, - None => Err(LegacyError::NoCanonicalState { - topology, - certification, - }), - } - } - - /// Project a canonical state onto the legacy grounding. `None` if the - /// state asserts none (`Open`). Observed states project to `Direct` - /// (lossy: `Observed` has no legacy spelling). - #[must_use] - pub const fn project_topology(state: EpistemicState5) -> Option { - let f = state.facts(); - if f & fact::DIRECT != 0 { - Some(LegacyTopology::Direct) - } else if f & fact::INTERMEDIATE_KNOWN != 0 { - Some(LegacyTopology::IndirectKnown) - } else if f & fact::INTERMEDIATE_UNKNOWN != 0 { - Some(LegacyTopology::IndirectUnknown) - } else { - None - } - } - - /// Project a canonical state onto the strongest legacy certification it - /// asserts. - #[must_use] - pub const fn project_certification(state: EpistemicState5) -> LegacyCertification { - let f = state.facts(); - if f & fact::CAUSES != 0 { - LegacyCertification::Causes - } else if f & fact::SUPPORTS != 0 { - LegacyCertification::Contributes - } else if f & fact::RELATED != 0 { - LegacyCertification::Related - } else if f & fact::ASSOCIATED != 0 { - LegacyCertification::Associated - } else { - LegacyCertification::Open - } - } - - /// Compatibility writer for a legacy certification update: keep the - /// state's grounding, replace the certification, resolve to a canonical - /// code — or refuse. Never a 3-bit overwrite. - pub const fn recertify( - state: EpistemicState5, - certification: LegacyCertification, - ) -> Result { - match project_topology(state) { - Some(t) => translate(state.generation(), t, certification), - None => match certification { - LegacyCertification::Open => Ok(state), - _ => Err(LegacyError::NoGrounding), - }, - } - } - - /// Compatibility writer for a legacy topology update: keep the state's - /// certification, replace the grounding, resolve — or refuse. Never a - /// 2-bit overwrite. - pub const fn retopologize( - state: EpistemicState5, - topology: LegacyTopology, - ) -> Result { - translate(state.generation(), topology, project_certification(state)) - } -} - #[cfg(test)] mod tests { - use super::legacy::*; + use super::fact::*; use super::*; + use Certification3 as C; + use Topology2 as T; const CLASS: ClassId = 0x0902; const RAIL: RailAxis = RailAxis::Taxonomy; @@ -552,165 +498,157 @@ mod tests { d } - #[test] - fn v1_declares_exactly_the_ten_measured_codes() { - let declared: Vec = (0..32u8) - .filter(|c| CODEBOOK_V1[*c as usize].is_some()) - .collect(); - assert_eq!(declared, vec![0, 1, 3, 5, 7, 12, 17, 20, 25, 30]); + fn code(t: T, c: C) -> u8 { + EpistemicState5::new(Epi5Gen::V1, t, c).raw() } - /// The four operator examples, pinned by facts. + /// The layout is the product: `raw5 = t | c << 2`, 24 meaningful, 8 + /// reserved, and decode inverts it on every code. #[test] - fn the_operator_examples_mean_what_they_say() { - use fact::*; - let f = |c: u8| EpistemicState5::decode(Epi5Gen::V1, c).unwrap().facts(); - assert_eq!(f(25), IND_UNKNOWN | ASSOCIATED | RELATED); - assert_eq!(f(30), IND_KNOWN | ASSOCIATED | RELATED | SUPPORTS); - assert_eq!(f(7), DIRECT | OBSERVED | ASSOCIATED); - assert_eq!(f(17), IND_KNOWN | ASSOCIATED | RELATED | SUPPORTS | CAUSES); + 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); } - /// F1 (contract half): every declared code projects to itself. + /// The operator's example coordinates. #[test] - fn projection_round_trips_every_declared_code() { - let d = decl(); - for c in (0..32u8).filter(|c| CODEBOOK_V1[*c as usize].is_some()) { - let s = d - .project_state5(CLASS, RAIL, Epi5Gen::V1, c, EdgeProvenance::V2Stamped) - .unwrap(); - assert_eq!(s.raw(), c); - assert_eq!(s.generation(), Epi5Gen::V1); + 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 projection_refuses_every_failure_and_admits_the_good_case() { - let d = decl(); - let ok = d.project_state5(CLASS, RAIL, Epi5Gen::V1, 25, EdgeProvenance::V3Register); - assert!(ok.is_ok()); + 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!( - d.project_state5(0x0999, RAIL, Epi5Gen::V1, 25, EdgeProvenance::V2Stamped), - Err(Epi5ReadError::UndeclaredClass(0x0999)) + f(21), + IND_KNOWN | UP_TO_SUPPORTS | CAUSAL_CANDIDATE | CAUSES ); - for p in [EdgeProvenance::V1Legacy, EdgeProvenance::Unknown] { - assert_eq!( - d.project_state5(CLASS, RAIL, Epi5Gen::V1, 25, p), - Err(Epi5ReadError::UnknownProvenance(p)) - ); + 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_eq!( - d.project_state5(CLASS, RAIL, Epi5Gen::V1, 2, EdgeProvenance::V2Stamped), - Err(Epi5ReadError::UndeclaredCode(2)) - ); - assert_eq!( - EpistemicState5::decode(Epi5Gen::V1, 32), - Err(Epi5ReadError::UndeclaredCode(32)) - ); + assert!(COMPILED_FACTS_V1[24..].iter().all(Option::is_none)); } - /// Every legacy cell that translates comes back through the legacy - /// projections unchanged (Open carries no grounding). + /// Orthogonality: an intervention-certified relation with an unresolved + /// mechanism or topology is a meaningful state. #[test] - fn legacy_translation_round_trips_through_the_projections() { - let mut mapped = 0; - for t in LegacyTopology::ALL { - for c in LegacyCertification::ALL { - if let Ok(s) = translate(Epi5Gen::V1, t, c) { - mapped += 1; - assert_eq!(project_certification(s), c, "{t:?} {c:?}"); - if c != LegacyCertification::Open { - assert_eq!(project_topology(s), Some(t), "{t:?} {c:?}"); - } - } - } + 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)); } - // 4 Open cells + 7 grounded cells. - assert_eq!(mapped, 11); + assert!(EpistemicState5::decode(Epi5Gen::V1, 22) + .unwrap() + .asserts(IND_UNKNOWN | CAUSES)); + assert!(EpistemicState5::decode(Epi5Gen::V1, 23) + .unwrap() + .asserts(TOPOLOGY_UNKNOWN | CAUSES)); } - /// F3: a certified Causes is canonical Causes, never Related. + /// Certification entailment is the cumulative chain (P7a's `>=` on 0..=5). #[test] - fn legacy_causes_translates_to_a_causes_code() { - for t in [LegacyTopology::Direct, LegacyTopology::IndirectKnown] { - let s = translate(Epi5Gen::V1, t, LegacyCertification::Causes).unwrap(); - assert!(s.asserts(fact::CAUSES)); - assert!(!matches!(s.raw(), code_v1::DIRECT_RELATED)); + 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:?}"); + } } - assert_eq!( - translate( - Epi5Gen::V1, - LegacyTopology::IndirectKnown, - LegacyCertification::Causes - ) - .unwrap() - .raw(), - 17 - ); } - /// F4: an undeclared legacy combination refuses. + /// A factor update moves only its own coordinate. #[test] - fn undeclared_legacy_combinations_refuse() { - for (t, c) in [ - (LegacyTopology::Unknown, LegacyCertification::Causes), - (LegacyTopology::IndirectUnknown, LegacyCertification::Causes), - (LegacyTopology::Direct, LegacyCertification::Contributes), - (LegacyTopology::Direct, LegacyCertification::CausalCandidate), - ( - LegacyTopology::IndirectKnown, - LegacyCertification::Associated, - ), - ] { - assert_eq!( - translate(Epi5Gen::V1, t, c), - Err(LegacyError::NoCanonicalState { - topology: t, - certification: c - }) - ); + 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); + } + } } - assert_eq!( - LegacyCertification::from_code(6), - Err(LegacyError::ReservedCertification(6)) - ); } - /// F2 + F9: the compatibility writers resolve a whole state or refuse, - /// where the raw half-writes would land on another valid code (1 → 3, - /// 12 → 20). #[test] - fn compatibility_writers_never_produce_an_accidental_code() { - let causes = EpistemicState5::decode(Epi5Gen::V1, code_v1::DIRECT_CAUSES).unwrap(); - // Raw: topology half := 3 gives code 3 (Direct × Observed). Wrapper: - assert_eq!( - retopologize(causes, LegacyTopology::Unknown), - Err(LegacyError::NoCanonicalState { - topology: LegacyTopology::Unknown, - certification: LegacyCertification::Causes - }) - ); - let assoc = EpistemicState5::decode(Epi5Gen::V1, code_v1::DIRECT_ASSOCIATED).unwrap(); - // Raw: band half := 5 gives code 20 = Related, a claim nobody made. - // The wrapper with the legacy meaning of 5 (Causes) resolves to 1. + 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!( - recertify(assoc, LegacyCertification::Causes).unwrap().raw(), - code_v1::DIRECT_CAUSES + (s.topology(), s.certification()), + (T::IndirectUnknown, C::Related) ); - // A legal grounding move keeps the certification. - let rel = EpistemicState5::decode(Epi5Gen::V1, code_v1::DIRECT_RELATED).unwrap(); assert_eq!( - retopologize(rel, LegacyTopology::IndirectUnknown) - .unwrap() - .raw(), - code_v1::IND_UNKNOWN_RELATED + d.project_state5(0x0999, RAIL, Epi5Gen::V1, 10, EdgeProvenance::V2Stamped), + Err(Epi5ReadError::UndeclaredClass(0x0999)) ); - // Open has no grounding to keep. - let open = EpistemicState5::decode(Epi5Gen::V1, 0).unwrap(); + 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!( - recertify(open, LegacyCertification::Related), - Err(LegacyError::NoGrounding) + EpistemicState5::decode(Epi5Gen::V1, 32), + Err(Epi5ReadError::UndeclaredCode(32)) ); - assert_eq!(recertify(open, LegacyCertification::Open), Ok(open)); } } 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 3fcd5f797..12750ab64 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 @@ -59,10 +59,15 @@ //! 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. Writes the historical -//! `CausalTopology` / `ReasoningBand` halves of CE64 bits 59..63, which the -//! canonical `EpistemicState5` reading (`contract::epistemic_state5`) does -//! not translate. Kept as a record of that reading. +//! 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. #![allow(deprecated)] use causal_edge::layout::{CausalTopology, ReasoningBand}; diff --git a/crates/lance-graph-planner/src/dismech_counterfactual.rs b/crates/lance-graph-planner/src/dismech_counterfactual.rs index 9bb8bde42..25f323e34 100644 --- a/crates/lance-graph-planner/src/dismech_counterfactual.rs +++ b/crates/lance-graph-planner/src/dismech_counterfactual.rs @@ -142,12 +142,15 @@ pub struct EdgeRole { /// `lance_graph_contract::epistemic_state5::Epi5Declarations::project_state5` /// by a caller that knows the class. This is the canonical field. pub epistemic_raw5: u8, - /// LEGACY projection of the low half of `epistemic_raw5` through the - /// historical `CausalTopology` lens. Kept for compatibility; it is not a - /// reading of the field and must not be used to decide anything. + /// 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, - /// LEGACY projection of the high half through the historical - /// `ReasoningBand` lens. Same caveat. + /// LEGACY: the certification coordinate (bits 61..63) read through the + /// historical `ReasoningBand` names, which are reasoning levels, not + /// certifications. No mapping exists; kept for compatibility only. The + /// canonical certification is `epistemic_raw5 >> 2` under a declaration. pub band: causal_edge::layout::ReasoningBand, } From 18f36185a62d55558027f3ab398d2240c6d67c60 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 11:43:44 +0000 Subject: [PATCH 07/17] Board: Cartesian V1 re-inventory, MIG-0 entry, issues, README - Inventory re-run under Topology2 x Certification3: with_topology is canonical, P7a migrates by identity packing, entropy_topology migrated; three historical-ReasoningBand probes and EdgeRole::band cannot migrate without inventing a mapping. - ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS resolved (every contract has a code under every topology); ISS-LEGACY-59-63-READINGS-REMAIN narrowed. - MIG-0 entry rewritten with the disable-verified falsifier map. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/ISSUES.md | 6 +- .claude/board/STATUS_BOARD.md | 4 +- .claude/board/entries/2026-10-07-epi-mig-0.md | 103 +++++++++--------- .../entries/2026-10-07-epi-mig-inventory.md | 47 ++++++++ .../examples/README.md | 16 +-- 5 files changed, 112 insertions(+), 64 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index acc4b2783..2c9fb5b18 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,6 +1,6 @@ ## ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS — P7a certifications with no canonical EpistemicState5 code (2026-10-07) -**Status:** OPEN — operator decision. **Basis:** TEST-PINNED in `contract::epistemic_state5` (`undeclared_legacy_combinations_refuse`), `relational_certification_probe` and `epistemic_reading_conflict_probe` (census 13 refused cells). +**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` (`undeclared_legacy_combinations_refuse`), `relational_certification_probe` and `epistemic_reading_conflict_probe` (census 13 refused cells). - 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). @@ -10,8 +10,8 @@ **Status:** OPEN (tracking). **Basis:** VERIFIED-IN-CODE; inventory `entries/2026-10-07-epi-mig-inventory.md`. -- PROBE-ONLY legacy writers/readers of the historical `ReasoningBand` / `CausalTopology` / `TrustTexture` halves, kept with `#![allow(deprecated)]` because their readings have no canonical translation: `cognitive-shader-driver/examples/{reasoning_band_probe, probe_revision_kanban_hinge, entropy_topology_probe}.rs`, `lance-graph-planner/examples/probe_four_plane_causal_medium.rs`. -- Compatibility surfaces kept: `contract::band_reading` (regraded legacy), the deprecated half writers in `causal-edge`, the legacy lens readers (`truth`, `topology`, `spare`, `reasoning_band`), `dismech_counterfactual::EdgeRole::{topology, band}` (now beside the canonical `epistemic_raw5`). +- 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. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 591701c26..36f1cb4bb 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -45,8 +45,8 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **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-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` (codebook V1, declarations, `project_state5`, legacy translation); `CausalEdge64::{epistemic_raw5, with_epistemic_raw5}`; half writers deprecated | -| **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 + P7a + 4 probes + dismech + V3 lift migrated; conformance census 11/0/13 (was 3/5/16); F1–F10 pinned; open: P7a `CausalCandidate` has no V1 code | +| **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-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 | 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-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/entries/2026-10-07-epi-mig-0.md b/.claude/board/entries/2026-10-07-epi-mig-0.md index 9e7b2d950..07e4f0e8a 100644 --- a/.claude/board/entries/2026-10-07-epi-mig-0.md +++ b/.claude/board/entries/2026-10-07-epi-mig-0.md @@ -1,63 +1,64 @@ -# 2026-10-07 — D-EPI-CANON-0 / D-EPI-MIG-0: CE64 bits 59..63 are `EpistemicState5` +# 2026-10-07 — D-EPI-CANON-0 / D-EPI-MIG-0: CE64 bits 59..63 are `EpistemicState5 = Topology2 × Certification3` -Operator decision: bits 59..63 are ONE declared 5-bit `EpistemicState5` code, -a dense codebook over valid epistemic conjunctions whose factorization also -decides the legal recipes (`CausalEdge64 × RecipeLaw[64] → EligibleRecipes: -u64`, transient). The split readings survive only as compatibility. -Inventory taken first: `entries/2026-10-07-epi-mig-inventory.md`. +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`: `CODEBOOK_V1` (the ten #1370 - codes, adopted unchanged; ONE authoritative table), `fact::*`, `Epi5Gen`, - `EpistemicState5` (only constructible validated), `Epi5Reading::project` - (non-allocating), `Epi5Declarations::project_state5` — refuses undeclared - `(classid, rail)`, untrusted provenance, generation mismatch, undeclared - code. `ClassView::epistemic_reading` (default `None` = refuse). -- `legacy::{translate, project_topology, project_certification, recertify, - retopologize}`: the only declared translation is P7a certification × - legacy `CausalTopology` grounding (11 of 24 cells). Compatibility writers - resolve a whole state or refuse; never a 2- or 3-bit overwrite. -- `causal-edge`: `epistemic_raw5` / `with_epistemic_raw5` (joint read/write; - v1 stubs zero / no-op), `EPISTEMIC_MASK`; V3 lift restores the field - jointly; `with_truth` / `set_truth` / `with_topology` / `with_routing` / - `with_spare` / `set_spare` / `with_reasoning_band` `#[deprecated]`. -- `band_reading`: regraded legacy; no longer owns 59..63. - -## MEASURED (falsifiers; each disable-verified red unless noted) - -| F | where | disable that turns it red | +- `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` `every_code_round_trips_and_touches_only_59_63`; contract `projection_round_trips_every_declared_code` | — (round trips) | -| F2 | `causal-edge` `a_half_write_silently_produces_a_different_code` (1→3, 12→20 via the half writers); contract `compatibility_writers_never_produce_an_accidental_code` | grounding-free `recertify` admits → red | -| F3 | contract `legacy_causes_translates_to_a_causes_code`; probe `a_certified_causes_reads_as_causes` | translate `(Direct, Causes)` → 20 → 3 probe tests red; old split `stamp` → 3 red | -| F4 | contract `undeclared_legacy_combinations_refuse` | extra `(Unknown, Causes)` cell → 3 contract tests red | -| F5 | probe `the_state_survives_restart_from_le_bytes`; causal-edge LE + V3 lift tests | — | -| F6 | probe `measurement_is_unchanged_by_the_refactor` (vs the pre-migration body, 2048 cases); `affordance_measurement_probe` `f9` (0 allocations) | drop the Pearl gate → red; the first draft allocated per call and `f9` caught it | -| F7 | probe `one_factor_moves_only_its_recipes` (25↔5 = hydrate↔mechanism; 5↔30 = causal identification) | code 30 → unknown intermediate → 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` | — | -| F9 | contract compatibility-writer test (resolve or refuse) | as F2 | -| F10 | probe census `(11, 0, 13)` (was `(3, 5, 16)`) and `both_readings_agree_on_every_raw_code` (10 agree, 22 both refuse) | old split stamp → red | -| — | probe `undeclared_classes_refuse_under_both_readings` | classless `measure_declared` → red | +| 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`, `shared/certification_reading.rs` (P7a), - `affordance_measurement`, `ce64_cycle_survival`, `ce64_nextstate`, - `revision_epistemic_writer` (#1379), `epistemic_reading_conflict` (#1378 → - conformance), `relational_certification` (P7a), `dismech_counterfactual` - (`EdgeRole.epistemic_raw5`, legacy `topology`/`band` kept beside it), - `edge_v3` lift. +`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 -- `ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS`: no V1 code for any - `CausalCandidate`, nor `Contributes` under `Direct`. P7a's observational - fixtures certify `CausalCandidate` and now refuse to stamp. A V2 generation - is a codebook decision, not taken here. -- `ISS-LEGACY-59-63-READINGS-REMAIN`: four PROBE-ONLY legacy probes, the - compatibility surfaces, `temporal()` ordering. -- P7a's `MODEL_GROUNDING = Direct` is a producer declaration in the probe - (no intermediate in the sealed model), not derived from bits. -- No production class declares the canonical reading yet (no OGAR mint); - every declaration lives in probes. +- 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 index b520dd2b1..6034c9ee4 100644 --- a/.claude/board/entries/2026-10-07-epi-mig-inventory.md +++ b/.claude/board/entries/2026-10-07-epi-mig-inventory.md @@ -87,3 +87,50 @@ 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. diff --git a/crates/cognitive-shader-driver/examples/README.md b/crates/cognitive-shader-driver/examples/README.md index b69a275e6..5aa434747 100644 --- a/crates/cognitive-shader-driver/examples/README.md +++ b/crates/cognitive-shader-driver/examples/README.md @@ -179,11 +179,10 @@ cargo run -p cognitive-shader-driver --example reasoning_band_probe ## relational_certification_probe.rs -> **D-EPI-MIG-0 (2026-10-07):** P7a no longer owns bits 61..63. A -> certification is stamped by translating `(MODEL_GROUNDING = Direct, -> contract)` into the canonical `EpistemicState5` code and writing bits -> 59..63 jointly; `Contributes` and `CausalCandidate` have no V1 code and -> refuse (the observational fixtures certify `CausalCandidate`). +> **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 @@ -308,9 +307,10 @@ 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 `EpistemicState5` reading (`contract::epistemic_state5`): legacy -> grounding × P7a contract census 11 agree / 0 disagree / 13 refuse (was -> 3 / 5 / 16), both readings agree on all 32 raw codes, F3/F5–F8/F10 pinned. +> 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`) From 350e36427bbeaa3b32e0cf6594f1f007453148e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:09:45 +0000 Subject: [PATCH 08/17] Pin the streamdto S4 reread to code 4, not code 7 Codes 4 (Direct x Associated) and 7 (Unknown x Associated) grant the same recipes, so the expectation built from state[7] accepted the wrong topology. Compare against state[4] and assert the committed raw code. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../examples/streamdto_circuit_probe.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs index b27f929f2..35d73091a 100644 --- a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs +++ b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs @@ -360,10 +360,15 @@ 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 = 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"); } From 2bd4c6eed85b22c0f09cc7a2e7f18c28b68d3db1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:13:05 +0000 Subject: [PATCH 09/17] Probes: write and mask bits 59..63 through the declared field streamdto stamps with with_epistemic_raw5(4); revision checks the frame against EPISTEMIC_MASK; nextstate imports EPISTEMIC_MASK/EPISTEMIC_SHIFT instead of rebuilding them from the two half-field masks. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../examples/ce64_nextstate_probe.rs | 12 ++++++------ .../examples/revision_epistemic_writer_probe.rs | 4 ++-- .../examples/streamdto_circuit_probe.rs | 4 +--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs index ccddda583..69cf806e7 100644 --- a/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs +++ b/crates/cognitive-shader-driver/examples/ce64_nextstate_probe.rs @@ -51,16 +51,13 @@ 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; @@ -82,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. @@ -264,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 + ); } } 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 55ae2f579..48c1d1463 100644 --- a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs +++ b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs @@ -457,7 +457,7 @@ fn main() { mod tests { use super::affordance_law::{EPI_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; @@ -676,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, diff --git a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs index 35d73091a..35b6843b7 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)] @@ -351,8 +350,7 @@ mod tests { // 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 = - CausalEdge64((committed.0 & !(TRUTH_MASK | SPARE_MASK)) | (4u64 << TRUTH_SHIFT)); + let observed = committed.with_epistemic_raw5(4); let mb = d.mailbox_mut(MAILBOX).unwrap(); let cell = WriteCell { edge: Some(observed), From 6412707e3c8daded2076d07df261fe3ea97dab5a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:14:34 +0000 Subject: [PATCH 10/17] Census: three-axis sweep of bits 59..63 after the Codex finding streamdto's main table printed retired #1370 sample codes (25 and 30 are now reserved); it now prints the 24 meaningful states by coordinate. S4 pins the reread's topology and certification as well as its raw code. The inventory entry records the storage / raw5-semantics / name-collision census: MUL's TrustTexture types have no bridge to the CE64 lens. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../entries/2026-10-07-epi-mig-inventory.md | 41 +++++++++++++++++++ .../examples/streamdto_circuit_probe.rs | 19 ++++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.claude/board/entries/2026-10-07-epi-mig-inventory.md b/.claude/board/entries/2026-10-07-epi-mig-inventory.md index 6034c9ee4..11d4460e5 100644 --- a/.claude/board/entries/2026-10-07-epi-mig-inventory.md +++ b/.claude/board/entries/2026-10-07-epi-mig-inventory.md @@ -134,3 +134,44 @@ meaningful, 8 reserved). Every site above was re-classified against it on 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/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs index 35b6843b7..7c2618e08 100644 --- a/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs +++ b/crates/cognitive-shader-driver/examples/streamdto_circuit_probe.rs @@ -53,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; @@ -224,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 + ); + } } } @@ -363,6 +369,9 @@ mod tests { 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, From 6feb72554f5782f44296b702bb20db6ead08b753 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:19:59 +0000 Subject: [PATCH 11/17] D-EPI-POP-0: facts -> 32-state population as one u32 facts_population_v1(required) returns the V1 codes whose facts include every required fact; EpistemicState5::bit() is a state's bit in it. Reserved codes never appear; conjunction of requirements is intersection of populations. Transient, never stored on an edge. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../src/epistemic_state5.rs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/crates/lance-graph-contract/src/epistemic_state5.rs b/crates/lance-graph-contract/src/epistemic_state5.rs index 1b456d824..cfca0b066 100644 --- a/crates/lance-graph-contract/src/epistemic_state5.rs +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -274,6 +274,34 @@ pub const COMPILED_FACTS_V1: [Option; CODES] = { 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_v1(a | b) == +/// facts_population_v1(a) & facts_population_v1(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_v1(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 { @@ -353,6 +381,12 @@ impl EpistemicState5 { 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 { @@ -651,4 +685,77 @@ mod tests { 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_v1; + 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); + 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_v1(sub); + assert_eq!(p, population_by_states(sub), "requirement {sub:#x}"); + assert_eq!(p >> MEANINGFUL_STATES, 0, "a reserved code appeared"); + 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_v1(fa | fb), + facts_population_v1(fa) & facts_population_v1(fb) + ); + } + } + } } From 47bd36bd2edd1f06379e0aa54f6eed7a3d8a747a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:21:25 +0000 Subject: [PATCH 12/17] D-EPI-POP-0: pin exact population sets; board rows and inventory Tests assert the exact code sets, not only their sizes. STATUS_BOARD gains D-EPI-POP-0 and queues D-EPI-LEGACY-DEPROJECT-0 and D-PUZZLE-0 in that order; LATEST_STATE lists the operator. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/LATEST_STATE.md | 4 ++++ .claude/board/STATUS_BOARD.md | 3 +++ crates/lance-graph-contract/src/epistemic_state5.rs | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index d5b150c10..0fc620316 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -11,6 +11,10 @@ `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_v1(required) -> Population` (`const fn`; the codes whose + facts include every required fact; reserved codes never appear; conjunction + is intersection), `EpistemicState5::bit()`. Transient — never stored. - `ClassView::epistemic_reading(class, rail) -> Option` (provided, default `None` = refuse). - `band_reading` regraded: legacy compatibility surface, no longer owns 59..63. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 36f1cb4bb..f236aa89b 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -46,6 +46,9 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **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-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_v1, 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_v1(..)`, MUL, or another subsystem (e.g. counterfactual context) — never a 1:1 replacement enum | Queued | after D-EPI-POP-0; callsites: the LEGACY list in `entries/2026-10-07-epi-mig-inventory.md` | +| **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 | 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-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 | diff --git a/crates/lance-graph-contract/src/epistemic_state5.rs b/crates/lance-graph-contract/src/epistemic_state5.rs index cfca0b066..72d333d54 100644 --- a/crates/lance-graph-contract/src/epistemic_state5.rs +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -714,6 +714,13 @@ mod tests { 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(CAUSES) & pop(IND_UNKNOWN), EpistemicState5::new(Epi5Gen::V1, T::IndirectUnknown, C::Causes).bit() From eb5e94930475950c6b82e68fe959a183771b0170 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:26:54 +0000 Subject: [PATCH 13/17] D-EPI-POP-0: facts_population, factor-hold falsifiers, code-by-code spec Renames facts_population_v1 to facts_population. Adds the DIRECT|ASSOCIATED and TOPOLOGY_UNKNOWN|CAUSES pins, checks Unknown x Causes and IndirectUnknown x Causes stay separately addressable, checks every requirement code by code against facts_v1, and proves that a topology update moves only topology-conditioned membership and a certification update only certification-conditioned membership. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/LATEST_STATE.md | 2 +- .claude/board/STATUS_BOARD.md | 4 +- .../src/epistemic_state5.rs | 105 ++++++++++++++++-- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 0fc620316..7b995f019 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -12,7 +12,7 @@ (`project`), `Epi5ReadError`, `Epi5Declarations::{declare, get, project_state5}`. - D-EPI-POP-0: `Population = u32` (bit `r` = the state whose raw5 is `r`), - `facts_population_v1(required) -> Population` (`const fn`; the codes whose + `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. - `ClassView::epistemic_reading(class, rail) -> Option` (provided, diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index f236aa89b..57ef96ba5 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -46,8 +46,8 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **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-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_v1, 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_v1(..)`, MUL, or another subsystem (e.g. counterfactual context) — never a 1:1 replacement enum | Queued | after D-EPI-POP-0; callsites: the LEGACY list in `entries/2026-10-07-epi-mig-inventory.md` | +| **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 | Queued | after D-EPI-POP-0; callsites: the LEGACY list in `entries/2026-10-07-epi-mig-inventory.md` | | **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 | 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) | diff --git a/crates/lance-graph-contract/src/epistemic_state5.rs b/crates/lance-graph-contract/src/epistemic_state5.rs index 72d333d54..672e034e2 100644 --- a/crates/lance-graph-contract/src/epistemic_state5.rs +++ b/crates/lance-graph-contract/src/epistemic_state5.rs @@ -282,13 +282,13 @@ pub type Population = u32; /// The V1 codes whose facts include every fact in `required`. /// -/// Conjunction is intersection: `facts_population_v1(a | b) == -/// facts_population_v1(a) & facts_population_v1(b)`. Reserved codes (24..31) +/// 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_v1(required: Facts) -> Population { +pub const fn facts_population(required: Facts) -> Population { let mut population = 0; let mut raw = 0; while raw < CODES { @@ -704,7 +704,7 @@ mod tests { /// D-EPI-POP-0: the populations of the stated questions. #[test] fn populations_of_the_stated_questions() { - let pop = facts_population_v1; + let pop = facts_population; assert_eq!(pop(CAUSES).count_ones(), 4, "the Causes column"); assert_eq!( pop(RELATED).count_ones(), @@ -721,6 +721,15 @@ mod tests { 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() @@ -739,9 +748,18 @@ mod tests { let mut sub = all; let mut checked = 0; loop { - let p = facts_population_v1(sub); + 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; @@ -759,10 +777,83 @@ mod tests { for b in 0..Facts::BITS { let (fa, fb) = (1 << a & all, 1 << b & all); assert_eq!( - facts_population_v1(fa | fb), - facts_population_v1(fa) & facts_population_v1(fb) + 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" + ); + } } From 6026b642a5be43bac32f3d8580a56d249f0bd539 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:33:07 +0000 Subject: [PATCH 14/17] D-EPI-LEGACY-DEPROJECT-0: route the old 59..63 readers by what they asked Census of every ReasoningBand / CE64 TrustTexture reader and writer, by the question each caller asked (entries/2026-10-07-epi-legacy-deprojection.md). No ordinal translation is used anywhere. - dismech EdgeRole::band removed: "explains vs relates" is a certification question on epistemic_raw5 under the cut edge's declaration; the test pins raw5 21/9, decoded factors, and asks facts_population(CAUSES). - kanban hinge: its band was a non-interference witness whose meaning was never read; it now writes IndirectUnknown x Causes (raw 22), pinned. - V3 lift reads bits 59..63 through epistemic_raw5 (byte layout unchanged). - Three probe loops that selected subspaces by code arithmetic or law-table lookups now use facts_population. - Four-plane and reasoning_band probes stay records; their headers name the real home (execution context; P7a and dismech_counterfactual). - Stale with_routing doc (a dispatch_cycle that does not exist) corrected. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/ISSUES.md | 2 +- .claude/board/LATEST_STATE.md | 1 + .claude/board/STATUS_BOARD.md | 2 +- .../2026-10-07-epi-legacy-deprojection.md | 201 ++++++++++++++++++ .claude/board/entries/README.md | 3 +- crates/causal-edge/src/edge.rs | 4 +- crates/causal-edge/src/edge_v3.rs | 8 +- .../examples/affordance_measurement_probe.rs | 6 +- .../epistemic_reading_conflict_probe.rs | 12 +- .../examples/probe_revision_kanban_hinge.rs | 54 +++-- .../examples/reasoning_band_probe.rs | 8 + .../revision_epistemic_writer_probe.rs | 8 +- .../probe_four_plane_causal_medium.rs | 7 + .../src/dismech_counterfactual.rs | 75 ++++--- 14 files changed, 316 insertions(+), 75 deletions(-) create mode 100644 .claude/board/entries/2026-10-07-epi-legacy-deprojection.md diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 2c9fb5b18..627020f40 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -8,7 +8,7 @@ ## ISS-LEGACY-59-63-READINGS-REMAIN — historical half-field readings still live in probes (2026-10-07) -**Status:** OPEN (tracking). **Basis:** VERIFIED-IN-CODE; inventory `entries/2026-10-07-epi-mig-inventory.md`. +**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`). diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 7b995f019..7dac38ee0 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -15,6 +15,7 @@ `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. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 57ef96ba5..e307f7953 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -47,7 +47,7 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **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 | Queued | after D-EPI-POP-0; callsites: the LEGACY list in `entries/2026-10-07-epi-mig-inventory.md` | +| **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 | 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) | 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..1622d04d3 --- /dev/null +++ b/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md @@ -0,0 +1,201 @@ +# 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. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 241194e83..9eb491c98 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,7 +25,7 @@ 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. -245 entries, 2026-08-06 .. 2026-10-07. +246 entries, 2026-08-06 .. 2026-10-07. | date | entry id | finding | file | |---|---|---|---| @@ -34,6 +34,7 @@ exactly one of them, never both. | 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 cdc9c7ba1..9af601d3c 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -1146,7 +1146,9 @@ 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)`. diff --git a/crates/causal-edge/src/edge_v3.rs b/crates/causal-edge/src/edge_v3.rs index da0f8986e..722fb8240 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 } } diff --git a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs index 18e2856b0..d9c0959e8 100644 --- a/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs +++ b/crates/cognitive-shader-driver/examples/affordance_measurement_probe.rs @@ -495,8 +495,10 @@ mod tests { /// meaningful state, and only the topology recipes move. #[test] fn reserved_codes_refuse_and_a_topology_write_is_a_factor_update() { - for code in (0u8..32).filter(|c| EPI_LAW[*c as usize].is_none()) { - assert!(code >= 24); + 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!( 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 2f8b04ae5..488d93df0 100644 --- a/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs +++ b/crates/cognitive-shader-driver/examples/epistemic_reading_conflict_probe.rs @@ -41,7 +41,7 @@ use affordance_law::raw5; use causal_edge::edge::CausalEdge64; use causal_edge::pearl::CausalMask; use causal_edge::PlasticityState; -use certification_reading::{read, Contract, Refusal as P7aRefusal, CERT_CLASS, RAIL}; +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, @@ -141,6 +141,7 @@ mod tests { 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; @@ -189,9 +190,14 @@ mod tests { /// 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); - assert_eq!(raw5(e), 20 + t.ordinal()); + 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) @@ -199,6 +205,8 @@ mod tests { 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 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 5332e23d2..98a2dffb1 100644 --- a/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs +++ b/crates/cognitive-shader-driver/examples/probe_revision_kanban_hinge.rs @@ -312,21 +312,20 @@ //! //! Run: `cargo run -p cognitive-shader-driver --example probe_revision_kanban_hinge` //! -//! 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. -#![allow(deprecated)] +//! 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, }; @@ -1079,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!( + EpistemicState5::decode(Epi5Gen::V1, edge.epistemic_raw5()), + Ok(witness), + "pre-flight: the coordinate must decode back" ); assert_eq!( - edge.reasoning_band(), - ReasoningBand::Causal, - "pre-flight: reasoning-band lens must read back" + edge.topology(), + CausalTopology::IndirectUnknownIntermediates, + "pre-flight: topology factor must read back" ); let edge_raw_before = edge.0; @@ -1412,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 80a86799e..ea591b281 100644 --- a/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs +++ b/crates/cognitive-shader-driver/examples/reasoning_band_probe.rs @@ -84,6 +84,14 @@ //! 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; 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 48c1d1463..04944d01c 100644 --- a/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs +++ b/crates/cognitive-shader-driver/examples/revision_epistemic_writer_probe.rs @@ -455,7 +455,7 @@ 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::EPISTEMIC_MASK; @@ -736,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( @@ -747,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/lance-graph-planner/examples/probe_four_plane_causal_medium.rs b/crates/lance-graph-planner/examples/probe_four_plane_causal_medium.rs index 12750ab64..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 @@ -68,6 +68,13 @@ //! 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}; diff --git a/crates/lance-graph-planner/src/dismech_counterfactual.rs b/crates/lance-graph-planner/src/dismech_counterfactual.rs index 25f323e34..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; @@ -147,11 +150,6 @@ pub struct EdgeRole { /// `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, - /// LEGACY: the certification coordinate (bits 61..63) read through the - /// historical `ReasoningBand` names, which are reasoning levels, not - /// certifications. No mapping exists; kept for compatibility only. The - /// canonical certification is `epistemic_raw5 >> 2` under a declaration. - pub band: causal_edge::layout::ReasoningBand, } impl EdgeRole { @@ -262,7 +260,6 @@ pub fn counterfactual_replay( counterfactual: verdict_at(&counterfactual, bar), epistemic_raw5: cut_edge.epistemic_raw5(), topology: cut_edge.topology(), - band: cut_edge.reasoning_band(), }, factual, counterfactual, @@ -539,33 +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); - // Written jointly (D-EPI-CANON-0): topology ordinal 1, band ordinal 3. - let flavoured = plain.with_epistemic_raw5((3 << 2) | 1); - - 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), @@ -585,17 +578,21 @@ mod tests { ) .expect("in range") .expect("fits"); - assert_eq!(cf.role.epistemic_raw5, edge.epistemic_raw5()); - 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); - assert_ne!(plain.epistemic_raw5(), flavoured.epistemic_raw5()); + // 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 From 68008a1b7e4f6d8df8b168bf8c1e2828f109915f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:33:39 +0000 Subject: [PATCH 15/17] causal-edge: clear the two clippy lints this PR introduced let_and_return in CausalEdgeV3::rehydrate and an identity op in the field test. The other causal-edge lints predate the branch. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- crates/causal-edge/src/edge_v3.rs | 3 +-- crates/causal-edge/src/v2_layout_tests.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/causal-edge/src/edge_v3.rs b/crates/causal-edge/src/edge_v3.rs index 722fb8240..6cce816bc 100644 --- a/crates/causal-edge/src/edge_v3.rs +++ b/crates/causal-edge/src/edge_v3.rs @@ -293,8 +293,7 @@ impl CausalEdgeV3 { edge.set_w_slot(self.w_slot()); // Bits 59..63 are one field: restore it jointly (D-EPI-CANON-0), so // the lift can never write half of an EpistemicState5 code. - let edge = edge.with_epistemic_raw5(self.epistemic_raw5()); - edge + edge.with_epistemic_raw5(self.epistemic_raw5()) } /// The Lokal target node reference — the node whose 6×256² CAM-PQ facet IS diff --git a/crates/causal-edge/src/v2_layout_tests.rs b/crates/causal-edge/src/v2_layout_tests.rs index 7444a72e1..4536609cf 100644 --- a/crates/causal-edge/src/v2_layout_tests.rs +++ b/crates/causal-edge/src/v2_layout_tests.rs @@ -996,7 +996,7 @@ mod epistemic_field_tests { /// 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) | 0); // Direct × Causes + 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(), From 2d9bf0e56859a197d57508f3245f70dc33ed965b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:34:03 +0000 Subject: [PATCH 16/17] D-EPI-LEGACY-DEPROJECT-0: record disable runs and gates Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .../2026-10-07-epi-legacy-deprojection.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md b/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md index 1622d04d3..eb1870ac0 100644 --- a/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md +++ b/.claude/board/entries/2026-10-07-epi-legacy-deprojection.md @@ -199,3 +199,20 @@ 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). From 0ba5d24ebe705dfd9d72d8529243ef2b20a0271d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 15:35:35 +0000 Subject: [PATCH 17/17] Board: mark the pre-Cartesian codebook and census as historical The two resolved ISSUES Status lines still described the ten-code #1370 codebook, the 11/0/13 census and a test that no longer exists; they now state the Cartesian V1 contract and its 24/0/8 conformance. The D-EPI-CONFLICT-0 row is marked Superseded, its old pins labelled historical. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0177nSSyQaXqbNuGR1vMXXN6 --- .claude/board/ISSUES.md | 4 ++-- .claude/board/STATUS_BOARD.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 627020f40..cd22096a1 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,6 +1,6 @@ ## 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` (`undeclared_legacy_combinations_refuse`), `relational_certification_probe` and `epistemic_reading_conflict_probe` (census 13 refused cells). +**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). @@ -17,7 +17,7 @@ ## 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:** 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` (codebook V1 = the ten #1370 codes, per `(classid, rail, generation)`, projection refuses). The affordance law measures only a projected state; P7a stamps through the legacy translation `(grounding, contract) → code` and reads through the projection. The probe's pins were replaced by agreement tests (census 11 agree / 0 disagree / 13 refuse; both readings agree on all 32 raw codes), not re-pinned. Residue: `ISS-EPI5-CODEBOOK-LACKS-P7A-CONJUNCTIONS`. +**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). diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index e307f7953..bb013bfcc 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -50,7 +50,7 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **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 | 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-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 |