From 184efcc153d36e0a23706f98b04a5304b9889683 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 12:05:38 +0000 Subject: [PATCH 1/3] P3: wire the rung ascent loop + dedup RungLevel (D-TRI-6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executes the §3a Maslow-pyramid wiring that E-MASLOW-PYRAMID-OF-COGNITION-1 named as the missing piece. Four deliverables, all verified green. (D) Rung ascent loop — the driver already held a per-driver RungElevator and called on_gate() once per dispatch, but the rung never reached projection selection (stage [3] cascade used req.layer_mask verbatim). Now the elevator's current level (advanced by the previous cycle's gate) selects this cycle's cascade breadth via rung_widened_layer_mask, closing the loop across cycles. CORRECTNESS GATE resolved: p64 cascade's layer_mask is an 8-bit PREDICATE-PLANE mask (CAUSES..BECOMES), NOT the 3-bit SPO projection mask causal_mask_bits() returns — so the rung's Pearl level selects a predicate-plane widen-set applied as a UNION gated on elevation-above-base: identity at base (zero regression), superset-monotone above. D-TRI-6 settlement probe green (BLOCK ascends, FLOW relaxes to base, mask widens then returns to identity). (A) Dedup — thinking-engine's variant-identical RungLevel collapsed to a re-export of the contract type (added as_u8() to the contract; a discriminant-parity test is the measure step). Removed dead should_elevate, LayerId (zero consumers), and Agent.current_rung (write-once Surface, never mutated — the loop lives on the driver's elevator). (B) scenario.rs Pearl-numbering doc fix (interventions = rung 2, not 3). (C) nars_engine deprecation silenced with justified #[allow(deprecated)] (v2 inference_type() already routes through from_mantissa; temporal() inert) — planner clippy -D warnings exit 0. Verified: contract 905 tests + parity green + clippy exit 0; planner 215 + clippy exit 0; driver 103 + both D-TRI-6 probes green; thinking-engine lib builds. System-1 apply_delta arm left test-only (no emergence/coherence scalar in the driver hot path; fabricating one is forbidden). Board: STATUS_BOARD D-TRI-6 (ascent loop wired), EPIPHANIES E-RUNG-ASCENT-WIRED-1, TECH_DEBT TD-THINKING-ENGINE-EXCLUDED-DEBT-1 + TD-ONTOLOGY-CLIPPY-DEBT-1 (pre-existing debts surfaced, out of P3 scope). Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Awg6TXocHcwTtc6eGsHcdD --- .claude/board/EPIPHANIES.md | 11 ++ .claude/board/STATUS_BOARD.md | 2 +- .claude/board/TECH_DEBT.md | 8 + crates/cognitive-shader-driver/src/driver.rs | 140 ++++++++++++++++-- .../src/cognitive_shader.rs | 34 +++++ crates/lance-graph-contract/src/scenario.rs | 8 +- .../src/cache/nars_engine.rs | 10 ++ crates/thinking-engine/src/cognitive_stack.rs | 110 +------------- crates/thinking-engine/src/persona.rs | 6 +- crates/thinking-engine/src/world_model.rs | 2 +- 10 files changed, 207 insertions(+), 124 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index c5003f83..48179187 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,14 @@ +## 2026-07-17 — E-RUNG-ASCENT-WIRED-1 — the rung ascent loop is now WIRED in the shader driver (D-TRI-6, plateau P3); RungLevel deduped to the contract; the correctness gate was an axis mismatch, not the ladder + +**Status:** SHIPPED (plateau P3, PR pending). Executes the §3a wiring E-MASLOW-PYRAMID-OF-COGNITION-1 named as "the missing wiring". Four deliverables, all green. + +- **The centerpiece (D):** the driver already held a per-driver `RungElevator` and called `on_gate` once per dispatch, but its rung never reached projection selection — stage [3] cascade used `req.layer_mask` verbatim. Now the elevator's CURRENT level (advanced by the previous cycle's gate) selects THIS cycle's cascade breadth via `rung_widened_layer_mask`, closing the loop across cycles. +- **The one real correctness gate (HAZARD a) was an AXIS mismatch, not the ladder:** p64 `CognitiveShader::cascade`'s `layer_mask` is an 8-bit PREDICATE-PLANE mask (CAUSES..BECOMES), NOT the 3-bit SPO projection mask `RungLevel::causal_mask_bits()` returns. Feeding `causal_mask_bits()` raw is a category error AND collapses base-Surface to plane 0. Fix: the rung's *Pearl level* selects a predicate-plane widen-set (mirrors `p64_bridge::edge_to_layer_mask`), applied as a UNION gated on elevation-above-base → **identity at base (zero regression), superset-monotone above** (elevation only widens). D-TRI-6 settlement probe (sustained BLOCK ascends → sustained FLOW relaxes to base; mask widens then returns to identity) green. +- **Dedup (A):** thinking-engine's `RungLevel` was variant-identical to the contract's; collapsed to `pub use lance_graph_contract::cognitive_shader::RungLevel` (added `as_u8()` to the contract; discriminant-parity test IS the "measure" step — a repr(u8) variant-identical enum needs no jc battery). Deleted the dead `should_elevate`, `LayerId` (zero consumers), and `Agent.current_rung` (write-once Surface, never mutated — the ascent loop lives on the driver's elevator, not the orphaned Agent). +- **(B)** scenario.rs Pearl-numbering doc wobble fixed (interventions = rung 2, not 3). **(C)** nars_engine deprecation silenced with a justified `#[allow(deprecated)]` (v2 `inference_type()` already routes through `from_mantissa` internally; `temporal()` inert under v2) — planner clippy `-D warnings` exit 0. +- **Verified:** contract 905 tests + parity green, planner 215 + clippy exit 0, driver 103 + both D-TRI-6 probes green, thinking-engine lib builds. System-1 arm (`apply_delta(rung_delta())`) left test-only — the driver hot path has no emergence/coherence scalar to feed it; fabricating one is forbidden. +- Cross-ref: TD-THINKING-ENGINE-EXCLUDED-DEBT-1 + TD-ONTOLOGY-CLIPPY-DEBT-1 (pre-existing debts surfaced, out of P3 scope). + ## 2026-07-17 — E-MASLOW-PYRAMID-OF-COGNITION-1 — observation/gestalt FIRST, then SPO 2³ decomposition → CausalEdge64 → NARS candidate design; the ladder type is shipped, the stateful ascent loop is the missing wiring (operator-directed) **Status:** RULING (design banked, PR #707 §3a) + AUDIT (fleet sweep, same day). Operator: "Maslow pyramid of cognition, observation/gestalt first, then SPO 2³ decomposition, CausalEdge64, design NARS candidates" — each level engages only when the level below fails to settle; the base is wide (the D-TRI-6 EXPECTATION is that most stimuli settle at resonance, O(1) palette lookup), the apex narrow (counterfactual design, fork+simulate). Pyramid width IS the cost amortization. Middle-tier emissions ARE NARS candidates (doctrine `mailbox-kanban-model.md:42`); the upper tier DESIGNS (revises) them, it does not introduce NARS — and the shipped driver runs that revision optionally-per-cycle where the pyramid gates it on non-settlement (a named behavior delta, D-TRI-6-gated). diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index f460f063..624ebefc 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -9,7 +9,7 @@ Plan: `.claude/plans/triangle-tenants-gestalt-separation-v1.md`. Design shipped; | D-TRI-3 | Nail->hammer dispatch probe: object resonance -> atom retrieval vs inverted baseline; no inverted read path exists structurally | lance-graph | Queued | plan §3, §6 | | D-TRI-4 | Chess<->thinking transfer measurement (validity + reliability) — the quarantine-lift gate | lance-graph + stockfish-rs | Queued | plan §5, §6 | | D-TRI-5 | Emulation != resonance: counterfactual-goalstate emulation vs resonance-only baseline on opponent move prediction (operator correction §2a; builds on D-SF-OPPONENT-1/3) | lance-graph + stockfish-rs | Queued | plan §2a, §6 | -| D-TRI-6 | Pyramid settlement: settle-rung distribution over real shader cycles (base-heavy expected); homeostatic descent verified; elevator threshold jc-calibrated | lance-graph | Queued | plan §3a, §6 | +| D-TRI-6 | Pyramid settlement: settle-rung distribution over real shader cycles (base-heavy expected); homeostatic descent verified; elevator threshold jc-calibrated | lance-graph | In PR (P3) — ascent loop WIRED (driver rung→predicate-plane widen; identity-at-base, superset-monotone); settlement probe green; real-cycle distribution + jc threshold calibration still open | plan §3a, §6 | ## epiphany-integration-2026-07-04-v3 — membranes, parity, unified ruff phases diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index 12120de3..05b69d01 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -1,5 +1,13 @@ # Technical Debt Log — Open + Paid (double-entry, append-only) +## TD-THINKING-ENGINE-EXCLUDED-DEBT-1 — thinking-engine (excluded, off-CI) carries ~40 pre-existing clippy lints + test-compile failures (2026-07-17) + +**Surfaced during P3** (rung dedup). thinking-engine is a workspace-EXCLUDED crate (root Cargo.toml `exclude`), so no CI/clippy gate runs on it — it has accumulated ~40 `clippy -D warnings` lints (loop-index, `map_or` simplify, too-many-args, missing-Default; all in modules P3 never touched — e.g. `cognitive_stack.rs:242` MetaCognition::new, `world_model.rs:154` from_engine_state) and at least one test-compile break (`DualResult` missing `convergence_signed`/`convergence_unsigned` fields, a stale test). The **lib builds clean**; only `--tests` and `clippy -D warnings` fail. Correlates with E-RUNG-ASCENT-WIRED-1's finding that the whole crate is orphaned from the production spine (only bridge_gate→callcenter + dto→driver[feature] are external wires). PAYOFF: gated on the P4 ancestry-consolidation decision (wire the gems into the spine → then it earns a CI slot and the debt gets paid; or retire the orphaned cluster). Not paid in P3 (out of scope; excluded crate). + +## TD-ONTOLOGY-CLIPPY-DEBT-1 — lance-graph-ontology (member) has pre-existing clippy -D warnings errors that block dependent `-p` clippy (2026-07-17) + +**Surfaced during P3.** `cargo clippy -p cognitive-shader-driver -- -D warnings` fails (exit 101) entirely inside `lance-graph-ontology` (a transitive dep): `doc-lazy-continuation` ×N, `iter-cloned-collect` (`.iter().copied().collect()` → `to_vec()`), and deprecated `oxrdf::Subject` type-alias uses (ttl_parse.rs, owl.rs, op_emitter.rs, lib.rs). P3 touches none of ontology; the driver's OWN code is clippy-clean (0 findings in driver.rs). Pre-existing by construction (not in P3's diff). PAYOFF: mechanical (doc reflow + `to_vec()` + `NamedOrBlankNode` migration), its own small PR — not P3 scope. Flags that repo CI likely does not run full-workspace `clippy -D warnings`, or main is already red there. + ## TD-BGZ-TENSOR-PRE-LANE-REVIEW — bgz-tensor's adaptive codec predates turbovec/PolarQuant/helix; engineering follow-up review queued (2026-07-16) **Open.** Operator nudge: `bgz-tensor` (incl. `adaptive_codec.rs`'s diff --git a/crates/cognitive-shader-driver/src/driver.rs b/crates/cognitive-shader-driver/src/driver.rs index 36aab87a..18e5cb51 100644 --- a/crates/cognitive-shader-driver/src/driver.rs +++ b/crates/cognitive-shader-driver/src/driver.rs @@ -229,6 +229,25 @@ impl ShaderDriver { // below is written ONCE against `backing` — no `#[cfg]` branches here. let backing = self.backing(); + // ── Rung ascent loop (D-TRI-6) ────────────────────────────────────── + // The persistent per-driver RungElevator's CURRENT level — advanced by + // the PREVIOUS cycle's gate via `on_gate` at the end of this fn — selects + // THIS cycle's cascade plane breadth. A dispatch whose requested base + // rung differs from the elevator's tracked base resets the elevator + // first, so streaks never leak across unrelated dispatch bases. At base + // the mask is unchanged (no regression); above base it widens by union + // (see `rung_widened_layer_mask` + its HAZARD(a) note). + let effective_layer_mask = { + let mut elevator = self + .rung_elevator + .write() + .expect("rung_elevator RwLock poisoned"); + if elevator.base != req.rung { + *elevator = RungElevator::new(req.rung); + } + rung_widened_layer_mask(elevator.base, elevator.level, req.layer_mask) + }; + // [1] Cheap meta prefilter (u32 column sweep). let passed_rows = backing.prefilter(req.rows, &req.meta_prefilter); @@ -303,7 +322,7 @@ impl ShaderDriver { // Rows with edge=0 default to palette 0 (identity probe). let edge = backing.edge(row as usize); let query = edge.s_idx(); - let raw = shader.cascade(query, req.radius, req.layer_mask); + let raw = shader.cascade(query, req.radius, effective_layer_mask); for hit in raw.into_iter().take(4) { let resonance = 1.0 / (1.0 + (hit.distance as f32 / max_dist)); @@ -580,20 +599,17 @@ impl ShaderDriver { _ => None, }; - // Feed this cycle's already-decided `gate` into the persistent - // per-driver `RungElevator` (sustained-BLOCK elevation over the - // dispatched base — see the field doc on `ShaderDriver::rung_elevator`). - // A dispatch requesting a different base rung than the elevator - // currently tracks resets it to that new base first, so streaks - // never leak across unrelated dispatch contexts. + // Advance the persistent per-driver `RungElevator` with THIS cycle's + // already-decided `gate` (sustained-BLOCK elevates over the dispatched + // base; sustained-FLOW relaxes back to it). The base-reset already ran + // at the top of `run()` where the level was read for the cascade, so + // `elevator.base == req.rung` holds here. The resulting level is what the + // NEXT dispatch's cascade consults — closing the ascent loop across cycles. let elevated_rung: u8 = { let mut elevator = self .rung_elevator .write() .expect("rung_elevator RwLock poisoned"); - if elevator.base != req.rung { - *elevator = RungElevator::new(req.rung); - } elevator.on_gate(gate) as u8 }; @@ -646,6 +662,54 @@ impl ShaderDriver { } } +// ═══════════════════════════════════════════════════════════════════════════ +// Rung ascent loop — rung → predicate-plane widening (D-TRI-6) +// ═══════════════════════════════════════════════════════════════════════════ + +/// Combine the dispatch's 8-bit predicate-plane mask with the CURRENT rung. +/// At (or below) the dispatched base rung this returns `req_mask` UNCHANGED — +/// zero behaviour change for a thought sitting at its requested depth. Above +/// base, each Pearl level the rung has climbed UNIONs in additional predicate +/// planes, so the consulted set is a strict SUPERSET of `req_mask`: elevation +/// only ever WIDENS the cascade, never narrows it. +/// +/// HAZARD (a) — axis translation (CORRECTNESS GATE). The p64 +/// `CognitiveShader::cascade` `layer_mask` is an 8-bit PREDICATE-PLANE mask +/// (CAUSES..BECOMES; bit `z` gates plane `z`), NOT the 3-bit SPO projection +/// mask `RungLevel::causal_mask_bits()` returns. Feeding `causal_mask_bits()` +/// raw would be a category error AND, at base Surface (0b001), would collapse +/// the cascade to plane 0 only — a severe regression. So the rung's *Pearl +/// level* selects a predicate-plane widen-set, mirroring the Pearl→predicate +/// routing already in `p64_bridge::edge_to_layer_mask`. Because the widen is a +/// UNION, a caller that already asked for all planes (`0xFF`, the +/// `ShaderDispatch` default) sees no change — you cannot consult more than every +/// plane. The specific planes each Pearl level unlocks are a calibration a later +/// probe may retune; the identity-at-base and superset-monotone properties are +/// invariant. +fn rung_widened_layer_mask(base: RungLevel, level: RungLevel, req_mask: u8) -> u8 { + if (level as u8) <= (base as u8) { + return req_mask; + } + // p64 predicate-plane bit positions (mirror p64_bridge::{CAUSES..BECOMES}): + // CAUSES=0 ENABLES=1 SUPPORTS=2 CONTRADICTS=3 REFINES=4 ABSTRACTS=5 + // GROUNDS=6 BECOMES=7. + const ENABLES: u8 = 1 << 1; + const CONTRADICTS: u8 = 1 << 3; + const REFINES: u8 = 1 << 4; + const ABSTRACTS: u8 = 1 << 5; + const BECOMES: u8 = 1 << 7; + // Pearl-level → predicate widen-set (superset-monotone): + // L1 observation → nothing beyond req_mask, + // L2 intervention → +ENABLES +REFINES, + // L3 counterfactual → also +CONTRADICTS +ABSTRACTS +BECOMES. + let widen = match level.pearl_level() { + 1 => 0, + 2 => ENABLES | REFINES, + _ => ENABLES | REFINES | CONTRADICTS | ABSTRACTS | BECOMES, + }; + req_mask | widen +} + // ═══════════════════════════════════════════════════════════════════════════ // Pillar-7 — α-front-to-back composite helper (stage [7] sink mode) // ═══════════════════════════════════════════════════════════════════════════ @@ -1779,3 +1843,59 @@ mod tests { assert_eq!(hit_big.confidence_to_alpha(), 1.0); } } + +#[cfg(test)] +mod rung_ascent_tests { + use super::rung_widened_layer_mask; + use lance_graph_contract::cognitive_shader::{RungElevator, RungLevel}; + use lance_graph_contract::collapse_gate::GateDecision; + + // A narrow dispatch mask so union-widening is observable. The production + // default is 0xFF (all 8 planes); at 0xFF elevation cannot widen further, + // so the probe uses a single-plane base — mimics a Surface observation. + const BASE_MASK: u8 = 0b0000_0001; // CAUSES only + + #[test] + fn d_tri_6_rung_ascends_relaxes_and_cascade_mask_tracks() { + let mut e = RungElevator::new(RungLevel::Surface); + + // (i) at base: mask is EXACTLY the dispatch mask — no regression. + assert_eq!(rung_widened_layer_mask(e.base, e.level, BASE_MASK), BASE_MASK); + + // sustained BLOCK ascends. threshold = 2, so 6 BLOCKs climb 3 rungs: + // Surface(0) → Shallow(1) → Contextual(2) → Analogical(3, Pearl L2). + for _ in 0..6 { + e.on_gate(GateDecision::BLOCK); + } + assert_eq!(e.level, RungLevel::Analogical); + assert_eq!(e.level.pearl_level(), 2); + + // (ii) elevated across a Pearl boundary → strict SUPERSET of the dispatch + // mask (widened, changed, never narrowed). + let widened = rung_widened_layer_mask(e.base, e.level, BASE_MASK); + assert_ne!(widened, BASE_MASK, "elevation must change the consulted mask"); + assert_eq!(widened & BASE_MASK, BASE_MASK, "must be a superset"); + assert!(widened.count_ones() > BASE_MASK.count_ones(), "must widen"); + + // sustained FLOW relaxes back to base (never below): Analogical(3) → base + // Surface is 3 de-elevations = 6 FLOWs. + for _ in 0..6 { + e.on_gate(GateDecision::FLOW_BUNDLE); + } + assert_eq!(e.level, e.base); + assert_eq!(e.level, RungLevel::Surface); + + // back at base → mask returns to identity. + assert_eq!(rung_widened_layer_mask(e.base, e.level, BASE_MASK), BASE_MASK); + } + + #[test] + fn all_planes_dispatch_mask_saturates_widening_to_noop() { + // At the ShaderDispatch default (0xFF), elevation can't widen further. + let mut e = RungElevator::new(RungLevel::Surface); + for _ in 0..8 { + e.on_gate(GateDecision::BLOCK); // climb well past Pearl L3 + } + assert_eq!(rung_widened_layer_mask(e.base, e.level, 0xFF), 0xFF); + } +} diff --git a/crates/lance-graph-contract/src/cognitive_shader.rs b/crates/lance-graph-contract/src/cognitive_shader.rs index de128925..48726720 100644 --- a/crates/lance-graph-contract/src/cognitive_shader.rs +++ b/crates/lance-graph-contract/src/cognitive_shader.rs @@ -189,6 +189,14 @@ impl RungLevel { } } + /// The wire ordinal for this rung (`0..=9`) — the inverse of [`from_u8`]. + /// The (now-deduped) thinking-engine call sites read a rung as `u8` for DTO + /// fields and damping factors through this. + #[inline] + pub const fn as_u8(self) -> u8 { + self as u8 + } + /// One rung up, saturating at [`Transcendent`](RungLevel::Transcendent). #[inline] pub const fn elevate(self) -> Self { @@ -775,6 +783,32 @@ mod tests { } } + #[test] + fn rung_discriminant_parity_with_deduped_thinking_engine_enum() { + // D-TRI-6 dedup guard: thinking-engine's RungLevel (deleted; now a + // re-export of THIS type) carried the identical #[repr(u8)] discriminants + // Surface=0 … Transcendent=9. A repr(u8) variant-identical enum is + // provably substitutable by a discriminant equality assertion — this IS + // the "measure" step (there is no jc metric API). + let expected = [ + (RungLevel::Surface, 0u8), + (RungLevel::Shallow, 1), + (RungLevel::Contextual, 2), + (RungLevel::Analogical, 3), + (RungLevel::Abstract, 4), + (RungLevel::Structural, 5), + (RungLevel::Counterfactual, 6), + (RungLevel::Meta, 7), + (RungLevel::Recursive, 8), + (RungLevel::Transcendent, 9), + ]; + for (rung, ord) in expected { + assert_eq!(rung as u8, ord, "variant discriminant drift"); + assert_eq!(rung.as_u8(), ord, "as_u8 must equal the discriminant"); + assert_eq!(RungLevel::from_u8(ord), rung, "from_u8 roundtrip"); + } + } + #[test] fn elevator_elevates_on_sustained_block_and_relaxes_on_sustained_flow() { use crate::collapse_gate::GateDecision; diff --git a/crates/lance-graph-contract/src/scenario.rs b/crates/lance-graph-contract/src/scenario.rs index 2fbdfe5d..60993b26 100644 --- a/crates/lance-graph-contract/src/scenario.rs +++ b/crates/lance-graph-contract/src/scenario.rs @@ -16,7 +16,7 @@ //! iron rule. Scenario identity is *meta about which content version*, //! not content itself. //! 2. **A new `lance-graph-scenario` crate.** Rejected: the four pieces -//! that a scenario needs already exist (Pearl Rung 3 intervention in +//! that a scenario needs already exist (Pearl Rung 2 intervention in //! `lance-graph-cognitive::world::counterfactual`; dataset versioning //! plus diff in `lance-graph::graph::versioned::VersionedGraph`; //! archetype meta-state in `lance-graph-archetype::world::World`; @@ -27,7 +27,7 @@ //! //! | Piece | Where | Role in scenario | //! |---|---|---| -//! | `Intervention` (Pearl Rung 3) | `lance-graph-cognitive::world::counterfactual` | The "what if X were x'?" math via `bind/unbind` | +//! | `Intervention` (Pearl Rung 2) | `lance-graph-cognitive::world::counterfactual` | The "what if X were x'?" math via `bind/unbind` | //! | `World { dataset_uri, tick }` | `lance-graph-archetype::world` | Named branch handle wrapping Lance dataset path + version | //! | `VersionedGraph` (tag, at_version, diff) | `lance-graph::graph::versioned` | The actual storage substrate — ACID branching, time travel, diff | //! | `WorldModelDto` (gestalt, qualia, ripple state) | `contract::world_model` | The situational snapshot a scenario is reasoning about | @@ -75,7 +75,7 @@ /// - A parent identifier (Lance version + tag) that locates the fork point. /// - An optional archetype prior bundled into the trajectory. /// - A captured RNG seed for deterministic replay. -/// - A list of interventions (Pearl Rung 3) applied in order. +/// - A list of interventions (Pearl Rung 2) applied in order. /// - A default inference mode (typically `CounterfactualSynthesis`). /// /// The branch itself does not own the storage. It is a descriptor that @@ -121,7 +121,7 @@ pub struct ScenarioBranch { /// inference type, slot `[9996..10000)` in role_keys). pub inference_mode: u8, - /// Pearl Rung 3 interventions applied to the parent state to + /// Pearl Rung 2 interventions applied to the parent state to /// define this branch's "what if?" hypothesis. Order matters /// (later interventions operate on already-modified state per /// `cognitive::world::counterfactual::multi_intervene`). diff --git a/crates/lance-graph-planner/src/cache/nars_engine.rs b/crates/lance-graph-planner/src/cache/nars_engine.rs index bc351892..d1e7cab5 100644 --- a/crates/lance-graph-planner/src/cache/nars_engine.rs +++ b/crates/lance-graph-planner/src/cache/nars_engine.rs @@ -500,6 +500,15 @@ impl NarsEngine { } /// Hot path: CausalEdge64 → SpoHead for local processing. + // v1-API SpoHead round-trip (I-LEGACY-API-FEATURE-GATED): under the + // planner's default `causal-edge-v2-layout`, `inference_type()` already + // routes through the canonical `InferenceType::from_mantissa(...)` mapping + // (correct value, not the aliased 3-bit read), and `temporal()` is + // structural under v2 — its value is inert here because `to_causal_edge` + // packs it through the v2 no-op write-back. No non-deprecated public + // accessor exists for either field, so the deprecation is silenced at this + // single round-trip site. + #[allow(deprecated)] pub fn from_causal_edge(&self, edge: CausalEdge64) -> SpoHead { SpoHead { s_idx: edge.s_idx(), @@ -976,6 +985,7 @@ mod tests { } #[test] + #[allow(deprecated)] fn test_to_causal_edge_maps_intervention_and_counterfactual() { let dist = SpoDistances::new_zero(); let engine = NarsEngine::new(dist); diff --git a/crates/thinking-engine/src/cognitive_stack.rs b/crates/thinking-engine/src/cognitive_stack.rs index 35e34b03..74ab753d 100644 --- a/crates/thinking-engine/src/cognitive_stack.rs +++ b/crates/thinking-engine/src/cognitive_stack.rs @@ -16,44 +16,6 @@ //! L1 Recognition — tokenize → codebook lookup //! ``` -/// 10 cognitive layers. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum LayerId { - L1, // Recognition - L2, // Resonance - L3, // Appraisal - L4, // Routing (autopoiesis) - L5, // Execution - L6, // Delegation - L7, // Contingency - L8, // Integration - L9, // Validation - L10, // Crystallization -} - -impl LayerId { - pub fn as_u8(&self) -> u8 { - match self { - Self::L1 => 1, - Self::L2 => 2, - Self::L3 => 3, - Self::L4 => 4, - Self::L5 => 5, - Self::L6 => 6, - Self::L7 => 7, - Self::L8 => 8, - Self::L9 => 9, - Self::L10 => 10, - } - } - pub fn is_single_agent(&self) -> bool { - self.as_u8() <= 5 - } - pub fn is_multi_agent(&self) -> bool { - self.as_u8() > 5 - } -} - /// The 12-family orchestration space — canonical type lives in the /// contract (M9 dedup, D-TSC-1). The engine keeps its calibrated /// per-family parameters locally (`EngineStyleExt`). @@ -249,64 +211,12 @@ impl GateState { } } -/// Semantic depth levels (0-9). Rung elevation happens on: -/// - Sustained BLOCK state -/// - Predictive failure (free energy spike) -/// - Structural mismatch (no parse) -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -#[repr(u8)] -pub enum RungLevel { - #[default] - Surface = 0, // literal, immediate - Shallow = 1, // simple inference - Contextual = 2, // situation-dependent - Analogical = 3, // metaphor, similarity - Abstract = 4, // generalized patterns - Structural = 5, // schema-level - Counterfactual = 6, // what-if reasoning - Meta = 7, // reasoning about reasoning - Recursive = 8, // self-referential loops - Transcendent = 9, // beyond normal bounds -} - -impl RungLevel { - pub fn as_u8(&self) -> u8 { - *self as u8 - } - - pub fn from_u8(n: u8) -> Self { - match n { - 0 => Self::Surface, - 1 => Self::Shallow, - 2 => Self::Contextual, - 3 => Self::Analogical, - 4 => Self::Abstract, - 5 => Self::Structural, - 6 => Self::Counterfactual, - 7 => Self::Meta, - 8 => Self::Recursive, - _ => Self::Transcendent, - } - } - - /// Band for bucket addressing: Low(0-2), Mid(3-5), High(6-9). - pub fn band(&self) -> &'static str { - match self.as_u8() { - 0..=2 => "low", - 3..=5 => "mid", - _ => "high", - } - } - - /// Should elevate rung based on cascade behavior? - pub fn should_elevate( - consecutive_blocks: usize, - free_energy: f32, - cascade_depth: usize, - ) -> bool { - consecutive_blocks >= 3 || free_energy > 0.15 || cascade_depth >= 4 - } -} +/// Semantic depth levels (0–9) — canonical zero-dep type in the contract +/// (D-TRI-6 dedup). Re-exported so `crate::cognitive_stack::RungLevel` keeps +/// resolving for every existing consumer; variants, `from_u8`, and `as_u8` are +/// identical. The stateless `should_elevate` heuristic is retired in favour of +/// the stateful `RungElevator` (contract) that the shader driver actually drives. +pub use lance_graph_contract::cognitive_shader::RungLevel; /// Meta-cognition: calibration of confidence estimates. /// Brier score tracks how well-calibrated predictions are. @@ -427,14 +337,6 @@ mod tests { assert_eq!(gate, GateState::Flow); // 0.10 + (-0.15) = -0.05 → Flow } - #[test] - fn rung_elevation() { - assert!(RungLevel::should_elevate(3, 0.0, 0)); // sustained block - assert!(RungLevel::should_elevate(0, 0.2, 0)); // high free energy - assert!(RungLevel::should_elevate(0, 0.0, 5)); // deep cascade - assert!(!RungLevel::should_elevate(1, 0.05, 2)); // none triggered - } - #[test] fn metacog_brier() { let mut mc = MetaCognition::new(); diff --git a/crates/thinking-engine/src/persona.rs b/crates/thinking-engine/src/persona.rs index 18fd54e5..ecb35eec 100644 --- a/crates/thinking-engine/src/persona.rs +++ b/crates/thinking-engine/src/persona.rs @@ -205,7 +205,6 @@ pub struct Agent { pub ghosts: GhostField, pub council: CouncilWeights, pub current_style: StyleFamily, - pub current_rung: RungLevel, pub thought_count: u64, } @@ -226,7 +225,6 @@ impl Agent { ghosts, council, current_style: style, - current_rung: RungLevel::Surface, thought_count: 0, } } @@ -237,7 +235,7 @@ impl Agent { id: self.id.clone(), mode: self.persona.mode, style: self.current_style, - rung: self.current_rung.as_u8(), + rung: RungLevel::Surface.as_u8(), warmth: self.persona.priors.warmth, depth: self.persona.priors.depth, presence: self.persona.priors.presence, @@ -455,7 +453,7 @@ impl Agent { agent_id: self.id.clone(), mode: self.persona.mode, style: self.current_style, - rung: self.current_rung, + rung: RungLevel::Surface, gate, calibration_error, should_admit_ignorance: calibration_error > 0.2 && dissonance > 0.3, diff --git a/crates/thinking-engine/src/world_model.rs b/crates/thinking-engine/src/world_model.rs index 7b8b2478..5073861d 100644 --- a/crates/thinking-engine/src/world_model.rs +++ b/crates/thinking-engine/src/world_model.rs @@ -192,7 +192,7 @@ impl WorldModelDto { Self { self_state: SelfState { style: agent.current_style, - rung: agent.current_rung.as_u8(), + rung: 0, gate: hdr.gate(), viscosity: if free_energy < 0.05 { Viscosity::Ice From c793556f94dd64ad5b8869bf485d81587e35574d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 12:11:43 +0000 Subject: [PATCH 2/3] P3 fix (codex #708): keep elevator base-reset atomic with on_gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex correctly flagged a concurrency window I introduced. The per-driver RungElevator is a &self singleton behind a RwLock, so concurrent dispatches share it. My change read the elevator's level at the top of run() (for the cascade mask) and advanced it via on_gate at the bottom, in TWO separate critical sections — dropping the base-reset from the bottom block on the assumption elevator.base == req.rung still held. Under concurrency a sibling dispatch with a different req.rung can reset base in the window between, so on_gate then counts this cycle's gate against the wrong base and the next cascade widens with the wrong mask. Fix: restore the reset-if-mismatch inside the on_gate critical section (as the original code had it), keeping reset+advance atomic. Parity with the pre-PR atomicity; the deeper per-request elevator is the triangle-arc mailbox-SoA design, not this PR. Driver rebuilt: 103 tests pass, both D-TRI-6 probes green. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Awg6TXocHcwTtc6eGsHcdD --- crates/cognitive-shader-driver/src/driver.rs | 31 +++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/cognitive-shader-driver/src/driver.rs b/crates/cognitive-shader-driver/src/driver.rs index 18e5cb51..619954ca 100644 --- a/crates/cognitive-shader-driver/src/driver.rs +++ b/crates/cognitive-shader-driver/src/driver.rs @@ -601,15 +601,23 @@ impl ShaderDriver { // Advance the persistent per-driver `RungElevator` with THIS cycle's // already-decided `gate` (sustained-BLOCK elevates over the dispatched - // base; sustained-FLOW relaxes back to it). The base-reset already ran - // at the top of `run()` where the level was read for the cascade, so - // `elevator.base == req.rung` holds here. The resulting level is what the - // NEXT dispatch's cascade consults — closing the ascent loop across cycles. + // base; sustained-FLOW relaxes back to it). The base-reset is REPEATED + // here, inside the SAME critical section as `on_gate` — not only at the + // top-of-`run()` read. The elevator is a `&self` per-driver singleton + // behind a `RwLock`, so a concurrent dispatch with a different `req.rung` + // may have reset `base` in the window since the top read; re-validating + // here keeps reset+advance atomic, so this cycle's gate is never counted + // against another request's base (codex #708 r3603015026). The resulting + // level is what the NEXT dispatch on this base consults — closing the + // ascent loop across cycles. let elevated_rung: u8 = { let mut elevator = self .rung_elevator .write() .expect("rung_elevator RwLock poisoned"); + if elevator.base != req.rung { + *elevator = RungElevator::new(req.rung); + } elevator.on_gate(gate) as u8 }; @@ -1860,7 +1868,10 @@ mod rung_ascent_tests { let mut e = RungElevator::new(RungLevel::Surface); // (i) at base: mask is EXACTLY the dispatch mask — no regression. - assert_eq!(rung_widened_layer_mask(e.base, e.level, BASE_MASK), BASE_MASK); + assert_eq!( + rung_widened_layer_mask(e.base, e.level, BASE_MASK), + BASE_MASK + ); // sustained BLOCK ascends. threshold = 2, so 6 BLOCKs climb 3 rungs: // Surface(0) → Shallow(1) → Contextual(2) → Analogical(3, Pearl L2). @@ -1873,7 +1884,10 @@ mod rung_ascent_tests { // (ii) elevated across a Pearl boundary → strict SUPERSET of the dispatch // mask (widened, changed, never narrowed). let widened = rung_widened_layer_mask(e.base, e.level, BASE_MASK); - assert_ne!(widened, BASE_MASK, "elevation must change the consulted mask"); + assert_ne!( + widened, BASE_MASK, + "elevation must change the consulted mask" + ); assert_eq!(widened & BASE_MASK, BASE_MASK, "must be a superset"); assert!(widened.count_ones() > BASE_MASK.count_ones(), "must widen"); @@ -1886,7 +1900,10 @@ mod rung_ascent_tests { assert_eq!(e.level, RungLevel::Surface); // back at base → mask returns to identity. - assert_eq!(rung_widened_layer_mask(e.base, e.level, BASE_MASK), BASE_MASK); + assert_eq!( + rung_widened_layer_mask(e.base, e.level, BASE_MASK), + BASE_MASK + ); } #[test] From 17368ead5eeea4d5c713203b01e38bc7324f74d8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 12:22:04 +0000 Subject: [PATCH 3/3] P3 review (codex + coderabbit #708): advance elevator before sinks; v2 temporal sentinel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two valid review findings fixed: 1. (CodeRabbit, driver.rs) The sink early-returns (on_resonance/on_bus returning false, lines 556/579) bypassed the on_gate block at the bottom of run(), so a sink that stops processing would freeze the rung ascent ladder — now that the elevator drives the cascade this is observable. Moved the on_gate transition to immediately after gate is decided, BEFORE the sink callbacks, so every dispatch that reaches a gate advances the elevator. Verified no early return exists between gate-decision (l.454) and the sinks. The base-reset stays inside the same critical section (the codex atomicity fix, c793556). 2. (CodeRabbit, nars_engine.rs) from_causal_edge copied edge.temporal() into SpoHead.temporal. causal-edge defaults to causal-edge-v2-layout, where bits 52-63 are reclaimed (W-slot/lens/spare) and temporal() returns GARBAGE (edge.rs:551 doc). Per I-LEGACY-API-FEATURE-GATED, temporal is now the documented 0 sentinel (temporal is structural: SpoWitnessChain / AriGraph). inference_type() keeps #[allow(deprecated)] — it returns the CORRECT value under v2 (routes through from_mantissa), deprecated only as an API nudge. Added field-isolation test from_causal_edge_temporal_is_v2_sentinel_not_reclaimed_bits (raw reclaim-zone bits -> temporal() garbage -> SpoHead.temporal == 0). Verified: driver 103 tests + both D-TRI-6 probes green; planner 215 + the new isolation test green; planner clippy --all-targets exit 0. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01Awg6TXocHcwTtc6eGsHcdD --- crates/cognitive-shader-driver/src/driver.rs | 48 ++++++++++--------- .../src/cache/nars_engine.rs | 47 ++++++++++++++---- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/crates/cognitive-shader-driver/src/driver.rs b/crates/cognitive-shader-driver/src/driver.rs index 619954ca..1104d858 100644 --- a/crates/cognitive-shader-driver/src/driver.rs +++ b/crates/cognitive-shader-driver/src/driver.rs @@ -551,6 +551,32 @@ impl ShaderDriver { None }; + // Advance the persistent per-driver `RungElevator` with THIS cycle's + // already-decided `gate`, BEFORE the sink callbacks. The sink early + // returns (`on_resonance` / `on_bus` returning false) must NOT bypass + // the rung transition — a sink that stops processing would otherwise + // freeze the ascent ladder, now that the elevator drives the cascade + // (CodeRabbit #708). Sustained BLOCK elevates over the dispatched base; + // sustained FLOW relaxes back to it. The base-reset is REPEATED here, + // inside the SAME critical section as `on_gate` (not only at the + // top-of-`run()` read): the elevator is a `&self` per-driver `RwLock` + // singleton, so a concurrent dispatch with a different `req.rung` may + // have reset `base` in the window since the top read; re-validating here + // keeps reset+advance atomic, so this cycle's gate is never counted + // against another request's base (codex r3603015026). The resulting + // level is what the NEXT dispatch on this base consults — closing the + // ascent loop across cycles. + let elevated_rung: u8 = { + let mut elevator = self + .rung_elevator + .write() + .expect("rung_elevator RwLock poisoned"); + if elevator.base != req.rung { + *elevator = RungElevator::new(req.rung); + } + elevator.on_gate(gate) as u8 + }; + // [7] Sink callbacks. if !sink.on_resonance(&resonance_dto) { return ShaderCrystal { @@ -599,28 +625,6 @@ impl ShaderDriver { _ => None, }; - // Advance the persistent per-driver `RungElevator` with THIS cycle's - // already-decided `gate` (sustained-BLOCK elevates over the dispatched - // base; sustained-FLOW relaxes back to it). The base-reset is REPEATED - // here, inside the SAME critical section as `on_gate` — not only at the - // top-of-`run()` read. The elevator is a `&self` per-driver singleton - // behind a `RwLock`, so a concurrent dispatch with a different `req.rung` - // may have reset `base` in the window since the top read; re-validating - // here keeps reset+advance atomic, so this cycle's gate is never counted - // against another request's base (codex #708 r3603015026). The resulting - // level is what the NEXT dispatch on this base consults — closing the - // ascent loop across cycles. - let elevated_rung: u8 = { - let mut elevator = self - .rung_elevator - .write() - .expect("rung_elevator RwLock poisoned"); - if elevator.base != req.rung { - *elevator = RungElevator::new(req.rung); - } - elevator.on_gate(gate) as u8 - }; - // Materialized-awareness provenance — runs the F→34→F loop + HHTL fork as // a SIDE analysis over this cycle's observables. Provenance-only: it has // NOT influenced `gate` above and does not influence persistence; it only diff --git a/crates/lance-graph-planner/src/cache/nars_engine.rs b/crates/lance-graph-planner/src/cache/nars_engine.rs index d1e7cab5..ab7ea310 100644 --- a/crates/lance-graph-planner/src/cache/nars_engine.rs +++ b/crates/lance-graph-planner/src/cache/nars_engine.rs @@ -500,14 +500,20 @@ impl NarsEngine { } /// Hot path: CausalEdge64 → SpoHead for local processing. - // v1-API SpoHead round-trip (I-LEGACY-API-FEATURE-GATED): under the - // planner's default `causal-edge-v2-layout`, `inference_type()` already - // routes through the canonical `InferenceType::from_mantissa(...)` mapping - // (correct value, not the aliased 3-bit read), and `temporal()` is - // structural under v2 — its value is inert here because `to_causal_edge` - // packs it through the v2 no-op write-back. No non-deprecated public - // accessor exists for either field, so the deprecation is silenced at this - // single round-trip site. + // + // I-LEGACY-API-FEATURE-GATED — two deprecated accessors, handled differently: + // * `inference_type()` is deprecated only as an API nudge but returns the + // CORRECT value under the default `causal-edge-v2-layout` (it routes + // internally through `InferenceType::from_mantissa(...)`, not the aliased + // 3-bit read) — so it is silenced with `#[allow(deprecated)]`. + // * `temporal()` is NOT round-tripped. `causal-edge` defaults to + // `causal-edge-v2-layout`, where bits 52-63 are RECLAIMED (W-slot / lens + // / spare) and `temporal()` returns GARBAGE for v2 edges (edge.rs:551). + // Copying it into `SpoHead.temporal` would carry an unspecified value. + // Temporal causality is structural now (SpoWitnessChain chain-position / + // AriGraph `Triplet.timestamp`, edge.rs L-2), so the field is the + // documented `0` sentinel here — proven by + // `from_causal_edge_temporal_is_v2_sentinel_not_reclaimed_bits`. #[allow(deprecated)] pub fn from_causal_edge(&self, edge: CausalEdge64) -> SpoHead { SpoHead { @@ -518,7 +524,8 @@ impl NarsEngine { conf: edge.confidence_u8(), pearl: edge.causal_mask() as u8, inference: edge.inference_type() as u8, - temporal: edge.temporal() as u8, + // v2 sentinel — do NOT read the reclaimed bits 52-63 (see fn doc). + temporal: 0, } } @@ -984,6 +991,28 @@ mod tests { } } + #[test] + #[allow(deprecated)] + fn from_causal_edge_temporal_is_v2_sentinel_not_reclaimed_bits() { + // Bits 52-63 are the v2 reclaim zone (W-slot/lens/spare). An edge with + // non-zero bits there makes the deprecated `temporal()` read GARBAGE + // (edge.rs:551). `from_causal_edge` must isolate `SpoHead.temporal` from + // those bits — it is the documented 0 sentinel (temporal is structural). + // Raw-construct so the reclaim-zone bits are set regardless of feature. + let engine = NarsEngine::new(SpoDistances::new_zero()); + let dirty = CausalEdge64(0xABC_u64 << 52); + assert_ne!( + dirty.temporal(), + 0, + "precondition: reclaim-zone bits make temporal() non-zero garbage" + ); + let head = engine.from_causal_edge(dirty); + assert_eq!( + head.temporal, 0, + "SpoHead.temporal must be the v2 sentinel, never the reclaimed bits" + ); + } + #[test] #[allow(deprecated)] fn test_to_causal_edge_maps_intervention_and_counterfactual() {