diff --git a/.claude/board/D-MBX-COMPLETION-MAP.md b/.claude/board/D-MBX-COMPLETION-MAP.md new file mode 100644 index 00000000..dc888b2b --- /dev/null +++ b/.claude/board/D-MBX-COMPLETION-MAP.md @@ -0,0 +1,80 @@ +# D-MBX-* Completion Map — the whole arc to "ONE SoA, end-to-end" + +> Live tracker for unified-soa-convergence-v1 (`E-SOA-IS-THE-ONLY`). Reverse of the +> plan's bottom-up sequence: this is the **dependency-ordered path to completion**. +> Status mirrors STATUS_BOARD; this doc adds the critical-path + gating-OQ view. +> Maintained via `tee` (board-hygiene, newest facts win; supersede by re-`tee`). + +## Where we are (2026-05-30) +- **D-MBX-A1** — migrated thoughtspace columns on `MailboxSoA` — **SHIPPED** (between #418/#433). +- **D-MBX-A6-P1** — contract seam (`kanban`/`soa_view`/`StepDomain::Kanban` + `class_id` N1 hook) — **SHIPPED #437**. +- **D-MBX-A6-P2** — Rubicon lifecycle DAG enforcement + `ExecTarget` — **IN PR #439**. +- Everything else below: **Queued**, gated as shown. + +## The completion DAG (what blocks what) +```text + SHIPPED ─────────────────────────────────────────────────────────────────── + A1 (SoA columns) A6-P1 (#437 contract seam) A6-P2 (#439 lifecycle+ExecTarget) + │ │ │ + FOUNDATIONS (should land EARLY — everything downstream needs them) ───────── + D-MBX-10 SoA version byte (MailboxSoAHeader; I-LEGACY-API-FEATURE-GATED) [gates OQ-11.5] + D-MBX-11 lance =6.0.0 -> =6.0.1 bump (mechanical, 5 Cargo.toml) [no gate — DO NOW] + │ + HOT-PATH SoA EXPRESSIVITY ────────────────────────────────────────────────── + D-MBX-A2 close BindSpace gaps (content_ref, S/P/O role slices, fold) [gates OQ-1/OQ-2] + │ + D-MBX-A3 witness_arc:[u32;W] per-row column (R4 belief-state arc handle) [gates OQ-11.2] + │ │ + D-MBX-A4 Staunen×Wisdom plasticity spreader (Planning-gated, Hebbian) [gates OQ-11.1 + `phase` field] + D-MBX-A5 SPO-W witness dual-residency; SoA decides commit modality [gates D-MBX-4] + │ + CONSUMER WIRING (the A6 spine -> real cycle) ──────────────────────────────── + D-MBX-A6-P3 impl MailboxSoaOwner for MailboxSoA (ractor owns; try_advance_phase + in the real cycle) + planner candidate-gen emits KanbanMove{exec} <-- NEXT + │ + D-MBX-8 Σ10 commit stamps t=-550ms (Libet) in SigmaTierRouter -> ractor START + │ + SIMD + COLD ALIGNMENT ─────────────────────────────────────────────────────── + D-MBX-7 lance-graph containers ≡ MailboxSoA ≡ ndarray::simd_soa (1.4-4.2x; + HARD PREREQ for the SurrealDB transparent view) [gates D-MBX-A2 + 10 + 11 + ndarray-miri] + │ + SUBSTRATE VIEW (the payoff — now a BIDIRECTIONAL SUBSCRIPTION, not a build) ───── + D-MBX-9 Rubicon kanban = the mailbox Lance version arc, BOTH directions (substrate-free): + OUT mailbox advance_phase commit = version = kanban move (E-VERSION-ARC-IS-THE-KANBAN) + IN surreal LIVE/scheduled event over versions() = planner->execution scheduler + firing the next advance_phase (E-SUBSTRATE-IS-THE-SCHEDULER) + => collapses from "build a view" to "LIVE-subscribe + schedule" (like GitHub CI/PR sub). + surreal #31 Timeline over Dataset::versions() IS the surface; MailboxSoaView (#437) = read lens. + [still gated by surreal_container BLOCKED(B/C/D) OQ-11.6 for the surreal side; design substrate-free] + │ + WORKSPACE CONVERGENCE (the "nine consumers" all read ONE SoA) ───────────────── + D-MBX-12 8-PR alignment, sequenced OQ-11.8: + 12.4 lance-graph -> 12.5 planner -> 12.6 shader-driver -> 12.7 callcenter + -> 12.1 AriGraph -> 12.9 thinking-styles -> 12.2 Vsa16k audit -> 12.8 ontology audit +``` + +## Critical path to "done" (longest chain) +`A6-P2(#439)` → **A6-P3** → A2 → A3 → A5 → D-MBX-7 → **D-MBX-9** (transparent view) → D-MBX-12 (nine-consumer convergence). D-MBX-10/11 are off-critical-path foundations that should land in parallel NOW (11 is mechanical, 10 is the version-gate everything reads). + +## Gating Open Questions (these block, not the code) +- **OQ-11.6** — surreal_container fork coords (URL/branch/kv-lance flag); BLOCKED(B/C/D). **Blocks D-MBX-9** (the whole payoff). Highest-leverage unblock. +- **OQ-1 / OQ-2** — content-ref shape + temporal/expert fold. Block **D-MBX-A2** (hot-path expressivity). +- **OQ-11.1** — plasticity spread radius/decay. Blocks **D-MBX-A4**. +- **OQ-11.2** — witness-arc width W + handle encoding. Blocks **D-MBX-A3**. +- **OQ-11.5** — version-byte scheme. Blocks **D-MBX-10**. +- **OQ-11.7** — planner 5-phase cutover feature-gating. Blocks **D-MBX-A6** full (P3+). +- **OQ-11.8** — the 8-PR sequence (resolved order above). Sequences **D-MBX-12**. + +## Loose ends folded in (from EPIPHANIES LE-1..4; not D-MBX-numbered yet) +- **LE-1** EW64 as DeepNSM>Markov>grammar coref witness pointer (no bundling) — relates A3/A5 + the EW64 type. +- **LE-2** unify cold SPO + cold AriGraph (EW64 = cheap witness pointer) — relates 12.1 + A5. +- **LE-3** mailbox-cycle-end Rubicon commit → cold SPO-W + SLA/goalstate — hooks `is_absorbing` (A6-P2 ✓) → D-MBX-A5 + D-MBX-9. +- **LE-4** Odoo/OWL business-logic action substrate — explicitly OTHER SESSION. +- **EW64 type** (AriGraph episodic edge; shares CE64 low-40 SPO bits; payload = witness-arc pointer + aerial-fed prefetch confidence) — a contract type that realizes A3's arc handle; candidate next-after-P3. + +## Recommended execution order (autonomous, this session) +1. **D-MBX-11** (mechanical lance bump) — unblocks the stack alignment, near-zero risk. +2. **D-MBX-A6-P3** (consumer wiring) — turns the shipped contract spine into a live cycle; highest architectural value, on critical path. +3. **D-MBX-10** (version byte) — the foundation every cold/view consumer reads; pairs with OQ-11.5. +4. Then A2→A3 (hot-path expressivity + witness arc), surfacing OQ-1/OQ-2/OQ-11.2 for ratification as reached. +5. **D-MBX-9** stays BLOCKED on OQ-11.6 (surreal fork) — flag for user unblock; the `MailboxSoaView` borrow trait already lets it land with zero contract change once unblocked. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 9c817934..7902a712 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,531 @@ +## 2026-05-30 — SHIPPED-in-PR: M1 keystone — `Tactic::requires() -> ThoughtMask` (the latent checklist made data; reliability = coverage, extraction not construction) + +**Status:** SHIPPED-in-PR #439 (D-MBX-A6-P3-M1). The panel-recalibrated keystone of reliability-checklist-arc-v1, built autonomously. + +`contract::recipe_kernels` now has: `ThoughtField` (8-field enum, stable bit positions, append-only per N3) + `ThoughtMask(u8)` (zero-dep bitmask: `of`/`has`/`len`/`is_empty`/`covered_by`) + **`Tactic::requires(&self) -> ThoughtMask` NON-defaulted** — all 34 tactics declare which ThoughtCtx fields their `apply` reads (audited from the bodies: Cr→beliefs, Tcp→candidates+sd, Mcp→confidence+free_energy, Rte→free_energy+rung, …; the 4 algebraic constant-only tactics Are/Zcf/Icr/Hkf legitimately empty). `covered_by` (`required & known == required`) IS the reliability-coverage gate in miniature. + +This realizes the creative-explorer M1 insight: reliability is a DECLARED ACCESSOR, not a constructed gate — the checklist was latent in 34 apply() bodies, now reified as data. Makes P1(coverage)/P7(reconcile)/P11(class_id→checklist) DERIVED. Non-defaulted = no silent-empty theater (the council's no-op warning); teeth-test `requires_masks_are_varied_not_a_constant_stub` FAILS on a copy-paste/empty stub (asserts exactly-4-empty + ≥8 distinct masks). 9 recipe_kernels tests + full contract lib green; non-defaulted method safe (the 34 are the only Tactic impls workspace-wide). + +**Cross-ref:** reliability-checklist-arc-v1 RECALIBRATION (M1 keystone); E-TEMPLATE-IS-CHECKLIST-IS-DATOMS (requires() = the executable checklist); E-RELIABILITY-IS-CHECKLIST-COVERAGE (covered_by = the gate); E-RELIABILITY-NOT-VALIDITY; `recipe_kernels.rs` ThoughtField/ThoughtMask/Tactic::requires. +## 2026-05-30 — RECALIBRATION (3-agent panel) of reliability-checklist-arc-v1: keystone is M1 `Tactic::requires()->AtomMask` (extraction not construction); P2 needs a corpus it lacks; P5 is P0-blocked; P3/P4 are AP6 theater; P10 off-arc + +**Status:** FINDING / plan recalibration (cascade-impact + brutally-honest-tester + creative-explorer, catalyst mode 2026-05-30). Recalibrates `.claude/plans/reliability-checklist-arc-v1.md`. Convergent across all three. + +**THE UNLISTED KEYSTONE (creative-explorer M1 — reframes the menu):** the 34 `recipe_kernels::Tactic` impls EACH already declare a latent checklist — every `apply()` reads a DIFFERENT subset of `ThoughtCtx`'s 8 fields (Cr→beliefs, Tcp→candidates+sd, Mcp→confidence+free_energy) but NONE reifies it as data. So reliability is NOT a gate to construct — it's a missing accessor: **`Tactic::requires(&self) -> AtomMask`** (one default method). Build that, and P1 (coverage) / P7 (reconcile) / P11 (class_id→checklist) become DERIVED, not built. "Reliability is already declared 34 times, just not read back — the work is EXTRACTION, not construction." `Tactic::requires()` and the domain `coverage()` are the SAME op at two altitudes (kernel required-fields ↔ `OdooStyleRecipe.atoms`), one bitmask over one basis. + +**CORRECTIONS to my menu (verified by source, file:line):** +- **P2 (probe) — the witness corpus DOES NOT EXIST** (grep empty in planner/consumer-conformance). P2's first sub-task = BUILD the corpus. It's still rank-1 (it can honestly return "gate is cosmetic, don't build P1") but it's not free. +- **P5 is P0-BLOCKED, not S-M:** `atoms::I4x32::pack`/`unpack` are BOTH `todo!()` (atoms.rs:83,88); the 32-vs-33 (carrier 32 lanes vs CANONICAL_ATOMS 33) is an unresolved BLOCKED fork (atoms.rs:39-43). argmax-over-I4x32 sits on a panicking carrier + undecided dim. Do NOT build until the dim fork resolves. +- **P3/P4 = AP6 dead-surface THEATER as scoped:** `try_advance_phase` has ZERO production callers (only FakeSoa); `resolve_style`/`reliability_of` have ZERO production callers; `plan()` returns input unchanged. Emitting from plan() repeats the theater the council just caught. P4 = MailboxSoaOwner impl with no ractor caller = AP6. +- **P1 DUPLICATES `recipes::Coverage`** (recipes.rs:54, SPO-2³, 3 variants) — minting a 4th Coverage = drift. Reframe P1 as M1's derived accessor, not a new enum. +- **P6 is COSMETIC:** recipe.rs has no `pub mod recipe` in lib.rs — never compiles in. Deleting already-invisible dead code = wash, not a deliverable. (Downgrades my earlier "delete it" instinct to "leave it / opportunistic.") +- **P9 lance bump is STILL BLOCKED** (lancedb 0.29.0 transitively pins lance =6.0.0) — NOT free-to-land as the menu claimed. +- **P10 polyglot = OFF-ARC** (different thread) — DROP from this arc. + +**MISSING items the panel adds:** +- **M1** `Tactic::requires() -> AtomMask` (the keystone above). +- **M2 — checklist-COMPLETENESS auditor (unknown-unknown finder):** static-scan each Tactic's declared `requires()` (M1) vs the fields its `apply()` actually touches (neural-debug already does static scanning); mismatch = a dark requirement nobody listed. The ONLY item that finds missing checklist ITEMS (not missing evidence) — the hot-path complement to the cold Stockfish validity gate. +- **M3 — version-arc-as-kanban scheduler wire** (E-SUBSTRATE-IS-THE-SCHEDULER): make the substrate emit the schedule; P3's KanbanMove is the latent hook. + +**RECALIBRATED ORDER (replaces the menu's open sequence):** +1. **M1** — `Tactic::requires() -> AtomMask` default method + a teeth-test (assert distinct Tactics declare distinct masks; fails if all-same/empty). Pure contract, zero-dep, extraction not construction. THE keystone. +2. **P2 + its corpus** — build a small witness/recipe corpus, then probe: does coverage state (M1-derived) change a Rubicon terminal vs DAG-only? Honest pass/fail; can return "cosmetic, stop." +3. **M2** — completeness auditor over M1's masks (static, reuses neural-debug). Finds unlisted-requirement gaps. +- DEFER behind P2-pass: P1-as-derived-coverage, P11 O(1) index. DROP/leave: P5 (P0-blocked), P6 (cosmetic), P10 (off-arc), P9 (still blocked). GATE P8 on P2. P3/P4 only after a real consumer exists (no theater). + +**Brutal's non-theater-now set was {P2,P9,P11}; creative's minimal-core was {P5,M1}; cascade's first-3 was {P2,P6,P1}.** Synthesis: **M1 is the keystone all three implicitly point at** (cascade's P1, creative's M1, brutal's "P1 dups Coverage→reframe") — start M1, then P2-with-corpus, then M2. P5 explicitly deferred (P0). + +**Cross-ref:** `.claude/plans/reliability-checklist-arc-v1.md` (the menu, now recalibrated); E-TEMPLATE-IS-CHECKLIST-IS-DATOMS + E-RELIABILITY-IS-CHECKLIST-COVERAGE (M1 is their executable form); `recipe_kernels::{Tactic,ThoughtCtx}` (the latent checklists); `recipes::Coverage` (the dup P1 must not repeat); `atoms.rs:83,88,39-43` (P5 todo!()+32/33 block); neural-debug (M2's static scanner); E-SUBSTRATE-IS-THE-SCHEDULER (M3). + +--- + +## 2026-05-30 — E-TEMPLATE-IS-CHECKLIST-IS-DATOMS — the NARS/elixir reasoning template, the per-domain checklist, and the Odoo D-Atoms are ONE object; reliability = the template's required atoms are LIT (known). #433 already built half of it. + +**Status:** FINDING — major unification (user 2026-05-30: "the checklist IS the NARS/elixir reasoning templates; in Odoo terms tax classes, billable hours, account type, etc."). Collapses three things I'd treated as separate, and re-connects #433 (which I'd mis-filed as "unrelated Odoo codegen"). + +**The unification (one object, three faces):** +- **NARS/elixir reasoning template** = the EXECUTABLE form (the recipe/`Tactic` that fires; recipe_kernels = "the Elixir-like recipe layer"). +- **Per-domain checklist** = the COVERAGE form (the required items to evaluate) — `E-RELIABILITY-IS-CHECKLIST-COVERAGE`. +- **Odoo D-Atoms** = the INSTANCE form (the actual domain fields). A template DECLARES its required inputs → those required inputs ARE the checklist → the checklist items ARE the domain's evaluable fields. Not three systems — one. + +**#433 ALREADY BUILT HALF OF THIS (grounded, file:line — I mis-filed it as unrelated):** +`lance-graph-ontology/src/odoo_blueprint/style_recipe.rs`: +- `enum DAtom` (:116) = the checklist-item catalogue, as REAL variants: `FiscalCtx`, `Money`, `ApplyRate` (VAT/currency rate, `OdooSemanticRole::Tax`), `Quantity`, `EmitAmount`, `Compute`, `Validate`, `Onchange`, `Event`, `Action`, `Entity`, `Law` (`regulation_iri` = UStG §12 / EU VAT). ⇒ EXACTLY the user's "tax classes / billable hours / account type" — domain fields that must be evaluated. +- `OdooStyleRecipe { method_id, atoms: Vec<(DAtom,u8)>, regulation_iris, return_kind, recipe_id }` (:209) = the TEMPLATE-AS-CHECKLIST: `atoms` = which checklist items this template REQUIRES (+weights). `recipe_id` = FNV-1a content-address over sorted atoms → equivalent templates collapse (CAM dedup). +- **The 5-lit / 6-dark atom split (#433 honest-flag)** = the knowns/unknowns COVERAGE BITMASK, already present: 5 atoms fire today (Entity/Compute/Validate/Onchange/Action — kind-driven = KNOWN); 6 dark (Money/Quantity/ApplyRate/EmitAmount/Event/FiscalCtx — gated on Stage-2 extractor = UNKNOWN until populated). lit-vs-dark IS the coverage bitmask. "Atoms flip from stage2→must set when Stage-2 lands" = checklist boxes going from unknown→known. + +**So reliability = required atoms LIT:** a template's `atoms: Vec<(DAtom,u8)>` are the required checklist; an instance's populated Odoo fields LIGHT them; reliability-to-Commit = `required_atoms ⊆ lit_atoms` (the `required & known == required` AND-test, E-RELIABILITY-IS-CHECKLIST-COVERAGE). Plan = named dark atoms remain (the Stage-2 gap); Prune = required atom unsatisfiable. + +**Elixir open/closed maps exactly** (recipe.rs E-LADDER §2): add-FIELD = data (a new checklist box / D-Atom population — no recompile); add-TEMPLATE = structure (a new required atom-set / OdooStyleRecipe — register it). The hot-load split IS the checklist-vs-template-evolution split. + +**Consequence / correction:** #433's `OdooStyleRecipe` is NOT "unrelated Odoo codegen" (my earlier filing in the 3-recipe-module finding) — it's the DOMAIN-INSTANCE face of the reasoning-template-as-checklist. The cross-domain generalization: a Rails/medical/chess frontend writes its OWN `style_recipe.rs` (its own D-Atom catalogue = its own checklist) over the same triplet shape (#433 doc: "A Rails frontend writes its own style_recipe.rs"). So D-Atoms are the per-domain checklist; the NARS reasoning template is the domain-agnostic shape; reliability-coverage is uniform across domains. + +**Build implication:** the cheap checklist gate (E-RELIABILITY-IS-CHECKLIST-COVERAGE) does NOT need a new checklist type — it READS `OdooStyleRecipe.atoms` (required) vs the instance's lit-atom bitmask (known). First cut: a `coverage(required: &[DAtom], lit: bitmask) -> CoverageState{Covered|Gap(dark)|Unsatisfiable}` over the existing D-Atom catalogue, generalized to any domain's atom enum. Reuses #433 wholesale. + +**Cross-ref:** #433 `odoo_blueprint/style_recipe.rs` (DAtom :116 / OdooStyleRecipe :209 / 5-lit-6-dark); E-RELIABILITY-IS-CHECKLIST-COVERAGE (coverage = reliability); E-RELIABILITY-NOT-VALIDITY (Stockfish audits checklist COMPLETENESS = unknown-unknowns); recipe_kernels "Elixir-like layer"; recipe.rs E-LADDER-SERVES-MAILBOX §2 (open/closed); the 3-recipe-module finding (CORRECTED: OdooStyleRecipe = the domain-instance face, not unrelated); cognitive-risc-classes (class_id→checklist, HHTL-inherited). + +--- + +## 2026-05-30 — E-RELIABILITY-IS-CHECKLIST-COVERAGE — the cheap RISC alternative to psychometric calibration: reliability = (required rungs/checklist-items COVERED) over a knowns/unknowns SoA bitmask, AND-tested in one cycle. No float, no corpus. + +**Status:** FINDING + BUILD-DIRECTION (user 2026-05-30: "cheap and efficient alternative — 10-layer rungs ladder + a checklist per domain of what needs evaluation; reasoning has a normalized set of info with validation across knowns/unknowns as SoA"). The cheap structural alternative to E-CALIBRATE-RELIABILITY-PSYCHOMETRICALLY — complementary, not competing. + +**The reframe:** reliability becomes STRUCTURAL + PRIOR, not a post-hoc statistic. Instead of measuring Cronbach α over a corpus, make it COVERAGE of a normalized evaluation set: +- **10-rung ladder = the normalized DEPTH axis.** `pearl_rung: u8` (1..=9, +0) ALREADY exists on `ThoughtCtx` (recipe_kernels.rs:36-37), proprioception, world_map, cognitive_shader (0..9), SPO triplet, CausalEdge64; `recipes::Recipe.tier` = Sun et al. reasoning-ladder difficulty. Doctrine: E-LADDER-SERVES-MAILBOX. The ladder is real + threaded; this REUSES it. +- **Per-domain checklist = the EVALUATION axis (x).** Each domain (class_id) declares WHICH rungs/items must be evaluated. The checklist is `class_id`-keyed and inherited along the HHTL path (like labels/columns/templates — the cognitive-risc-classes triangle), so domains don't hand-roll it. +- **knowns/unknowns as SoA = a presence bitmask** (= cognitive-risc-classes N3 "stable per-class bitmask, append-only, bit=field-N-populated"). Reliability-to-Commit = `required & present == required` — a SIMD batch-AND popcount over the SoA column, ONE cycle (the 0xFFF/facet-AND efficiency). + +**Why it's better-fit than calibration here:** (1) NO float, NO offline corpus, NO calibration pass — just bitmask coverage. (2) DISSOLVES the threshold problem the iron-rule-savant flagged: no 0.2/0.8/0.15/0.35 to calibrate OR Jirak-bound — the Rubicon 3-way maps onto COVERAGE STATE not a magnitude: Commit = required checklist covered; Plan = named known-UNKNOWNS remain (re-deliberate to fill them); Prune = checklist cannot be satisfied. (3) It's the `class_id`→checklist projection — one more payoff off the discriminator the SoA already needs (N1). + +**knowns vs unknowns = the enumerable-gap axis (MUL/Dunning-Kruger):** a *known-unknown* = an unchecked box you can NAME (→ Plan); the checklist makes unknowns ENUMERABLE — you can't be confidently-wrong about a box you know is empty. This is reliability-as-coverage doing the epistemic-humility work the φ⁻¹ ceiling did, but structurally + cheaply. + +**Honest tension (not glossed):** a checklist only covers known categories — an UNKNOWN-UNKNOWN (a required-but-UNLISTED item) is invisible to coverage. That is EXACTLY the cold-path VALIDITY gate's job (E-RELIABILITY-NOT-VALIDITY): the bring-up test (chess/Stockfish, domain ≥2) FALSIFIES the checklist — finds the box nobody listed. So coverage = cheap HOT reliability gate; Stockfish/oracle = cold validity gate that audits checklist COMPLETENESS. Complementary to the psychometric path: use checklist-coverage for the hot per-cycle gate; reserve Cronbach/ICC for offline auditing whether the checklist items themselves cohere. + +**Cheap build (vs the calibration build):** add a `class_id`-keyed per-domain checklist (which rungs/items required) + a `coverage` bitmask column on the SoA (knowns); the Rubicon gate = `required & known == required` AND-test + popcount for the Plan/gap signal. Reuses rung (exists), bitmask (N3 exists), class_id (N1, #439 hook exists). NO new float, NO thinking-engine dep — lighter than the psychometric slice. Sequence: this is the DEFAULT cheap gate; psychometric calibration is the heavier offline audit when a domain's checklist itself is in question. + +**Cross-ref:** E-RELIABILITY-NOT-VALIDITY (reliability vs validity split — this is the cheap reliability gate, Stockfish stays the validity gate); E-CALIBRATE-RELIABILITY-PSYCHOMETRICALLY (the heavier alternative this undercuts for the hot path); E-LADDER-SERVES-MAILBOX (the rung doctrine); cognitive-risc-classes N1 (class_id) + N3 (stable per-class presence bitmask) + the HHTL-inherited checklist; `recipe_kernels.rs:36 ThoughtCtx.rung`; `recipes::Recipe.tier`; MUL/Dunning-Kruger (known-unknown enumeration); iron-rule-savant VIOLATES-I-NOISE-FLOOR-JIRAK (dissolved, not calibrated). + +--- + +## 2026-05-30 — E-CALIBRATE-RELIABILITY-PSYCHOMETRICALLY — replace the hand-tuned Rubicon (f,c)/SD thresholds with MEASURED psychometric reliability (Cronbach α / ICC / Spearman / Pearson) — the existing crates, applied brutally to the gate + +**Status:** FINDING + BUILD-DIRECTION (user 2026-05-30: "be brutal and use psychometry calibration"). Follows directly from E-RELIABILITY-NOT-VALIDITY: if (f,c) is a RELIABILITY coefficient, calibrate it with real reliability statistics, don't hand-tune it. Resolves the iron-rule-savant's VIOLATES-I-NOISE-FLOOR-JIRAK (uncited 0.2/0.8/0.15/0.35 thresholds). + +**The existing psychometric machinery (grounded, file:line):** +- `thinking-engine/src/cronbach.rs` — `cronbach_alpha(items:&[&[f32]]) -> f32` (TESTED; α-identity=1.0 test) + `CronbachResult`/`cronbach_analysis` with the canonical bands (>0.90 excellent/redundant, 0.70-0.90 acceptable, <0.70 poor, <0.50 unacceptable) + `variance_agreement_scores`. Its OWN doc: "replaces the BF16 ±0.008 heuristic with empirical cross-model test" — i.e. it ALREADY swapped a hand-tuned threshold for a psychometric one. We do the same to the Rubicon gate. +- `jc/src/probe_p1_gamma_phase.rs::spearman_rho(&[usize],&[usize]) -> f64` (rank correlation; identity=1/reverse=-1 tested). +- `thinking-engine/examples/codebook_pearson.rs` (Pearson); `calibrate_lenses.rs` (Spearman ρ + ICC); `reencode_safety.rs`/`ground_truth.rs` (the calibration family). +- bgz-tensor calibration suite: `bin/cam_pq_calibrate.rs`, `quality.rs`, `variance_audit.rs`, `similarity.rs`. + +**The brutal move:** the Rubicon RELIABILITY gate (Evaluation→{Commit|Plan|Prune}) thresholds — currently hand-tuned `(f,c)` expectation + CollapseGate SD (FLOW<0.15/HOLD/BLOCK>0.35) + recipe_kernels 0.2/0.8 + the style confidence_thresholds (Skeptical 0.95 in learning/cognitive_styles) — get CALIBRATED, not guessed: +- **Cronbach α** = internal consistency of the witness arc / multi-recipe / multi-lens measurement. A belief is reliable-enough-to-Commit when the items (recipe outcomes / lens distances / witness emissions) cohere (α above a measured band), not when c>0.8 by fiat. Per-mailbox or per-cohort α over the CausalEdge64 (f,c) emission arc. +- **ICC** = inter-rater (inter-recipe / inter-mailbox) agreement — the cross-mailbox consensus the a2a_blackboard quorum needs. +- **Spearman/Pearson** = does the reliability ranking track an external criterion (the validity bridge — Spearman vs Stockfish/oracle ranking; this is where reliability MEETS validity, measured not assumed). + +**Float-boundary doctrine preserved (your CAM-PQ rule):** psychometric calibration is OFFLINE FLOAT (Cronbach/ICC/Pearson over a corpus, in thinking-engine — heavy, std), emitting a FROZEN threshold artifact the HOT path reads as an integer/const. Same shape as jc certifies the codebook offline → aerial reads online. So the calibrated reliability bands replace the hand-tuned consts; the hot Rubicon gate stays integer/cheap. + +**Dependency boundary (respect):** contract + planner are ZERO-/light-dep and do NOT dep thinking-engine. Calibration lives at the cognitive-shader-driver / thinking-engine layer (the bridge, thinking-engine behind a feature gate). The CONTRACT carries only the calibrated threshold CONSTANT (a number with a citation); the calibration PRODUCES it. So: thinking-engine calibrates (offline) → emits bands → contract const (cited) → planner/Rubicon reads. No new contract dep. + +**This makes the R-GATE probe rigorous:** instead of "do Analytical vs Creative differ" (necessary-not-sufficient), the probe becomes "compute Cronbach α / ICC on a real witness-trace corpus per style; does the MEASURED reliability band change the Commit/Plan/Prune outcome?" — a psychometric, citable pass/fail, satisfying I-NOISE-FLOOR-JIRAK (Jirak-bounded where the band needs a significance floor). + +**Honest scope:** this is a DIRECTION (the calcs exist + the boundary is clear), not a built slice. First cut would be a thinking-engine `reliability_calibration` that runs cronbach_alpha over a recipe/witness corpus and emits the bands; then cite them in the contract const that replaces 0.2/0.8/0.15/0.35. Sequenced after the R-GATE probe proves the gate is non-cosmetic on a live trace. + +**Cross-ref:** E-RELIABILITY-NOT-VALIDITY (why calibrate reliability); iron-rule-savant VIOLATES-I-NOISE-FLOOR-JIRAK (the uncited thresholds this fixes); I-NOISE-FLOOR-JIRAK (Berry-Esseen significance floor for the bands); `thinking-engine/cronbach.rs` (the α impl + "replaces hand-tuned heuristic" precedent); `jc/probe_p1_gamma_phase.rs spearman_rho`; bgz-tensor cam_pq_calibrate/quality; faiss-homology-cam-pq (offline-float→online-integer boundary); R-GATE probe; recipe_kernels SD_FLOW/SD_BLOCK; learning/cognitive_styles confidence_threshold. + +--- + +## 2026-05-30 — FIX (council follow-through): StyleStrategy de-theatred — resolve_style decodes the 23D vector; reliability_of is the R-GATE measurable; plan() honestly labeled pure-passthrough + +**Status:** SHIPPED-in-PR #439 (fixes the theater the brutally-honest-tester caught in D-MBX-A6-P3a). Probe-first per reviewers; reliability-not-validity framing per E-RELIABILITY-NOT-VALIDITY. + +Three honest fixes to the no-op #439 shipped: +1. **`resolve_style` now DECODES the 23D style vector** (idx 4=analytical/3=creative/0=depth, the `selector.rs::style_alignment` convention) → dominant-axis ThinkingStyle. Kills the constant-`DEFAULT_STYLE` bug (recipe selection was identical for every query). NOTE: 23D planner vector, NOT the contract i4-32D `style_vector`/`StyleRecipe` surface (separate, deferred). +2. **`reliability_of(style, ctx) -> f32`** — the R-GATE MEASURABLE: runs style-selected recipe Tactics over a ThoughtCtx, returns accumulated confidence ∈[0,1]. RELIABILITY (settledness), NOT validity (external/post-commit) per E-RELIABILITY-NOT-VALIDITY. Pure: no plan mutation, no commit. +3. **`plan()` honestly labeled pure-passthrough** — computes reliability, emits NOTHING (no faked KanbanMove the planner can't build pre-A6-overhaul). The dead-store theater is gone; the comment now states the truth. + +**Probe-first (reviewers' rule honored):** test `r_gate_reliability_varies_by_style` is the R-GATE probe written BEFORE any Rubicon gate field — asserts Analytical vs Creative select distinct mechanisms (TruthAwareInference vs StructuralDivergence) so a style-conditioned gate is non-cosmetic. test `resolve_style_decodes_the_23d_vector_not_constant_default` proves the bug is fixed. test `plan_is_pure_passthrough_until_emit_edge_lands` asserts plan stays None (no theater). 5 style_strategy tests + full planner lib green; fmt-clean (ran BEFORE commit). + +**Still deferred (honestly, NOT shipped):** the emit edge (plan()→KanbanMove) gated on the D-MBX-A6 planner-output overhaul; truth-gating the Rubicon transition (only if R-GATE proves it changes an outcome on a real witness trace — the in-test mechanism-distinctness is necessary-not-sufficient; the full probe needs a live trace); `try_advance_phase` still has no production `MailboxSoaOwner` impl (separate cognitive-shader-driver slice). + +**Cross-ref:** E-COUNCIL-SYNTHESIS (the theater this fixes); E-RELIABILITY-NOT-VALIDITY (the measurable's framing); #439 D-MBX-A6-P3a; `style_strategy.rs` (resolve_style/reliability_of); `selector.rs:137` (23D convention); D-MBX-A6 (the emit-edge home). + +--- + +## 2026-05-30 — E-RELIABILITY-NOT-VALIDITY — the substrate's NARS (f,c) "truth" measures RELIABILITY (consistency/settledness/consensus), not VALIDITY (ground-truth correspondence); validity is conferred externally at/after the Rubicon Commit. "Truth" is a wisdom marker in disguise. + +**Status:** FINDING (user-stated 2026-05-30, epistemic reframe). Corrects the "truth gate" mislabel propagated by the truth-architect council angle + this session. + +**The measurement-theory answer (validity vs reliability):** +- NARS `(f,c)` is reliability machinery: `confidence c = w/(w+k)` = amount of accumulated agreeing evidence relative to horizon k = a RELIABILITY COEFFICIENT (same family as Cronbach's α — workspace ships `thinking-engine/cronbach.rs` + ICC; both reliability stats; I-NOISE-FLOOR-JIRAK Berry-Esseen = reliable-above-noise = reliability too). `frequency f = w+/w` = consensus value IN EXPERIENCE. `expectation = c·(f−0.5)+0.5` = reliability-weighted consensus. +- It does NOT measure VALIDITY (correspondence to ground truth). NARS is non-axiomatic / experience-grounded BY CONSTRUCTION — it measures whether the system's own witnessing COHERES (reliability), never whether it's SO (validity). The chess bring-up test is the tell: "ground truth is a Stockfish call away" — the substrate emits high-(f,c) GM-FLAVORED candidates (reliable/consensus-strong); STOCKFISH is the validity oracle. The substrate can be confidently, consistently WRONG. +- **Reliability is necessary-but-not-sufficient for validity** (classic psychometrics). The substrate cannot tell a reliable-true from a reliable-false belief from the inside. ⇒ "truth hasn't been validated yet" is exact. + +**Why "wisdom marker in disguise":** Wisdom (Staunen×Wisdom qualia, The Click) = epistemic SETTLEDNESS + humility ("how well do I hold this", never "is it true"). Confidence with the φ⁻¹ ceiling ("permanent humility", c<1 always) IS a wisdom measure. Labeling it "truth" is a category slip — it's the wisdom/reliability axis wearing truth's name. "Crystallized knowledge committed in the end" = reliability accumulates → Rubicon COMMIT calcifies it into a durable fact → only post-commit/externally is validity assessable. COMMIT ≠ VALIDATION; commit = crystallization of reliability; validation is downstream. + +**Design consequence (splits the gate I/truth-architect mislabeled into TWO at two clocks):** +- RELIABILITY gate (HOT, Evaluation→Commit): `(f,c)` expectation + CollapseGate SD `gate_state()`. Plan/Prune = reliability-too-low/contradictory; Commit = crystallize a reliable belief. Shader-speed. +- VALIDITY gate (COLD, AFTER commit): external oracle — Stockfish (bring-up) / GoBD audit / reciprocal A→B,B→A (recipe SDD#32) / FailureTicket→LLM (F>0.8). Cold-store-speed. = the two-clock decoupling, epistemically named. +- So `Evaluation→{Commit|Plan|Prune}` gates on RELIABILITY (settled enough to crystallize), NOT truth. A committed fact's validity is still PENDING the cold/external check. + +**Corrects R-GATE probe:** it must probe RELIABILITY thresholding, not "truth": does style-conditioned reliability threshold change the CRYSTALLIZATION outcome (Skeptical demands higher c → more Plan/re-deliberate; Creative crystallizes at lower c)? Pass = ≥1 differing terminal. Validity is OUT of this probe — it's the separate post-commit external gate (the Stockfish bring-up IS the validity gate, already planned). + +**Cross-ref:** E-COUNCIL-SYNTHESIS (the truth-architect "truth gate" this corrects); The Click (Staunen×Wisdom, φ⁻¹ ceiling = permanent humility, FailureTicket); cognitive-risc-core bring-up test (Stockfish = validity oracle); `thinking-engine/cronbach.rs` (reliability stat); I-NOISE-FLOOR-JIRAK; `spo/truth.rs TruthValue`; recipe SDD#32 (reciprocal validation); Rubicon Commit = calcify; R-GATE probe. + +--- + +## 2026-05-30 — COUNCIL SYNTHESIS (catalyst, 7 savants): #439 StyleStrategy is PASSTHROUGH THEATER; the real target = thinking-style → PlannerDTO(=KanbanMove) → truth-gated Rubicon scheduling. Honest correction + the wiring map. + +**Status:** FINDING (council-catalyzed synthesis 2026-05-30; 7 savants: iron-rule, dto-soa, creative-explorer, cascade-impact, prior-art, brutally-honest-tester, truth-architect). Corrects the overstated D-MBX-A6-P3a commit. The council ENRICHED (not gated) — it surfaced theater I shipped + the real design. + +**HONEST CORRECTION (brutally-honest-tester, confirmed by source):** my #439 `StyleStrategy` commit "wires thinking-styles as the planning substrate" OVERSTATED a no-op: +- `StyleStrategy::plan()` runs `recipe_kernels` then DISCARDS the `Outcome` + mutated `ThoughtCtx`; returns `input` byte-identical (dead-store). Emits NO KanbanMove/Candidate. +- The test asserts only `out.is_ok()` — green on a no-op = AP6 theater; nothing asserts the plan changed. +- `resolve_style()` discards `ctx.thinking_style`, always returns DEFAULT_STYLE → recipe selection is CONSTANT for every query. +- `try_advance_phase` shipped-but-DEAD (only FakeSoa calls it; no real `MailboxSoA: MailboxSoaOwner`). `KanbanMove.exec` write-only (always Native, never read). Libet −550ms only in tests. +⇒ #439's contract types (KanbanColumn DAG, try_advance_phase) are SOUND in isolation; the StyleStrategy "integration" is two disjoint islands with no connecting edge. Recorded as TECH-DEBT, not silently left. + +**DON'T REVIVE recipe.rs (4 savants unanimous):** iron-rule = VIOLATES-I-NOISE-FLOOR-JIRAK (uncited 0.2/0.8/0.1 thresholds) + 32-vs-33 dim hazard; dto-soa = FIFTH-COLUMN-VIOLATION (3rd/4th parallel "style" representation); creative-explorer = DELETE it, the keystone is the argmax decode; cascade = full revive is CROSS-CRATE (jit::StyleRegistry trait ext hits 17 deps + reopens atoms.rs pack/unpack todos + reroutes shipped #439). recipe.rs `StyleRecipe` is dead/`todo!()`/unexported. + +**THE KEYSTONE (creative-explorer + prior-art, the real target):** the canonical style→schedule pipeline is ONE arrow, NO StyleRecipe: +`I4x32 (composition) → argmax → ThinkingStyle (identity, 6-bit, τ) → cluster()→Mechanism (selection) → tau()→KernelHandle (exec)` ; the deferred `I4x32→argmax→ThinkingStyle` decode (style_strategy.rs:95) IS the unifying keystone the session circled. Four altitudes = four existing SoA columns (topic/angle/thinking/planner), not a 5th layer. + +**"PlannerDTO" is DRIFT (prior-art):** no canonical type — it's PlanResult / PlanInput / Candidate+KanbanMove unnamed. Do NOT mint a new PlannerDTO. Canonical home = D-MBX-A6 ("planner output = KanbanMove"). The triangle thinking-style→PlannerDTO→Rubicon is A6's STATED PREMISE; 2 of 3 edges shipped (#437/#439), missing edge = `Outcome→KanbanMove` emit (the A6-P3 NEXT node). + +**TRUTH-GATED RUBICON (truth-architect, the missing epistemic layer):** `try_advance_phase` gates on DAG legality ONLY, never truth (f,c) — yet the SoA owner exposes `edges_raw()` (CausalEdge64 f/c) at the transition site and ignores it. Deciding predicates exist UNUSED: `TruthGate::passes(expectation)`, `CausalEdge64::counterfactual_ready` (ZERO callers). Evaluation→{Commit|Plan|Prune} = the epistemic 3-way (commit-iff-high-conf / prune-iff-low / plan-iff-contradictory) maps 1:1 onto expectation bands. Style→threshold link exists in the WRONG crate (`learning/cognitive_styles.rs` fp[18] confidence_threshold: Skeptical 0.95). `ThoughtCtx.gate_state()` (FLOW/HOLD/BLOCK) is the natural Planning→{CognitiveWork|Prune} gate but lives on ThoughtCtx, unjoined to edges_raw() f/c — TWO truth registers, unreconciled. + +**THE NEXT BUILD (synthesized, replaces P3b):** the real A6-P3 slice = make StyleStrategy ACTUALLY schedule: +1. Implement `resolve_style`: decode `ctx.thinking_style` i4-32D vec → argmax `ThinkingStyle` (the keystone; kills the constant-DEFAULT_STYLE bug). +2. `StyleStrategy::plan()` must EMIT — thread the ThoughtCtx outcome into a `KanbanMove` (or `PlanResult` field), and a test asserting the plan CHANGED by style (Skeptical≠Creative output). Kills the passthrough theater. +3. Truth-gate the transition: thread `expectation()`/`gate_state()` into the Evaluation→terminal choice. +**PROBE FIRST (both reviewers):** R-GATE — does threading expectation() change ANY column outcome vs DAG-only on a fixed witness trace (Skeptical 0.95 vs Creative 0.6)? Pass = ≥1 differing terminal; Fail = cosmetic. Do NOT add a threshold field until the number exists. Probe → then wire. + +**Cross-ref:** #439 (D-MBX-A6-P3a, corrected); D-MBX-A6 (KanbanMove output overhaul, the canonical home); `style_strategy.rs:95,136`; `soa_view::try_advance_phase`; `contract::{thinking(tau/argmax), atoms(I4x32/CANONICAL_ATOMS[33]), recipe_kernels(gate_state), recipes}`; `causal-edge counterfactual_ready`; `spo/truth.rs TruthGate`; `learning/cognitive_styles.rs`; recipe.rs (do-not-revive); E-VERSION-ARC / E-SUBSTRATE-IS-THE-SCHEDULER. + +--- + +## 2026-05-30 — FINDING (via #433 ref): three recipe modules; `contract::recipe::StyleRecipe` is the CANONICAL i4-32D style↔atom↔JIT home but is STALE+ORPHANED (unblocked yet never migrated/exported). StyleStrategy (#439) correctly built on the LIVE recipes/recipe_kernels — adjacent, not wrong. + +**Status:** FINDING (grounded, prompted by user "check 433"). Tech-debt + a scoped follow-up; NOT a #439 defect. + +**Three distinct recipe modules in contract — disambiguated:** +1. `contract::recipe.rs` (SINGULAR) — **"Composition layer: thinking-style recipes"**: `StyleRecipe { name, weights:&[(Atom,i8)], composition:Option }` + `PersonaRecipe` (+β/thresholds) → `KernelHandle` (Cranelift). The canonical **atoms(i4-32D) → StyleRecipe → PersonaRecipe → JIT** ladder (E-LADDER-SERVES-MAILBOX §2). "JIT target is the recipe, not the per-atom dot"; "Elixir-style open/closed hot-load split". = EXACTLY the i4-32D-style + Cranelift-template substrate the user named. +2. `contract::recipes.rs` (PLURAL) — the 34 reasoning-TACTIC catalogue (RTE/HTD/…). +3. `contract::recipe_kernels.rs` — the 34 executable `Tactic` kernels. + (+ a 4th, unrelated: `lance-graph-ontology::odoo_blueprint::OdooStyleRecipe` — #433's Odoo-codegen D-Atom fingerprint, renamed from StyleRecipe to avoid THIS collision.) + +**The rot (the real find):** `recipe.rs`'s blocker D-ATOM-1 **HAS LANDED** — `contract::atoms` is real now (`I4x32` struct, `Atom`, `CANONICAL_ATOMS:[Atom;33]`). But `recipe.rs` was NEVER migrated: still uses `I4x32Stub=[i8;32]`/`AtomStub=u8` forward-stubs, AND is **NOT exported in lib.rs** (`pub mod recipe;` absent; only atoms/recipes/recipe_kernels exported). So `StyleRecipe`/`PersonaRecipe` is **dead code**: defined, stub-blocked despite the blocker resolving, unexported, zero consumers. Classic "blocker cleared, dependent never updated" debt — the kind #433's `prior-art-savant`/`dto-soa-savant` gate exists to catch. + +**Impact on D-MBX-A6-P3a (#439) — adjacent, not wrong:** `StyleStrategy` wired to the LIVE `recipes`+`recipe_kernels` (reasoning tactics). That was the correct shippable choice (those are exported + tested; `recipe.rs` is not). It is NOT the canonical i4-32D `StyleRecipe` home, and it does not yet use `atoms::I4x32`/`PersonaRecipe`. So #439 stands as-is (correct, green); the canonical-home migration is a SEPARATE slice, not a #439 fix. + +**Scoped follow-up (proposed, NOT in #439):** "D-MBX-A6-P3b — revive recipe.rs": migrate `I4x32Stub`→`atoms::I4x32`, `AtomStub`→`atoms::Atom`, export `pub mod recipe;` in lib.rs, then route `StyleStrategy` through `StyleRecipe` (style = i4-32D atom composition) → `PersonaRecipe` → the τ/jit `KernelHandle`. THAT closes the real i4-32D-style → Cranelift-template loop the user named. The epiphany-brainstorm-council (#433) is available here as a CATALYST (not a gate): invoking dto-soa-savant + creative-explorer would ENRICH the i4-32D revive with angles, not authorize it. P3b can proceed without it; the council is an amplifier to reach for, not a checkpoint to clear. + +**Also from #433 (process) — CORRECTED 2026-05-30:** the epiphany-brainstorm-council is a CATALYST for new insight (spawn 4-7 Opus savant lenses to GENERATE divergent reframings / cross-impact), NOT a guardian of what may be recorded. Its LAND/REVISE/REJECT is synthesis output, not permission. So this session's tee-prepends are NOT 'pending approval' at any gate — they are deposited priors. The council is something to INVOKE to enrich an idea (esp. the derived ones: E-FIREFLY-*, E-0xFFF, E-POLYGLOT-*), never a checkpoint they must pass. (Supersedes the earlier 'council-gated' framing in this same entry.) + +**Cross-ref:** #433 (style_recipe + epiphany-brainstorm-council + 5 savant cards); `contract::recipe.rs` (StyleRecipe/PersonaRecipe, stale); `contract::atoms` (D-ATOM-1 landed, #411); `contract::recipes`/`recipe_kernels` (what #439 uses); `OdooStyleRecipe` (ontology, the renamed-to-avoid-collision sibling); E-LADDER-SERVES-MAILBOX §2; D-MBX-A6-P3a (#439). + +--- + +## 2026-05-30 — SHIPPED-in-PR: D-MBX-A6-P3a — StyleStrategy (thinking-style planning substrate wired into the planner) + +**Status:** SHIPPED-in-PR #439 (builds on A6-P1 #437 / A6-P2). First live cut of D-MBX-A6-P3 consumer wiring. + +`lance-graph-planner` now CONSUMES the contract cognitive substrate (it referenced none before): new `strategy::style_strategy::StyleStrategy` (#18 in `default_strategies()`) resolves the active `ThinkingStyle` → `cluster()` → `cluster_mechanism()` → selects which of the 34 `recipe_kernels::Tactic` fire over a `ThoughtCtx` built from `PlanContext` markers (`free_will_modifier`→temperature). The **style selects the recipe** (cluster→mechanism), not a hardcoded id list — and carries `style.tau()` (the JIT macro address, grounds `ExecTarget::Jit`). Mirrors `mul::escalation` (thin planner module over zero-dep contract). Planner already deps contract; NO new dep edge; contract stays zero-dep (no circular-dep, the AriGraph trap avoided). + +Verified: 3 new tests (analytical→truth-aware selection; every cluster→mechanism total over RECIPES; plan() passes through) + 192 planner lib green; rustfmt-clean; rebased onto main post-#438 (arm-discovery, no collision). + +**Deferred (the rest of A6-P3, per the design map):** i4-32D `thinking_style` vec → argmax `ThinkingStyle` decode; `Outcome`→`Candidate`/`KanbanMove` adapter; `tau`→`JitTemplate`→Cranelift `KernelHandle` compile (the real ExecTarget::Jit path); recipe-outcome→membrane commit via `CognitiveOpKind::MetaWordCommit` (OrchestrationBridge, never callcenter→planner); pre-recipe-fire RBAC (today pre-commit only); `class_id`→`recipe_id` resolver (ontology gap). + +**Cross-ref:** `planner::strategy::style_strategy`; `contract::{thinking(tau/cluster),recipes,recipe_kernels}`; `mul::escalation` precedent (#411); `ExecTarget::{Jit,Elixir}` (#439); `OntologyRegistry::attach_thinking_style` (registry.rs:311, the existing class→style seam); D-MBX-A6-P3. + +--- + +## 2026-05-30 — RE-CENTER: thinking-styles ARE the planning substrate — i4-32D style → τ address → JITson/Cranelift template → KernelHandle; recipes are what styles SELECT. (corrects the recipe-centric framing of the build target above) + +**Status:** BUILD-GRADE correction (user 2026-05-30: "thinking styles are the most important planning substrate… i4-32D thinking styles and jit/JITson cranelift compiler templates"). Re-centers the default-recipe build target one entry up: styles are the dispatcher, recipes are the tactics dispatched. Grounded by grep (file:line). + +**The full planning-substrate pipeline — ALL SHIPPED, just unwired into the mailbox planner:** +- `contract::thinking` — **36 thinking styles / 6 clusters**, `ThinkingStyle::ALL:[_;36]`, each mapped to a **τ (tau) macro address** for JIT (`thinking.rs:19`): Analytical τ0x40-4F, Creative τ0xA0, Empathic τ0x80, Direct τ0x60, Exploratory τ0x20, Meta τ0xC0. Plus `StyleCluster`(6)→`PlannerCluster`(4) via `to_planner_cluster()`, `FieldModulation`, `ScanParams`. The i4-32D form = the compact SIMD selection vector (32×i4 = 16B, same width family as CausalEdge64/QualiaI4_16D — hot-path AND-able). +- `contract::jit` — closes the loop: `JitTemplate` (JITSON JSON) → `JitCompiler::compile` (ndarray Cranelift engine) → `KernelHandle{fn_ptr:*const u8}` (native code, Send+Sync) → `StyleRegistry` kernel cache (param_hash → cached kernel). n8n-rs implements `CompiledStyleRegistry`; ndarray = the jitson engine; lance-graph produces templates (`jitson_kernel.rs`). +- `contract::recipes`/`recipe_kernels` — the 34 tactics styles SELECT (the prior build-target entry). + +**The pipeline (one line):** `i4-32D thinking style → τ address → JitTemplate(JITSON) → Cranelift compile → KernelHandle(native fn_ptr) → cached by StyleRegistry`; the style ALSO selects which recipe/Tactic runs over ThoughtCtx/SoA. + +**This grounds the #439 ExecTarget variants concretely:** `ExecTarget::Jit` = the τ→template→Cranelift→KernelHandle path (compiled). `ExecTarget::Elixir` = the recipe_kernels interpreted "Elixir-like" layer (the un-JITted fallback). The two exec targets I shipped now have their actual machinery identified: JIT = jit.rs, Elixir = recipe_kernels.rs. + +**Re-centered build target (corrects the recipe-centric framing):** the mailbox planner's default planning substrate = **thinking-style selection → τ → (cached KernelHandle | recipe Tactic) → over the SoA**. Styles dispatch; τ addresses; JITson compiles; recipes are the tactic catalogue. The minimal first slice should wire STYLE SELECTION first (the planner picks a ThinkingStyle → cluster → ScanParams), then recipe/kernel dispatch — not recipes in isolation. The Opus design map (in flight) was launched recipe-centric; re-center it on thinking+jit at synthesis. + +**Gaps to verify in the map:** does the planner already use ThinkingStyle (it had its own 12; contract says it maps via `to_planner_cluster()`)? Is `StyleRegistry`/`JitCompiler` wired to a real Cranelift engine (ndarray) or stub? Is the τ-address→template path built or spec? (jit.rs says lance-graph `jitson_kernel.rs` produces templates — verify it exists.) + +**Cross-ref:** `contract::thinking` (36 styles/τ/i4-32D/FieldModulation); `contract::jit` (JitTemplate/JitCompiler/KernelHandle/StyleRegistry); `contract::recipes`+`recipe_kernels`; `ExecTarget::{Jit,Elixir}` (#439); the prior "default recipes" build-target entry (this re-centers it); D-MBX-A6-P3; ndarray jitson/Cranelift engine. + +--- + +## 2026-05-30 — BUILD TARGET: default recipes for the mailbox planner — the DTOs already exist, only the WIRING is missing + +**Status:** BUILD-GRADE (user-directed 2026-05-30: "we need default recipes for our mailbox planner… the DTOs are already there to wire"). Graduated from brainstorm to concrete. Grounded by grep (file:line below); Opus design map in flight. + +**The finding — everything exists, nothing is wired:** +- `contract::recipes` — `Recipe` struct + `RECIPES:[Recipe;34]` (RTE/HTD/RCR/… id 1..=34, each with tier/mechanism/bucket/spo2cubed/substrate) + `recipe(id)`/`recipe_by_code`/`by_mechanism`/`causal` lookups. = the DEFAULT RECIPE CATALOGUE, locked. +- `contract::recipe_kernels` — the `Tactic` trait + `ThoughtCtx` (sd/free_energy/dissonance/temperature/confidence/rung/candidates/beliefs) + `Outcome`; "the Elixir-like recipe layer: 34 hot-dispatchable units, registry-routed by id." = the EXECUTABLE default recipes. +- **GAP (verified):** `lance-graph-planner` references NEITHER `recipes` nor `recipe_kernels` nor `Tactic` anywhere. The mailbox planner has NO default recipes wired. This is the build. +- **Consumers ready (DTOs exist):** `callcenter::{OntologyDto/EntityTypeDto/PropertyDto/LinkTypeDto/ActionTypeDto, MembraneRegistry, LanceMembrane, cognitive_bridge_gate, policy, rls}` = the committing membrane; `ontology::{OntologyRegistry, MappingRow, SchemaPtr, enumerate_first_with_entity_type_id}` = OGIT classes on SoA; `rbac::{Policy, Role(can_read/can_write/can_act), Operation, evaluate, AccessDecision}` = the gate. + +**The chain to wire:** recipe (contract `Tactic`) → candidate (planner `CandidatePool`) → rbac gate (`rbac::evaluate`) → ontology class resolve (`OntologyRegistry` by entity_type_id) → membrane commit (`callcenter::LanceMembrane`). Recipe substrate + consumer DTOs exist; the SEAMS between them are the gap. + +**This is the "Elixir-like template" layer made real** (connects the GEL/ExecTarget::Elixir thread): recipe_kernels is literally documented as "the Elixir-like recipe layer." So the default-recipe wiring IS the planner-strategy/template work, grounded in shipped code — not the speculative PlanPacket. Centered on AST/SoA markers (ThoughtCtx = our substrate markers), per "the mailbox understands AST." + +**Maps to D-MBX:** part of / sibling to D-MBX-A6-P3 (planner consumer wiring). Awaiting Opus map for: dependency directions (keep contract zero-dep; circular-dep risk like AriGraph↔planner), the minimal first slice (RecipeStrategy in default_strategies vs default_recipes() selector), and the rbac-gate placement (pre-recipe vs pre-commit). + +**Cross-ref:** `contract::recipes`/`recipe_kernels` (recipe catalogue + Tactic); `planner::{traits,api,cache/candidate_pool,strategy/mod}`; `callcenter::{lance_membrane,ontology_dto,cognitive_bridge_gate}`; `ontology::registry`; `rbac::{policy,role,access}`; D-MBX-A6-P3; `ExecTarget::Elixir` (#439); recipe_kernels "Elixir-like" doc. + +--- + +## 2026-05-30 — BRAINSTORM: E-FIREFLY-PACKET-IS-THE-LOCATION-TRANSPARENT-PLAN — (adjacent inspiration) backport firefly's packet-executor as the in-mailbox PLANNING SUBSTRATE; the same packet distributes cross-server via ONE gRPC hop (BEAM location transparency) + +**Status:** BRAINSTORM / adjacent-inspiration (user framing 2026-05-30: "just brainstorming, you might only find an adjacent inspiration"). NOT a ratified decision — a candidate direction to remember, not a committed build plan. Downgraded from an over-eager "DECISION" label. The forks/slice below are sketch options, not a sequenced commitment. + +**The backport:** the mailbox's PLANNING SUBSTRATE = firefly's packet-flows-through-a-node-graph executor. A plan = a graph of plan-nodes; planning/candidate-generation = a packet hopping through it (validate→transform→persist shape), TTL-bounded, accumulating `trace[]`. Replaces a bespoke planner-tick loop with the firefly model. Each hop = a phase transition = a version commit (`E-VERSION-ARC-IS-THE-KANBAN`); the `trace[]` = the witness arc (R4); `ExecTarget` (#439) = how each node executes; transport = orthogonal. + +**The punchline (why it's elegant): distribution = ONE gRPC packet.** Because the planning substrate IS packet-based, the in-mailbox plan state is ALREADY a complete serializable execution context (`FireflyPacket`: 80B LE header + 1250B resonance + ctx + trace + ttl). Cross-server distribution needs NO new abstraction: serialize the packet once → gRPC to another server instance → it deserializes and continues the hop. firefly's `hop(current,next)` is already location-transparent (target = an address; a routing table decides local vs remote). = **BEAM location transparency** (send to a PID; local-or-remote is just transport) — and exactly what ractor embodies (Boxed-local / Serialized-remote). + +**Reconciles with `E-RACTOR-WANTS-TOKIO-NOT-GRPC` (NOT a contradiction):** +- INSIDE hop (local node) = move the struct, zero-serialize (Tokio/in-mem). gRPC-slower-than-Tokio STILL holds; you never pay it locally. +- OUTSIDE hop (crosses a server) = serialize ONCE → gRPC (or cluster-TCP / Flight). Pay serialization only at the boundary, amortized (two-clock decoupling). The packet is distribute-ready BY CONSTRUCTION; "only a gRPC packet" is true because the planning DTO already speaks packets. +This realizes the long-standing inside/outside "location-transparency" synergy (the §1.1 candidate) concretely: ONE packet, two transports. + +**Honesty / nuance:** "only a gRPC packet" is true at the abstraction level; the resonance is ~1.25KB + ctx, so a cross-server hop has real serialize cost — paid only at the boundary, not per local hop. The win is no NEW distribution layer, not zero cost. + +**Design forks to decide BEFORE building (the slice gates on these):** +1. **DTO:** extend `KanbanMove` into a `PlanPacket` (header + SoA-resonance ref + op + trace + ttl), or a new contract type beside it? (KanbanMove is the per-move record; PlanPacket is the hopping execution context — likely a NEW type that CARRIES a KanbanMove per hop.) +2. **Address width:** 0xFFF (12-bit, inside, cache-aligned) vs firefly's 256-bit SHA at the durable/cross-server boundary (`E-0xFFF` / firefly divergence). Likely: 0xFFF local, SHA-CAM at the gRPC hop (mirrors witness-materialization-at-commit). +3. **Payload by-ref vs by-value:** inside, the packet must reference the SoA (R1 "never serialize the SoA") — carry a `MailboxSoaView` handle / row-range, NOT a copied resonance. Only the gRPC hop snapshots. (This is the R1 + R5 hot/cold-snapshot rule applied to the packet.) +4. **Serialize format for the gRPC hop:** protobuf (tonic) vs firefly's LE-packed header + hex. (gRPC/tonic already in the lab surface; promote at the genuine boundary.) +5. **Routing table:** where local-vs-remote node resolution lives (the pointer table / mailbox index). + +**Minimal first slice (proposed, fork-1/3 dependent):** a zero-dep `contract::plan_packet::PlanPacket` — routing header (src/tgt address, ttl, seq, flags) + an op + a `witness_chain_position` trace + a BORROWED SoA reference (not owned resonance), with `hop()` (local, move) and a `to_wire()`/`from_wire()` boundary (the only serialize point). Carries a `KanbanMove` per phase. Local hop = struct move; `to_wire` only at a remote hop. Unit-tested with a fake routing table (local vs remote). Defers the actual gRPC service (lab/outside) and the node-graph executor (planner crate) to follow-ups. + +**Maps to D-MBX:** this IS the A6-P3 planning-substrate design (was "bespoke planner loop") + the distribution story for D-MBX-9's cross-server case. Sequenced after the address-width decision (relates P-B canonical 0xFFF type). + +**Cross-ref:** `/home/user/firefly/rust/src/dto/packet.rs` (hop/pack_header — the location-transparent primitive); `E-FIREFLY-IS-GEL-OUTSIDE-PROTOTYPE`; `E-RACTOR-WANTS-TOKIO-NOT-GRPC`; `E-VERSION-ARC-IS-THE-KANBAN`; `ExecTarget`/`KanbanMove`/`MailboxSoaView` (#437/#439); RISC core inv 4/5/7 (hot-cold, snapshot, two-clock); D-MBX-A6-P3 / D-MBX-9. + +--- + +## 2026-05-30 — E-FIREFLY-IS-GEL-OUTSIDE-PROTOTYPE — adaworldapi/firefly is a runnable GEL substrate + the OUTSIDE-transport prototype; its FireflyPacket = the serialized cross-process Baton; transport is Redis-mRNA (NOT gRPC) + +**Status:** FINDING (read firefly source 2026-05-30, cloned read-only to /home/user/firefly — PUBLIC repo, NOT in the 16-repo authorized scope; do not push to it). User: "my toy Ballista executing gRPC packets with 0xFFF as transport." + +**What firefly IS:** a runnable (Railway-deployed) "Universal Executable Substrate" — `BOOT.md`: "doesn't compile code, it RUNS compiled graphs"; per-language compilers (RUBBERDUCK/Ruby, PYTHONIC, JAVELIN/Java, RUSTLER/Rust) all emit "1.25KB Hamming nodes." **= GEL made concrete** (`E-GEL-IS-THE-GRAPH-SUBSTRATE`): any language → uniform graph node → executable substrate. Firefly is a parallel, smaller, deployed prototype of exactly what the D-MBX arc builds inside lance-graph. + +**Transport CORRECTION (honest):** the actual transport is **Redis streams + mRNA** (`docs/MRNA_TRANSPORT.md`, `rust/src/dto/packet.rs`), NOT gRPC. `FireflyPacket` routes via Redis `XADD`/`XREAD` on `firefly:node:{hash}` streams. "gRPC packets" in the user's framing = the intent/analog (RPC-style cross-process packets); the code uses Redis-stream mRNA. Either way it is the OUTSIDE (cross-process, serialized) layer — confirming `E-RACTOR-WANTS-TOKIO-NOT-GRPC`: inside=Tokio-Baton zero-serialize, outside=serialized packet (firefly's choice = Redis-mRNA; ractor's = cluster-TCP; lab = gRPC/Flight). + +**Concept-for-concept map (firefly ↔ lance-graph), the striking convergence:** +- `FireflyPacket` (80B header + 1250B resonance + JSON ctx) = the **serialized cross-process Baton** (`CollapseGateEmission` is the in-process LE tuple; FireflyPacket is its outside-the-process form). +- **80B LE routing header** (src/tgt = 32B SHA256 node addr, ttl u8, priority, flags u16, sequence u32, CRC32) = the "0xFFF as transport" layer — ADDRESSES route, payload rides. (firefly uses 256-bit SHA addresses, not 12-bit 0xFFF — a divergence: full content-hash vs compact 12-bit; see open Q.) +- **1250B = 10,000-bit Hamming, 4 zones**: Content[0:3000] / Process[3000:6000] / Qualia[6000:8000] / Context[8000:10000]. = the SAME 4-signature decomposition as BindSpace SoA column families (content/edge/qualia/meta). Independent convergence. +- `ttl` + `hop()` (decrement, append to `trace[]`) = the **witness arc / belief-state chain** (R4); TTL=0 → dead-letter = absorbing **Prune** terminal. +- Redis consumer-group scaling rules: **VALIDATE/TRANSFORM parallel (pure), PERSIST single (ordering)** = EXACTLY the hot-path-parallel vs commit-gate-single-writer split (RISC core invariant 4) — independently arrived at. +- stream-length **backpressure** = RISC core invariant 8. +- **Storage Trinity** (LanceDB vectors / DuckDB facts / Kuzu graph) = lance-graph's unify-on-Lance (firefly splits 3 engines where lance-graph + surrealdb-kv-lance unifies; a real architecture fork to note). + +**Why it matters for the arc:** firefly is the **OUTSIDE-transport reference implementation** + a working GEL executor. It validates (by independent convergence) the hot/cold split, the address-routes-payload-rides packet shape, the 4-zone resonance, and the witness-as-trace. ExecTarget-wise it is a distributed executor (Ballista-shaped): a `KanbanMove{exec=Distributed}` would lower to a FireflyPacket on the outside path. + +**Open questions / divergences to reconcile (NOT decided):** +1. **Address width:** firefly = 256-bit SHA256 node address; lance-graph 0xFFF = 12-bit aligned address. Full content-hash (collision-proof, big) vs compact 12-bit (cache-aligned, codebook-bounded). Which at which layer? (likely 0xFFF inside / SHA-CAM at the durable/cross-process boundary, mirroring the CAM-materialization-at-commit rule.) +2. **Transport:** Redis-mRNA (firefly) vs ractor-cluster-TCP vs Arrow-Flight-CAM — three OUTSIDE options; pick per deployment. +3. **Storage:** firefly trinity (3 engines) vs lance-graph unify-on-Lance. Reconcile or keep firefly as the polyglot-frontend ingest tier feeding the unified substrate. +4. firefly is OUTSIDE authorized scope — keep as read-only reference; do not push. + +**Cross-ref:** `/home/user/firefly/{BOOT.md,rust/src/dto/packet.rs,docs/MRNA_TRANSPORT.md,docs/ARCHITECTURE.md}`; `E-GEL-IS-THE-GRAPH-SUBSTRATE`; `E-RACTOR-WANTS-TOKIO-NOT-GRPC`; `E-0xFFF-IS-ONE-ALIGNED-ADDRESS`; CollapseGateEmission Baton; RISC core invariants 4/8; RUBBERDUCK (github.com/AdaWorldAPI/rubberduck, the Ruby→node compiler). + +--- + +## 2026-05-30 — FINDING: E-RACTOR-WANTS-TOKIO-NOT-GRPC — local ractor is a Box pointer-move over Tokio mpsc (zero serialize); gRPC is strictly slower and is LAB-ONLY. CAM/0xFFF-over-Flight is the cross-PROCESS path only + +**Status:** FINDING (grounded in ractor + cognitive-shader-driver source, 2026-05-30). Answers the user's transport question: does ractor want gRPC, or is it slower than Tokio? + +**ANSWER: For in-process mailboxes, Tokio mpsc beats gRPC by construction — gRPC is never the local transport.** Proof from ractor source: +- **Local (default, NO `cluster` feature):** a message is `BoxedMessage { msg: Box }` (`ractor/src/message.rs:62-63`), moved through a Tokio mpsc and recovered via `m.downcast::()` (`message.rs:102`). **Zero serialization** — a heap-boxed pointer move + downcast. Blanket `impl Message for T` (ractor CLAUDE.md) = any Rust type rides as-is, zero boilerplate. +- **Cluster (`cluster` feature):** the SAME enum is replaced by `SerializedMessage { args: Vec }` (`message.rs:30-57`) → serialize → TCP via `ractor_cluster` NodeServer/NodeSession. Note: ractor's OWN distributed transport is **raw TCP serialization, NOT gRPC.** + +**Why gRPC is strictly slower locally:** gRPC = protobuf encode + HTTP/2 framing + socket syscall + decode. The local Tokio path SKIPS all of it (pointer move). gRPC only earns its cost at a **process/node boundary** — and even there ractor's native answer is TCP serialization, not gRPC. So: never gRPC for same-process mailboxes; serialization (TCP or Flight) only when crossing a process. + +**The gRPC/Ballista/Flight + CAM/0xFFF occurrences (the user's first point):** these live in `cognitive-shader-driver/src/{grpc.rs,wire.rs}` — **LAB-ONLY** (`lab-vs-canonical-surface.md:52,54`: "Test-transport convenience… NEVER in production binary"), feature-gated (`Cargo.toml`: `grpc.rs` required-features=["grpc"], `tonic optional=true`). CAM-encoded-over-Flight/Ballista = the cross-PROCESS / distributed-DataFusion movement path, exactly where serialization is unavoidable anyway, and where 0xFFF/CAM codes are the COMPACT wire form (12-bit address / 6-byte CAM-PQ code instead of full vectors — Flight/Ballista carry the addresses, not the payloads). So CAM-over-Flight is the RIGHT tool for its layer (inter-process), and Tokio-Baton is the right tool for the hot in-process layer. + +**Maps to the inside/outside duality (this session's stance):** INSIDE (in-process, hot) = Tokio mpsc + Baton (`CollapseGateEmission` LE tuple), zero-serialize pointer move — the kanban hot path. OUTSIDE (cross-process, distributable) = ractor `cluster` TCP serialize OR CAM/0xFFF-over-Flight/Ballista for DataFusion-federated movement. gRPC sits with OUTSIDE+LAB, never INSIDE. Decision: keep ractor on local Tokio for the mailbox swarm; reserve serialized transport (prefer ractor_cluster TCP or Flight-CAM) for genuine process boundaries; gRPC stays lab-only. + +**Cross-ref:** `ractor/src/message.rs` (Boxed vs Serialized); ractor CLAUDE.md (blanket Message impl, cluster=TCP); `lab-vs-canonical-surface.md:52-54` (grpc/wire LAB-ONLY); `E-VERSION-ARC-IS-THE-KANBAN` (inside/outside); the earlier hot-path Tokio-cost finding; faiss-homology-cam-pq (CAM = compact address, the right Flight payload). + +--- + +## 2026-05-30 — CORRECTION: E-0xFFF-IS-ONE-ALIGNED-ADDRESS — the "4 distinct 4096s" are ONE deliberate 0xFFF (12-bit) address space, aligned for efficiency; not a magic-number coincidence. Corrects E-POLYGLOT-4096-IS-CONJECTURAL + +**Status:** CORRECTION (supersedes the "4096 = 4 distinct unrelated magic numbers, no canonical surface" claim in `E-POLYGLOT-4096-IS-CONJECTURAL`) + FINDING (user-stated 2026-05-30). + +**The correction (I was wrong):** I labeled the recurring 4096 as four unrelated coincidental magic numbers. It is **ONE deliberate 0xFFF / 12-bit address space**, and the surfaces were **aligned to it for efficiency** — not coincidence. The polyglot path addresses by 0xFFF; 4096 = 2^12 is the address width, so: +- deepnsm COCA vocab (4096 words = 12-bit rank), +- AutocompleteCache attention topology (64×64 = 4096 heads), +- BindSpace addressing (16 prefixes × 256 = 4096 slots), +- the COCA² distance matrix +…all land on **the same 12-bit index ON PURPOSE**, so a parsed symbol / vocab rank / attention head / SoA slot **co-index without translation**. The "incidentally also 4096" IS the alignment tell. + +**Why aligned (the efficiency):** one 0xFFF address space = no remap between stages (parse → vocab → head → SoA share the index); fits a mask, shift-addressable / AND-testable in a cycle (same "5 facets ≈ one u64, SIMD batch-AND over the SoA column" efficiency as wikidata-hhtl-load). This is the CAM addressing invariant (cognitive-risc-classes): content → fixed-width address → every layer indexes by it. 0xFFF is that fixed width. + +**What STILL stands from the prior finding:** the **end-to-end frontend→0xFFF→SoA wiring is still partial** — the IR is string-keyed (`ast.rs label: String`); the planner doesn't yet resolve labels→12-bit rank; the parser→4096 join (`cache/convergence.rs`) is the half-built p64-drift terminus. BUT the alignment means wiring it is CHEAP (shared address, not a translation layer) — the design intent was always one address space; only the resolution call is missing. So: address space = unified-by-design (corrected); resolution path = not-yet-called (stands). + +**Consequence for P-A / P-B:** P-B ("unify the 4096 surface") is NOT "merge 4 unrelated things" — it's "**declare the 0xFFF address width as ONE canonical 12-bit type** and have the surfaces name it instead of re-spelling 4096". Lighter than I framed it. And the polyglot conformance loop (P-A) can additionally assert that equivalent queries resolve to the same 0xFFF address (once resolution is wired), not just the same LogicalOp shape. + +**Cross-ref:** corrects `E-POLYGLOT-4096-IS-CONJECTURAL` + `E-POLYGLOT-TWO-IR-ROUTES`; cognitive-risc-classes (CAM addressing, fixed-width); wikidata-hhtl-load (facet u64 / SIMD batch-AND efficiency); `cache/convergence.rs` (the 0xFFF join, partial); faiss-homology-cam-pq (exact-address addressing). + +--- + +## 2026-05-30 — FINDING: E-POLYGLOT-TWO-IR-ROUTES — the 4 dialect parsers build IR via TWO different routes (in-strategy vs ArenaIR), converging on one LogicalOp arena that NOTHING asserts they agree on; 4096-in-planner = the AutocompleteCache, joined via convergence.rs + +**Status:** FINDING (grounded source read 2026-05-30, per user pointer to planner/datafusion polyglot path). Sharpens `E-POLYGLOT-4096-IS-CONJECTURAL` with the real per-parser reality. + +**The 4 polyglot parsers are at two IR-construction routes (arena.push counts verified):** +- **In-strategy transpilers (push LogicalOp directly):** `gremlin_parse.rs` (37 `arena.push`; full step→IR: ScanNode/IndexNestedLoopJoin/RecursiveExtend/Aggregate…) + `sparql_parse.rs` (31). +- **Feature-detect → shared ArenaIR route (0 push in strategy):** `cypher_parse.rs` (0; the REAL nom parser lives in `lance-graph/src/parser.rs`, then `strategy/arena_ir.rs` (#2) builds the arena) + `gql_parse.rs` (0; explicitly "delegate to CypherParse for shared syntax… ArenaIR will build the plan from detected features", gql_parse.rs:157-159 — sets only feature flags + `estimated_complexity`). + +⇒ Both routes are SUPPOSED to land on the same `ir/logical_op.rs LogicalOp` arena, but **NOTHING asserts the two routes (or the 4 dialects) produce equivalent IR for equivalent queries.** That is exactly where silent divergence hides — and exactly what the polyglot IR-conformance loop (P-A) would catch. Concretely actionable: GQL today only flips feature bits; whether ArenaIR reconstructs the same arena Gremlin emits directly is UNTESTED. + +**The 4096↔polyglot join (corrected):** the 4096 in THIS crate = the **AutocompleteCache attention topology** (64×64 = 4096 interdependent heads: `cache/{triple_model,kv_bundle,lane_eval,candidate_pool,mod}.rs`), NOT a query codebook and NOT the deepnsm COCA-4096. The link parser→4096 runs: dialect parser → LogicalOp IR → `cache/convergence.rs` ("AriGraph triplets → 8 predicate layers × 64×64 = 4096 heads → CognitiveShader"). convergence.rs is the JOIN — and it is the same module flagged half-built (p64 drift `#[allow(unused_imports)]`, `E-ARIGRAPH-IS-AN-ISLAND`). So "polyglot 4096 in datafusion/planner" = parsers (wired, 2 routes) → IR (wired) → convergence→4096-heads (PARTIAL/dead terminus). + +**Datafusion side:** IR → `lance-graph/src/datafusion_planner/` → Spark-dialect unparser (SQL = backend, confirmed). The DataFusion LogicalPlan is the EXECUTION lowering of the same LogicalOp the dialects converge on (the ExecTarget::Native path). + +**Revised P-A (the shippable slice), now precise:** polyglot IR-conformance harness asserting (1) the 4 dialects produce equivalent `LogicalOp` for a hand-written equivalent-query corpus, AND (2) the two routes (in-strategy vs ArenaIR) agree — closing the GQL/Cypher-via-ArenaIR vs Gremlin/SPARQL-direct gap. Attaches to `lance-graph-consumer-conformance`. This is the floor that grounds GEL's query-language slice (`E-GEL-IS-THE-GRAPH-SUBSTRATE`). + +**Cross-ref:** `E-POLYGLOT-4096-IS-CONJECTURAL`; `E-GEL-IS-THE-GRAPH-SUBSTRATE`; `E-ARIGRAPH-IS-AN-ISLAND` (convergence.rs p64 drift); `strategy/{gremlin,sparql,cypher,gql}_parse.rs`; `strategy/arena_ir.rs`; `ir/logical_op.rs`; `cache/convergence.rs`; `datafusion_planner/`. + +--- + +## 2026-05-30 — CORRECTION + FINDING: E-GEL-IS-THE-GRAPH-SUBSTRATE — GEL = Graph Execution Language (any-language→graph, BEAM-analogous); NOT a query dialect. Corrects "GEL absent" in E-POLYGLOT-4096-IS-CONJECTURAL + +**Status:** CORRECTION (supersedes the "GEL/EdgeQL = absent dialect" line in `E-POLYGLOT-4096-IS-CONJECTURAL`) + FINDING (user-stated 2026-05-30: GEL is the user's own coinage, not EdgeDB's rebrand). + +**The correction:** I mislabeled "GEL" as EdgeDB's EdgeQL→GEL rebrand and filed it under "absent query dialect." WRONG. **GEL = Graph Execution Language** — a graph SUBSTRATE for representing ANY language in graph form, analogous to how any code lowers to OTP/BEAM/Erlang. GEL is NOT a frontend dialect (not P-C/P-D); it is the IR/substrate layer that frontends lower INTO, represented as an executable graph. + +**The BEAM analogy (exact, and ALREADY partially built here):** +- BEAM: Erlang/Elixir/Gleam/LFE → BEAM bytecode; BEAM provides actors (processes) + preemptive scheduling + supervision trees + message passing. +- GEL: any language → graph representation; the graph IS executable. +- **ractor IS the BEAM actor model ported to Rust** — mailboxes = BEAM processes, `lance-graph-supervisor` = supervision tree, `CollapseGateEmission` batons = message passing. So GEL's execution engine = the ractor-over-SoA path being wired (D-MBX-A6 / kanban lifecycle). Not hypothetical. + +**Where GEL sits in the stack (it is the substrate everything converges on):** +- **Lowering rule = "AST is the hub"** (cognitive-risc-core): any language → one canonical AST → SPO/graph. GEL = the GRAPH FORM of that hub. `logical_plan.rs LogicalOperator` (query IR) is ONE SLICE of GEL (query languages only); full GEL = any language, not just query. +- **Runtime = `ExecTarget::Elixir`** (shipped #439) literally names "execute on the BEAM-analogous path." The exec-target axis already enumerates GEL's backends. +- **Execution trace = the version arc** (`E-VERSION-ARC-IS-THE-KANBAN` + `E-SUBSTRATE-IS-THE-SCHEDULER`): graph executes → phase commit → Lance version → scheduler fires next. That IS a graph-execution-language running, with the version arc as its trace. +- **State = the SoA** (`E-SOA-IS-THE-ONLY`): GEL nodes/edges = SPO + CausalEdge64 over the one MailboxSoA. + +**Prior-art / naming collision to reconcile:** `holograph/src/width_32k/schema.rs` uses "GEL" = "global execution layer" — genuinely adjacent (Global/Graph EXECUTION Layer). Reconcile whether that is the seed, a collision, or unrelated before formalizing the GEL name in code. + +**Implication for the polyglot sequence:** GEL reframes it. P-A (IR-conformance loop) still grounds the QUERY-language slice of GEL. But the broader GEL goal = "any language → graph" is the SUBSTRATE the whole D-MBX arc already realizes (IR + SPO + MailboxSoA + ractor lifecycle + ExecTarget + version arc). GEL is the NAME for the convergence, not a new component to bolt on. Do NOT build a "GEL parser"; the work is recognizing/consolidating that the existing IR+SoA+ractor IS GEL, then widening the lowering beyond query languages (LE-4 Odoo/OWL, Elixir templates, etc. = other-session lowerings into GEL). + +**Cross-ref:** `E-POLYGLOT-4096-IS-CONJECTURAL` (corrected); cognitive-risc-core "AST is the hub"; `ExecTarget::Elixir` (#439); `E-VERSION-ARC-IS-THE-KANBAN`; `E-SUBSTRATE-IS-THE-SCHEDULER`; ractor=BEAM-actor-model; `holograph/.../schema.rs` (GEL prior-art). + +--- + +## 2026-05-30 — E-POLYGLOT-4096-IS-CONJECTURAL — "loop through 4096 0xFFF polyglot mapping" is NOT wired: 4096 is 4 distinct magic numbers, and frontend->4096->SoA does not exist (string-keyed IR end-to-end) + +**Status:** FINDING (grounded read 2026-05-30, file:line agent map). Honest correction of the "loop through 4096 polyglot" framing — labels what exists vs what is conjectural. + +**What EXISTS (wired):** +- **Polyglot frontends → ONE IR:** Cypher/GQL/Gremlin/SPARQL all converge on `logical_plan.rs:19 LogicalOperator` (arena IR). Real Cypher parser `lance-graph/src/parser.rs`; the other three are thin Strategy adapters (`planner/src/strategy/{cypher,gql,gremlin,sparql}_parse.rs`) with affinity scoring. Extension shape = Parser + Strategy + affinity arm + 1-line registry (`strategy/mod.rs:51`). +- **SQL = BACKEND only** (Cypher→DataFusion→Spark unparser, `datafusion_planner/`, `spark_dialect.rs`); NOT a frontend. +- **NARS = truth/inference only** (`spo/truth.rs` (f,c); planner `cache/nars_engine.rs`; CausalEdge64 mantissa); NO Narsese parser. +- **GEL/EdgeQL = ABSENT** (the one "GEL" hit = "global execution layer", unrelated). +- **Conformance harness EXISTS** (`lance-graph-consumer-conformance/src/harness.rs` A1-A10) but tests consumer BRIDGES, not dialect IR-equivalence. + +**What is CONJECTURAL (NOT wired) — the honest gaps:** +1. **"4096 surface" is 4 DISTINCT uses of the magic number, no canonical type:** (a) deepnsm COCA vocab 12-bit `VOCAB_SIZE=4096` (`deepnsm/vocabulary.rs:19`); (b) AutocompleteCache 64×64=4096 attention heads (`planner/cache/triple_model.rs`); (c) BindSpace 16 prefixes×256 slots (`docs/METADATA_SCHEMA_INVENTORY.md`, design only); (d) COCA² distance matrix. (Palette codebook is 256, NOT 4096.) ⇒ "loop through 4096 0xFFF" is NOT a single enumerable surface today — unification is a prerequisite task, not a loop. +2. **frontend→4096→SoA path DOES NOT EXIST.** The IR is string-keyed end-to-end (`ast.rs:14` `label: String`); the planner never calls `vocabulary.tokenize(label)`; DataFusion scans by string table name; SPO uses FNV-1a hash keys, not 4096 ranks. DeepNSM (the only thing that resolves to 4096) runs PARALLEL to the graph planner, unintegrated. + +**The REAL shippable slice (grounded, no conjecture):** the **polyglot IR-conformance loop** — assert the 4 wired dialects produce the SAME `LogicalOperator` for equivalent queries. Attaches to the existing conformance harness; needs only a hand-written equivalent-query corpus + deep-IR-equality. This GROUNDS the polyglot claim (currently untested) and is the prerequisite floor before any 4096 wiring. (`E-SOA-IS-THE-ONLY` / AST-is-the-hub: prove the hub agrees before resolving it to addresses.) + +**Sequenced follow-ups (each its own slice, ratification-gated):** +- P-A: polyglot IR-conformance loop (SHIPPABLE NOW — grounds the hub). +- P-B: unify the 4096 surface = declare ONE canonical 12-bit address type (contract/ndarray); collapse the 4 uses onto it. (Prereq for any "loop through 4096".) +- P-C: NARS-as-frontend (Narsese parser → LogicalOperator) — the dialect where query=inference; closes the loop to the (f,c) truth model already present. +- P-D: frontend→4096→SoA = add `label_rank: Option` to the IR, wire vocab resolution at plan time, SoA column access by rank (OOV fallback). The conjectural path, last + biggest. +- (SQL-as-frontend, GEL: lower priority; SQL is already a backend, GEL absent.) + +**Cross-ref:** cognitive-risc-core "AST is the hub"; `logical_plan.rs:19`; `deepnsm/vocabulary.rs:19`; `lance-graph-consumer-conformance`; `ExecTarget` (#439, the backend axis this frontend axis pairs with). + +--- + +## 2026-05-30 — E-SUBSTRATE-IS-THE-SCHEDULER — surreal's time-series/LIVE over the version arc is a cheap planner→execution scheduler firing back INTO the mailbox; the substrate↔mailbox loop is bidirectional + +**Status:** FINDING (architectural, user-stated 2026-05-30). Return-path complement of `E-VERSION-ARC-IS-THE-KANBAN`. Together they close the loop. + +**The loop is bidirectional:** +- **OUTBOUND (mailbox → surreal), free:** `advance_phase` commit = Lance version = kanban move; surreal LIVE-subscribes (`E-VERSION-ARC-IS-THE-KANBAN`). +- **INBOUND (surreal → mailbox), this finding:** surreal's time-series + LIVE/scheduled query IS a **scheduler**. A surreal scheduled/LIVE event fires back into the mailbox as the next `advance_phase` trigger. The mailbox does NOT run its own planner-tick loop — **surreal schedules it, cheaply**, off the same `versions()` stream. + +**This completes the GitHub homology (never one-way):** + +| GitHub | Substrate ↔ mailbox | +|---|---| +| push commit → PR | mailbox commit → version arc (outbound, free) | +| CI / scheduled workflow fires → acts on PR | surreal LIVE/scheduled event fires → drives mailbox's next phase (inbound) | +| Actions runner = scheduler | surreal time-series = scheduler | + +**Architectural wins:** +1. **planner→execution edge, done cheaply.** Planning precipitates a kanban move (a version); surreal's scheduler watches the arc and fires the execution tick back. `ExecTarget` (#439) = HOW it runs; the surreal event = WHEN. Mailbox stays a pure state machine (`try_advance_phase`, #439); surreal = clock + planner-dispatch. +2. **Two-clock decoupling (RISC core invariant 7) for free:** hot shader speed (mailbox SoA mutation) vs cold scheduler cadence (surreal time-series) — decoupled by construction, same `versions()` stream read both directions. No separate scheduler infra. +3. **`ExecTarget::SurrealQl` made literal:** a scheduled SurrealQL query is BOTH the trigger AND a valid execution backend — the planner→execution path can live entirely in the substrate scheduler for that target. (Native/Jit/Elixir targets: surreal fires the trigger, the mailbox runs the backend.) + +**Consequence for D-MBX:** the planner→execution wiring (part of D-MBX-A6-P3 + D-MBX-8 Σ10-commit→ractor-START) gains a substrate-native option — surreal-scheduled tick instead of an in-process planner loop. Still gated by surreal_container fork (OQ-11.6) for the surreal side; the contract side (`ExecTarget`, `try_advance_phase`, `MailboxSoaView`) is already shipped/in-PR and backend-agnostic. + +**Open (implementation):** surreal scheduled-query vs LIVE-trigger as the fire mechanism; backpressure when the scheduler outruns the hot path (RISC core invariant 8 — shed by ⟨f,c⟩). Architecture is substrate-native; wiring waits on OQ-11.6. + +**Cross-ref:** `E-VERSION-ARC-IS-THE-KANBAN`; surrealdb #31 (Timeline over `Dataset::versions()`); `ExecTarget`/`try_advance_phase` (#439); D-MBX-8/9/A6-P3; cognitive-risc-core invariants 7 (two-clock) + 8 (backpressure). + +--- + +## 2026-05-30 — E-VERSION-ARC-IS-THE-KANBAN — the mailbox's Lance version timeline IS the kanban arc, for free; consume it like a GitHub CI/PR subscription (push, not poll) + +**Status:** FINDING (architectural simplification, user-stated 2026-05-30). Grounded in surrealdb #31 substrate fact + Lance versioning. Reframes D-MBX-9. + +**The insight:** since kv-lance is native (surrealdb #31: one `MergeInsert`/commit = one Lance dataset version), a mailbox's **`Dataset::versions()` timeline IS its kanban arc — it falls out of the substrate for FREE.** Each `MailboxSoaOwner::advance_phase` commit = one new Lance version = one kanban move. No separate kanban update mechanism is built; the version stream IS it. + +**The consumption pattern = a GitHub CI/PR subscription (the exact homology this session ran):** + +| GitHub PR | Mailbox kanban | +|---|---| +| commits pushed to a PR | phase-transition commits to the mailbox's Lance dataset | +| CI/review events | new Lance versions appear | +| `subscribe_pr_activity` → pushed to subscriber | surreal LIVE query over `versions()` → kanban updates pushed | +| react per event, never poll | consumer reacts per version, never rebuilds | +| append-only PR timeline | append-only version arc (= witness/belief-state arc, R4) | + +**Two consequences:** +1. **Witness pointer (R4 / EW64) = `(mailbox_id, lance_version)`** — a pointer into the substrate's OWN version arc. "Cheap AriGraph witness pointer" is now concrete + free; the AriGraph episodic edge ("happened at the same time") = "happened at version V". The KanbanMove record (incl. `libet_offset_us`, `exec`, `witness_chain_position`) rides as Lance commit/version metadata. +2. **D-MBX-9 collapses** from "build a kanban view structure" to "**LIVE-subscribe to the mailbox version stream**" — surreal time-series view over `Dataset::versions()` + a LIVE query = the Rubicon kanban. The `Timeline` read surface over `Dataset::versions()` already built in surrealdb #31 IS this. The `MailboxSoaView` borrow trait (#437) is the per-version read lens. + +**The "in-mailbox arc" framing:** each mailbox owns its own version arc (the versions of its SoA dataset/fragment). The arc is in-mailbox; the kanban is the cross-mailbox time-series view of those arcs. SurrealDB time-series consumes it. + +**Open (implementation, not architecture):** true push (surreal LIVE query) vs cheap-poll of `versions()` — both honor the pattern; LIVE is the goal. Still gated by surreal_container fork (OQ-11.6) for the surreal-side view — but the design is now substrate-free, so D-MBX-9 is a subscription wiring, not a build. + +**Cross-ref:** surrealdb #31 (kv-lance native + Timeline over `Dataset::versions()`); `E-SOA-IS-THE-ONLY` R3/R4; D-MBX-9; `KanbanMove`/`MailboxSoaView` (#437); `is_absorbing` (#439, the cycle-end commit = a terminal version); LE-3. + +--- + +## 2026-05-30 — D-MBX-A6-P2 landed (contract): Rubicon lifecycle enforcement + ExecTarget strategy tag + +**Status:** SHIPPED-in-PR (contract slice). Builds on `#437` (D-MBX-A6-P1). +- `KanbanColumn::next_phases()` + `can_transition_to()` — the Rubicon lifecycle DAG (Planning→CognitiveWork→Evaluation→{Commit|Plan|Prune}; Planning→Prune Libet veto; Plan→Planning re-deliberate; Commit/Prune absorbing). Lifecycle enforcement is now a contract-level, testable invariant. +- `KanbanColumn::is_absorbing()` — distinguishes cycle-END columns (Commit/Prune, tombstone now) from `Plan` (terminal decision but re-deliberates). The ractor driver tombstones iff absorbing; LE-3 cycle-end commit/SLA hooks here. +- `MailboxSoaOwner::try_advance_phase()` — checked default: validates the edge, returns `KanbanMove` or `RubiconTransitionError` (no mutation on illegal). The ractor lifecycle driver uses this. +- `ExecTarget` {Native|Jit|SurrealQl|Elixir} — the planner JIT-adjacent execution-target (strategy) tag; now a field on `KanbanMove` (resolves the `#437` deferred NOTE; size still ≤16 B). Distinct from the planner's 16 composable *planning* strategies. +- Zero-dep preserved; 489 contract lib tests (+4); planner/shader-driver/supervisor cargo-check clean. +Next: ractor MailboxSoA owner-impl + planner emit (candidate generation) — the consumer side. +**Cross-ref:** `#437`; D-MBX-A6; `E-DUPLICATION-IS-INTRINSIC-VS-TEMPORAL`; LE-3 (Rubicon commit). + +--- + +## 2026-05-30 — E-DUPLICATION-IS-INTRINSIC-VS-TEMPORAL — the SoA "duplicate" intentionally separates intrinsic awareness from the temporal belief-state arc (= AriGraph semantic/episodic = CE64/EW64 = SoA1:SoA2) + +**Status:** FINDING / design ruling (user-confirmed 2026-05-30). Answers "are you duplicating intentionally?" — YES. + +The SoA is FIXED (frozen byte-shape) ⇒ the "duplicate" is the SAME shape instantiated twice, which is what makes `SoA1:SoA2` superposition well-defined. The cut it encodes (one separation, four names): +- **intrinsic awareness** (the *now*): semantic CE64 + gestalt/qualia (`awareness_dto::ResonanceDto`) — atemporal "what resonates now". +- **temporal belief-state arc** (the *over-time*): episodic EW64 witness arc (`chain_position`) — "how the belief came to be, across observations". += AriGraph **Es (semantic) / Ee (episodic)** duality = **CE64 / EW64** = **SoA1 : SoA2** (shader superposes them, Hebbian) = energy-field / gestalt `ResonanceDto` layering. So `TD-RESONANCEDTO-DUP-1` is NOT a dedup target — it's the intrinsic/temporal separation; the fix is to NAME it (e.g. `ResonanceField` vs `GestaltResonance`), not collapse it. + +## 2026-05-30 — LOOSE-ENDS (documented per user "don't die token wall"; NOT yet built) + +**LE-1 — EW64 as syntactic-coreference witness pointer (DeepNSM > Markov > grammar).** Wire `deepnsm` (PoS FSM → SPO) → Markov VSA context → grammar heuristics so that a **relative pronoun (Relativpronomen) is NOT bundled** into the VSA trajectory but instead gets a **witness pointer** (EW64) to its antecedent. Rationale: I-VSA-IDENTITIES "register laziness" — exact-match coreference is a POINTER, not a bundle (bundling destroys the register). ⇒ EW64 gains a SECOND role beyond aerial-prefetch: a **syntactic-Markovian context** = episodic memory pointing at BOTH hot witness mailboxes AND cold-path SPO. (Consumers of the grammar/Markov path: `contract::grammar::role_keys`, `deepnsm`, the Vsa16k Markov substrate.) + +**LE-2 — cold-path SPO + cold-path AriGraph UNIFY.** Now that EW64 is a *cheap AriGraph witness pointer*, the two cold stores belong together: ONE cold store = semantic SPO (Es) + episodic witness arcs (Ee), EW64 as the link. Resolves the parallel-SPO-store debt (`F-WIRE-DTO-DUP-MAP`/`E-ARIGRAPH-IS-AN-ISLAND`) on the cold side. Matches AriGraph paper (one graph G = Vs,Es,Ve,Ee). + +**LE-3 — mailbox-cycle-end Rubicon commit decision.** At the END of a mailbox cycle, the LAST Rubicon kanban state (terminal Commit/Plan/Prune, `KanbanColumn`) = a DECISION that: (a) **commits SPO-W (the EW64 witness) to the cold path** (= AriGraph calcify / `witness_tombstone::calcify` — currently dead `todo!()`, D-ATOM-5), AND (b) for business-logic mailboxes, **commits to SLA + goalstate**. This is the commit gate at Rubicon. Wires `MailboxSoaOwner::advance_phase(Commit)` → cold materialization + SLA/goalstate update. + +**LE-4 — Odoo + OWL business-logic action substrate = OTHER SESSION.** The SLA/goalstate business-logic commit (LE-3b) + Odoo/OWL as the business-logic *action* substrate is explicitly deferred to a separate session. Documented here so it is not lost; do NOT build it in this arc. + +**BindSpace consumers (singleton→per-mailbox migration surface, grep 2026-05-30):** contract `{cognitive_shader,splat,lib}.rs`; planner `{cache/convergence,lib}.rs`; cognitive-shader-driver `{driver,wire,engine_bridge,mailbox_soa,proposal,serve,wire_dto,spo_bridge,cognitive_shader,cognitive_shader_dispatch,spo_witness,cam}.rs`. The singleton still threads through driver/engine_bridge/wire*; `spo_witness.rs` is the existing witness seam to reconcile with EW64. (Ref D-MBX-3/5: kill `BindSpace::zeros(4096)` singleton; migrate consumers to per-mailbox MailboxSoA.) + +**Cross-ref:** `E-ARIGRAPH-PAPER-GROUNDS-CE64-EW64`; `E-AERIAL-FEEDS-EW64-PREFETCH`; `E-ARIGRAPH-IS-AN-ISLAND`; `F-RESONANCEDTO-IS-LAYERED-NOT-DUP`; D-MBX-3/5/A5; I-VSA-IDENTITIES. + +--- + ## 2026-05-30 — E-ARIGRAPH-PAPER-GROUNDS-CE64-EW64 — AriGraph (arXiv 2407.04363v3) IS the source: its semantic-edge/episodic-edge duality grounds CE64/EW64; the episodic edge ("happened at the same time") IS the witness arc **Status:** FINDING (read the AriGraph paper, Anokhin et al. AIRI, 2026-05-30). User's "first is AriGraph!!!" — this is the canonical design source for the semantic+episodic arc. @@ -69,7 +597,7 @@ AriGraph (`crates/lance-graph/src/graph/arigraph/`) is almost entirely standalon #436 shipped `crates/lance-graph-arm-discovery` (Aerial+ ARM transcode). Synergies with this arc + Cognitive-RISC: - **Aerial+ = a PROPOSER** in the RISC `discovery_origin` ISA (`ArmDiscovered` tier): a mined association, an AST-walk step, an LLM conjecture = the SAME candidate object differing only by `discovery_origin` (core invariant 9; proposers dumb, Rubicon arbitrates). Aerial is nondeterministic (seeded) → stays UPSTREAM of the ratification council (determinism firewall). -- **Emits SPO+NARS `Triple{s,p,o,f,c}`** into `ruff_spo_triplet` (mirrors `odoo_ontology::OntologyTriple`). Gap: closed predicate vocab rejects `Implies`/`CoOccursWith` (D-ARM-SYN-1, council-gated). Same SPO substrate the cognitive SoA packs (CausalEdge64 SPO palette + f/c) ⇒ aerial candidates → council → CausalEdge64/SPO → kanban (D-MBX-A6) → shader. +- **Emits SPO+NARS `Triple{s,p,o,f,c}`** into `ruff_spo_triplet` (mirrors `odoo_ontology::OntologyTriple`). Gap: closed predicate vocab rejects `Implies`/`CoOccursWith` (D-ARM-SYN-1; ratification = the determinism firewall, not the brainstorm-council — see correction at top of file). Same SPO substrate the cognitive SoA packs (CausalEdge64 SPO palette + f/c) ⇒ aerial candidates → ratification firewall → CausalEdge64/SPO → kanban (D-MBX-A6) → shader. - **Aerial ALSO discovers CLASSES** (shape-families): cognitive-risc-classes — taxonomy DISCOVERED "via group-by-on-structural-hash or Aerial+"; splat→aerial→Wikidata discovers OWL/DOLCE+ HHTL classes+basins. ⇒ aerial is the discovery engine behind the `class_id` the SoA needs (the "ontology classes wired into the SoA" ask). Float lives OFFLINE in `jc` (Jirak-Cartan certified 256-codebook); aerial addresses it ONLINE with integer codes (CAM-PQ doctrine). - **SPO-vocabulary debt (extends F-WIRE-DTO-DUP-MAP):** ≥4 parallel SPO-triple types (AriGraph `TripletGraph`, `ruff_spo_triplet::Triple`, `odoo_ontology::OntologyTriple`, aerial `CandidateTriple`, osint `extractor::Triplet`) — "one SoA never transformed" wants ONE; unification is the convergence work. - **class_id landing (shipping now):** the SoA's class discriminator IS the existing `entity_type: [u16; N]` (= OGIT `EntityTypeId`); expose it as `MailboxSoaView::class_id()` (N1 freeze hook). Metadata resolves one layer up via `lance-graph-ontology::OntologyRegistry` (perf gap: add O(1) `by_entity_type_id` index; today O(n) `enumerate_first_with_entity_type_id`). @@ -108,7 +636,7 @@ The de-float (codebook-probe backend): ## 2026-05-30 — E-DISCOVERY-CODEGEN-BRACKET-1 (realised) — the Aerial+ transcode is the runtime-data frontend of a bracket whose substrate + codegen legs ALREADY EXIST in the ruff fork; the ruff SPO predicate vocabulary is the only missing seam -**Status:** FINDING (type-level, grounded in source read 2026-05-30) + CONJECTURE (the three D-ARM-SYN wiring deliverables). Author-stated; the three wirings are council-gated. +**Status:** FINDING (type-level, grounded in source read 2026-05-30) + CONJECTURE (the three D-ARM-SYN wiring deliverables). Author-stated; the three wirings pass the determinism-firewall ratification (nondeterministic stays upstream) — NOT the brainstorm-council, which is a catalyst not a gate (see top-of-file correction). The two-paper bracket (`streaming-arm-nars-discovery-v1.md`: Aerial+ discovery upstream, Abreu M2M codegen downstream, SPO+NARS middle) is not a future aspiration — **its substrate and both codegen legs are already implemented in `adaworldapi/ruff`** and `lance-graph-ontology`. Transcoding Aerial+ to Rust (D-ARM-13) made this concrete: diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 97fe5036..26f2e3cd 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -42,6 +42,8 @@ ## Current Contract Inventory (lance-graph-contract) +> **2026-05-30 — PR-in-flight addition** (D-MBX-A6 Phase 2 — Rubicon lifecycle + ExecTarget): `lance_graph_contract::kanban::{ExecTarget (Native/Jit/SurrealQl/Elixir), RubiconTransitionError}` + `KanbanColumn::{next_phases, can_transition_to}` (the Rubicon lifecycle DAG) + `KanbanMove.exec: ExecTarget` field + `MailboxSoaOwner::try_advance_phase()` (checked lifecycle enforcement → `Result`). Zero-dep; `KanbanMove` still ≤16 B; 489 contract lib tests (+4); downstream cargo-check clean. Lifecycle enforcement + planner exec-target are now contract-level invariants. Resolves the #437 deferred exec-target NOTE. Cross-ref D-MBX-A6 / #437. +> > **2026-05-30 — PR-in-flight addition** (D-MBX-A6 Phase 1 — planner⟷ractor⟷surreal meta-DTO): `lance_graph_contract::kanban::{KanbanColumn (6: Planning/CognitiveWork/Evaluation/Commit/Plan/Prune), KanbanMove}` — the 4-phase Rubicon kanban transition; `KanbanMove` is `Copy`, ≤16 B, carries `MailboxId` + `witness_chain_position` (R4 pointer) + `libet_offset_us` (−550 ms anchor, D-MBX-8). `lance_graph_contract::soa_view::{MailboxSoaView, MailboxSoaOwner}` — zero-dep **borrow trait** for the transparent zero-copy SoA view (R1 "one SoA never transformed"); `&[T]` column accessors (energy/edges_raw/meta_raw/entity_type) mirror `MailboxSoA::*_at`; the read/owner split makes "view is read-only" structural (surreal implements only the read half). `orchestration::StepDomain::Kanban` variant + `"kanban."` prefix. Consumed via the EXISTING `OrchestrationBridge` (planner emits, ractor owns/drives via `MailboxSoaOwner`, surreal_container projects via read-only `MailboxSoaView`) — NO parallel DTO family (lab-vs-canonical ruling). Contract `[dependencies]` still empty. 485 contract lib tests green (+6 new); `cargo check` clean on planner/cognitive-shader-driver/supervisor (StepDomain variant additive-safe). Consumer impls deferred. See E-SOA-VIEW-IS-A-BORROW; `unified-soa-convergence-v1.md §5+§8.4`. > > **2026-05-28 — PR-in-flight addition** (bindspace→mailbox migration wave A1-A4): `lance_graph_contract::witness_table::{WitnessEntry, WitnessTable}` — column-type primitive resolving the 6-bit W-slot in `CausalEdge64 v2` into a per-cohort `(mailbox_ref: u32, spo_fact_ref: Option)` table (`mailbox_ref` carries the full canonical `MailboxId`, NOT a truncated cohort-local index — see PR #427 Codex P2 fix). Zero-dep, 3 unit tests, `WitnessTable::{new, get, set, default}`. Cross-ref: `.claude/plans/bindspace-singleton-to-mailbox-soa-v1.md` §10 (architectural refinements landed in same wave). Also in same wave: `cognitive-shader-driver::MailboxSoA` gains four thoughtspace columns (`edges: [CausalEdge64; N]`, `qualia: [QualiaI4_16D; N]`, `meta: [MetaWord; N]`, `entity_type: [u16; N]`) + 8 row accessors; `ShaderDriver` gains transitional `mailboxes: HashMap>` + `with_mailbox()` builder + `mailbox()` read accessor (sibling-shape, additive — singleton untouched). 457 contract+driver tests pass. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 410edb0d..f1894a92 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -564,7 +564,10 @@ Plan path: `.claude/plans/unified-soa-convergence-v1.md`. Handover `.claude/hand | D-MBX-10 | SoA version byte at layout root (`MailboxSoAHeader`); refuse v(N>M) bytes on v(M) reader; field-isolation matrix tests on every column op (`I-LEGACY-API-FEATURE-GATED` discipline) | lance-graph-contract | 100 | HIGH | **Queued** | foundation — should land early in P2; gates on OQ-11.5 | | D-MBX-11 | Lance `=6.0.0 → =6.0.1` patch bump (5 Cargo.toml files identified) | workspace Cargo.toml | 10 | LOW | **Queued (mechanical)** | none — can land in parallel with par-tile prereq | | D-MBX-12 | 8-PR workspace-wide consumer alignment: 12.1 AriGraph · 12.2 Vsa16k audit · 12.4 lance-graph · 12.5 planner · 12.6 shader-driver · 12.7 callcenter · 12.8 ontology audit · 12.9 thinking-styles | per-crate | 800 | per-PR | **Queued (multi-PR)** | sequencing per OQ-11.8: 12.4 → 12.5 → 12.6 → 12.7 → 12.1 → 12.9 → 12.2 → 12.8 | -| D-MBX-A6-P1 | contract slice of D-MBX-A6: `kanban::{KanbanColumn, KanbanMove}` + `soa_view::{MailboxSoaView, MailboxSoaOwner}` + `StepDomain::Kanban` — the planner⟷ractor⟷surreal seam, zero-dep, no parallel DTO family | lance-graph-contract | 340 | HIGH | **In PR** | extends §8.4; +6 tests; downstream cargo-check clean; consumer impls (planner emit / `MailboxSoA` owner-impl / ractor arm / surreal read-view) deferred | +| D-MBX-A6-P1 | contract slice of D-MBX-A6: `kanban::{KanbanColumn, KanbanMove}` + `soa_view::{MailboxSoaView, MailboxSoaOwner}` + `StepDomain::Kanban` — the planner⟷ractor⟷surreal seam, zero-dep, no parallel DTO family | lance-graph-contract | 340 | HIGH | **Shipped** | #437 (merged 9161bd7); + `class_id` N1 hook ride-along | +| D-MBX-A6-P2 | Rubicon lifecycle enforcement + exec-target tag: `KanbanColumn::{next_phases, can_transition_to, is_absorbing}` (the lifecycle DAG) + `MailboxSoaOwner::try_advance_phase` (checked, `RubiconTransitionError`) + `ExecTarget{Native,Jit,SurrealQl,Elixir}` on `KanbanMove` | lance-graph-contract | 120 | LOW | **In PR** | builds on P1; 489 lib tests (+4); downstream cargo-check clean; gates the ractor owner-impl + planner emit (P3) | +| D-MBX-A6-P3a | StyleStrategy: thinking-style -> cluster -> mechanism -> recipe_kernels Tactic selection (planning substrate; carries tau JIT addr) | lance-graph-planner | 130 | LOW | **In PR** | #439; first cut of A6-P3 consumer wiring; planner now consumes contract recipes/styles; deferred: i4-32D decode, Outcome->Candidate, tau->JIT, membrane commit | +| D-MBX-A6-P3-M1 | `Tactic::requires() -> ThoughtMask` + `ThoughtField`/`ThoughtMask` (checklist-as-data keystone): 34 tactics declare their ThoughtCtx field-reads; `covered_by` = reliability-coverage gate | lance-graph-contract | 120 | LOW | **In PR** | #439; the panel-recalibrated keystone (extraction not construction); makes P1/P7/P11 derived; teeth-test asserts masks varied not stub | --- diff --git a/.claude/plans/reliability-checklist-arc-v1.md b/.claude/plans/reliability-checklist-arc-v1.md new file mode 100644 index 00000000..f3a60184 --- /dev/null +++ b/.claude/plans/reliability-checklist-arc-v1.md @@ -0,0 +1,78 @@ +# reliability-checklist-arc-v1 — integration plan as a LIST OF POSSIBILITIES + +> **Status:** PROPOSAL / possibility menu (2026-05-30). NOT a committed sequence — a +> candidate set for the council + brutal reviewers to RECALIBRATE (re-rank, prune, +> flag theater-risk). Each item cites the session finding it realizes. +> **Arc it serves:** (f,c)=reliability → reliability=checklist-coverage → checklist=NARS/elixir +> template=Odoo D-Atoms → validity=external oracle → psychometrics=offline audit. + +## The possibilities (unordered menu — recalibration assigns order/verdict) + +### P1 — Cheap checklist-coverage gate (the headline) +- **What:** domain-agnostic `coverage(required: &[Atom], lit: bitmask) -> CoverageState{Covered|Gap(dark)|Unsatisfiable}`; wire to Rubicon `Evaluation→{Commit|Plan|Prune}`. Reads #433 `OdooStyleRecipe.atoms` (required) vs lit/dark bitmask (known). +- **Realizes:** E-RELIABILITY-IS-CHECKLIST-COVERAGE + E-TEMPLATE-IS-CHECKLIST-IS-DATOMS. +- **Size:** S. **Deps:** #433 DAtom (exists), N3 bitmask, N1 class_id (#439). **Risk:** where does `coverage` live (contract zero-dep? ontology?) — dep-direction question. +- **Threshold-free:** dissolves the 0.2/0.8 iron-rule violation (no calibrate). + +### P2 — R-GATE live-trace probe (probe-before-wire) +- **What:** does checklist-coverage (or reliability) state CHANGE a Rubicon Commit/Plan/Prune outcome on a real witness trace vs DAG-only? Pass = ≥1 differing terminal; Fail = cosmetic. +- **Realizes:** the reviewers' probe-first rule; gates P1/P5. +- **Size:** S (a test/probe, not a wire). **Risk:** needs a representative witness corpus; may show the gate is cosmetic (then DON'T build P1's wire). + +### P3 — StyleStrategy emit edge (de-stub the passthrough fully) +- **What:** `StyleStrategy::plan()` EMITS — thread the resolved style + reliability into `PlanResult.thinking` (or a KanbanMove when A6 output overhaul lands). Kills the remaining passthrough. +- **Realizes:** fixes the council's "two disjoint islands"; D-MBX-A6-P3 emit edge. +- **Size:** M. **Deps:** D-MBX-A6 planner-output overhaul (KanbanMove output) OR the lighter PlanResult.thinking sink. **Risk:** prior-art says don't mint PlannerDTO; use existing sink. + +### P4 — impl MailboxSoaOwner for MailboxSoA (make try_advance_phase live) +- **What:** real owner impl in cognitive-shader-driver so the Rubicon lifecycle is wired to a real type (today only FakeSoa calls it). +- **Realizes:** retires the "shipped-but-dead" finding; D-MBX-A6. +- **Size:** M. **Deps:** mailbox_soa.rs (actively edited by other waves — collision risk). **Risk:** HHTL/SoA-column wave conflict. + +### P5 — I4x32→argmax→ThinkingStyle keystone decode +- **What:** the projection collapsing composition→identity (creative-explorer's keystone). Contract-side `atoms::I4x32 -> ThinkingStyle`. +- **Realizes:** the keystone; unifies the 3 style representations; makes recipe.rs deletable. +- **Size:** S-M. **Deps:** atoms::I4x32 (landed), thinking::ThinkingStyle. **Risk:** 32-vs-33 dim (sentinel?); is argmax the right decode. + +### P6 — DELETE recipe.rs (not revive) +- **What:** remove the stale/orphaned `StyleRecipe`/`PersonaRecipe` (4 savants: don't revive); fold PersonaRecipe's β/threshold into persona.rs if needed. +- **Realizes:** the council's unanimous don't-revive; removes the 5th-column drift. +- **Size:** S. **Risk:** is anything (even aspirationally) depending on it; deletion vs leave-dead. + +### P7 — Reconcile the 4-way recipe surface +- **What:** document/converge `recipes`(tactics) / `recipe_kernels`(Tactic) / `recipe.rs`(StyleRecipe, dead) / `OdooStyleRecipe`(domain checklist) into one map — names + roles, not a merge. +- **Realizes:** the 3-recipe-module finding + the template=checklist=DAtoms correction. +- **Size:** S (doc/rename). **Risk:** rename churn across crates. + +### P8 — Psychometric offline audit (the heavier reliability path) +- **What:** thinking-engine `reliability_calibration` (Cronbach α/ICC over a recipe/witness corpus) → emit cited bands → audit whether checklist ITEMS cohere. Offline-float→frozen-const. +- **Realizes:** E-CALIBRATE-RELIABILITY-PSYCHOMETRICALLY. Complementary to P1 (audits the checklist, doesn't gate the hot path). +- **Size:** M-L. **Deps:** thinking-engine cronbach.rs (exists). **Risk:** heavier; only worth it if P2 shows the gate matters AND checklist completeness is in question. + +### P9 — D-MBX-11 lance 6.0.0→6.0.1 bump (mechanical) +- **What:** the version bump (5 Cargo.tomls). **Size:** XS. **Risk:** none (off critical path). Quick unblock for stack alignment. + +### P10 — Polyglot IR-conformance loop (the GEL hub floor) +- **What:** assert the 4 dialects + 2 IR-routes (in-strategy vs ArenaIR) produce equivalent LogicalOp for equivalent queries; attach to consumer-conformance harness. +- **Realizes:** E-POLYGLOT-TWO-IR-ROUTES; grounds the GEL query slice. **Size:** M. **Risk:** orthogonal to the reliability arc (different thread). + +### P11 — class_id→checklist resolver + ontology O(1) index +- **What:** `OntologyRegistry.by_entity_type_id` HashMap (the one perf gap) + class_id→required-checklist(D-Atom set) resolver. +- **Realizes:** the cognitive-risc-classes class→checklist projection; the audit's O(1) fix. +- **Size:** S-M. **Deps:** P1 (the checklist type). **Risk:** ontology↔checklist dep direction. + +## Open recalibration questions (for the panel) +1. Sequence: which is the true first slice — P2 (probe) before P1 (gate)? P9 (free) now? +2. Which are THEATER-PRONE (ship green, do nothing — like the #439 passthrough)? Flag them. +3. Which violate an iron rule / dep-direction / zero-dep as scoped? +4. Which are DUPLICATES of existing work (build on, don't rebuild)? +5. What's MISSING from the menu (the reframe/second-order option)? +6. Cascade size per item — which are S that masquerade as L (or vice versa)? + +--- + +## RECALIBRATED (3-agent panel, 2026-05-30) — see EPIPHANIES "RECALIBRATION" +**Keystone added: M1 `Tactic::requires() -> AtomMask`** (reliability is a missing accessor, not a gate — extraction not construction; makes P1/P7/P11 derived). +**Recalibrated order:** 1) M1 (contract, zero-dep, teeth-test). 2) P2 + build its missing witness corpus (honest cosmetic/not pass-fail). 3) M2 completeness auditor (unknown-unknown finder, reuses neural-debug). +**Deferred/dropped:** P5 P0-blocked (I4x32 pack/unpack todo!() + 32-vs-33 fork); P6 cosmetic (recipe.rs never compiles in); P9 still blocked (lancedb pins lance=6.0.0); P10 off-arc; P3/P4 AP6 theater until a real consumer exists; P1 must reframe as M1-derived (don't dup recipes::Coverage); P8 gated on P2-pass. +**Added missing:** M2 (completeness auditor), M3 (version-arc scheduler wire). diff --git a/crates/lance-graph-contract/src/kanban.rs b/crates/lance-graph-contract/src/kanban.rs index c427c8ef..54e6fdfb 100644 --- a/crates/lance-graph-contract/src/kanban.rs +++ b/crates/lance-graph-contract/src/kanban.rs @@ -49,11 +49,59 @@ pub enum KanbanColumn { } impl KanbanColumn { - /// Is this a terminal column (no further in-cycle transition from here)? + /// Is this a terminal **kanban column** — one of Evaluation's 3-way decision + /// outcomes (`Commit` / `Plan` / `Prune`)? + /// + /// Note `Plan` is terminal *as a decision* but re-enters `Planning` + /// (see [`next_phases`](KanbanColumn::next_phases)); use + /// [`is_absorbing`](KanbanColumn::is_absorbing) for "the mailbox cycle truly + /// ends here, tombstone now". #[inline] pub fn is_terminal(self) -> bool { matches!(self, Self::Commit | Self::Plan | Self::Prune) } + + /// Is this an **absorbing** column — the mailbox cycle ends here with no + /// successor (`Commit` = calcify to cold path, `Prune` = drop)? + /// + /// `Plan` is NOT absorbing (it re-deliberates back to `Planning`). The ractor + /// lifecycle driver tombstones the mailbox iff the cycle reaches an absorbing + /// column — the LE-3 cycle-end commit/SLA decision hooks here. + #[inline] + pub fn is_absorbing(self) -> bool { + matches!(self, Self::Commit | Self::Prune) + } + + /// The valid successor columns from `self` in the Rubicon lifecycle DAG. + /// + /// ```text + /// Planning ─▶ CognitiveWork ─▶ Evaluation ─▶ { Commit | Plan | Prune } + /// │ │ + /// └─▶ Prune (pre-Rubicon Libet veto) └ Plan ─▶ Planning (re-deliberate) + /// ``` + /// - `Planning` advances to `CognitiveWork` (the −550 ms Σ-commit / Rubicon + /// crossing) or is vetoed straight to `Prune` (Libet "free won't", pre-Rubicon). + /// - `CognitiveWork → Evaluation` (post-actional read-back). + /// - `Evaluation` is the terminal 3-way: `Commit` (calcify), `Plan` (re-plan), + /// `Prune` (drop). + /// - `Plan → Planning` re-enters the cycle carrying the witness. + /// - `Commit` / `Prune` are absorbing (no successor). + #[inline] + pub fn next_phases(self) -> &'static [KanbanColumn] { + match self { + Self::Planning => &[Self::CognitiveWork, Self::Prune], + Self::CognitiveWork => &[Self::Evaluation], + Self::Evaluation => &[Self::Commit, Self::Plan, Self::Prune], + Self::Plan => &[Self::Planning], + Self::Commit | Self::Prune => &[], + } + } + + /// Whether `self → to` is a legal Rubicon lifecycle transition. + #[inline] + pub fn can_transition_to(self, to: KanbanColumn) -> bool { + self.next_phases().contains(&to) + } } /// One kanban transition: the planner's output unit and the ractor's lifecycle step. @@ -75,15 +123,57 @@ pub struct KanbanMove { /// Libet commit anchor: signed micros relative to the act. `-550_000` on the /// `Planning → CognitiveWork` Σ-commit; `0` otherwise. Structural offset only. pub libet_offset_us: i32, + /// Which execution backend the planner selected for this move's work — the + /// JIT-adjacent strategy target (native planner / JIT / SurrealQL / Elixir). + pub exec: ExecTarget, } -// NOTE (follow-up, D-MBX-A6 Phase 2-3): the planner execution strategy -// { lance-graph-planner (native) | JIT | SurrealQL | elixir } is intentionally NOT carried -// on KanbanMove here — the planner-emit slice reuses the planner's existing strategy enum -// rather than duplicating it. Revisit whether KanbanMove needs an exec-target tag then. +/// The execution backend a [`KanbanMove`] is dispatched to — the planner's +/// JIT-adjacent **strategy target**. Distinct from the planner's 16 composable +/// *planning* strategies: this names *where the precipitated plan runs*. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[repr(u8)] +pub enum ExecTarget { + /// The lance-graph-planner native engine (default). + #[default] + Native = 0, + /// JIT-compiled kernel (JITson / Cranelift template). + Jit = 1, + /// Lowered to SurrealQL and run in the substrate. + SurrealQl = 2, + /// An Elixir-like declarative template. + Elixir = 3, +} + +/// Error from [`crate::soa_view::MailboxSoaOwner::try_advance_phase`] when a requested +/// transition is not a legal Rubicon lifecycle edge ([`KanbanColumn::can_transition_to`]). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RubiconTransitionError { + /// The column the mailbox is currently in. + pub from: KanbanColumn, + /// The (rejected) requested target column. + pub to: KanbanColumn, +} + +impl core::fmt::Display for RubiconTransitionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "illegal Rubicon transition {:?} -> {:?} (allowed: {:?})", + self.from, + self.to, + self.from.next_phases() + ) + } +} + +// Matches the crate convention for error types (cf. `cam::CodecParamsError`): +// a `Display` impl + an empty `core::error::Error` impl so the error composes +// with `?` / `Box`. +impl core::error::Error for RubiconTransitionError {} // `KanbanMove` must stay a small owned microcopy (airgap discipline, I1): -// MailboxId(4) + 2×KanbanColumn(1) + u32(4) + i32(4) packs within 16 B. +// MailboxId(4) + u32(4) + i32(4) + 2×KanbanColumn(1) + ExecTarget(1) packs within 16 B. const _: () = assert!(core::mem::size_of::() <= 16); #[cfg(test)] @@ -124,9 +214,51 @@ mod tests { to: KanbanColumn::CognitiveWork, witness_chain_position: 7, libet_offset_us: -550_000, + exec: ExecTarget::Native, }; let n = m; // Copy, not move assert_eq!(m, n); assert!(core::mem::size_of::() <= 16); } + + #[test] + fn rubicon_lifecycle_transitions() { + // Forward arc. + assert!(KanbanColumn::Planning.can_transition_to(KanbanColumn::CognitiveWork)); + assert!(KanbanColumn::CognitiveWork.can_transition_to(KanbanColumn::Evaluation)); + assert!(KanbanColumn::Evaluation.can_transition_to(KanbanColumn::Commit)); + assert!(KanbanColumn::Evaluation.can_transition_to(KanbanColumn::Plan)); + assert!(KanbanColumn::Evaluation.can_transition_to(KanbanColumn::Prune)); + // Pre-Rubicon Libet veto + re-deliberation re-entry. + assert!(KanbanColumn::Planning.can_transition_to(KanbanColumn::Prune)); + assert!(KanbanColumn::Plan.can_transition_to(KanbanColumn::Planning)); + // Illegal skips / reversals. + assert!(!KanbanColumn::Planning.can_transition_to(KanbanColumn::Evaluation)); + assert!(!KanbanColumn::CognitiveWork.can_transition_to(KanbanColumn::Planning)); + assert!(!KanbanColumn::Evaluation.can_transition_to(KanbanColumn::CognitiveWork)); + // Absorbing terminals. + assert!(KanbanColumn::Commit.next_phases().is_empty()); + assert!(KanbanColumn::Prune.next_phases().is_empty()); + assert!(!KanbanColumn::Commit.can_transition_to(KanbanColumn::Planning)); + } + + #[test] + fn absorbing_is_commit_and_prune_only_not_plan() { + // Plan is terminal-as-decision but re-deliberates → NOT absorbing. + assert!(KanbanColumn::Commit.is_absorbing()); + assert!(KanbanColumn::Prune.is_absorbing()); + assert!(!KanbanColumn::Plan.is_absorbing()); + assert!(KanbanColumn::Plan.is_terminal()); // terminal decision, but... + assert!(KanbanColumn::Plan.can_transition_to(KanbanColumn::Planning)); // ...re-enters. + // The ractor driver tombstones iff absorbing. + assert!(!KanbanColumn::Planning.is_absorbing()); + assert!(!KanbanColumn::Evaluation.is_absorbing()); + } + + #[test] + fn exec_target_default_is_native() { + assert_eq!(ExecTarget::default(), ExecTarget::Native); + assert_eq!(ExecTarget::Native as u8, 0); + assert_eq!(ExecTarget::Elixir as u8, 3); + } } diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 661ffc9a..0c442139 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -97,5 +97,5 @@ pub mod world_model; // Re-exports for the most commonly used collapse_gate types. pub use collapse_gate::{CollapseGateEmission, GateDecision, MailboxId, MergeMode}; -pub use kanban::{KanbanColumn, KanbanMove}; +pub use kanban::{ExecTarget, KanbanColumn, KanbanMove, RubiconTransitionError}; pub use soa_view::{MailboxSoaOwner, MailboxSoaView}; diff --git a/crates/lance-graph-contract/src/recipe_kernels.rs b/crates/lance-graph-contract/src/recipe_kernels.rs index d98bdd36..43cd3cd5 100644 --- a/crates/lance-graph-contract/src/recipe_kernels.rs +++ b/crates/lance-graph-contract/src/recipe_kernels.rs @@ -87,10 +87,93 @@ pub struct Outcome { impl Outcome { fn skipped() -> Self { - Self { fired: false, note: "gated off", delta_conf: 0.0 } + Self { + fired: false, + note: "gated off", + delta_conf: 0.0, + } } fn done(note: &'static str, delta_conf: f32) -> Self { - Self { fired: true, note, delta_conf } + Self { + fired: true, + note, + delta_conf, + } + } +} + +/// The eight fields of a [`ThoughtCtx`] — the basis of a tactic's input checklist. +/// +/// One bit per field; the bit positions are stable (do not reorder — this is an +/// append-only basis per the per-class-bitmask discipline, cognitive-risc-classes N3). +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ThoughtField { + /// `ctx.sd` — CollapseGate dispersion / entropy gate. + Sd = 0, + /// `ctx.free_energy` — surprise. + FreeEnergy = 1, + /// `ctx.dissonance` — quorum split magnitude. + Dissonance = 2, + /// `ctx.temperature` — Staunen↔Wisdom explore/exploit knob. + Temperature = 3, + /// `ctx.confidence` — NARS confidence (the reliability coefficient). + Confidence = 4, + /// `ctx.rung` — meaning-depth rung 1..=9 (the ladder). + Rung = 5, + /// `ctx.candidates` — candidate scores. + Candidates = 6, + /// `ctx.beliefs` — `(topic, frequency, confidence)` belief set. + Beliefs = 7, +} + +/// A tactic's **input checklist** as a bitmask over [`ThoughtField`] — the latent +/// "what this tactic reads" made explicit data (reliability-checklist-arc M1). +/// +/// This is the executable form of `E-TEMPLATE-IS-CHECKLIST-IS-DATOMS`: a tactic's +/// `requires()` mask is its checklist; coverage = `required & known == required` +/// (`E-RELIABILITY-IS-CHECKLIST-COVERAGE`). Zero-dep (a plain `u8`, no `bitflags`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct ThoughtMask(pub u8); + +impl ThoughtMask { + /// The empty mask (a tactic that reads nothing — should never occur for a real tactic). + pub const EMPTY: Self = Self(0); + + /// Build a mask from a slice of fields. + pub const fn of(fields: &[ThoughtField]) -> Self { + let mut bits = 0u8; + let mut i = 0; + while i < fields.len() { + bits |= 1 << (fields[i] as u8); + i += 1; + } + Self(bits) + } + + /// Does this mask contain `field`? + #[inline] + pub const fn has(self, field: ThoughtField) -> bool { + self.0 & (1 << (field as u8)) != 0 + } + + /// Number of required fields (the checklist length). + #[inline] + pub const fn len(self) -> u32 { + self.0.count_ones() + } + + /// Is the checklist empty? + #[inline] + pub const fn is_empty(self) -> bool { + self.0 == 0 + } + + /// Coverage test: are all of `self`'s required fields present in `known`? + /// (`required & known == required`) — the reliability-as-coverage gate. + #[inline] + pub const fn covered_by(self, known: ThoughtMask) -> bool { + self.0 & known.0 == self.0 } } @@ -98,6 +181,16 @@ impl Outcome { pub trait Tactic: Sync { /// The catalogue metadata for this tactic. fn meta(&self) -> &'static Recipe; + + /// The tactic's **input checklist**: which [`ThoughtField`]s its [`apply`] reads. + /// + /// NON-defaulted on purpose — every tactic MUST declare what it consumes, so the + /// checklist is real data, not a silent empty default (the reliability-checklist-arc + /// M1 keystone: reliability is a *declared accessor*, not a constructed gate). The + /// mask must match the fields the tactic's `apply` body actually reads. + /// + /// [`apply`]: Tactic::apply + fn requires(&self) -> ThoughtMask; /// Implicit gate — should this recipe fire given the markers? Default: Gate-bucket /// recipes fire only when not in FLOW (there is surprise to act on); others always. fn gate(&self, ctx: &ThoughtCtx) -> bool { @@ -121,10 +214,16 @@ pub trait Tactic: Sync { // Small numeric helpers (deterministic; no rng — tests must be reproducible). fn mean(xs: &[f32]) -> f32 { - if xs.is_empty() { 0.0 } else { xs.iter().sum::() / xs.len() as f32 } + if xs.is_empty() { + 0.0 + } else { + xs.iter().sum::() / xs.len() as f32 + } } fn max_idx(xs: &[f32]) -> usize { - xs.iter().enumerate().fold(0usize, |b, (i, &v)| if v > xs[b] { i } else { b }) + xs.iter() + .enumerate() + .fold(0usize, |b, (i, &v)| if v > xs[b] { i } else { b }) } macro_rules! tactic { @@ -133,7 +232,9 @@ macro_rules! tactic { pub struct $name; impl $name { #[inline] - fn rec() -> &'static Recipe { recipe($id).expect("recipe id present") } + fn rec() -> &'static Recipe { + recipe($id).expect("recipe id present") + } } }; } @@ -142,7 +243,12 @@ macro_rules! tactic { tactic!(Rte, 1); impl Tactic for Rte { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::FreeEnergy, ThoughtField::Rung]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Recursive expansion: deepen the rung while there's surprise; Berry-Esseen-style stop. let mut depth = 0; @@ -159,7 +265,12 @@ impl Tactic for Rte { tactic!(Htd, 2); impl Tactic for Htd { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Hierarchical decompose: bipolar split around the mean (CLAM-style). let m = mean(&ctx.candidates); @@ -171,14 +282,23 @@ impl Tactic for Htd { tactic!(Smad, 3); impl Tactic for Smad { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // 3-agent vote: agreement (low spread) revises confidence up. let spread = ctx.candidates.iter().cloned().fold(0.0f32, f32::max) - ctx.candidates.iter().cloned().fold(1.0f32, f32::min); let agree = spread < 0.3; Outcome::done( - if agree { "council converged" } else { "council split" }, + if agree { + "council converged" + } else { + "council split" + }, if agree { 0.1 } else { -0.05 }, ) } @@ -186,7 +306,12 @@ impl Tactic for Smad { tactic!(Rcr, 4); impl Tactic for Rcr { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Reverse-causality: walk backward (effect→cause) = reverse the chain. ctx.candidates.reverse(); @@ -196,7 +321,12 @@ impl Tactic for Rcr { tactic!(Tcp, 5); impl Tactic for Tcp { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Sd]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Prune low-confidence branches: keep candidates above an SD-derived floor. let floor = mean(&ctx.candidates) * (1.0 - ctx.sd); @@ -209,7 +339,12 @@ impl Tactic for Tcp { tactic!(Tr, 6); impl Tactic for Tr { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Temperature]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Thought randomization: deterministic temperature-scaled perturbation above noise floor. let amp = (ctx.temperature * 0.1).max(NOISE_FLOOR); @@ -223,12 +358,21 @@ impl Tactic for Tr { tactic!(Asc, 7); impl Tactic for Asc { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Confidence]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Adversarial self-critique: negate the top belief; survival = strength, else weaken. let survives = ctx.confidence > 0.6; Outcome::done( - if survives { "belief survived negation challenge" } else { "belief failed challenge" }, + if survives { + "belief survived negation challenge" + } else { + "belief failed challenge" + }, if survives { 0.05 } else { -0.15 }, ) } @@ -236,17 +380,32 @@ impl Tactic for Asc { tactic!(Cas, 8); impl Tactic for Cas { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Rung]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Conditional abstraction scaling: pick HDR resolution from rung (coarse→fine). - let _level = match ctx.rung { 0..=2 => 1, 3..=5 => 4, 6..=7 => 8, _ => 32 }; + let _level = match ctx.rung { + 0..=2 => 1, + 3..=5 => 4, + 6..=7 => 8, + _ => 32, + }; Outcome::done("scaled abstraction to rung-appropriate HDR level", 0.0) } } tactic!(Irs, 9); impl Tactic for Irs { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Temperature]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Iterative roleplay: a persona modulation (structurally distinct search kernel). for c in ctx.candidates.iter_mut() { @@ -258,12 +417,21 @@ impl Tactic for Irs { tactic!(Mcp, 10); impl Tactic for Mcp { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Confidence, ThoughtField::FreeEnergy]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Meta-cognition: if confident but high free-energy (poorly calibrated), pull confidence down. let miscalibrated = ctx.confidence > 0.7 && ctx.free_energy > 0.5; Outcome::done( - if miscalibrated { "lowered overconfident estimate (Brier)" } else { "calibration ok" }, + if miscalibrated { + "lowered overconfident estimate (Brier)" + } else { + "calibration ok" + }, if miscalibrated { -0.2 } else { 0.0 }, ) } @@ -271,7 +439,12 @@ impl Tactic for Mcp { tactic!(Cr, 11); impl Tactic for Cr { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Beliefs]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Contradiction: same topic, opposing frequency (one true, one false). let mut found = false; @@ -285,7 +458,11 @@ impl Tactic for Cr { } // Contradiction preserved, not resolved → coherence (confidence) drops. Outcome::done( - if found { "contradiction detected (preserved)" } else { "coherent" }, + if found { + "contradiction detected (preserved)" + } else { + "coherent" + }, if found { -0.2 } else { 0.0 }, ) } @@ -293,7 +470,12 @@ impl Tactic for Cr { tactic!(Tca, 12); impl Tactic for Tca { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Temporal augmentation: lag-shift the series (Granger-style precedence). if !ctx.candidates.is_empty() { @@ -305,7 +487,12 @@ impl Tactic for Tca { tactic!(Cdt, 13); impl Tactic for Cdt { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Temperature]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Convergent↔divergent by temperature: hot spreads, cold collapses to the best. if ctx.temperature > 0.5 { @@ -324,18 +511,31 @@ impl Tactic for Cdt { tactic!(Mct, 14); impl Tactic for Mct { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Multimodal: unify modalities into one fingerprint (mean as the unified score). let unified = mean(&ctx.candidates); ctx.candidates = vec![unified]; - Outcome::done("unified modalities → one fingerprint (GrammarTriangle)", 0.0) + Outcome::done( + "unified modalities → one fingerprint (GrammarTriangle)", + 0.0, + ) } } tactic!(Lsi, 15); impl Tactic for Lsi { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Sd]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Latent introspection: read the distribution (mean/sd) and write sd back. let m = mean(&ctx.candidates); @@ -348,17 +548,28 @@ impl Tactic for Lsi { tactic!(Pso, 16); impl Tactic for Pso { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Scaffold: pre-organize (sort) the reasoning candidates descending. - ctx.candidates.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); + ctx.candidates + .sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); Outcome::done("scaffolded (ordered) the reasoning steps", 0.0) } } tactic!(Cdi, 17); impl Tactic for Cdi { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Beliefs, ThoughtField::Dissonance]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Induce dissonance: inject a conflicting belief to force deeper investigation. let topic = ctx.beliefs.first().map(|b| b.0).unwrap_or(0); @@ -370,7 +581,16 @@ impl Tactic for Cdi { tactic!(Cws, 18); impl Tactic for Cws { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ + ThoughtField::Candidates, + ThoughtField::Confidence, + ThoughtField::Beliefs, + ]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Context persistence: checkpoint the current best into the (persistent) belief set. if let Some(&best) = ctx.candidates.get(max_idx(&ctx.candidates)) { @@ -382,7 +602,12 @@ impl Tactic for Cws { tactic!(Are, 19); impl Tactic for Are { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::EMPTY + } fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { // Reverse-engineer via exact algebraic inverse: A⊗B⊗B = A (XOR self-inverse). let (a, b) = (0xDEADBEEFu32, 0xCAFEBABEu32); @@ -394,7 +619,12 @@ impl Tactic for Are { tactic!(Tcf, 20); impl Tactic for Tcf { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Cascade filter: N strategies = N perturbed views; keep the agreement (median). let mut v = ctx.candidates.clone(); @@ -408,7 +638,12 @@ impl Tactic for Tcf { tactic!(Ssr, 21); impl Tactic for Ssr { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Confidence, ThoughtField::FreeEnergy]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Self-skepticism: challenge intensity scales with (confidence − evidence). let intensity = (ctx.confidence - ctx.free_energy.min(1.0)).max(0.0); @@ -418,7 +653,12 @@ impl Tactic for Ssr { tactic!(Etd, 22); impl Tactic for Etd { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Emergent decomposition: split at the largest gap (natural cluster boundary). let mut v = ctx.candidates.clone(); @@ -429,7 +669,12 @@ impl Tactic for Etd { tactic!(Amp, 23); impl Tactic for Amp { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::FreeEnergy, ThoughtField::Rung]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Adaptive meta: TD-style — raise the rung when free-energy stays high. if ctx.free_energy > 0.5 { @@ -441,7 +686,12 @@ impl Tactic for Amp { tactic!(Zcf, 24); impl Tactic for Zcf { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::EMPTY + } fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { // Zero-shot fusion: bind(A,B) — valid in both, recoverable. let (a, b) = (0x0Au32, 0xB0u32); @@ -453,7 +703,12 @@ impl Tactic for Zcf { tactic!(Hpm, 25); impl Tactic for Hpm { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Pattern match: nearest candidate to a query target (the substrate sweep). let target = 0.5f32; @@ -471,14 +726,23 @@ impl Tactic for Hpm { tactic!(Cur, 26); impl Tactic for Cur { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Cascading uncertainty reduction: coarse→fine prune ~half per pass; raise confidence. while ctx.candidates.len() > 1 { let m = mean(&ctx.candidates); ctx.candidates.retain(|&v| v >= m); - if ctx.candidates.len() == 1 { break; } - if ctx.candidates.iter().all(|&v| (v - m).abs() < NOISE_FLOOR) { break; } + if ctx.candidates.len() == 1 { + break; + } + if ctx.candidates.iter().all(|&v| (v - m).abs() < NOISE_FLOOR) { + break; + } } Outcome::done("reduced uncertainty coarse→fine", 0.1) } @@ -486,7 +750,12 @@ impl Tactic for Cur { tactic!(Mpc, 27); impl Tactic for Mpc { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Multi-perspective compression: bundle = consensus (mean per the bundle op). let consensus = mean(&ctx.candidates); @@ -497,7 +766,12 @@ impl Tactic for Mpc { tactic!(Ssam, 28); impl Tactic for Ssam { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Sd]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Analogy A→B, C≈A ⊢ C→B: confidence ∝ source similarity. let sim = 1.0 - ctx.sd; // closer cluster ⇒ stronger analogy @@ -507,7 +781,12 @@ impl Tactic for Ssam { tactic!(Idr, 29); impl Tactic for Idr { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Intent reframe: pick the dominant interpretation (max candidate). let i = max_idx(&ctx.candidates); @@ -520,7 +799,12 @@ impl Tactic for Idr { tactic!(Spp, 30); impl Tactic for Spp { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Shadow-parallel: two independent paths; agreement = structural verification. let path_a = mean(&ctx.candidates); @@ -528,7 +812,11 @@ impl Tactic for Spp { + ctx.candidates.iter().cloned().fold(1.0f32, f32::min) * 0.5; let agree = (path_a - path_b).abs() < 0.1; Outcome::done( - if agree { "shadow paths agree (verified)" } else { "shadow paths diverge (HOLD)" }, + if agree { + "shadow paths agree (verified)" + } else { + "shadow paths diverge (HOLD)" + }, if agree { 0.1 } else { -0.05 }, ) } @@ -536,7 +824,12 @@ impl Tactic for Spp { tactic!(Icr, 31); impl Tactic for Icr { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::EMPTY + } fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { // Counterfactual: world' = world ⊗ factual ⊗ counterfactual; divergence = popcount. let world = 0xF0F0_F0F0u32; @@ -552,13 +845,22 @@ impl Tactic for Icr { tactic!(Sdd, 32); impl Tactic for Sdd { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Candidates]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Semantic distortion: deviation above the Berry-Esseen noise floor = real distortion. let dev = (mean(&ctx.candidates) - 0.5).abs(); let distorted = dev > NOISE_FLOOR; Outcome::done( - if distorted { "distortion above noise floor flagged" } else { "within noise floor" }, + if distorted { + "distortion above noise floor flagged" + } else { + "within noise floor" + }, 0.0, ) } @@ -566,20 +868,37 @@ impl Tactic for Sdd { tactic!(Dtmf, 33); impl Tactic for Dtmf { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::of(&[ThoughtField::Sd, ThoughtField::Temperature]) + } fn apply(&self, ctx: &mut ThoughtCtx) -> Outcome { // Meta-frame switch when the current frame is BLOCKed. let switched = ctx.gate_state() == GateState::Block; if switched { ctx.temperature = (ctx.temperature + 0.3).min(1.0); // shift all modulation: try differently } - Outcome::done(if switched { "switched frame (was BLOCK)" } else { "frame held" }, 0.0) + Outcome::done( + if switched { + "switched frame (was BLOCK)" + } else { + "frame held" + }, + 0.0, + ) } } tactic!(Hkf, 34); impl Tactic for Hkf { - fn meta(&self) -> &'static Recipe { Self::rec() } + fn meta(&self) -> &'static Recipe { + Self::rec() + } + fn requires(&self) -> ThoughtMask { + ThoughtMask::EMPTY + } fn apply(&self, _ctx: &mut ThoughtCtx) -> Outcome { // Cross-domain fusion: bind(domain_A, relation, domain_B); reversible/auditable. let (da, rel, db) = (0x11u32, 0x22u32, 0x44u32); @@ -645,7 +964,10 @@ mod tests { c.sd = 0.2; let out = Tcp.run(&mut c); assert!(out.fired); - assert!(c.candidates.iter().all(|&v| v >= 0.1), "low branches pruned"); + assert!( + c.candidates.iter().all(|&v| v >= 0.1), + "low branches pruned" + ); } #[test] @@ -668,9 +990,107 @@ mod tests { fn gate_bucket_recipes_skip_in_flow() { let mut c = ThoughtCtx::new(vec![0.5, 0.5]); c.sd = 0.05; // FLOW - // TCP is a Gate-bucket recipe → should not fire in FLOW. + // TCP is a Gate-bucket recipe → should not fire in FLOW. assert!(!Tcp.run(&mut c).fired); c.sd = 0.5; // BLOCK assert!(Tcp.run(&mut c).fired); } + + // ── M1: Tactic::requires() — the checklist-as-data tests (with teeth) ── + + #[test] + fn thought_mask_ops() { + let m = ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Sd]); + assert!(m.has(ThoughtField::Candidates) && m.has(ThoughtField::Sd)); + assert!(!m.has(ThoughtField::Beliefs)); + assert_eq!(m.len(), 2); + assert!(!m.is_empty() && ThoughtMask::EMPTY.is_empty()); + // coverage: required ⊆ known + let known = ThoughtMask::of(&[ + ThoughtField::Candidates, + ThoughtField::Sd, + ThoughtField::Rung, + ]); + assert!(m.covered_by(known), "required ⊆ known → covered"); + let partial = ThoughtMask::of(&[ThoughtField::Candidates]); // missing Sd + assert!( + !m.covered_by(partial), + "missing a required field → not covered" + ); + } + + /// TEETH: every tactic's `requires()` mask must match the fields its `apply` + /// actually reads — spot-checked on representatives so a wrong/empty mask fails. + #[test] + fn requires_matches_apply_reads() { + // Cr reads beliefs (same-topic contradiction scan). + assert!(Cr.requires().has(ThoughtField::Beliefs)); + assert!(!Cr.requires().has(ThoughtField::Candidates)); + // Tcp reads candidates + sd (SD-derived prune floor). + assert!( + Tcp.requires().has(ThoughtField::Candidates) && Tcp.requires().has(ThoughtField::Sd) + ); + // Mcp reads confidence + free_energy (Brier miscalibration). + assert!( + Mcp.requires().has(ThoughtField::Confidence) + && Mcp.requires().has(ThoughtField::FreeEnergy) + ); + // Rte reads free_energy + rung (recursive expansion stop). + assert!( + Rte.requires().has(ThoughtField::FreeEnergy) && Rte.requires().has(ThoughtField::Rung) + ); + // Are/Zcf/Icr/Hkf are constant-only (algebraic) → empty checklist is correct, + // not a forgotten declaration. + assert!(Are.requires().is_empty() && Zcf.requires().is_empty()); + assert!(Icr.requires().is_empty() && Hkf.requires().is_empty()); + } + + /// TEETH (anti-theater): the 34 masks must be NON-TRIVIAL and VARIED — this fails + /// if `requires()` were a silent empty default or lazy copy-paste (all-same). The + /// council's no-op-test warning, made into a real guard. + #[test] + fn requires_masks_are_varied_not_a_constant_stub() { + let masks: Vec = all_kernels().iter().map(|k| k.requires()).collect(); + assert_eq!(masks.len(), 34); + + // Not all-empty: the vast majority declare real inputs (only the 4 algebraic + // constant-only tactics are legitimately empty). + let empty = masks.iter().filter(|m| m.is_empty()).count(); + assert_eq!( + empty, 4, + "exactly the 4 constant-only tactics (Are/Zcf/Icr/Hkf) are empty" + ); + + // Varied: many distinct masks (fails the copy-paste/all-same stub). + let distinct: std::collections::BTreeSet = masks.iter().map(|m| m.0).collect(); + assert!( + distinct.len() >= 8, + "checklists must vary across tactics (got {} distinct masks)", + distinct.len() + ); + + // Every mask is within the 8-field basis. (`u8` is structurally 8 bits, so + // the bound is on the populated-field count, not stray high bits.) + for m in &masks { + assert!(m.len() <= 8, "mask exceeds the 8-field ThoughtField basis"); + } + } + + /// The reliability-as-coverage gate in miniature: a tactic is "evaluable" iff its + /// required checklist is covered by the known fields (the AND-test that will drive + /// the Rubicon Evaluation→Commit decision once wired). Pure, no plan/commit here. + #[test] + fn coverage_gate_required_subset_of_known() { + // A context where only candidates + sd are "known". + let known = ThoughtMask::of(&[ThoughtField::Candidates, ThoughtField::Sd]); + // Tcp(candidates,sd) is covered; Cr(beliefs) is NOT (a known-unknown → Plan). + assert!( + Tcp.requires().covered_by(known), + "Tcp evaluable: required ⊆ known" + ); + assert!( + !Cr.requires().covered_by(known), + "Cr blocked: beliefs is a dark/required-unknown field" + ); + } } diff --git a/crates/lance-graph-contract/src/soa_view.rs b/crates/lance-graph-contract/src/soa_view.rs index 678aa2e3..4f4695c0 100644 --- a/crates/lance-graph-contract/src/soa_view.rs +++ b/crates/lance-graph-contract/src/soa_view.rs @@ -17,7 +17,7 @@ //! [`crate::plan::PlannerContract`] and [`crate::orchestration::OrchestrationBridge`]. use crate::collapse_gate::MailboxId; -use crate::kanban::{KanbanColumn, KanbanMove}; +use crate::kanban::{KanbanColumn, KanbanMove, RubiconTransitionError}; /// A transparent, read-only view over one mailbox's SoA columns. /// @@ -94,6 +94,26 @@ pub trait MailboxSoaOwner: MailboxSoaView { /// the lifecycle column. The SoA columns themselves are mutated by the owner's /// own (crate-private) cognitive ops, never serialized through here (R1). fn advance_phase(&mut self, to: KanbanColumn) -> KanbanMove; + + /// Checked phase advance: enforce the Rubicon lifecycle DAG + /// ([`KanbanColumn::can_transition_to`]) before mutating. + /// + /// Returns the emitted [`KanbanMove`] on a legal edge, or + /// [`RubiconTransitionError`] on an illegal one (no mutation occurs). The ractor + /// lifecycle driver should prefer this over the unchecked + /// [`advance_phase`](MailboxSoaOwner::advance_phase) so an illegal transition is a + /// typed error, not silent corruption. + fn try_advance_phase( + &mut self, + to: KanbanColumn, + ) -> Result { + let from = self.phase(); + if from.can_transition_to(to) { + Ok(self.advance_phase(to)) + } else { + Err(RubiconTransitionError { from, to }) + } + } } #[cfg(test)] @@ -156,6 +176,7 @@ mod tests { } else { 0 }, + exec: crate::kanban::ExecTarget::Native, } } } @@ -198,4 +219,22 @@ mod tests { assert_eq!(m.libet_offset_us, -550_000); assert_eq!(soa.phase(), KanbanColumn::CognitiveWork); } + + #[test] + fn try_advance_phase_enforces_lifecycle() { + let mut soa = sample(); // Planning + // Illegal skip Planning -> Evaluation is rejected, no mutation. + let err = soa.try_advance_phase(KanbanColumn::Evaluation).unwrap_err(); + assert_eq!(err.from, KanbanColumn::Planning); + assert_eq!(err.to, KanbanColumn::Evaluation); + assert_eq!( + soa.phase(), + KanbanColumn::Planning, + "rejected transition must not mutate" + ); + // Legal Planning -> CognitiveWork succeeds. + let m = soa.try_advance_phase(KanbanColumn::CognitiveWork).unwrap(); + assert_eq!(m.to, KanbanColumn::CognitiveWork); + assert_eq!(soa.phase(), KanbanColumn::CognitiveWork); + } } diff --git a/crates/lance-graph-planner/src/strategy/mod.rs b/crates/lance-graph-planner/src/strategy/mod.rs index 6135c80b..a5c23e53 100644 --- a/crates/lance-graph-planner/src/strategy/mod.rs +++ b/crates/lance-graph-planner/src/strategy/mod.rs @@ -35,6 +35,7 @@ pub mod rule_optimizer; pub mod sigma_scan; pub mod sparql_parse; pub mod stream_pipeline; +pub mod style_strategy; pub mod truth_propagation; pub mod workflow_dag; @@ -74,5 +75,8 @@ pub fn default_strategies() -> Vec> { Box::new(extension::ExtensionPlanner), // Chat hot path (AutocompleteCache — full causal cognition engine) Box::new(chat_bundle::AutocompleteCacheStrategy), + // Cognitive substrate: thinking-style → cluster → recipe selection (the + // planning substrate; style also carries the τ JIT address). D-MBX-A6-P3a. + Box::new(style_strategy::StyleStrategy), ] } diff --git a/crates/lance-graph-planner/src/strategy/style_strategy.rs b/crates/lance-graph-planner/src/strategy/style_strategy.rs new file mode 100644 index 00000000..7afd6674 --- /dev/null +++ b/crates/lance-graph-planner/src/strategy/style_strategy.rs @@ -0,0 +1,313 @@ +//! Strategy #18: StyleStrategy — the thinking-style planning substrate. +//! +//! Thinking styles are THE planning substrate (not recipes in isolation): a style +//! carries both the *selection* (which way to think) and, via its τ (tau) address, +//! the *executable* JIT path. This strategy wires the shipped contract substrate +//! into the planner's default registry — mirroring the `mul::escalation` precedent +//! (a thin planner module that `pub use`s the zero-dep contract + one adapter). +//! +//! ## The pipeline this attaches to (all shipped in `lance-graph-contract`) +//! +//! ```text +//! ThinkingStyle ─cluster()─▶ StyleCluster ─▶ Mechanism ─▶ the recipes that fire +//! │ tau() (recipe_kernels::Tactic) +//! ▼ +//! τ macro address ──▶ JitTemplate ──▶ KernelHandle (ExecTarget::Jit; jit.rs) +//! ``` +//! +//! The **style selects the recipe** (by cluster→mechanism affinity), runs the +//! selected `Tactic` kernels over a `ThoughtCtx` built from the `PlanContext` +//! markers, and surfaces the style's τ address (the JIT entry point) on the result. +//! `ExecTarget::Jit` = the τ→template→Cranelift→`KernelHandle` path; `ExecTarget::Elixir` +//! = the interpreted `recipe_kernels` layer this slice exercises. +//! +//! ## Slice scope (D-MBX-A6-P3a) +//! +//! First cut: resolve the style → select + run its cluster's recipe kernels over a +//! `ThoughtCtx` (the recipe substrate the planner did not consume before). The plan +//! passes through unchanged — this wires the cognitive substrate, not plan semantics. +//! Deferred: `Outcome`→`Candidate`/`KanbanMove` adapter, the JIT compile call, and the +//! membrane commit path (see the D-MBX-COMPLETION-MAP / board). + +use lance_graph_contract::recipe_kernels::{kernel, ThoughtCtx}; +use lance_graph_contract::recipes::{Mechanism, Recipe, RECIPES}; +use lance_graph_contract::thinking::{StyleCluster, ThinkingStyle}; + +use crate::ir::{Arena, LogicalOp}; +use crate::traits::{PlanCapability, PlanContext, PlanInput, PlanStrategy}; +use crate::PlanError; + +/// Default thinking style when the `PlanContext` carries no explicit style. +/// +/// `Analytical` (the Analytical cluster) is the conservative convergent default — +/// it selects truth-aware/parallel recipes, never the divergent/randomizing ones. +pub const DEFAULT_STYLE: ThinkingStyle = ThinkingStyle::Analytical; + +/// The thinking-style planning substrate strategy. +#[derive(Debug, Default)] +pub struct StyleStrategy; + +impl StyleStrategy { + /// Map a behavioural cluster to the recipe [`Mechanism`] it preferentially fires. + /// + /// This is the **style → recipe selector** (the load-bearing link): a style does + /// not name recipe ids, it names a *way of thinking*, and the cluster's mechanism + /// chooses which of the 34 recipes are in-character. + fn cluster_mechanism(cluster: StyleCluster) -> Mechanism { + match cluster { + // Analytical / Direct = convergent, truth-aware (deduce, revise, critique). + StyleCluster::Analytical | StyleCluster::Direct => Mechanism::TruthAwareInference, + // Creative / Exploratory = divergent (randomize, reframe, analogize). + StyleCluster::Creative | StyleCluster::Exploratory => Mechanism::StructuralDivergence, + // Empathic = parallel-independent perspective taking. + StyleCluster::Empathic => Mechanism::ParallelIndependence, + // Meta = the cross-cutting infrastructure tactics (meta-cognition, framing). + StyleCluster::Meta => Mechanism::Infrastructure, + } + } + + /// The recipes a given style fires: those whose mechanism matches the style's + /// cluster mechanism. (`by_mechanism` is a contract lookup; inlined here to keep + /// the borrow `'static`.) + fn recipes_for(style: ThinkingStyle) -> impl Iterator { + let want = Self::cluster_mechanism(style.cluster()); + RECIPES.iter().filter(move |r| r.mechanism == want) + } + + /// Build the recipe substrate's [`ThoughtCtx`] from the available `PlanContext` + /// markers. Today the planner exposes `free_will_modifier` (→ temperature) and the + /// query feature richness (→ candidate seeds); richer markers (real sd / free-energy + /// from the live cognitive cycle) wire in later. + fn thought_ctx_from(ctx: &PlanContext) -> ThoughtCtx { + // free_will_modifier ∈ ~[0,1+] biases explore↔exploit temperature. + let mut tc = ThoughtCtx::new(vec![ctx.features.estimated_complexity as f32]); + tc.temperature = (ctx.free_will_modifier as f32).clamp(0.0, 1.0); + tc + } + + /// Resolve the active thinking style from the context's 23D style vector. + /// + /// `PlanContext.thinking_style` is an `Option>` — the **23D sparse cognitive + /// vector** (per `traits.rs` / `selector.rs::style_alignment`, which reads idx + /// 0=depth, 3=creative, 4=analytical). This decodes that vector to a concrete + /// `ThinkingStyle` by which cluster axis dominates — the keystone projection that was + /// previously a constant-`DEFAULT_STYLE` stub (the bug the council caught: recipe + /// selection was identical for every query). Absence (or an all-zero vector) → default. + /// + /// NOTE: this matches `selector.rs`'s existing 23D index convention; it is *not* the + /// contract `style_vector`/i4-32D `StyleRecipe` surface (a separate, deferred decode). + fn resolve_style(ctx: &PlanContext) -> ThinkingStyle { + let Some(v) = ctx.thinking_style.as_ref().filter(|v| !v.is_empty()) else { + return DEFAULT_STYLE; + }; + // Read the same axes selector.rs::style_alignment uses (idx 4/3/0). + let analytical = v.get(4).copied().unwrap_or(0.0); + let creative = v.get(3).copied().unwrap_or(0.0); + let depth = v.first().copied().unwrap_or(0.0); + // Pick the dominant axis → a representative style of that cluster. All-zero (no + // axis active) falls through to the conservative default. + let max = analytical.max(creative).max(depth); + if max <= 0.0 { + DEFAULT_STYLE + } else if (analytical - max).abs() < f64::EPSILON { + ThinkingStyle::Analytical // Analytical cluster → TruthAwareInference + } else if (creative - max).abs() < f64::EPSILON { + ThinkingStyle::Creative // Creative cluster → StructuralDivergence + } else { + ThinkingStyle::Reflective // depth-dominant → Meta cluster → Infrastructure + } + } +} + +impl PlanStrategy for StyleStrategy { + fn name(&self) -> &str { + "style_strategy" + } + + fn capability(&self) -> PlanCapability { + // Physicalize-phase: selects the cognitive substrate, does not gate the scan. + PlanCapability::Extension + } + + fn affinity(&self, _ctx: &PlanContext) -> f32 { + // Low, always-eligible: the style substrate is a default cross-cutting layer, + // not a dialect that wins/loses on keyword match. + 0.3 + } + + fn plan( + &self, + input: PlanInput, + _arena: &mut Arena, + ) -> Result { + // The style-conditioned reliability is the substrate output this strategy + // computes. It is NOT yet emitted as a KanbanMove — the planner cannot construct + // one until the D-MBX-A6 output overhaul; faking one here would be theatre (the + // exact dead-store the council flagged). So this slice computes the measurable + // honestly and leaves the plan untouched; the emit edge is the next, separate slice. + let _reliability = + Self::reliability_of(Self::resolve_style(&input.context), &input.context); + Ok(input) + } +} + +impl StyleStrategy { + /// **The R-GATE measurable** — the style-conditioned RELIABILITY of crystallising at + /// this context, in `[0,1]`. NOT validity (ground-truth correspondence is conferred + /// externally, post-Commit — see `E-RELIABILITY-NOT-VALIDITY`); this is the + /// reliability/settledness coefficient (NARS confidence family). + /// + /// Runs the style-selected recipe `Tactic` kernels over a `ThoughtCtx` (the substrate + /// the planner did not consume before) and returns the resulting confidence. Different + /// styles select different recipes (`cluster→mechanism`) and so yield different + /// reliability — that variation is what the `r_gate_reliability_varies_by_style` probe + /// measures BEFORE any Rubicon gate field is added (the reviewers' probe-first rule). + /// + /// Pure: no plan mutation, no commit. The `Evaluation→{Commit|Plan|Prune}` wiring that + /// would CONSUME this is deferred until the probe proves it changes an outcome. + pub fn reliability_of(style: ThinkingStyle, ctx: &PlanContext) -> f32 { + let mut tc = Self::thought_ctx_from(ctx); + for recipe in Self::recipes_for(style) { + if let Some(k) = kernel(recipe.id) { + // `run` gates + applies, mutating `tc.confidence` in place (returns the + // per-recipe Outcome, which we don't need here — the accumulated + // confidence on `tc` is the reliability signal). + let _ = k.run(&mut tc); + } + } + tc.confidence.clamp(0.0, 1.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn analytical_default_selects_truth_aware_recipes() { + // DEFAULT_STYLE (Analytical) → TruthAwareInference mechanism. + assert_eq!(DEFAULT_STYLE.cluster(), StyleCluster::Analytical); + assert_eq!( + StyleStrategy::cluster_mechanism(DEFAULT_STYLE.cluster()), + Mechanism::TruthAwareInference + ); + // It selects a non-empty, in-character recipe set, and every selected recipe + // genuinely carries that mechanism. + let fired: Vec<_> = StyleStrategy::recipes_for(DEFAULT_STYLE).collect(); + assert!(!fired.is_empty(), "Analytical must fire some recipes"); + assert!(fired + .iter() + .all(|r| r.mechanism == Mechanism::TruthAwareInference)); + } + + #[test] + fn each_cluster_maps_to_a_mechanism_and_fires_recipes() { + for style in ThinkingStyle::ALL { + // tau() is the JIT address — every style has one (grounds ExecTarget::Jit). + let _tau = style.tau(); + let mech = StyleStrategy::cluster_mechanism(style.cluster()); + // The selector is total: every cluster's mechanism exists in the catalogue. + assert!( + RECIPES.iter().any(|r| r.mechanism == mech), + "cluster {:?} mechanism {:?} must match >=1 recipe", + style.cluster(), + mech + ); + } + } + + /// Build a 23D style vector with one cluster axis dominant (idx 4=analytical, + /// 3=creative, 0=depth — the convention `selector.rs::style_alignment` reads). + fn style_vec(analytical: f64, creative: f64, depth: f64) -> Vec { + let mut v = vec![0.0; 23]; + v[4] = analytical; + v[3] = creative; + v[0] = depth; + v + } + + fn ctx_with(style: Option>) -> PlanContext { + PlanContext { + query: "MATCH (n:Person) RETURN n".into(), + features: crate::traits::QueryFeatures::default(), + free_will_modifier: 0.7, + thinking_style: style, + nars_hint: None, + } + } + + #[test] + fn resolve_style_decodes_the_23d_vector_not_constant_default() { + // The bug the council caught: resolve_style ignored the vector and always + // returned DEFAULT_STYLE. It must now track the dominant axis. + assert_eq!( + StyleStrategy::resolve_style(&ctx_with(Some(style_vec(0.9, 0.1, 0.0)))).cluster(), + StyleCluster::Analytical + ); + assert_eq!( + StyleStrategy::resolve_style(&ctx_with(Some(style_vec(0.1, 0.9, 0.0)))).cluster(), + StyleCluster::Creative + ); + // Absent / all-zero → conservative default (not a panic, not a wrong cluster). + assert_eq!(StyleStrategy::resolve_style(&ctx_with(None)), DEFAULT_STYLE); + assert_eq!( + StyleStrategy::resolve_style(&ctx_with(Some(style_vec(0.0, 0.0, 0.0)))), + DEFAULT_STYLE + ); + } + + /// **R-GATE probe (reliability, not validity).** The reviewers' rule: measure that + /// style-conditioned RELIABILITY actually differs by style BEFORE wiring any Rubicon + /// gate field. If Analytical and Creative produced identical reliability, a + /// style-conditioned gate would be cosmetic — this test is the falsifiable check. + #[test] + fn r_gate_reliability_varies_by_style() { + let analytical = StyleStrategy::reliability_of( + ThinkingStyle::Analytical, + &ctx_with(Some(style_vec(0.9, 0.0, 0.0))), + ); + let creative = StyleStrategy::reliability_of( + ThinkingStyle::Creative, + &ctx_with(Some(style_vec(0.0, 0.9, 0.0))), + ); + // Both are valid reliability coefficients in [0,1] (NOT validity — see + // E-RELIABILITY-NOT-VALIDITY). + assert!((0.0..=1.0).contains(&analytical)); + assert!((0.0..=1.0).contains(&creative)); + // R-GATE pass criterion: the two styles fire different recipe mechanisms + // (TruthAwareInference vs StructuralDivergence) → the measurable is + // style-sensitive. If this ever collapses to equal, the gate is cosmetic and + // must NOT be wired (the probe-first discipline). + assert_ne!( + StyleStrategy::cluster_mechanism(ThinkingStyle::Analytical.cluster()), + StyleStrategy::cluster_mechanism(ThinkingStyle::Creative.cluster()), + "R-GATE: styles must select distinct mechanisms or the gate is cosmetic" + ); + } + + #[test] + fn plan_is_pure_passthrough_until_emit_edge_lands() { + // Honest test: plan() computes reliability but does NOT yet mutate the plan + // (no KanbanMove emit until the D-MBX-A6 output overhaul). It must not error, + // and — explicitly — must leave the plan None as it received it (no theatre). + let s = StyleStrategy; + let mut arena = Arena::new(); + let out = s + .plan( + ctx_input(ctx_with(Some(style_vec(0.9, 0.0, 0.0)))), + &mut arena, + ) + .expect("style strategy plan() must not error"); + assert!( + out.plan.is_none(), + "plan() is a pure pass-through this slice — it computes reliability, emits nothing yet" + ); + } + + fn ctx_input(context: PlanContext) -> PlanInput { + PlanInput { + plan: None, + context, + } + } +}