From fcbccaf368935aa76ef1ebb172e1e7abdda77b54 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 21 Jul 2026 13:35:06 +0000 Subject: [PATCH] =?UTF-8?q?feat(contract):=20recipe=5Floci=20=E2=80=94=20D?= =?UTF-8?q?oor=20C,=20the=2024-loci=20organ=20gate=20on=20recipe=20dispatc?= =?UTF-8?q?h=20(Maslow=20carry=20+=20prune,=20organ-derived=20rung)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #780's Axis-B gap on the DISPATCH path. #780 wired the organs into the ctx (SubstrateView) but the dispatch gate stayed on the 8-field scalar proxy. recipe_loci is the missing half: a dispatch gate keyed to the live CausalWitnessFacet 24 loci. - required_loci(id): each recipe's consumed Locus dimensions, grounded in the recipe's own recipes.rs::substrate string. - loci_disqualifier / reachable: per-specific-dimension gate (never a popcount) — a recipe fires only when every required dimension is BOUND. - locus_rung / loci_rung / loci_dispatch_order: the ORDER is organ-derived too — a recipe is as deep as its deepest required dimension (ICR #31 → Counterfactual apex because Kausal/Contradiction ARE apex dimensions), replacing the static Tier+inference table (kept as a documented cross-check). - carried_awareness: lower-rung groundings read UP the Maslow climb, monotone, never re-derived (the anti-exponential-rediscovery property; #777). - active_after_prune: higher thinking subsumes lower-related (strict dimension superset) and drops it from active dispatch. Complements the two shipped reach-doors (A style-fan / B select_tactic) which gate on style/mechanism/scalar and never read the organ. Additive, zero-dep, nothing existing touched. 11 module tests + examples/recipe_loci_walk.rs with 4 measured gates (selector 7/34, organ 34/34 grounded, carry monotone, prune fires + apex survives). Board: E-RECIPE-LOCI-ORGAN-GATE-1, LATEST_STATE inventory, STATUS_BOARD D-REC-LOCI-1. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Awg6TXocHcwTtc6eGsHcdD --- .claude/board/EPIPHANIES.md | 15 + .claude/board/LATEST_STATE.md | 5 + .claude/board/STATUS_BOARD.md | 1 + .../examples/recipe_loci_walk.rs | 204 ++++++ crates/lance-graph-contract/src/lib.rs | 1 + .../lance-graph-contract/src/recipe_loci.rs | 583 ++++++++++++++++++ 6 files changed, 809 insertions(+) create mode 100644 crates/lance-graph-contract/examples/recipe_loci_walk.rs create mode 100644 crates/lance-graph-contract/src/recipe_loci.rs diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index b83202a02..2cc2f5ce3 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -55,6 +55,21 @@ **Pruned dead-ends the council killed (recorded so they don't resurface):** reversibility/cynefin/margin as new code (→ MUL, Tier 0); any new rung-3 recipe #35 (34-lock); storing any magnitude (regret price, oracle eval, lindy age, proof hash) in a reserved A9 locus (pointers-not-magnitudes + RESERVE-DON'T-RECLAIM); qualia gating/scaling logic (additive-stakes-only); contract-net auction (all kernels read the same ctx — no private info); a TeacherService/verify-endpoint/ProofChain struct (AGI-as-SoA + no new serialization); retuning `select_tactic` (E-LADDER-UNSHADOWS-SELECTOR-1 closed the gap by mode composition); widening the i4 nibble; VSA-bundling steps with natural ids (I-VSA-IDENTITIES Test 0). **Cross-ref:** `E-META-UNCERTAINTY-IS-MUL-1` (Tier 0), `E-LADDER-UNSHADOWS-SELECTOR-1` (the two modes the router chooses between), `E-RECIPE-SUBSTRATE-WIRING-1` (the substrate Tier 3 upgrades), `E-SELF-DIRECTED-GRAPH-1` + peer chess thrust (Tier 4), PCRLLM 2511.08392 + Causal-ML 2206.15475 + MME 2505.21327 (the anchors). +## 2026-07-21 — E-RECIPE-LOCI-ORGAN-GATE-1 — Door C: the recipe dispatch gate keyed to the REAL 24-loci causal-witness organ, closing #780 Axis B on the dispatch path; the Maslow rung climb CARRIES lower-rung awareness up (anti-rediscovery) and higher thinking PRUNES lower-related + +**Status:** FINDING (this session; `recipe_loci` module + `recipe_loci_walk` probe, additive/zero-dep, nothing existing touched). **Confidence:** High for the mechanism (shipped + 10 unit tests + 4 measured probe gates green); the A1-basin-6 composition is documented-not-wired (honest next step, not claimed). + +**What #780 left open (the precise gap).** #780's Axis-B audit measured that **34/34 recipe kernels read ONLY the 8-field scalar proxy — 0 read a real organ**. #780 wired the organs INTO the ctx (`SubstrateView`) but the DISPATCH gate (`recipe_dispatch::ladder` NaN-check, and the two shipped reach-doors) stayed scalar/style. This entry supplies the missing half: a dispatch gate keyed to the live `CausalWitnessFacet` 24 loci. + +**Door C — the organ gate (complements, does not replace, the two shipped doors).** `docs/NARS_RECIPES_DISPATCH.md` §7 already reaches 34/34 by **Door A** (style→mechanism fan, the wide door) + **Door B** (surprise selector `select_tactic`, the narrow door — MEASURED here at **7/34** distinct over a 270-ctx sweep, tightening the doc's "8"). Both gate on style/mechanism/scalar-surprise; NEITHER reads the organ. **Door C** (`recipe_loci::reachable`) reaches a recipe by GROUNDING its 24-dimension loci — the organ decides, not the style label. It does NOT raise the count above Door A's 34 (measured: fully-bound witness → 34); its contribution is making reachability CONTINGENT ON THE REAL ORGAN. Each recipe's `required_loci(id)` is a documented classification grounded in the recipe's own `recipes.rs::substrate` string (RCR/TCA/IDR/ICR read `Kausal`; MCT/HKF read S/P/O; SMAD/SPP read `Quorum`+`Contradiction`; …). + +**Carry (anti-exponential rediscovery, #777 "orientation for free").** The 24-loci register is READ, not consumed, across the whole rung walk, so a dimension grounded for a shallow recipe stays grounded for every deeper one (`carried_awareness`, MEASURED monotone non-decreasing 4→14 loci up the ascent). The cause is placed once at its rung and read by all higher rungs — observational causal discovery pays super-exponentially to RE-search it each step; here the apex reads the carried `Kausal`/`BasinAnchor`/`Antecedent` locus. The rung level occupies ZERO stored bytes (#778) — carried by WHICH loci are bound, named in the shipped `RungLevel` Maslow-pyramid vocabulary (`rung_level`), climbed by the shipped `RungElevator`. **The rung ORDER is itself organ-derived** (`loci_rung(id)` = `max(locus_rung(l))` over the required dimensions — a recipe is as deep as its deepest organ), so the WHOLE walk (order AND gate) is 24-based, not a static Tier+inference table: ICR #31 lands at the Counterfactual apex (rung 6) BECAUSE `Kausal`/`Contradiction` are apex dimensions, not because a table said 9 (the static `recipe_dispatch::rung` is kept only as a documented cross-check — the `loci_rung_is_organ_derived` test asserts they differ). This answers "still brute-force popcount, or 24-based?": the gate is per-specific-dimension (never a count), and now the order is too; the legacy scalar `nan_disqualifier` popcount path remains beside it, unchanged (#780). + +**Prune (higher thinking subsumes lower-related).** The dual of carry: a reachable recipe is dropped from active dispatch when a strictly-higher-rung reachable recipe requires a strict superset of its dimensions (`active_after_prune`, MEASURED 34 reachable → 18 active at the apex; RTE #1 `[MeaningLevel]` pruned by SSR #21 `[Contradiction,MeaningLevel]`; apex ICR #31 never pruned). Carry + prune = the Maslow economy: lower groundings ride UP, subsumed lower work drops OUT. + +**The shape it clarifies (witness vs awareness; CE64 vs V3).** The **witness** (`CausalWitnessFacet`) is POINTER-ONLY — 24×i4 = WHERE each dimension's filler sits (0=NaN). **Actual awareness** = witness ⊕ rung (elected ClassView, zero bytes) ⊕ the last-6 `part_of:is_a` NaN-skipped (the A1 `SpoFacet` L1 `6×(8:8)` taxonomy ancestry the witness's `BasinAnchor` only POINTS at) ⊕ truth+direction (A3+A6) ⊕ higher-prunes-lower. This IS the V3 vs **CausalEdge64** move: CE64 crams *(what × how-confidently × where-in-time)* into 64 fixed bits (the `I-LEGACY-API-FEATURE-GATED` pain); V3 DECOMPOSES that into full-width, content-blind facet readings over the 12-byte register (`6×2` = the `6×(8:8)` rails; `24×i4` = the loci) — the witness carries WHERE, A3/A6 carry WHAT/HOW, the ClassView elects the reading. Door C gates on the witness (the minimal organ); wiring the A1-basin-6 chain into the gate is the documented next composition (a sibling reading, no new storage). + +**Cross-ref:** `E-LADDER-UNSHADOWS-SELECTOR-1` (the two shipped modes Door C sits beside), `E-RECIPE-SELECTOR-REACHABILITY-1` (the 8/34 — measured 7 here), `E-RECIPE-SUBSTRATE-WIRING-1` (#780, the ctx-side organ wiring this completes on the dispatch side), `E-FOVEATED-AWARENESS-1` (the Maslow-pyramid RungLevel vocabulary + `docs/NARS_RECIPES_DISPATCH.md` §7), `E-H268-REPLAYABLE-TILE-1` (one register, N readings), `causal_witness.rs` / `awareness_facet.rs` / `cognitive_shader::{RungLevel,RungElevator}`. Files: `recipe_loci.rs` (module + 10 tests), `examples/recipe_loci_walk.rs` (4 measured gates). ## 2026-07-21 — E-LADDER-UNSHADOWS-SELECTOR-1 — the measured 8/34 selector-reachability gap (`E-RECIPE-SELECTOR-REACHABILITY-1`, `66f5a73`) closes by MODE COMPOSITION, not selector retuning: `select_tactic` = the foveated SACCADE (one tactic per surprise cycle), `recipe_dispatch::ladder` = the systematic SWEEP (all 34 in rung order, each gated only by its own NaN checklist) — two dispatch modes, one catalogue diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index b257088bd..7ed2b3cdc 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,8 @@ +## 2026-07-21 — branch `claude/review-claude-board-files-nhqgx1` — `recipe_loci` (Door C): the recipe dispatch gate keyed to the real 24-loci causal-witness organ + Maslow carry/prune (additive, zero-dep, nothing existing touched) + +### Current Contract Inventory — new entry +- `lance_graph_contract::recipe_loci::{required_loci, loci_disqualifier, is_grounded, locus_rung, loci_rung, loci_dispatch_order, LociStep, loci_ladder, reachable, rung_level, carried_awareness, active_after_prune}` — **Door C, the 24-dimension organ gate** on recipe dispatch (closes #780 Axis B on the dispatch path). Each recipe declares which of the 16 named `causal_witness::Locus` dimensions it consumes (`required_loci`, grounded in `recipes.rs::substrate`); the rung-level walk (`loci_ladder`, in the **organ-derived** `loci_dispatch_order`) fires a recipe only when every required locus is BOUND (`loci_disqualifier` — per-specific-dimension, the 24-dim analog of `nan_disqualifier`, NOT a popcount). **Order is 24-based too:** `loci_rung(id) = max(locus_rung(l))` over the required dimensions — a recipe is as deep as its deepest organ (ICR #31 → Counterfactual apex because `Kausal`/`Contradiction` are apex dimensions), replacing the static `recipe_dispatch::rung` Tier table (kept as a documented cross-check). `rung_level` names it in the shipped `RungLevel` Maslow-pyramid vocabulary. `carried_awareness` = lower-rung groundings read UP the climb (monotone, anti-rediscovery, #777); `active_after_prune` = higher thinking subsumes lower-related (strict dimension-superset). Complements the two shipped reach-doors (A style-fan / B `select_tactic`) — neither read the organ. 11 tests + `examples/recipe_loci_walk.rs` (4 measured gates: selector 7/34, organ 34/34 when grounded, carry monotone, prune fires + apex survives). Zero deps. + ## 2026-07-21 — branch `claude/x265-x266-plans-review-h9osnl` — recipes wired to real tenants: `causal_witness` (A9 24 loci) + `recipe_substrate` + `recipe_dispatch` (additive, zero-dep, nothing existing touched) ### Current Contract Inventory — new entry diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 9491c3ceb..5e86d57b9 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -6,6 +6,7 @@ Plan: `.claude/plans/causal-rung-standing-wave-v1.md`. Consumes M20 A1 (shipped) |---|---|---|---|---| | D-DISORDER-GATE-1 | Tier-1 mode router (E-DISORDER-GATE-1): defect-backed NaN groundedness pre-gate + Cynefin mechanical-core classify→elect (saccade/sweep/field-gather/stabilize) + MUL MountStupid veto | lance-graph | **In PR** (branch) — `dispatch_mode` module + `disorder_gate_probe`, 982 tests green, clippy clean, 5 probe gates green | `dispatch_mode.rs`; probe `disorder_gate_probe`; E-DISORDER-GATE-1 | | D-REC-WIRE-1 | Recipe claim-audit (34 kernels measure on a scalar proxy, not the real organ) → wire the 3 real tenants (A9 CausalWitnessFacet 24 loci + SPO + qualia) into a rung-ordered, NaN-gated causal ladder keyed by NARS inference type | lance-graph | **Shipped** — PR #780 MERGED `8a00988` (operator-gated) — 3 contract modules + 3 probes, 970 tests green, clippy clean, all example gates green | `causal_witness.rs`/`recipe_substrate.rs`/`recipe_dispatch.rs`; probes `recipe_claim_audit`/`loci_recipe_relevance`/`recipe_ladder_over_substrate`; E-RECIPE-SUBSTRATE-WIRING-1 / E-LADDER-UNSHADOWS-SELECTOR-1 | +| D-REC-LOCI-1 | Door C — recipe dispatch gated on the real 24-loci causal-witness organ (closes #780 Axis B on the dispatch path); rung ORDER organ-derived (`loci_rung` = deepest required dimension); Maslow climb CARRIES lower-rung awareness up (anti-rediscovery) + higher thinking PRUNES lower-related | lance-graph | **In PR** (branch, this session) — `recipe_loci` module (11 tests) + `recipe_loci_walk` probe (4 measured gates: selector 7/34, organ 34/34 grounded, carry monotone, prune fires + apex survives), clippy clean | `recipe_loci.rs`; `examples/recipe_loci_walk.rs`; E-RECIPE-LOCI-ORGAN-GATE-1 | | D-CSW-0 | Plan doc + O1 decision (canonical ladder masks + per-class facet election) | lance-graph | In PR #777; **O1 DECIDED (operator 2026-07-21: canonical masks)** | plan §2 | | D-CSW-1 | Standing-wave probe: per-rung persistence over an ordered stream separates causal from coincidental vs single-cycle + p64 3×u8 baseline; escalation cascade prunes at zero separation cost | lance-graph | **v5 SPLIT VERDICT** — CORE standing-wave claim GREEN (auc_wave .997 vs single .878, vs p64-**wave** .500 [M3], vs **reverse .000** [M2 orientation control, +.997]; cascade .997); the SEPARATE §0.5 escalation-ECONOMICS gate 3a **KILLS** (pruned .333 < .40 registered — reported not retuned; the M1 witness correction dropped it from v4's .458) → DEFERRED. v1 fixture-ceiling + v2 mean−std KILLs recorded on the way; leg 2 (real temporal.rs/Lance versions, wild corpora) OPEN. _(v3 historical: .972/.875/.375/.458 — pre M1/M2/M3 fixes.)_ | probe `deepnsm/examples/probe_dcsw1_standing_wave.rs` (v5); E-DCSW1-V5-SPLIT-VERDICT / E-CAUSAL-TISSUE-ALREADY-SHIPS-1 / E-DCSW1-LEG1-GREEN; plan §6.5 | | D-CSW-2 | Basin→causal-edge candidate probe: co-occupancy + rung survival vs basin-only / rung-only ablations | lance-graph | Queued (gated on D-CSW-1) | plan §5a/§6 | diff --git a/crates/lance-graph-contract/examples/recipe_loci_walk.rs b/crates/lance-graph-contract/examples/recipe_loci_walk.rs new file mode 100644 index 000000000..3cc517987 --- /dev/null +++ b/crates/lance-graph-contract/examples/recipe_loci_walk.rs @@ -0,0 +1,204 @@ +//! PROBE recipe_loci_walk — Door C (the 24-dimension organ gate) over a +//! **Maslow rung ascent**, measured against the shipped Door B (the surprise +//! selector). +//! +//! Answers, on runnable code: *does gating the 34 recipes on the real +//! [`CausalWitnessFacet`](lance_graph_contract::causal_witness) 24-loci organ +//! (a) reach recipes the selector cannot, (b) carry lower-rung awareness UP so a +//! deeper recipe re-derives nothing, and (c) let higher thinking prune lower +//! related work* — the #780 Axis-B close on the dispatch path. +//! +//! ## What it measures (nothing planted) +//! +//! 1. **Door B reach (MEASURED):** sweep the shipped +//! [`materialize::select_tactic`](lance_graph_contract::materialize::select_tactic) +//! across a grid of `ThoughtCtx` (free_energy × dissonance × sd-gate × rung) +//! and collect the distinct winners — the selector's real reachable set. +//! 2. **Door C reach (MEASURED):** a **rung ascent** — a sequence of witnesses +//! that bind progressively more loci as the Maslow climb deepens (Surface +//! grounds the shallow loci; the counterfactual apex grounds Kausal + +//! Contradiction + S/P/O). At each step: how many recipes the organ reaches, +//! the carried-awareness set, and the active set after higher-prunes-lower. +//! +//! ## Registered gates (fixed before first run) +//! +//! 1. Door B reaches a STRICT SUBSET of 34 (the selector shadows the rest — +//! `E-RECIPE-SELECTOR-REACHABILITY-1`). +//! 2. Door C at full grounding reaches ALL 34 (the organ, not the style label). +//! 3. Carry is monotone: the carried-awareness set never shrinks as the climb +//! deepens. +//! 4. Prune fires: under full grounding the active set is a strict subset of the +//! reachable set (higher thinking subsumes lower related), and the apex ICR +//! #31 is never pruned. +//! +//! KILL: any gate fails (recorded loudly). +//! +//! ## Run +//! ```bash +//! cargo run -p lance-graph-contract --example recipe_loci_walk +//! ``` + +use lance_graph_contract::causal_witness::{CausalWitnessFacet, Locus}; +use lance_graph_contract::materialize::select_tactic; +use lance_graph_contract::recipe_kernels::ThoughtCtx; +use lance_graph_contract::recipe_loci::{ + active_after_prune, carried_awareness, reachable, required_loci, rung_level, +}; +use std::collections::HashSet; + +/// A witness that binds every locus at or below a given Maslow depth. The ascent +/// grounds loci in cognitive order: shallow observation/meaning first, then +/// consensus, then the counterfactual causal apex — so a deeper step is a strict +/// superset of a shallower one (the #778 "more loci bound, shape unchanged"). +fn witness_for_depth(depth: usize) -> CausalWitnessFacet { + // loci grouped by the pyramid level at which they first become available. + let tiers: [&[Locus]; 5] = [ + &[ + Locus::Temporal, + Locus::SMeaning, + Locus::PMeaning, + Locus::OMeaning, + ], // observe/ground + &[ + Locus::BasinAnchor, + Locus::Supports, + Locus::Antecedent, + Locus::Lokal, + ], // relate/basin + &[ + Locus::MeaningLevel, + Locus::RunbookEvidence, + Locus::QualiaReference, + Locus::Modal, + ], // meta/texture + &[Locus::Quorum, Locus::SupportedBy], // consensus + &[Locus::Kausal, Locus::Contradiction], // causal / counterfactual apex + ]; + let mut w = CausalWitnessFacet::ZERO; + for tier in tiers.iter().take(depth + 1) { + for &l in *tier { + w = w.with(l, -1); // one event back (bound) + } + } + w +} + +fn main() { + // ── Door B (MEASURED): sweep the shipped selector ─────────────────────── + let mut selector_reach: HashSet = HashSet::new(); + for fe in [0.0f32, 0.2, 0.4, 0.6, 0.8, 1.0] { + for diss in [0.0f32, 0.5, 0.9] { + for sd in [-1.0f32, 0.0, 1.0] { + for rung in [0u8, 3, 5, 7, 9] { + let ctx = ThoughtCtx { + sd, + free_energy: fe, + dissonance: diss, + temperature: 0.5, + confidence: 0.5, + rung, + candidates: vec![0.5], + beliefs: vec![(7, 0.9, 0.8)], + }; + selector_reach.insert(select_tactic(&ctx)); + } + } + } + } + println!( + "Door B (surprise selector, MEASURED over {} ctx): reaches {}/34 distinct recipes {:?}", + 6 * 3 * 3 * 5, + selector_reach.len(), + { + let mut v: Vec = selector_reach.iter().copied().collect(); + v.sort_unstable(); + v + } + ); + + // ── Door C (MEASURED): the Maslow rung ascent over the organ ──────────── + println!("\nDoor C (24-loci organ gate) — the Maslow rung ascent:"); + println!( + "{:<7} {:>6} {:>9} {:>7} {:>7} newly-carried loci", + "depth", "bound", "reachable", "carried", "active" + ); + let mut prev_carried: HashSet = HashSet::new(); + let mut carry_monotone = true; + let mut full_reach = 0usize; + let mut full_active = 0usize; + let mut apex_active = false; + for depth in 0..5 { + let w = witness_for_depth(depth); + let bound = Locus::ALL.iter().filter(|&&l| w.is_bound(l)).count(); + let reach = reachable(&w); + // carried awareness at the deepest rung present (rung 9 = whole pyramid). + let carried: Vec = carried_awareness(&w, 9); + let carried_set: HashSet = carried.iter().copied().collect(); + let active = active_after_prune(&w); + // monotonicity of carry across the ascent + if !prev_carried.is_subset(&carried_set) { + carry_monotone = false; + } + let newly: Vec<&str> = carried + .iter() + .filter(|l| !prev_carried.contains(l)) + .map(|l| l.label()) + .collect(); + println!( + "{:<7} {:>6} {:>9} {:>7} {:>7} {}", + depth, + bound, + reach.len(), + carried.len(), + active.len(), + newly.join(",") + ); + prev_carried = carried_set; + if depth == 4 { + full_reach = reach.len(); + full_active = active.len(); + apex_active = active.contains(&31); + } + } + + // ── Maslow rung naming of a few recipes ───────────────────────────────── + println!("\nMaslow rung of sample recipes (shipped RungLevel vocabulary):"); + for id in [5u8, 25, 4, 31] { + println!( + " recipe #{id:<2} → rung_level {:?} (reads {:?})", + rung_level(id), + required_loci(id) + ); + } + + // ── Registered gates ──────────────────────────────────────────────────── + println!("\n== gates =="); + let g1 = selector_reach.len() < 34; + let g2 = full_reach == 34; + let g3 = carry_monotone; + let g4 = full_active < 34 && apex_active; + println!("gate 1 selector shadows some (reach < 34): {g1}"); + println!("gate 2 organ reaches all 34 when grounded: {g2}"); + println!("gate 3 carry is monotone up the pyramid: {g3}"); + println!("gate 4 prune fires + apex ICR#31 survives: {g4}"); + assert!(g1, "KILL gate 1: selector unexpectedly reaches all 34"); + assert!( + g2, + "KILL gate 2: organ gate does not reach 34 when fully grounded" + ); + assert!( + g3, + "KILL gate 3: carried awareness shrank up the climb (rediscovery)" + ); + assert!( + g4, + "KILL gate 4: prune inert, or the apex recipe was pruned" + ); + + println!( + "\nPASS — Door C gates the Maslow climb on the REAL 24-loci organ: the selector\n\ + shadows {}/34; the organ reaches all 34 when grounded; lower-rung awareness is\n\ + carried UP (monotone, no rediscovery); higher thinking prunes lower-related.", + 34 - selector_reach.len() + ); +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 325681136..7a55c7efc 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -133,6 +133,7 @@ pub mod materialize; pub mod reasoning; pub mod recipe_dispatch; pub mod recipe_kernels; +pub mod recipe_loci; pub mod recipe_substrate; pub mod recipes; pub mod repository; diff --git a/crates/lance-graph-contract/src/recipe_loci.rs b/crates/lance-graph-contract/src/recipe_loci.rs new file mode 100644 index 000000000..20a6d8ca3 --- /dev/null +++ b/crates/lance-graph-contract/src/recipe_loci.rs @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright The Lance Authors + +//! `recipe_loci` — wire all 34 recipes to the **24-dimension causal-witness +//! organ**, closing the #780 Axis-B gap on the DISPATCH path. +//! +//! # What #780 left open +//! +//! [`recipe_dispatch::ladder`](crate::recipe_dispatch::ladder) already sweeps all +//! 34 recipes in rung order — but it gates each one on the **8-field scalar +//! proxy** ([`ThoughtField`](crate::recipe_kernels::ThoughtField): `sd`, +//! `free_energy`, `dissonance`, …). The #780 audit's Axis B measured exactly this: +//! *34/34 kernels read ONLY the scalar proxy, 0 read a real organ* +//! (`E-RECIPE-SUBSTRATE-WIRING-1`). #780 wired the ORGANS into the ctx +//! ([`recipe_substrate::SubstrateView`](crate::recipe_substrate)) but the +//! DISPATCH gate stayed scalar. This module supplies the missing half: a +//! **loci gate** keyed to the real [`CausalWitnessFacet`](crate::causal_witness) +//! 24-dimension reading. +//! +//! # The 24 dimensions × the rung-level walk +//! +//! Each recipe declares WHICH of the 16 named [`Locus`](crate::causal_witness::Locus) +//! dimensions it consumes ([`required_loci`]) — grounded in the recipe's own +//! documented [`substrate`](crate::recipes::Recipe::substrate) + inference. The +//! **rung-level walk** ([`loci_ladder`]) then visits the 34 in +//! [`dispatch_order`](crate::recipe_dispatch::dispatch_order) (ascending rung) +//! and fires a recipe only when **every required locus is BOUND** (nonzero offset +//! — the dimension is placed in the `±8` `temporal.rs` window). An UNBOUND +//! required locus ([`loci_disqualifier`]) is the 24-dimension analog of +//! [`nan_disqualifier`](crate::recipe_dispatch::nan_disqualifier): a recipe never +//! fires on a dimension the row could not ground. +//! +//! # Door C — the organ gate (complements the two shipped doors) +//! +//! `docs/NARS_RECIPES_DISPATCH.md` §7 already reaches all 34 by TWO doors: +//! **Door A** the style→mechanism fan (`recipes_for` over the 4 covering styles — +//! the wide door, all 34), **Door B** the surprise selector +//! ([`materialize::select_tactic`](crate::materialize::select_tactic) — the +//! narrow door, 8; `E-RECIPE-SELECTOR-REACHABILITY-1`). Both gate on +//! **style / mechanism / scalar surprise** — NEITHER reads the real +//! causal-witness organ. That is precisely #780's Axis-B gap. +//! +//! This module is **Door C — the organ gate**: a recipe is reached by +//! GROUNDING its 24-dimension loci in the row's live +//! [`CausalWitnessFacet`](crate::causal_witness), not by style coverage or a +//! surprise-band argmax. It is the dispatch path that actually READS the +//! substrate the #780 audit found unread. [`reachable`] returns the +//! loci-reachable set; the counterfactual ICR #31 fires unshadowed at the +//! deepest rung once its `Kausal` + `Contradiction` + S/P/O loci are all placed, +//! and a CrossTier recipe fires exactly when its basin/temporal/meaning loci are +//! bound — the organ decides, not the style label. (Door C does not *raise* the +//! reachable count above Door A's 34; it makes reachability CONTINGENT ON THE +//! REAL ORGAN — the honest #780-closing contribution, not a reachability claim.) +//! +//! # The Maslow pyramid of cognition (the shipped rung vocabulary) +//! +//! The rung a recipe fires at IS a level of the operator-ruled **Maslow pyramid +//! of cognition** — the shipped 10-name +//! [`RungLevel`](crate::cognitive_shader::RungLevel) `Surface..Transcendent` +//! (`docs/NARS_RECIPES_DISPATCH.md` §7 table, `E-FOVEATED-AWARENESS-1`), climbed +//! by the shipped [`RungElevator`](crate::cognitive_shader::RungElevator) +//! (elevate on sustained BLOCK, never below `base` — Maslow-monotone). [`rung_level`] +//! names a recipe's rung in that vocabulary; this module never re-invents the +//! ladder, it gates each pyramid level on the organ. +//! +//! # Carrying lower-rung awareness — the anti-rediscovery property +//! +//! The 24-loci register is **read, not consumed**, across the ENTIRE rung walk. +//! A dimension grounded for a shallow-rung recipe (e.g. `Kausal` placed for +//! RCR #4 at its rung) stays grounded for every deeper recipe that reads it +//! (ICR #31 at the deepest rung). So the substrate itself **carries the +//! lower-level rung awareness forward** — the cause is placed once, at its rung, +//! and READ by all higher rungs ([`carried_awareness`]), never re-derived. This +//! is the concrete mechanism behind #777's "orientation for free": observational +//! causal discovery pays super-exponentially to RE-search for the cause at each +//! step, whereas here the deeper rung reads the `Kausal`/`BasinAnchor`/ +//! `Antecedent` locus the lower rung already placed. Escalation is **monotone +//! accumulation** (the elected schema binds MORE loci, never fewer — #778's +//! "more loci bound, shape unchanged"), so the cumulative grounded set is +//! non-decreasing in rung (the `carried_is_monotone` test proves it). The rung +//! level occupies ZERO stored bytes — it is carried by WHICH loci are bound, not +//! by a magnitude (#778 / le-contract §2 slot purity). +//! +//! # Discipline +//! +//! Additive, zero-dep, reading-only. `required_loci` is a documented +//! classification (like [`recipe_dispatch::inference`](crate::recipe_dispatch::inference)), +//! never a stored byte. The witness is READ (loci are identity pointers, +//! `I-VSA-IDENTITIES` clean); nothing here mutates a register or reserves a slot. + +use crate::causal_witness::{CausalWitnessFacet, Locus}; +use crate::cognitive_shader::RungLevel; +use crate::recipe_dispatch::{inference, RecipeInference}; + +/// The [`Locus`] dimensions recipe `id` consumes, grounded in the recipe's +/// documented [`substrate`](crate::recipes::Recipe::substrate) + its NARS +/// inference. Every recipe reads at least one dimension — the wiring is real (no +/// recipe trivially always-fires). The 8 reserved loci (`16..24`) are never +/// required; only the 16 named dimensions are consumable. +/// +/// The classification (per `recipes.rs` `substrate` string): +/// - **causal / backward** recipes read [`Kausal`](Locus::Kausal) (their stored +/// cause) — RCR #4, TCA #12, ICR #31, IDR #29, SSAM #28. +/// - **S/P/O-grounding** recipes read the meaning loci — MCT #14, ARE #19, +/// ZCF #24, SDD #32, HKF #34, RCR #4, ICR #31. +/// - **consensus / revision** recipes read [`Quorum`](Locus::Quorum) and/or +/// [`Contradiction`](Locus::Contradiction) — SMAD #3, ASC #7, CR #11, CDI #17, +/// TCF #20, SSR #21, MPC #27, SPP #30, ICR #31. +/// - **hierarchy / basin** recipes read [`BasinAnchor`](Locus::BasinAnchor) and +/// the `hi/lo`-chain loci — HTD #2, CAS #8, LSI #15, ETD #22, HPM #25. +/// - **meta / template / rung** recipes read +/// [`MeaningLevel`](Locus::MeaningLevel) and +/// [`RunbookEvidence`](Locus::RunbookEvidence) — RTE #1, MCP #10, PSO #16, +/// AMP #23, CUR #26, DTMF #33. +/// - **texture / divergence** recipes read +/// [`QualiaReference`](Locus::QualiaReference) — TR #6, IRS #9, CDT #13. +/// - **episodic / temporal** recipes read [`Temporal`](Locus::Temporal) and +/// [`Antecedent`](Locus::Antecedent) — TCA #12, CWS #18. +#[must_use] +pub fn required_loci(id: u8) -> &'static [Locus] { + use Locus::*; + match id { + 1 => &[MeaningLevel], // RTE: rung-depth expand + 2 => &[BasinAnchor, Supports], // HTD: hierarchical decompose + 3 => &[Quorum, Contradiction], // SMAD: debate → consensus + 4 => &[Kausal, SMeaning, OMeaning], // RCR: backward S_O + Granger + 5 => &[Supports], // TCP: prune the branch it supports + 6 => &[QualiaReference], // TR: temperature perturb + 7 => &[Contradiction], // ASC: adversarial self-critique + 8 => &[MeaningLevel, BasinAnchor], // CAS: abstraction scaling + 9 => &[Modal, QualiaReference], // IRS: persona synthesis + 10 => &[MeaningLevel, QualiaReference], // MCP: meta-cognition calibrate + 11 => &[Contradiction, Quorum], // CR: contradiction resolution + 12 => &[Temporal, Kausal], // TCA: Granger temporal + 13 => &[QualiaReference, MeaningLevel], // CDT: converge/diverge + 14 => &[SMeaning, PMeaning, OMeaning], // MCT: grammar-triangle fuse + 15 => &[BasinAnchor], // LSI: latent cluster read + 16 => &[RunbookEvidence], // PSO: scaffold slots + 17 => &[Contradiction], // CDI: dissonance induction + 18 => &[Temporal, Antecedent], // CWS: episodic memory + 19 => &[SMeaning, OMeaning], // ARE: unbind component + 20 => &[Quorum], // TCF: cascade agreement + 21 => &[Contradiction, MeaningLevel], // SSR: self-skepticism + 22 => &[BasinAnchor, Supports], // ETD: emergent decompose + 23 => &[RunbookEvidence, MeaningLevel], // AMP: adaptive meta-prompt + 24 => &[SMeaning, OMeaning], // ZCF: zero-shot fuse + 25 => &[BasinAnchor], // HPM: nearest pattern + 26 => &[MeaningLevel], // CUR: coarse→fine reduce + 27 => &[Quorum], // MPC: majority compress + 28 => &[SMeaning, PMeaning, OMeaning], // SSAM: analogical mapping + 29 => &[SMeaning, PMeaning, Kausal], // IDR: causality-flow reframe + 30 => &[Quorum, Contradiction], // SPP: shadow parallel agree + 31 => &[Kausal, Contradiction, SMeaning, PMeaning, OMeaning], // ICR: counterfactual + 32 => &[SMeaning, OMeaning], // SDD: reciprocal validate + 33 => &[RunbookEvidence, MeaningLevel], // DTMF: meta-frame switch + 34 => &[SMeaning, PMeaning, OMeaning], // HKF: cross-domain fuse + _ => &[], + } +} + +/// The first required [`Locus`] that is **unbound** (offset `0`) in `w` — so the +/// recipe is DISQUALIFIED — or `None` if every required dimension is grounded. +/// The 24-dimension analog of +/// [`nan_disqualifier`](crate::recipe_dispatch::nan_disqualifier): the loci-side +/// reliability gate (a recipe never fires on a dimension the row could not +/// ground). An unknown id (`required_loci == []`) is trivially grounded → `None` +/// (matches `nan_disqualifier`'s "no requirement → never disqualified"). +#[must_use] +pub fn loci_disqualifier(w: &CausalWitnessFacet, id: u8) -> Option { + required_loci(id).iter().copied().find(|&l| !w.is_bound(l)) +} + +/// Whether every dimension recipe `id` requires is bound in `w`. +#[must_use] +pub fn is_grounded(w: &CausalWitnessFacet, id: u8) -> bool { + loci_disqualifier(w, id).is_none() +} + +/// The **Maslow rung a single [`Locus`] dimension sits at** (`0..=6`, +/// [`RungLevel`](crate::cognitive_shader::RungLevel)-aligned): raw observation + +/// meaning grounding shallow, basin/relate mid, meta/texture higher, consensus +/// structural, causal/counterfactual at the apex. This is the organ's own depth +/// scale — a dimension IS a cognitive need level. +/// +/// - `Temporal` = 0 (Surface — the raw time signal) +/// - `SMeaning`/`PMeaning`/`OMeaning` = 1 (Shallow — match to known meaning) +/// - `Lokal`/`BasinAnchor`/`Antecedent` = 2 (Contextual — relate to neighbors, Pearl L1) +/// - `Supports`/`SupportedBy`/`Modal` = 3 (Analogical — map onto structure) +/// - `MeaningLevel`/`RunbookEvidence`/`QualiaReference` = 4 (Abstract — generalize) +/// - `Quorum` = 5 (Structural — consensus models the mechanism, Pearl L2) +/// - `Kausal`/`Contradiction` = 6 (Counterfactual — the cause / preserved +/// contradiction, Pearl L3) +#[must_use] +pub const fn locus_rung(l: Locus) -> u8 { + match l { + Locus::Temporal => 0, + Locus::SMeaning | Locus::PMeaning | Locus::OMeaning => 1, + Locus::Lokal | Locus::BasinAnchor | Locus::Antecedent => 2, + Locus::Supports | Locus::SupportedBy | Locus::Modal => 3, + Locus::MeaningLevel | Locus::RunbookEvidence | Locus::QualiaReference => 4, + Locus::Quorum => 5, + Locus::Kausal | Locus::Contradiction => 6, + } +} + +/// The **organ-derived rung**: a recipe is as deep as the DEEPEST dimension it +/// requires — `max(locus_rung(l))` over [`required_loci`]. This is the 24-based +/// dispatch order (the whole walk, order AND gate, keyed on the organ), replacing +/// the static `Tier`+`inference` classification of +/// [`recipe_dispatch::rung`](crate::recipe_dispatch::rung) (kept as a documented +/// cross-check, not the order). So ICR #31 (reads `Kausal`+`Contradiction`) lands +/// at the counterfactual apex BECAUSE those are apex dimensions, not because a +/// Tier table said so; HPM #25 (reads only `BasinAnchor`) sits shallow. A recipe +/// with no required loci is rung `0`. +#[must_use] +pub fn loci_rung(id: u8) -> u8 { + required_loci(id) + .iter() + .map(|&l| locus_rung(l)) + .max() + .unwrap_or(0) +} + +/// The 34 recipe ids in **organ-derived dispatch order** — ascending [`loci_rung`], +/// then ascending id. The 24-based sibling of +/// [`recipe_dispatch::dispatch_order`](crate::recipe_dispatch::dispatch_order) +/// (which orders by the static rung): shallow-organ recipes first, the +/// causal/counterfactual apex last. +#[must_use] +pub fn loci_dispatch_order() -> [u8; 34] { + let mut ids: [u8; 34] = core::array::from_fn(|i| i as u8 + 1); + ids.sort_by_key(|&id| (loci_rung(id), id)); + ids +} + +/// One rung-ordered loci-gated dispatch decision — the 24-dimension sibling of +/// [`RecipeStep`](crate::recipe_dispatch::RecipeStep). Records whether the recipe +/// fired, at what rung, on which inference, how many of its required loci were +/// bound (the grounded coverage that licensed it), and — if disqualified — the +/// first unbound dimension. +#[derive(Debug, Clone, Copy)] +pub struct LociStep { + /// Recipe id (1..=34). + pub id: u8, + /// Rung this recipe fires at — the organ-derived [`loci_rung`] (the deepest + /// dimension it requires), NOT the static `recipe_dispatch::rung`. + pub rung: u8, + /// Inference the recipe embodies. + pub inference: RecipeInference, + /// How many [`Locus`] dimensions the recipe requires. + pub required: u8, + /// How many of those required dimensions are bound in the witness. + pub bound: u8, + /// `None` = fired (all required loci bound); `Some(locus)` = disqualified + /// because that dimension was unbound. + pub disqualified_by: Option, +} + +impl LociStep { + /// Did this recipe fire (every required dimension grounded)? + #[must_use] + pub const fn fired(&self) -> bool { + self.disqualified_by.is_none() + } +} + +/// **The rung-level walk over the 24 dimensions.** Visits all 34 recipes in +/// [`loci_dispatch_order`] (ascending organ-derived [`loci_rung`], then id), +/// gating each on [`loci_disqualifier`]. The returned steps are the causal chain +/// of the loci-gated orchestration: shallow-organ recipes first, the +/// causal/counterfactual apex last — each fired iff its 24-dimension checklist is +/// grounded. Order AND gate are keyed on the organ, not on a static Tier table. +#[must_use] +pub fn loci_ladder(w: &CausalWitnessFacet) -> Vec { + loci_dispatch_order() + .into_iter() + .map(|id| { + let req = required_loci(id); + let bound = req.iter().filter(|&&l| w.is_bound(l)).count() as u8; + LociStep { + id, + rung: loci_rung(id), + inference: inference(id), + required: req.len() as u8, + bound, + disqualified_by: loci_disqualifier(w, id), + } + }) + .collect() +} + +/// The recipes **reachable** under witness `w` — those whose every required +/// dimension is bound — in rung-dispatch order. With a fully-bound witness this +/// is all 34; the count grows monotonically as more loci are grounded. +#[must_use] +pub fn reachable(w: &CausalWitnessFacet) -> Vec { + loci_ladder(w) + .into_iter() + .filter(LociStep::fired) + .map(|s| s.id) + .collect() +} + +/// The **Maslow-pyramid** rung a recipe fires at, named in the shipped +/// [`RungLevel`](crate::cognitive_shader::RungLevel) vocabulary +/// (`Surface..Transcendent`). Bridges the organ-derived [`loci_rung`] into the +/// operator-ruled cognitive pyramid via +/// [`RungLevel::from_u8`](crate::cognitive_shader::RungLevel::from_u8) — the ONE +/// u8→rung mapping. A recipe's cognitive "need level" IS its deepest required +/// organ dimension; the [`RungElevator`](crate::cognitive_shader::RungElevator) +/// climbs the pyramid Maslow-monotone. +#[must_use] +pub fn rung_level(id: u8) -> RungLevel { + RungLevel::from_u8(loci_rung(id)) +} + +/// **The carried lower-rung awareness at a pyramid level** — the set of witness +/// dimensions already grounded by every recipe at rung `<= up_to`, in canonical +/// [`Locus::ALL`](crate::causal_witness::Locus) order. This is what the substrate +/// carries UP the Maslow climb: a deeper recipe reads these already-placed loci +/// (the `Kausal` cause, the `BasinAnchor`, the `Antecedent`) instead of +/// re-deriving them — the anti-exponential-rediscovery property (#777 "orientation +/// for free"). Because the register is read (never consumed) and escalation only +/// binds MORE loci, [`carried_awareness`] is **monotone non-decreasing** in +/// `up_to` (a lower rung's grounding is never lost to a higher one). +#[must_use] +pub fn carried_awareness(w: &CausalWitnessFacet, up_to: u8) -> Vec { + let mut carried: Vec = Vec::new(); + for &id in loci_dispatch_order().iter().filter(|&&id| loci_rung(id) <= up_to) { + for &l in required_loci(id) { + if w.is_bound(l) && !carried.contains(&l) { + carried.push(l); + } + } + } + // canonical Locus::ALL order (deterministic, independent of dispatch order) + Locus::ALL + .into_iter() + .filter(|l| carried.contains(l)) + .collect() +} + +/// **Higher-rung thinking prunes lower-related.** The dual of +/// [`carried_awareness`]: carry accumulates lower→higher; prune subsumes +/// higher→lower. A reachable recipe is PRUNED when some reachable recipe at a +/// STRICTLY HIGHER rung requires a **strict superset** of its dimensions — the +/// higher, more-complete thinking already grounds everything the lower related +/// one would, so re-firing the lower is redundant. Returns the ACTIVE recipes +/// after prune, in rung-dispatch order. This keeps the Maslow climb from +/// re-doing subsumed lower work (the attention-economy complement to +/// anti-rediscovery): the substrate carries the lower groundings UP, and the +/// higher thinking prunes the lower related ones from active dispatch. +/// +/// The prune is by dimension-subsumption only (a documented model, not the only +/// possible rule); a recipe reading dimensions no higher recipe covers always +/// stays active. +#[must_use] +pub fn active_after_prune(w: &CausalWitnessFacet) -> Vec { + let reach = reachable(w); + reach + .iter() + .copied() + .filter(|&id| { + let mine = required_loci(id); + // pruned iff a strictly-higher-rung reachable recipe's loci ⊋ mine + !reach.iter().any(|&other| { + other != id && loci_rung(other) > loci_rung(id) && { + let theirs = required_loci(other); + mine.iter().all(|l| theirs.contains(l)) && theirs.len() > mine.len() + } + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::causal_witness::Locus; + + /// A witness with every named locus bound (offset −1 = one event back). + fn fully_bound() -> CausalWitnessFacet { + let mut w = CausalWitnessFacet::ZERO; + for l in Locus::ALL { + w = w.with(l, -1); + } + w + } + + #[test] + fn every_recipe_requires_at_least_one_named_locus() { + for id in 1..=34u8 { + let req = required_loci(id); + assert!(!req.is_empty(), "recipe {id} declares no loci — wiring gap"); + // never requires a reserved slot (only the 16 named loci are consumable) + for &l in req { + assert!( + Locus::ALL.contains(&l), + "recipe {id} requires non-named locus {l:?}" + ); + } + } + } + + #[test] + fn fully_bound_witness_reaches_all_34() { + let w = fully_bound(); + let r = reachable(&w); + assert_eq!(r.len(), 34, "a fully-bound witness grounds every recipe"); + // a permutation of 1..=34 + let mut sorted = r.clone(); + sorted.sort_unstable(); + assert_eq!(sorted, (1..=34).collect::>()); + } + + #[test] + fn unbound_witness_reaches_nothing() { + // ZERO witness: no dimension placed → every recipe disqualified. + let r = reachable(&CausalWitnessFacet::ZERO); + assert!( + r.is_empty(), + "no dimension grounded → no recipe fires (all NaN-gated)" + ); + } + + #[test] + fn unbinding_a_dimension_disqualifies_exactly_its_consumers() { + // Start fully bound, then unbind Kausal: the Kausal-consuming recipes + // (RCR #4, TCA #12, IDR #29, ICR #31) must drop out; others stay. + let mut w = fully_bound(); + w = w.with(Locus::Kausal, 0); // unbind + let reached: std::collections::HashSet = reachable(&w).into_iter().collect(); + for id in [4u8, 12, 29, 31] { + assert!( + !reached.contains(&id), + "recipe {id} requires Kausal — unbinding it must disqualify {id}" + ); + assert_eq!(loci_disqualifier(&w, id), Some(Locus::Kausal)); + } + // A recipe that does NOT read Kausal (HPM #25 reads BasinAnchor) still fires. + assert!( + reached.contains(&25), + "HPM #25 does not read Kausal → still fires" + ); + } + + #[test] + fn ladder_is_rung_ascending_and_records_coverage() { + let w = fully_bound(); + let steps = loci_ladder(&w); + assert_eq!(steps.len(), 34); + assert!( + steps.windows(2).all(|s| s[0].rung <= s[1].rung), + "the loci walk fires shallow → deep" + ); + // every step's bound == required under a fully-bound witness. + assert!(steps.iter().all(|s| s.bound == s.required && s.fired())); + // ICR #31 (counterfactual) sits at the deepest ORGAN rung (Kausal=6) and + // reads 5 loci — deepest because it reads apex dimensions, not by a table. + let icr = steps.iter().find(|s| s.id == 31).unwrap(); + assert_eq!(icr.required, 5, "ICR reads Kausal+Contradiction+S/P/O"); + assert_eq!(icr.rung, 6, "counterfactual apex = Kausal/Contradiction rung"); + assert_eq!(icr.rung, steps.iter().map(|s| s.rung).max().unwrap()); + } + + #[test] + fn rung_level_names_the_maslow_pyramid() { + // ICR #31 (counterfactual) sits high on the pyramid; a shallow forward + // deduction (TCP #5, CrossTier deduction) sits low. + assert!( + rung_level(31) >= RungLevel::Counterfactual, + "ICR climbs to the counterfactual apex region" + ); + assert!( + rung_level(5) < rung_level(31), + "shallow deduction is a lower cognitive need than counterfactual" + ); + // the mapping is the shipped one — round-trips through RungLevel::from_u8. + for id in 1..=34u8 { + assert_eq!(rung_level(id), RungLevel::from_u8(loci_rung(id))); + } + } + + #[test] + fn carried_is_monotone() { + // The substrate carries lower-rung awareness UP: the cumulative grounded + // dimension set never shrinks as the pyramid climbs (anti-rediscovery). + let w = fully_bound(); + for r in 1..9u8 { + let lo: std::collections::HashSet = + carried_awareness(&w, r).into_iter().collect(); + let hi: std::collections::HashSet = + carried_awareness(&w, r + 1).into_iter().collect(); + assert!( + lo.is_subset(&hi), + "rung {r} awareness must be carried into rung {}", + r + 1 + ); + } + } + + #[test] + fn deep_recipe_stands_on_carried_lower_rung_loci() { + // ICR #31 reads Kausal + Contradiction + S/P/O. Under a fully-bound + // witness, every dimension it needs was ALREADY placed by recipes at or + // below its rung — so reaching the counterfactual apex re-derives + // nothing; it reads the carried substrate (the #777 "orientation for free"). + let w = fully_bound(); + let carried: std::collections::HashSet = + carried_awareness(&w, loci_rung(31)).into_iter().collect(); + for &l in required_loci(31) { + assert!( + carried.contains(&l), + "ICR's dimension {l:?} must be carried, not rediscovered at the apex" + ); + } + } + + #[test] + fn higher_rung_thinking_prunes_lower_related() { + // Under a fully-bound witness, all 34 are reachable; prune removes the + // lower-related ones a higher-rung recipe strictly subsumes. + let w = fully_bound(); + let active: std::collections::HashSet = active_after_prune(&w).into_iter().collect(); + let reach: std::collections::HashSet = reachable(&w).into_iter().collect(); + assert!(active.is_subset(&reach), "prune only removes, never adds"); + // ICR #31 is the deepest rung — nothing above it, so it is never pruned. + assert!(active.contains(&31), "the apex recipe is never pruned"); + // RTE #1 reads only [MeaningLevel]; SSR #21 (higher rung) reads + // [Contradiction, MeaningLevel] ⊋ [MeaningLevel] → RTE is subsumed. + assert!(loci_rung(21) > loci_rung(1) && required_loci(21).contains(&Locus::MeaningLevel)); + assert!( + !active.contains(&1), + "RTE #1 is pruned — a higher-rung recipe already grounds MeaningLevel + more" + ); + assert!(active.len() < 34, "some lower-related thinking is pruned"); + } + + #[test] + fn loci_rung_is_organ_derived_not_the_static_tier_table() { + // ICR #31: organ rung = its deepest dimension (Kausal/Contradiction = 6, + // Counterfactual). The static Tier+inference table put it at 9 — so the + // ORDER now comes from the organ, not the classification. + assert_eq!(loci_rung(31), 6); + assert_ne!( + crate::recipe_dispatch::rung(31), + loci_rung(31), + "organ-derived rung must differ from the static Tier table" + ); + // loci_dispatch_order: a permutation of 1..=34, ascending loci_rung. + let order = loci_dispatch_order(); + let mut sorted = order; + sorted.sort_unstable(); + assert_eq!(sorted, core::array::from_fn::(|i| i as u8 + 1)); + let rungs: Vec = order.iter().map(|&id| loci_rung(id)).collect(); + assert!( + rungs.windows(2).all(|w| w[0] <= w[1]), + "organ-ascending order: {rungs:?}" + ); + // every named dimension's rung is a Pearl-aligned 0..=6. + for l in Locus::ALL { + assert!(locus_rung(l) <= 6, "{l:?} rung out of range"); + } + } + + #[test] + fn partial_witness_reaches_a_monotone_subset() { + // Bind only the meaning loci → S/P/O-grounding recipes fire; consensus + // and causal recipes do not. + let w = CausalWitnessFacet::ZERO + .with(Locus::SMeaning, -1) + .with(Locus::PMeaning, -1) + .with(Locus::OMeaning, -1); + let reached: std::collections::HashSet = reachable(&w).into_iter().collect(); + assert!(reached.contains(&14), "MCT #14 (S/P/O) fires"); + assert!(reached.contains(&34), "HKF #34 (S/P/O) fires"); + assert!( + !reached.contains(&3), + "SMAD #3 needs Quorum+Contradiction → no" + ); + assert!(!reached.contains(&31), "ICR #31 needs Kausal too → no"); + } +}