From bbab354196d3f6c9e6321f36b3fa2fb1aeeca7b3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 10:34:23 +0000 Subject: [PATCH 1/3] =?UTF-8?q?CE64:=20additive=20CausalTopology=20+=20Tex?= =?UTF-8?q?tureBand=20lenses=20over=20bits=2059..63=20=E2=80=94=20no=20wir?= =?UTF-8?q?e=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strictly mechanical, strictly additive. No bit moves, no layout version, no CE64 v3. The u64 wire is byte-identical before and after; this PR only adds a second way to READ two fields that already exist. ## What was added Bits 59..60 (existing `TRUTH_SHIFT`, existing `TrustTexture`) gain a factual sibling view: CausalTopology { Direct=0b00, IndirectKnownIntermediates=0b01, IndirectUnknownIntermediates=0b10, Unknown=0b11 } The ordinal identity with `TrustTexture` is the MIGRATION CONTRACT, not coincidence: Crystalline==Direct==0, Solid==IndirectKnown==1, Fuzzy==IndirectUnknown==2, Murky==Unknown==3. A consumer that knows only `truth()` keeps seeing the exact same ordinal and the exact same behaviour; a new consumer may read the SAME two bits as topology instead. Bits 61..63 (existing `SPARE_SHIFT`) gain a 3-bit cognitive-level view, provisionally `TextureBand` — Surface / Association / Relation / Causal / Counterfactual / Perspective / Meta / Transcendent. `spare()` and `with_spare()` are untouched and still see the same 3 bits. **The type name carries a `// TODO(name)` marker.** A vocabulary audit across the workspace is still open; the final name is deliberately not settled by whoever typed first, because a public type name in a zero-dep crate that four repos consume is the one irreversible choice in this change. ## Orthogonality, stated in the docs because the words collide `TextureBand::Causal` is NOT `CausalMask` (bits 40..42, which says WHICH Pearl/SPO projection is represented). `TextureBand::Counterfactual` is NOT the signed inference mantissa's -6 (a specific NARS operation being counterfactual). `TextureBand::Perspective` is NOT `direction` (bits 43..45). Two tests pin the counterfactual pair in BOTH directions so the claim is falsifiable rather than merely asserted. `Transcendent` is an ordinal and nothing more — no mystical behaviour, by construction and by doc comment. ## Historical honesty wire compatibility: exact ordinal compatibility: exact legacy behavioural projection: intentional historical factual provenance: NOT guaranteed for old rows Old rows had bits 59..60 written with truth-texture semantics. Source- authoritative topology begins only when a later producer explicitly writes `CausalTopology`. No old row is inferred or repaired here. `CausalEdge64::ZERO` therefore reads `Direct` under the new view exactly as it reads `Crystalline` under the old. That is intentional staged-migration compatibility, not a sentinel, and it is documented as such rather than "fixed". ## No auto-derivation Nothing derives `TextureBand` from CausalMask, InferenceType, confidence, topology, MUL, ReasoningGap or ThinkingStyle. Raw encode/decode, accessors, consuming builders, tests, docs. The orchestration layer decides when the field changes; this layer only stores it. ## API shape Consuming builders, matching the crate's existing `with_truth`/`with_spare` idiom rather than introducing mutable setters into a register-style value type: edge.topology() / edge.with_topology(..) edge.texture_band() / edge.with_texture_band(..) so the usage stays compositional. The `#[cfg(not(feature = "causal-edge-v2-layout"))]` stub twins mirror the existing v1 stub convention exactly. ## Verified - `cargo test` (default v2 layout): 66 passed, 0 failed. - `cargo test --no-default-features` (v1 stub path): 35 passed, 0 failed. - clippy: 7 lib / 8 lib-test errors — MEASURED IDENTICAL on main with this branch stashed, so this change introduces none. Not fixed here; unrelated. - fmt: applied to the new code. `main` is fmt-dirty already; the one pre-existing hunk that `cargo fmt` wanted to move in `edge.rs:651` was reverted so it stays out of this diff. Three disable runs, each red-then-green: with_topology writes SPARE bits instead of TRUTH -> 4 arms red topology() reads SPARE bits instead of 59..60 -> 5 arms red CausalTopology ordinals 1 and 2 swapped -> 5 arms red The isolation tests use an XOR diff of the whole word against the field mask, so "changed only these bits" is proven for all 64 bits at once rather than field by field. ## Not done No consumer was migrated to the new APIs. No OGAR-loco wiring, no recipe semantics, no DisMech, no HHTL, no MUL/Rubicon change. --- crates/causal-edge/src/edge.rs | 76 ++++ crates/causal-edge/src/layout.rs | 222 ++++++++++ crates/causal-edge/src/lib.rs | 7 + crates/causal-edge/src/v2_layout_tests.rs | 472 +++++++++++++++++++++- 4 files changed, 776 insertions(+), 1 deletion(-) diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 41e47ba14..22123a2f5 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -936,6 +936,23 @@ impl CausalEdge64 { ((self.0 >> TRUTH_SHIFT) & BITS2_MASK) as u8 } + /// Causal-topology lens: reads the identical two bits as `truth()` / + /// `truth_raw()` (bits 59-60), projected through + /// [`crate::layout::CausalTopology`] instead of + /// [`crate::layout::TrustTexture`]. Purely additive — no bits move. + /// + /// See [`crate::layout::CausalTopology`] for the full compatibility + /// statement: wire and ordinal compatibility with `TrustTexture` are + /// exact, the legacy behavioural projection onto old rows is + /// intentional, but the *historical factual provenance* of old rows is + /// **not** guaranteed — do not treat this as ground truth for a row + /// written before this accessor existed. + #[inline(always)] + pub fn topology(self) -> crate::layout::CausalTopology { + use crate::layout::{CausalTopology, BITS2_MASK, TRUTH_SHIFT}; + CausalTopology::from_bits_2(((self.0 >> TRUTH_SHIFT) & BITS2_MASK) as u8) + } + /// Spare 3-bit field (bits 61-63). Reserved for sprint-12+ use. /// Returns 0 for ZERO edges and all v1-written edges (temporal MSBs were ≤ 0xFFF). #[inline(always)] @@ -944,6 +961,18 @@ impl CausalEdge64 { ((self.0 >> SPARE_SHIFT) & BITS3_MASK) as u8 } + /// Quantized reasoning-level projection: reads the identical three bits + /// as `spare()` (bits 61-63), projected through + /// [`crate::layout::TextureBand`]. Purely additive — no bits move, and + /// nothing auto-derives this value; see the type's doc comment for the + /// no-auto-derivation guarantee and the orthogonality notes against + /// `CausalMask` / the inference mantissa / `direction`. + #[inline(always)] + pub fn texture_band(self) -> crate::layout::TextureBand { + use crate::layout::{TextureBand, BITS3_MASK, SPARE_SHIFT}; + TextureBand::from_bits_3(((self.0 >> SPARE_SHIFT) & BITS3_MASK) as u8) + } + // ── Builder-Shape Setters (functional update, returns Self) ───────────── /// Return new edge with W slot set to `w` (0..=63). @@ -961,6 +990,19 @@ impl CausalEdge64 { Self((self.0 & !TRUTH_MASK) | ((t.to_bits_2() as u64 & BITS2_MASK) << TRUTH_SHIFT)) } + /// Return new edge with the causal-topology lens set (bits 59-60). + /// + /// 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. + #[inline] + pub fn with_topology(self, topo: crate::layout::CausalTopology) -> Self { + use crate::layout::{BITS2_MASK, TRUTH_MASK, TRUTH_SHIFT}; + Self((self.0 & !TRUTH_MASK) | ((topo.to_bits_2() as u64 & BITS2_MASK) << TRUTH_SHIFT)) + } + /// Return new edge with signed inference mantissa set (range −8..+7). /// /// Stored as 4-bit two's-complement in bits 46-49. Values outside −8..+7 @@ -981,6 +1023,20 @@ impl CausalEdge64 { Self((self.0 & !SPARE_MASK) | ((s as u64 & BITS3_MASK) << SPARE_SHIFT)) } + /// Return new edge with the texture-band projection set (bits 61-63). + /// + /// Consuming builder, register-style (`edge.with_texture_band(b)`), + /// matching `with_spare`/`with_topology`. Shares storage with + /// `spare`/`with_spare` — the last writer of either view wins, by + /// construction, since it is one 3-bit register read through two + /// lenses. See [`crate::layout::TextureBand`] for the full semantics, + /// the orthogonality notes, and the no-auto-derivation guarantee. + #[inline] + pub fn with_texture_band(self, band: crate::layout::TextureBand) -> Self { + use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; + Self((self.0 & !SPARE_MASK) | ((band.to_bits_3() as u64 & BITS3_MASK) << SPARE_SHIFT)) + } + /// 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. @@ -1061,10 +1117,22 @@ impl CausalEdge64 { pub fn truth_raw(self) -> u8 { 0 } + /// V1 stub: additive `CausalTopology` lens is a no-op under v1 (matches + /// the `truth()` stub above — same bits, same "feature off" behaviour). + #[inline(always)] + pub fn topology(self) -> crate::layout::CausalTopology { + crate::layout::CausalTopology::Direct + } #[inline(always)] pub fn spare(self) -> u8 { 0 } + /// V1 stub: additive `TextureBand` lens is a no-op under v1 (matches + /// the `spare()` stub above — same bits, same "feature off" behaviour). + #[inline(always)] + pub fn texture_band(self) -> crate::layout::TextureBand { + crate::layout::TextureBand::Surface + } #[inline] pub fn with_w_slot(self, _w: u8) -> Self { self @@ -1074,6 +1142,10 @@ impl CausalEdge64 { self } #[inline] + pub fn with_topology(self, _topo: crate::layout::CausalTopology) -> Self { + self + } + #[inline] pub fn with_inference_mantissa(self, _m: i8) -> Self { self } @@ -1082,6 +1154,10 @@ impl CausalEdge64 { self } #[inline] + pub fn with_texture_band(self, _band: crate::layout::TextureBand) -> Self { + self + } + #[inline] pub fn with_routing(self, _w: u8, _t: crate::layout::TrustTexture) -> Self { self } diff --git a/crates/causal-edge/src/layout.rs b/crates/causal-edge/src/layout.rs index 8a158abdf..91d00d16b 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -55,10 +55,22 @@ pub const W_SHIFT: u32 = 53; /// Truth-band lens: 2-bit TrustTexture ordinal (bits 59-60). /// 0 = Crystalline. Per cognitive-substrate-convergence-v1.md L-7. +/// +/// Same two bits also carry an ADDITIVE factual view, [`CausalTopology`] +/// (below) — see its doc comment for the wire/ordinal/behavioural/ +/// provenance compatibility statement. `TrustTexture` remains the +/// canonical epistemic-trust reading; `CausalTopology` is a second, +/// orthogonal reading of the identical bits for producers that want to +/// record topology instead of (or alongside) trust texture. No bits move. pub const TRUTH_SHIFT: u32 = 59; /// Spare: 3-bit reserved for sprint-12+ (bits 61-63). /// Candidates: Rubicon-commit marker, Markov-decay quantum, I-NOISE-FLOOR-JIRAK threshold. +/// +/// Same three bits also carry an ADDITIVE quantized-projection view, +/// [`TextureBand`] (below; name pending a vocabulary-collision audit — see +/// its `// TODO(name)` marker). No auto-derivation: nothing writes this +/// field except an explicit `with_texture_band()` call. pub const SPARE_SHIFT: u32 = 61; // ── Common masks ───────────────────────────────────────────────────────────── @@ -141,3 +153,213 @@ impl TrustTexture { self as u8 } } + +/// Two-bit CAUSAL TOPOLOGY lens — an ADDITIVE factual view over the same +/// two bits (59-60) that [`TrustTexture`] occupies. No bits move; this is +/// a second reading of the identical register, not a new field and not a +/// layout revision (no CE64 v3, no `ENVELOPE_LAYOUT_VERSION` bump). +/// +/// `TrustTexture` reads those bits as an EPISTEMIC-TRUST texture (how much +/// to trust the edge: mastered/calibrated/uncertain/contradiction). +/// `CausalTopology` reads the identical bits as a FACTUAL TOPOLOGY +/// classification (how the edge's causal path is structured — direct vs. +/// mediated, known vs. unknown intermediates). +/// +/// ```text +/// 0b00 = Direct +/// 0b01 = IndirectKnownIntermediates +/// 0b10 = IndirectUnknownIntermediates +/// 0b11 = Unknown +/// ``` +/// +/// ## Migration contract — the ordinal identity is deliberate, not coincidence +/// +/// `Crystalline == Direct == 0`, `Solid == IndirectKnownIntermediates == 1`, +/// `Fuzzy == IndirectUnknownIntermediates == 2`, `Murky == Unknown == 3`. +/// +/// Four compatibility axes, stated explicitly because they are NOT the same +/// claim and must not be conflated: +/// +/// ```text +/// wire compatibility: exact +/// ordinal compatibility: exact +/// legacy behavioural projection: intentional +/// historical factual provenance: NOT guaranteed for old rows +/// ``` +/// +/// - **Wire compatibility is exact** — same two bits (59-60), same shift, +/// same mask, same byte layout. Nothing about the CE64 wire changes. +/// - **Ordinal compatibility is exact** — `TrustTexture as u8 == +/// CausalTopology as u8` for every one of the four variants (verified by +/// test). +/// - **Legacy behavioural projection is intentional.** Reading an existing +/// row's bits 59-60 through [`CausalEdge64::topology`] reproduces exactly +/// the ordinal a `TrustTexture` reader would have gotten from the same +/// bits. That projection is BY DESIGN, so a consumer that has not moved to +/// `CausalTopology` yet is unaffected by rows a `CausalTopology`-aware +/// writer produces, and vice versa — this is a staged migration, not a +/// flag day. +/// - **Historical factual provenance is NOT guaranteed for old rows.** A row +/// written before this change had its bits 59-60 stamped with +/// TRUST-TEXTURE semantics (how confident the writer was in the edge), not +/// TOPOLOGY semantics (how the causal path is shaped). The two concepts +/// are correlated in practice but they are not the same fact, and nothing +/// in this change infers, repairs, or backfills the topology of a +/// pre-existing row. **Do not treat `old_edge.topology()` as ground truth +/// about that row's actual causal topology** — it is only the +/// same-ordinal projection through the new lens. Source-authoritative +/// topology begins only when a later producer explicitly writes +/// `CausalTopology` via [`CausalEdge64::with_topology`]. +/// +/// [`CausalEdge64::ZERO`] therefore reads `CausalTopology::Direct` under +/// this view exactly as it reads `TrustTexture::Crystalline` under the old +/// one — that is intentional for this staged migration (the all-zero +/// default), not a sentinel asserting "known to be direct." +/// +/// [`CausalEdge64::ZERO`]: super::edge::CausalEdge64::ZERO +/// [`CausalEdge64::topology`]: super::edge::CausalEdge64::topology +/// [`CausalEdge64::with_topology`]: super::edge::CausalEdge64::with_topology +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +#[repr(u8)] +pub enum CausalTopology { + /// Direct causal edge, no intermediates. Default. + /// Ordinal-identical to `TrustTexture::Crystalline`. + #[default] + Direct = 0, + /// Indirect, with known/named intermediate nodes on the causal path. + /// Ordinal-identical to `TrustTexture::Solid`. + IndirectKnownIntermediates = 1, + /// Indirect, but the intermediate nodes are unknown/unnamed. + /// Ordinal-identical to `TrustTexture::Fuzzy`. + IndirectUnknownIntermediates = 2, + /// Topology not established. Ordinal-identical to `TrustTexture::Murky`. + Unknown = 3, +} + +impl CausalTopology { + /// Construct from the raw 2-bit field value (bits masked automatically). + #[inline] + pub fn from_bits_2(v: u8) -> Self { + match v & 0b11 { + 0 => Self::Direct, + 1 => Self::IndirectKnownIntermediates, + 2 => Self::IndirectUnknownIntermediates, + _ => Self::Unknown, + } + } + + /// Return the raw 2-bit value (0..=3). + #[inline] + pub fn to_bits_2(self) -> u8 { + self as u8 + } +} + +/// Three-bit quantized reasoning-level projection over the SPARE bits +/// (61-63) — the same three bits [`super::edge::CausalEdge64::spare`] / +/// [`super::edge::CausalEdge64::with_spare`] expose as a raw, uninterpreted +/// 3-bit scalar. ADDITIVE only: no bits move, no layout revision. +/// +/// ```text +/// 0b000 = Surface +/// 0b001 = Association +/// 0b010 = Relation +/// 0b011 = Causal +/// 0b100 = Counterfactual +/// 0b101 = Perspective +/// 0b110 = Meta +/// 0b111 = Transcendent +/// ``` +/// +/// This is a QUANTIZED HOT-PATH PROJECTION of a potentially richer future +/// texture model — 8 ordinals is what fits in 3 bits on the hot register, +/// not a claim that reasoning has exactly 8 levels. Treat it as a coarse, +/// register-resident classifier only, never as that richer model itself. +/// +/// ## Deliberately orthogonal to existing fields with colliding names +/// +/// The words above collide with existing `CausalEdge64` vocabulary. Every +/// collision below is INTENTIONAL and the two meanings are ORTHOGONAL — +/// setting one never implies, derives, or requires the other: +/// +/// - `Causal` = the cognition currently operating at the causal reasoning +/// level. This is **not** [`super::pearl::CausalMask`] (bits 40-42), +/// which says WHICH Pearl/SPO projection (S/P/O planes) is represented. +/// An edge may carry `TextureBand::Causal` under any `CausalMask`. +/// - `Counterfactual` = the reasoning CONTEXT is counterfactual. This is +/// **not** the signed inference mantissa's −6 slot (bits 46-49; see +/// `InferenceType::Counterfactual::to_mantissa() == -6`), which names one +/// specific NARS operation as counterfactual. An edge may carry +/// `TextureBand::Counterfactual` while its mantissa encodes any NARS +/// rule, and an edge with mantissa −6 need not carry +/// `TextureBand::Counterfactual`. +/// - `Perspective` = perspective/decentration reasoning (I/Thou/It, +/// Self/Other/World). This is **not** +/// [`super::edge::CausalEdge64::direction`] (bits 43-45), the +/// pathology-per-plane sign triad. +/// - `Meta` = reasoning ABOUT reasoning/evidence/revision (meta-cognition), +/// independent of every other field on the edge. +/// - `Transcendent` = mechanically, the highest ordinal this 3-bit +/// projection can express — the topmost cross-frame reasoning level in +/// this band. Nothing more: no mystical or philosophical behaviour is +/// implied or triggered by this value anywhere in this crate. +/// +/// ## No auto-derivation +/// +/// Nothing in this crate derives this field from `CausalMask`, +/// `InferenceType`, NARS frequency/confidence, MUL, ReasoningGap, +/// potholes, or `ThinkingStyle`. It is set ONLY by an explicit +/// `with_texture_band()` call, and reads whatever the SPARE bits already +/// hold otherwise (0 / `Surface` for `CausalEdge64::ZERO` and for every row +/// produced by this crate's own constructors, since `spare()` already +/// defaults to 0 there — but not guaranteed for a raw `u64` from elsewhere). +// TODO(name): "TextureBand" is a placeholder pending a vocabulary-collision +// audit against the rest of the workspace (a parallel session is checking +// whether the name is already spoken for elsewhere). Do not treat this name +// as final; it may be renamed before this lands. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +#[repr(u8)] +pub enum TextureBand { + /// Surface-level reasoning. Default. + #[default] + Surface = 0, + /// Association-level reasoning. + Association = 1, + /// Relation-level reasoning. + Relation = 2, + /// Causal-level reasoning. See the orthogonality note re: `CausalMask`. + Causal = 3, + /// Counterfactual reasoning context. See the orthogonality note re: the + /// inference mantissa's −6 slot. + Counterfactual = 4, + /// Perspective/decentration reasoning. See the orthogonality note re: + /// `direction`. + Perspective = 5, + /// Meta-cognitive reasoning (reasoning about reasoning/evidence/revision). + Meta = 6, + /// Highest ordinal in this band. Mechanical only — see note above. + Transcendent = 7, +} + +impl TextureBand { + /// Construct from the raw 3-bit field value (bits masked automatically). + #[inline] + pub fn from_bits_3(v: u8) -> Self { + match v & 0b111 { + 0 => Self::Surface, + 1 => Self::Association, + 2 => Self::Relation, + 3 => Self::Causal, + 4 => Self::Counterfactual, + 5 => Self::Perspective, + 6 => Self::Meta, + _ => Self::Transcendent, + } + } + + /// Return the raw 3-bit value (0..=7). + #[inline] + pub fn to_bits_3(self) -> u8 { + self as u8 + } +} diff --git a/crates/causal-edge/src/lib.rs b/crates/causal-edge/src/lib.rs index 334c0c1f1..40c374ca9 100644 --- a/crates/causal-edge/src/lib.rs +++ b/crates/causal-edge/src/lib.rs @@ -72,3 +72,10 @@ pub use syllogism::{Figure, Syllogism}; // Re-export v2 layout types under cfg for downstream consumers #[cfg(feature = "causal-edge-v2-layout")] pub use layout::TrustTexture; + +// Additive factual/quantized lenses over the same TrustTexture (bits 59-60) +// and spare (bits 61-63) registers — see layout.rs doc comments for the +// full wire/ordinal/behavioural/provenance compatibility statement. No bits +// move; these are new readings of existing storage, not a new layout. +#[cfg(feature = "causal-edge-v2-layout")] +pub use layout::{CausalTopology, TextureBand}; diff --git a/crates/causal-edge/src/v2_layout_tests.rs b/crates/causal-edge/src/v2_layout_tests.rs index 224403c7f..95249613d 100644 --- a/crates/causal-edge/src/v2_layout_tests.rs +++ b/crates/causal-edge/src/v2_layout_tests.rs @@ -19,7 +19,7 @@ #[cfg(feature = "causal-edge-v2-layout")] mod v2_layout_tests { use crate::edge::{CausalEdge64, InferenceType}; - use crate::layout::TrustTexture; + use crate::layout::{CausalTopology, TextureBand, TrustTexture}; use crate::pearl::CausalMask; use crate::plasticity::PlasticityState; @@ -474,4 +474,474 @@ mod v2_layout_tests { "pack(Intervention) under v2 must decode back to Intervention" ); } + + // ═══════════════════════════════════════════════════════════════════ + // CausalTopology (bits 59-60, additive factual view over TrustTexture) + // + TextureBand (bits 61-63, additive quantized view over `spare`) + // ═══════════════════════════════════════════════════════════════════ + // + // These fields do NOT move any bits and do NOT introduce a new layout + // version — they are second readings of the identical TrustTexture / + // spare registers. See layout.rs doc comments for the full + // wire/ordinal/behavioural/provenance compatibility statement. + + // ── 1. Raw u64 fixtures round-trip byte-for-byte unchanged ───────────── + + #[test] + fn test_raw_fixtures_round_trip_byte_for_byte_via_topology_and_texture_band() { + // Arbitrary fixtures covering varied bit patterns across the whole + // word, including every combination of the two shared registers + // (bits 59-60, 61-63). + let fixtures: [u64; 6] = [ + 0x0000_0000_0000_0000, + 0xFFFF_FFFF_FFFF_FFFF, + 0x1234_5678_9ABC_DEF0, + 0xDEAD_BEEF_CAFE_F00D, + 0x8000_0000_0000_0001, + 0x5555_5555_5555_5555, + ]; + for &raw in &fixtures { + let edge = CausalEdge64(raw); + // Reading a lens then writing back exactly what was read must be + // a complete no-op on the raw word — the new code touches no bit + // it did not read. + let via_topology = edge.with_topology(edge.topology()); + assert_eq!( + via_topology.0, raw, + "read-then-write-back through CausalTopology changed the raw word for {raw:#018x}" + ); + let via_texture = edge.with_texture_band(edge.texture_band()); + assert_eq!( + via_texture.0, raw, + "read-then-write-back through TextureBand changed the raw word for {raw:#018x}" + ); + } + } + + // ── 2. truth() returns exactly what it did before, all four ordinals ─── + + #[test] + fn test_truth_unaffected_by_new_topology_lens_for_all_four_ordinals() { + for t in [ + TrustTexture::Crystalline, + TrustTexture::Solid, + TrustTexture::Fuzzy, + TrustTexture::Murky, + ] { + let edge = CausalEdge64::ZERO.with_truth(t); + assert_eq!(edge.truth(), t, "truth() must be unchanged for {t:?}"); + } + } + + // ── 3. topology() reads the same two raw bits as truth_raw() ─────────── + + #[test] + fn test_topology_reads_the_same_two_bits_as_truth_raw() { + for raw2 in 0u8..=3 { + let edge = CausalEdge64::ZERO.with_truth(TrustTexture::from_bits_2(raw2)); + assert_eq!(edge.truth_raw(), raw2, "truth_raw setup mismatch"); + assert_eq!( + edge.topology().to_bits_2(), + edge.truth_raw(), + "topology() must read the identical raw bits as truth_raw() for raw={raw2}" + ); + } + } + + // ── Bonus: topology() round-trips via with_topology(), all 4 ordinals ── + + #[test] + fn test_topology_roundtrip() { + for t in [ + CausalTopology::Direct, + CausalTopology::IndirectKnownIntermediates, + CausalTopology::IndirectUnknownIntermediates, + CausalTopology::Unknown, + ] { + let edge = CausalEdge64::ZERO.with_topology(t); + assert_eq!(edge.topology(), t, "topology round-trip failed for {t:?}"); + } + } + + // ── 4. All four ordinal aliases are exact (pairwise `as u8`) ──────────── + + #[test] + fn test_causal_topology_ordinals_are_exactly_trust_texture_ordinals() { + assert_eq!( + TrustTexture::Crystalline as u8, + CausalTopology::Direct as u8, + "Crystalline/Direct must alias to the same ordinal" + ); + assert_eq!( + TrustTexture::Solid as u8, + CausalTopology::IndirectKnownIntermediates as u8, + "Solid/IndirectKnownIntermediates must alias to the same ordinal" + ); + assert_eq!( + TrustTexture::Fuzzy as u8, + CausalTopology::IndirectUnknownIntermediates as u8, + "Fuzzy/IndirectUnknownIntermediates must alias to the same ordinal" + ); + assert_eq!( + TrustTexture::Murky as u8, + CausalTopology::Unknown as u8, + "Murky/Unknown must alias to the same ordinal" + ); + } + + // ── 5. with_topology changes ONLY bits 59-60 (exact XOR-diff-mask form) ─ + + #[test] + fn test_with_topology_changes_only_bits_59_60_exact_mask_diff() { + // Fixture with every OTHER field non-zero/non-default; truth-bits + // (and therefore topology) left at the pack_v2 default of 0. Going + // 0 -> Unknown (0b11, the field's max) makes the XOR diff mask equal + // exactly TRUTH_MASK if and only if with_topology touches nothing + // else in the word. + let base = CausalEdge64::pack_v2( + 0xAA, + 0xBB, + 0xCC, + 0xDD, + 0xEE, + CausalMask::SPO, + 0b111, + PlasticityState::ALL_HOT, + ) + .with_w_slot(63) + .with_inference_mantissa(-7) + .with_spare(0b111); + assert_eq!( + base.topology(), + CausalTopology::Direct, + "fixture truth-bits must start at 0 for the min->max diff to be exact" + ); + + let after = base.with_topology(CausalTopology::Unknown); + let diff = base.0 ^ after.0; + assert_eq!( + diff, + crate::layout::TRUTH_MASK, + "with_topology(min->max) must flip exactly the TRUTH_MASK bits, nothing else" + ); + + // Named-field cross-check (belt and suspenders): every other field + // survives untouched. + assert_eq!(after.s_idx(), 0xAA, "S disturbed by with_topology"); + assert_eq!(after.p_idx(), 0xBB, "P disturbed by with_topology"); + assert_eq!(after.o_idx(), 0xCC, "O disturbed by with_topology"); + assert_eq!( + after.frequency_u8(), + 0xDD, + "frequency disturbed by with_topology" + ); + assert_eq!( + after.confidence_u8(), + 0xEE, + "confidence disturbed by with_topology" + ); + assert_eq!( + after.causal_mask(), + CausalMask::SPO, + "causal_mask disturbed by with_topology" + ); + assert_eq!( + after.direction(), + 0b111, + "direction disturbed by with_topology" + ); + assert_eq!( + after.inference_mantissa(), + -7, + "inference_mantissa disturbed by with_topology" + ); + assert_eq!( + after.plasticity(), + PlasticityState::ALL_HOT, + "plasticity disturbed by with_topology" + ); + assert_eq!(after.w_slot(), 63, "w_slot disturbed by with_topology"); + assert_eq!(after.spare(), 0b111, "spare disturbed by with_topology"); + } + + // ── 6. with_texture_band changes ONLY bits 61-63 (exact XOR-diff-mask) ── + + #[test] + fn test_with_texture_band_changes_only_bits_61_63_exact_mask_diff() { + let base = CausalEdge64::pack_v2( + 0xAA, + 0xBB, + 0xCC, + 0xDD, + 0xEE, + CausalMask::SPO, + 0b111, + PlasticityState::ALL_HOT, + ) + .with_w_slot(63) + .with_truth(TrustTexture::Murky) + .with_inference_mantissa(-7); + assert_eq!( + base.texture_band(), + TextureBand::Surface, + "fixture spare-bits must start at 0 for the min->max diff to be exact" + ); + + let after = base.with_texture_band(TextureBand::Transcendent); + let diff = base.0 ^ after.0; + assert_eq!( + diff, + crate::layout::SPARE_MASK, + "with_texture_band(min->max) must flip exactly the SPARE_MASK bits, nothing else" + ); + + assert_eq!(after.s_idx(), 0xAA, "S disturbed by with_texture_band"); + assert_eq!(after.p_idx(), 0xBB, "P disturbed by with_texture_band"); + assert_eq!(after.o_idx(), 0xCC, "O disturbed by with_texture_band"); + assert_eq!( + after.frequency_u8(), + 0xDD, + "frequency disturbed by with_texture_band" + ); + assert_eq!( + after.confidence_u8(), + 0xEE, + "confidence disturbed by with_texture_band" + ); + assert_eq!( + after.causal_mask(), + CausalMask::SPO, + "causal_mask disturbed by with_texture_band" + ); + assert_eq!( + after.direction(), + 0b111, + "direction disturbed by with_texture_band" + ); + assert_eq!( + after.inference_mantissa(), + -7, + "inference_mantissa disturbed by with_texture_band" + ); + assert_eq!( + after.plasticity(), + PlasticityState::ALL_HOT, + "plasticity disturbed by with_texture_band" + ); + assert_eq!(after.w_slot(), 63, "w_slot disturbed by with_texture_band"); + assert_eq!( + after.truth(), + TrustTexture::Murky, + "truth disturbed by with_texture_band" + ); + } + + // ── 7. spare() stays consistent with texture_band() after its write ──── + + #[test] + fn test_spare_stays_consistent_with_texture_band_after_a_texture_band_write() { + for band in [ + TextureBand::Surface, + TextureBand::Association, + TextureBand::Relation, + TextureBand::Causal, + TextureBand::Counterfactual, + TextureBand::Perspective, + TextureBand::Meta, + TextureBand::Transcendent, + ] { + let edge = CausalEdge64::ZERO.with_texture_band(band); + assert_eq!( + edge.spare(), + band.to_bits_3(), + "spare() must still report the identical 3-bit value texture_band() encodes for {band:?}" + ); + } + } + + // ── 8. All eight texture-band values round-trip ───────────────────────── + + #[test] + fn test_texture_band_all_eight_values_round_trip() { + for band in [ + TextureBand::Surface, + TextureBand::Association, + TextureBand::Relation, + TextureBand::Causal, + TextureBand::Counterfactual, + TextureBand::Perspective, + TextureBand::Meta, + TextureBand::Transcendent, + ] { + let edge = CausalEdge64::ZERO.with_texture_band(band); + assert_eq!( + edge.texture_band(), + band, + "texture_band round-trip failed for {band:?}" + ); + } + } + + // ── 9. Field-isolation matrix: composed setters, every other field ───── + + #[test] + fn test_field_isolation_matrix_survives_both_new_setters_composed() { + // One shared fixture with every field at a distinct, non-zero value, + // then BOTH new setters applied together (a composable builder + // chain, matching the intended call-site pattern: + // `edge.with_topology(...).with_texture_band(...)`). + let base = CausalEdge64::pack_v2( + 11, // S + 22, // P + 33, // O + 44, // frequency + 55, // confidence + CausalMask::SO, + 0b110, // direction + PlasticityState::P_HOT, + ) + .with_w_slot(17) + .with_inference_mantissa(5); + + let edge = base + .with_topology(CausalTopology::IndirectUnknownIntermediates) + .with_texture_band(TextureBand::Causal); + + assert_eq!(edge.s_idx(), 11, "S disturbed by new setters"); + assert_eq!(edge.p_idx(), 22, "P disturbed by new setters"); + assert_eq!(edge.o_idx(), 33, "O disturbed by new setters"); + assert_eq!( + edge.frequency_u8(), + 44, + "frequency disturbed by new setters" + ); + assert_eq!( + edge.confidence_u8(), + 55, + "confidence disturbed by new setters" + ); + assert_eq!( + edge.causal_mask(), + CausalMask::SO, + "causal_mask disturbed by new setters" + ); + assert_eq!( + edge.direction(), + 0b110, + "direction disturbed by new setters" + ); + assert_eq!( + edge.inference_mantissa(), + 5, + "inference_mantissa disturbed by new setters" + ); + assert_eq!( + edge.plasticity(), + PlasticityState::P_HOT, + "plasticity disturbed by new setters" + ); + assert_eq!(edge.w_slot(), 17, "w_slot disturbed by new setters"); + + // And the two new fields landed correctly, composed. + assert_eq!( + edge.topology(), + CausalTopology::IndirectUnknownIntermediates + ); + assert_eq!(edge.texture_band(), TextureBand::Causal); + } + + // ── 10. Counterfactual mantissa (-6) round-trips independent of band ─── + + #[test] + fn test_counterfactual_mantissa_round_trips_independently_of_texture_band() { + let base = CausalEdge64::pack_v2( + 1, + 2, + 3, + 200, + 200, + CausalMask::SPO, + 0, + PlasticityState::ALL_FROZEN, + ) + .with_inference_mantissa(InferenceType::Counterfactual.to_mantissa()); + assert_eq!( + base.inference_mantissa(), + -6, + "fixture must carry mantissa -6" + ); + + for band in [ + TextureBand::Surface, + TextureBand::Meta, + TextureBand::Transcendent, + ] { + let edge = base.with_texture_band(band); + assert_eq!( + edge.inference_mantissa(), + -6, + "counterfactual mantissa must survive a texture_band write for {band:?}" + ); + assert_eq!(edge.texture_band(), band); + } + } + + // ── 11. TextureBand::Counterfactual does NOT imply mantissa == -6 ─────── + + #[test] + fn test_texture_band_counterfactual_does_not_imply_mantissa_minus_six() { + // TextureBand::Counterfactual set, mantissa left at a DIFFERENT + // value — proves the two are orthogonal, never derived from one + // another. + let edge = CausalEdge64::ZERO + .with_texture_band(TextureBand::Counterfactual) + .with_inference_mantissa(3); + assert_eq!(edge.texture_band(), TextureBand::Counterfactual); + assert_eq!( + edge.inference_mantissa(), + 3, + "mantissa must NOT be derived from texture_band" + ); + assert_ne!(edge.inference_mantissa(), -6); + + // And the converse: mantissa == -6 (the Counterfactual InferenceType + // slot) with a DIFFERENT texture_band. + let edge2 = CausalEdge64::ZERO + .with_inference_mantissa(InferenceType::Counterfactual.to_mantissa()) + .with_texture_band(TextureBand::Meta); + assert_eq!(edge2.inference_mantissa(), -6); + assert_eq!( + edge2.texture_band(), + TextureBand::Meta, + "texture_band must NOT be derived from inference_mantissa" + ); + assert_ne!(edge2.texture_band(), TextureBand::Counterfactual); + } + + // ── 12. CausalTopology::Direct does not imply any NARS confidence ────── + + #[test] + fn test_causal_topology_direct_does_not_imply_any_nars_confidence() { + // Direct topology (0) coexists with every confidence level — no + // auto-derivation from/to NARS confidence in either direction. + for conf in [0u8, 1, 128, 254, 255] { + let edge = CausalEdge64::pack_v2( + 1, + 2, + 3, + 100, + conf, + CausalMask::None, + 0, + PlasticityState::ALL_FROZEN, + ) + .with_topology(CausalTopology::Direct); + assert_eq!(edge.topology(), CausalTopology::Direct); + assert_eq!( + edge.confidence_u8(), + conf, + "CausalTopology::Direct must not constrain or derive confidence={conf}" + ); + } + } } From 9891cca6ae691581eee529adc0ba229e35571c2d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 10:36:36 +0000 Subject: [PATCH 2/3] CE64: name the 61..63 lens ReasoningBand, and correct three doc defects the audit found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the additive-lens commit, applying a workspace vocabulary audit. No bits moved, no wire change, no API removed. `TrustTexture` is untouched. ## The name: ReasoningBand, not TextureBand The `// TODO(name)` marker is resolved. "Texture" is the most collided word in this stack's cognitive vocabulary and the collision is already booked debt whose recorded remedy is a rename: 1. FOUR distinct `TrustTexture` enums exist, not two — this crate's (Crystalline/Solid/Fuzzy/Murky), the contract's `mul::TrustTexture` (Calibrated/Overconfident/Uncertain/Underconfident), the planner's FIVE-variant `mul::trust::TrustTexture` (adds `Dissonant`, which cannot fit a 2-bit field), and AriGraph's THREE-variant orchestrator one. 2. An operator ruling holds "Texture = binding topology, not polarity" — Texture is deliberately NOT an ordinal. Naming a 3-bit ordinal `TextureBand` puts a retired instrument's name on the coarse projection meant to sit beside it. 3. Adjacency reads as continuation: `TrustTexture` at 59..60 directly above `TextureBand` at 61..63 would be read as one 5-bit widening. They are unrelated fields. `ReasoningBand` is the noun the type's own doc comment already used eight times ("Surface-level reasoning", "Causal-level reasoning", …). Verified ABSENT from every repo on disk, as are `ReasoningLevel` and `CognitiveBand`. Deliberately NOT named with `Rung` or `Level`: `RungLevel` shares FOUR variant names with this band at DIFFERENT ordinals (Surface 0==0, but Counterfactual 4 vs 6, Meta 6 vs 7, Transcendent 7 vs 9). A `*Rung*` name would assert a correspondence that is ordinally false in three of four cases. The doc now states the disagreement explicitly so nobody maps between them by ordinal. `TrustTexture` keeps its name — canonical, and unchanged here by design. The wider duplication is owned by an existing debt item; doing half of it in this PR would fragment that work. ## Three documentation defects, all pre-existing, all corrected in place 1. `layout.rs` claimed `lance_graph_contract::mul::TrustTexture` is "the canonical contract type" and "byte-compatible by construction". BOTH halves are false — different ontology, no semantic mapping, and no `From` impl exists in either direction. `docs/TYPE_DUPLICATION_MAP.md` rules the opposite outright: "Canonical: NONE — both are domain-correct and should keep distinct names." Corrected in place, marked as a correction rather than silently rewritten, so nobody builds a cast on the old sentence. 2. `spare()` claimed it returns 0 for "all v1-written edges (temporal MSBs were <= 0xFFF)". False: bits 61..63 are v1 temporal bits 9..11, so any v1 edge with `temporal >= 512` reads a NON-ZERO spare. Now states the real threshold, matching the pattern `truth()` already uses correctly for its own bits at `temporal >= 128`. 3. `with_reasoning_band` now carries the trap that matters most here: the deprecated `temporal()` reads bits 52..63, a window CONTAINING these three bits, decomposing as `plast | (w_slot << 1) | (truth << 7) | (band << 9)`. `Network::evidence_trail` sorts by `temporal()` and its comment claims the sort "degrades to a stable no-op" under v2 — true only while 52..63 are all zero, already false for any edge carrying a w_slot or truth, and a written band becomes the DOMINANT sort term. Nothing in-tree writes a band today, so this is a trap for the first producer, not a live defect. Named now rather than discovered later. ## Also documented `CausalTopology` means causal-PATH shape (how many intermediates) — NOT the "binding topology over loci" sense the Texture ruling attaches to that word. One line says so, so the two are never conflated. ## Verified - `cargo test`: 66 passed, 0 failed (default v2 layout). - `cargo test --no-default-features`: 35 passed, 0 failed (v1 stub path). - `TrustTexture` API diff vs main: EMPTY. The only lines mentioning its variants are the new ordinal-bridge doc comment. - fmt applied to the touched code only. --- crates/causal-edge/src/edge.rs | 48 ++++++-- crates/causal-edge/src/layout.rs | 65 +++++++--- crates/causal-edge/src/lib.rs | 2 +- crates/causal-edge/src/v2_layout_tests.rs | 140 +++++++++++----------- 4 files changed, 156 insertions(+), 99 deletions(-) diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 22123a2f5..4f2cfb924 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -651,7 +651,8 @@ impl CausalEdge64 { // weight.inference_type() is the v1 fallback below; v2 uses mantissa let resolved_infer = InferenceType::from_mantissa(weight.inference_mantissa()); #[cfg(not(feature = "causal-edge-v2-layout"))] - #[allow(deprecated)] // v1 layout: 3-bit unsigned inference type is the canonical read + #[allow(deprecated)] + // v1 layout: 3-bit unsigned inference type is the canonical read let resolved_infer = weight.inference_type(); let (f_out, c_out) = match resolved_infer { InferenceType::Deduction => { @@ -954,7 +955,14 @@ impl CausalEdge64 { } /// Spare 3-bit field (bits 61-63). Reserved for sprint-12+ use. - /// Returns 0 for ZERO edges and all v1-written edges (temporal MSBs were ≤ 0xFFF). + /// Returns 0 for ZERO edges. + /// + /// **CORRECTION (measured):** this line previously also claimed "and all + /// v1-written edges (temporal MSBs were <= 0xFFF)". That is false. Bits + /// 61-63 are v1 temporal bits **9-11**, so any v1 edge with + /// `temporal >= 512` reads a NON-ZERO spare. Apply a version gate on + /// edges of unknown provenance — the same rule `truth()` already states + /// for bits 59-60 at its own threshold (`temporal >= 128`). #[inline(always)] pub fn spare(self) -> u8 { use crate::layout::{BITS3_MASK, SPARE_SHIFT}; @@ -963,14 +971,14 @@ impl CausalEdge64 { /// Quantized reasoning-level projection: reads the identical three bits /// as `spare()` (bits 61-63), projected through - /// [`crate::layout::TextureBand`]. Purely additive — no bits move, and + /// [`crate::layout::ReasoningBand`]. Purely additive — no bits move, and /// nothing auto-derives this value; see the type's doc comment for the /// no-auto-derivation guarantee and the orthogonality notes against /// `CausalMask` / the inference mantissa / `direction`. #[inline(always)] - pub fn texture_band(self) -> crate::layout::TextureBand { - use crate::layout::{TextureBand, BITS3_MASK, SPARE_SHIFT}; - TextureBand::from_bits_3(((self.0 >> SPARE_SHIFT) & BITS3_MASK) as u8) + pub fn reasoning_band(self) -> crate::layout::ReasoningBand { + use crate::layout::{ReasoningBand, BITS3_MASK, SPARE_SHIFT}; + ReasoningBand::from_bits_3(((self.0 >> SPARE_SHIFT) & BITS3_MASK) as u8) } // ── Builder-Shape Setters (functional update, returns Self) ───────────── @@ -1025,14 +1033,28 @@ impl CausalEdge64 { /// Return new edge with the texture-band projection set (bits 61-63). /// - /// Consuming builder, register-style (`edge.with_texture_band(b)`), + /// Consuming builder, register-style (`edge.with_reasoning_band(b)`), /// matching `with_spare`/`with_topology`. Shares storage with /// `spare`/`with_spare` — the last writer of either view wins, by /// construction, since it is one 3-bit register read through two - /// lenses. See [`crate::layout::TextureBand`] for the full semantics, + /// lenses. See [`crate::layout::ReasoningBand`] for the full semantics, + /// # ⚠ Writing a non-zero band perturbs the deprecated `temporal()` + /// + /// `temporal()` reads bits **52..63**, a window that CONTAINS these three + /// bits (and the truth bits below them). Decomposed under v2 it is + /// `plast_bit2 | (w_slot << 1) | (truth << 7) | (band << 9)`. So a + /// non-zero band becomes the DOMINANT term of that composite. + /// + /// `Network::evidence_trail` sorts by `temporal()` and its comment claims + /// the sort "degrades to a stable no-op" under v2. That holds only while + /// bits 52..63 are all zero — it is already false for any edge carrying a + /// `w_slot` or a `truth`, and writing a band makes the ordering + /// band-dominated. That ordering is NOT meaningful. Nothing in-tree + /// 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. #[inline] - pub fn with_texture_band(self, band: crate::layout::TextureBand) -> Self { + pub fn with_reasoning_band(self, band: crate::layout::ReasoningBand) -> Self { use crate::layout::{BITS3_MASK, SPARE_MASK, SPARE_SHIFT}; Self((self.0 & !SPARE_MASK) | ((band.to_bits_3() as u64 & BITS3_MASK) << SPARE_SHIFT)) } @@ -1127,11 +1149,11 @@ impl CausalEdge64 { pub fn spare(self) -> u8 { 0 } - /// V1 stub: additive `TextureBand` lens is a no-op under v1 (matches + /// V1 stub: additive `ReasoningBand` lens is a no-op under v1 (matches /// the `spare()` stub above — same bits, same "feature off" behaviour). #[inline(always)] - pub fn texture_band(self) -> crate::layout::TextureBand { - crate::layout::TextureBand::Surface + pub fn reasoning_band(self) -> crate::layout::ReasoningBand { + crate::layout::ReasoningBand::Surface } #[inline] pub fn with_w_slot(self, _w: u8) -> Self { @@ -1154,7 +1176,7 @@ impl CausalEdge64 { self } #[inline] - pub fn with_texture_band(self, _band: crate::layout::TextureBand) -> Self { + pub fn with_reasoning_band(self, _band: crate::layout::ReasoningBand) -> Self { self } #[inline] diff --git a/crates/causal-edge/src/layout.rs b/crates/causal-edge/src/layout.rs index 91d00d16b..c06e528c8 100644 --- a/crates/causal-edge/src/layout.rs +++ b/crates/causal-edge/src/layout.rs @@ -68,9 +68,12 @@ pub const TRUTH_SHIFT: u32 = 59; /// Candidates: Rubicon-commit marker, Markov-decay quantum, I-NOISE-FLOOR-JIRAK threshold. /// /// Same three bits also carry an ADDITIVE quantized-projection view, -/// [`TextureBand`] (below; name pending a vocabulary-collision audit — see -/// its `// TODO(name)` marker). No auto-derivation: nothing writes this -/// field except an explicit `with_texture_band()` call. +/// [`ReasoningBand`] (below). No auto-derivation: nothing writes this +/// field except an explicit `with_reasoning_band()` call. +/// +/// **v1 provenance:** bits 61-63 were temporal bits 9-11, so a v1 edge with +/// `temporal >= 512` reads a NON-ZERO band. Apply a version gate on edges of +/// unknown provenance — the same rule `truth()` states for bits 59-60. pub const SPARE_SHIFT: u32 = 61; // ── Common masks ───────────────────────────────────────────────────────────── @@ -117,10 +120,22 @@ const _LAYOUT_COVERAGE: () = { /// 0b11 = Murky | Contradiction| Loud | Compass (veto) /// ``` /// -/// NOTE: Local definition in causal-edge (zero-dep crate). The canonical -/// contract type is `lance_graph_contract::mul::TrustTexture`. -/// The 2-bit encoding is byte-compatible by construction. -/// Long-term: add `From for contract::TrustTexture` at the planner boundary. +/// NOTE: Local definition in causal-edge (zero-dep crate). +/// +/// **CORRECTION (measured):** an earlier version of this note claimed +/// `lance_graph_contract::mul::TrustTexture` is "the canonical contract type" +/// and "byte-compatible by construction". Both halves are false. That type's +/// variants are `Calibrated / Overconfident / Uncertain / Underconfident` — +/// a different ontology (felt-vs-demonstrated competence), with no semantic +/// mapping onto `Crystalline / Solid / Fuzzy / Murky`, and no `From` impl +/// exists in either direction. `docs/TYPE_DUPLICATION_MAP.md` rules the +/// opposite of the old note: **"Canonical: NONE — both are domain-correct +/// and should keep distinct names."** Do not build a cast on the old claim. +/// +/// This enum is the LEGACY/COMPATIBILITY projection of bits 59-60; the +/// factual view over the same bits is [`CausalTopology`]. Not deprecated — +/// nothing is ready to move, and the wider rename is owned by the existing +/// TrustTexture-duplication debt item. #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] #[repr(u8)] pub enum TrustTexture { @@ -285,14 +300,14 @@ impl CausalTopology { /// - `Causal` = the cognition currently operating at the causal reasoning /// level. This is **not** [`super::pearl::CausalMask`] (bits 40-42), /// which says WHICH Pearl/SPO projection (S/P/O planes) is represented. -/// An edge may carry `TextureBand::Causal` under any `CausalMask`. +/// An edge may carry `ReasoningBand::Causal` under any `CausalMask`. /// - `Counterfactual` = the reasoning CONTEXT is counterfactual. This is /// **not** the signed inference mantissa's −6 slot (bits 46-49; see /// `InferenceType::Counterfactual::to_mantissa() == -6`), which names one /// specific NARS operation as counterfactual. An edge may carry -/// `TextureBand::Counterfactual` while its mantissa encodes any NARS +/// `ReasoningBand::Counterfactual` while its mantissa encodes any NARS /// rule, and an edge with mantissa −6 need not carry -/// `TextureBand::Counterfactual`. +/// `ReasoningBand::Counterfactual`. /// - `Perspective` = perspective/decentration reasoning (I/Thou/It, /// Self/Other/World). This is **not** /// [`super::edge::CausalEdge64::direction`] (bits 43-45), the @@ -309,17 +324,33 @@ impl CausalTopology { /// Nothing in this crate derives this field from `CausalMask`, /// `InferenceType`, NARS frequency/confidence, MUL, ReasoningGap, /// potholes, or `ThinkingStyle`. It is set ONLY by an explicit -/// `with_texture_band()` call, and reads whatever the SPARE bits already +/// `with_reasoning_band()` call, and reads whatever the SPARE bits already /// hold otherwise (0 / `Surface` for `CausalEdge64::ZERO` and for every row /// produced by this crate's own constructors, since `spare()` already /// defaults to 0 there — but not guaranteed for a raw `u64` from elsewhere). -// TODO(name): "TextureBand" is a placeholder pending a vocabulary-collision -// audit against the rest of the workspace (a parallel session is checking -// whether the name is already spoken for elsewhere). Do not treat this name -// as final; it may be renamed before this lands. +/// # Why NOT "TextureBand" +/// +/// The name was settled by a workspace vocabulary audit, not by preference. +/// "Texture" is the most collided word in this stack's cognitive vocabulary: +/// FOUR distinct `TrustTexture` enums exist (this crate's, the contract's +/// `mul::TrustTexture`, the planner's 5-variant `mul::trust::TrustTexture`, +/// and AriGraph's 3-variant orchestrator one), that duplication is already +/// booked debt whose recorded remedy is a RENAME, and an operator ruling +/// holds "Texture = binding topology, not polarity" — i.e. Texture is +/// deliberately NOT an ordinal. Naming a 3-bit ordinal `TextureBand`, in +/// this file, directly beneath `TrustTexture` and over the adjacent bits, +/// would read as one 5-bit widening of it. It is an unrelated field. +/// +/// # NOT `RungLevel`, despite four shared variant names +/// +/// This band shares four variant NAMES with +/// `lance_graph_contract::cognitive_shader::RungLevel` — `Surface`, +/// `Counterfactual`, `Meta`, `Transcendent` — at DIFFERENT ordinals +/// (0 / 6 / 7 / 9 there vs 0 / 4 / 6 / 7 here). The two are unrelated enums: +/// never cast, compare, or map between them by ordinal. #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] #[repr(u8)] -pub enum TextureBand { +pub enum ReasoningBand { /// Surface-level reasoning. Default. #[default] Surface = 0, @@ -341,7 +372,7 @@ pub enum TextureBand { Transcendent = 7, } -impl TextureBand { +impl ReasoningBand { /// Construct from the raw 3-bit field value (bits masked automatically). #[inline] pub fn from_bits_3(v: u8) -> Self { diff --git a/crates/causal-edge/src/lib.rs b/crates/causal-edge/src/lib.rs index 40c374ca9..0b72ba8ed 100644 --- a/crates/causal-edge/src/lib.rs +++ b/crates/causal-edge/src/lib.rs @@ -78,4 +78,4 @@ pub use layout::TrustTexture; // full wire/ordinal/behavioural/provenance compatibility statement. No bits // move; these are new readings of existing storage, not a new layout. #[cfg(feature = "causal-edge-v2-layout")] -pub use layout::{CausalTopology, TextureBand}; +pub use layout::{CausalTopology, ReasoningBand}; diff --git a/crates/causal-edge/src/v2_layout_tests.rs b/crates/causal-edge/src/v2_layout_tests.rs index 95249613d..2aa4fd662 100644 --- a/crates/causal-edge/src/v2_layout_tests.rs +++ b/crates/causal-edge/src/v2_layout_tests.rs @@ -19,7 +19,7 @@ #[cfg(feature = "causal-edge-v2-layout")] mod v2_layout_tests { use crate::edge::{CausalEdge64, InferenceType}; - use crate::layout::{CausalTopology, TextureBand, TrustTexture}; + use crate::layout::{CausalTopology, ReasoningBand, TrustTexture}; use crate::pearl::CausalMask; use crate::plasticity::PlasticityState; @@ -477,7 +477,7 @@ mod v2_layout_tests { // ═══════════════════════════════════════════════════════════════════ // CausalTopology (bits 59-60, additive factual view over TrustTexture) - // + TextureBand (bits 61-63, additive quantized view over `spare`) + // + ReasoningBand (bits 61-63, additive quantized view over `spare`) // ═══════════════════════════════════════════════════════════════════ // // These fields do NOT move any bits and do NOT introduce a new layout @@ -488,7 +488,7 @@ mod v2_layout_tests { // ── 1. Raw u64 fixtures round-trip byte-for-byte unchanged ───────────── #[test] - fn test_raw_fixtures_round_trip_byte_for_byte_via_topology_and_texture_band() { + fn test_raw_fixtures_round_trip_byte_for_byte_via_topology_and_reasoning_band() { // Arbitrary fixtures covering varied bit patterns across the whole // word, including every combination of the two shared registers // (bits 59-60, 61-63). @@ -510,10 +510,10 @@ mod v2_layout_tests { via_topology.0, raw, "read-then-write-back through CausalTopology changed the raw word for {raw:#018x}" ); - let via_texture = edge.with_texture_band(edge.texture_band()); + let via_texture = edge.with_reasoning_band(edge.reasoning_band()); assert_eq!( via_texture.0, raw, - "read-then-write-back through TextureBand changed the raw word for {raw:#018x}" + "read-then-write-back through ReasoningBand changed the raw word for {raw:#018x}" ); } } @@ -664,10 +664,10 @@ mod v2_layout_tests { assert_eq!(after.spare(), 0b111, "spare disturbed by with_topology"); } - // ── 6. with_texture_band changes ONLY bits 61-63 (exact XOR-diff-mask) ── + // ── 6. with_reasoning_band changes ONLY bits 61-63 (exact XOR-diff-mask) ── #[test] - fn test_with_texture_band_changes_only_bits_61_63_exact_mask_diff() { + fn test_with_reasoning_band_changes_only_bits_61_63_exact_mask_diff() { let base = CausalEdge64::pack_v2( 0xAA, 0xBB, @@ -682,79 +682,83 @@ mod v2_layout_tests { .with_truth(TrustTexture::Murky) .with_inference_mantissa(-7); assert_eq!( - base.texture_band(), - TextureBand::Surface, + base.reasoning_band(), + ReasoningBand::Surface, "fixture spare-bits must start at 0 for the min->max diff to be exact" ); - let after = base.with_texture_band(TextureBand::Transcendent); + let after = base.with_reasoning_band(ReasoningBand::Transcendent); let diff = base.0 ^ after.0; assert_eq!( diff, crate::layout::SPARE_MASK, - "with_texture_band(min->max) must flip exactly the SPARE_MASK bits, nothing else" + "with_reasoning_band(min->max) must flip exactly the SPARE_MASK bits, nothing else" ); - assert_eq!(after.s_idx(), 0xAA, "S disturbed by with_texture_band"); - assert_eq!(after.p_idx(), 0xBB, "P disturbed by with_texture_band"); - assert_eq!(after.o_idx(), 0xCC, "O disturbed by with_texture_band"); + assert_eq!(after.s_idx(), 0xAA, "S disturbed by with_reasoning_band"); + assert_eq!(after.p_idx(), 0xBB, "P disturbed by with_reasoning_band"); + assert_eq!(after.o_idx(), 0xCC, "O disturbed by with_reasoning_band"); assert_eq!( after.frequency_u8(), 0xDD, - "frequency disturbed by with_texture_band" + "frequency disturbed by with_reasoning_band" ); assert_eq!( after.confidence_u8(), 0xEE, - "confidence disturbed by with_texture_band" + "confidence disturbed by with_reasoning_band" ); assert_eq!( after.causal_mask(), CausalMask::SPO, - "causal_mask disturbed by with_texture_band" + "causal_mask disturbed by with_reasoning_band" ); assert_eq!( after.direction(), 0b111, - "direction disturbed by with_texture_band" + "direction disturbed by with_reasoning_band" ); assert_eq!( after.inference_mantissa(), -7, - "inference_mantissa disturbed by with_texture_band" + "inference_mantissa disturbed by with_reasoning_band" ); assert_eq!( after.plasticity(), PlasticityState::ALL_HOT, - "plasticity disturbed by with_texture_band" + "plasticity disturbed by with_reasoning_band" + ); + assert_eq!( + after.w_slot(), + 63, + "w_slot disturbed by with_reasoning_band" ); - assert_eq!(after.w_slot(), 63, "w_slot disturbed by with_texture_band"); assert_eq!( after.truth(), TrustTexture::Murky, - "truth disturbed by with_texture_band" + "truth disturbed by with_reasoning_band" ); } - // ── 7. spare() stays consistent with texture_band() after its write ──── + // ── 7. spare() stays consistent with reasoning_band() after its write ──── #[test] - fn test_spare_stays_consistent_with_texture_band_after_a_texture_band_write() { + fn test_spare_stays_consistent_with_reasoning_band_after_a_reasoning_band_write() { for band in [ - TextureBand::Surface, - TextureBand::Association, - TextureBand::Relation, - TextureBand::Causal, - TextureBand::Counterfactual, - TextureBand::Perspective, - TextureBand::Meta, - TextureBand::Transcendent, + ReasoningBand::Surface, + ReasoningBand::Association, + ReasoningBand::Relation, + ReasoningBand::Causal, + ReasoningBand::Counterfactual, + ReasoningBand::Perspective, + ReasoningBand::Meta, + ReasoningBand::Transcendent, ] { - let edge = CausalEdge64::ZERO.with_texture_band(band); + let edge = CausalEdge64::ZERO.with_reasoning_band(band); assert_eq!( edge.spare(), band.to_bits_3(), - "spare() must still report the identical 3-bit value texture_band() encodes for {band:?}" + "spare() must still report the identical 3-bit value reasoning_band() encodes for {band:?}" ); } } @@ -762,22 +766,22 @@ mod v2_layout_tests { // ── 8. All eight texture-band values round-trip ───────────────────────── #[test] - fn test_texture_band_all_eight_values_round_trip() { + fn test_reasoning_band_all_eight_values_round_trip() { for band in [ - TextureBand::Surface, - TextureBand::Association, - TextureBand::Relation, - TextureBand::Causal, - TextureBand::Counterfactual, - TextureBand::Perspective, - TextureBand::Meta, - TextureBand::Transcendent, + ReasoningBand::Surface, + ReasoningBand::Association, + ReasoningBand::Relation, + ReasoningBand::Causal, + ReasoningBand::Counterfactual, + ReasoningBand::Perspective, + ReasoningBand::Meta, + ReasoningBand::Transcendent, ] { - let edge = CausalEdge64::ZERO.with_texture_band(band); + let edge = CausalEdge64::ZERO.with_reasoning_band(band); assert_eq!( - edge.texture_band(), + edge.reasoning_band(), band, - "texture_band round-trip failed for {band:?}" + "reasoning_band round-trip failed for {band:?}" ); } } @@ -789,7 +793,7 @@ mod v2_layout_tests { // One shared fixture with every field at a distinct, non-zero value, // then BOTH new setters applied together (a composable builder // chain, matching the intended call-site pattern: - // `edge.with_topology(...).with_texture_band(...)`). + // `edge.with_topology(...).with_reasoning_band(...)`). let base = CausalEdge64::pack_v2( 11, // S 22, // P @@ -805,7 +809,7 @@ mod v2_layout_tests { let edge = base .with_topology(CausalTopology::IndirectUnknownIntermediates) - .with_texture_band(TextureBand::Causal); + .with_reasoning_band(ReasoningBand::Causal); assert_eq!(edge.s_idx(), 11, "S disturbed by new setters"); assert_eq!(edge.p_idx(), 22, "P disturbed by new setters"); @@ -847,13 +851,13 @@ mod v2_layout_tests { edge.topology(), CausalTopology::IndirectUnknownIntermediates ); - assert_eq!(edge.texture_band(), TextureBand::Causal); + assert_eq!(edge.reasoning_band(), ReasoningBand::Causal); } // ── 10. Counterfactual mantissa (-6) round-trips independent of band ─── #[test] - fn test_counterfactual_mantissa_round_trips_independently_of_texture_band() { + fn test_counterfactual_mantissa_round_trips_independently_of_reasoning_band() { let base = CausalEdge64::pack_v2( 1, 2, @@ -872,50 +876,50 @@ mod v2_layout_tests { ); for band in [ - TextureBand::Surface, - TextureBand::Meta, - TextureBand::Transcendent, + ReasoningBand::Surface, + ReasoningBand::Meta, + ReasoningBand::Transcendent, ] { - let edge = base.with_texture_band(band); + let edge = base.with_reasoning_band(band); assert_eq!( edge.inference_mantissa(), -6, - "counterfactual mantissa must survive a texture_band write for {band:?}" + "counterfactual mantissa must survive a reasoning_band write for {band:?}" ); - assert_eq!(edge.texture_band(), band); + assert_eq!(edge.reasoning_band(), band); } } - // ── 11. TextureBand::Counterfactual does NOT imply mantissa == -6 ─────── + // ── 11. ReasoningBand::Counterfactual does NOT imply mantissa == -6 ─────── #[test] - fn test_texture_band_counterfactual_does_not_imply_mantissa_minus_six() { - // TextureBand::Counterfactual set, mantissa left at a DIFFERENT + fn test_reasoning_band_counterfactual_does_not_imply_mantissa_minus_six() { + // ReasoningBand::Counterfactual set, mantissa left at a DIFFERENT // value — proves the two are orthogonal, never derived from one // another. let edge = CausalEdge64::ZERO - .with_texture_band(TextureBand::Counterfactual) + .with_reasoning_band(ReasoningBand::Counterfactual) .with_inference_mantissa(3); - assert_eq!(edge.texture_band(), TextureBand::Counterfactual); + assert_eq!(edge.reasoning_band(), ReasoningBand::Counterfactual); assert_eq!( edge.inference_mantissa(), 3, - "mantissa must NOT be derived from texture_band" + "mantissa must NOT be derived from reasoning_band" ); assert_ne!(edge.inference_mantissa(), -6); // And the converse: mantissa == -6 (the Counterfactual InferenceType - // slot) with a DIFFERENT texture_band. + // slot) with a DIFFERENT reasoning_band. let edge2 = CausalEdge64::ZERO .with_inference_mantissa(InferenceType::Counterfactual.to_mantissa()) - .with_texture_band(TextureBand::Meta); + .with_reasoning_band(ReasoningBand::Meta); assert_eq!(edge2.inference_mantissa(), -6); assert_eq!( - edge2.texture_band(), - TextureBand::Meta, - "texture_band must NOT be derived from inference_mantissa" + edge2.reasoning_band(), + ReasoningBand::Meta, + "reasoning_band must NOT be derived from inference_mantissa" ); - assert_ne!(edge2.texture_band(), TextureBand::Counterfactual); + assert_ne!(edge2.reasoning_band(), ReasoningBand::Counterfactual); } // ── 12. CausalTopology::Direct does not imply any NARS confidence ────── From 8a3cd4e622a24789d65ab54967d29c55acd1f068 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 10:37:45 +0000 Subject: [PATCH 3/3] Keep the pre-existing allow(deprecated) formatting out of this diff cargo fmt wants to move this comment onto its own line, but the hunk is pre-existing drift on main (which is already fmt-dirty here) and is unrelated to the additive lenses. Reverted so the diff contains only this change. --- crates/causal-edge/src/edge.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/causal-edge/src/edge.rs b/crates/causal-edge/src/edge.rs index 4f2cfb924..324bf13a8 100644 --- a/crates/causal-edge/src/edge.rs +++ b/crates/causal-edge/src/edge.rs @@ -651,8 +651,7 @@ impl CausalEdge64 { // weight.inference_type() is the v1 fallback below; v2 uses mantissa let resolved_infer = InferenceType::from_mantissa(weight.inference_mantissa()); #[cfg(not(feature = "causal-edge-v2-layout"))] - #[allow(deprecated)] - // v1 layout: 3-bit unsigned inference type is the canonical read + #[allow(deprecated)] // v1 layout: 3-bit unsigned inference type is the canonical read let resolved_infer = weight.inference_type(); let (f_out, c_out) = match resolved_infer { InferenceType::Deduction => {