diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 55bbee761..a7da9daad 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,27 @@ +## [Main thread / Opus, autoattended] 5-agent council reviewed the NAL syllogism capstone → SOUND kernel, integration roadmap spec'd + +**Branch:** claude/jolly-cori-clnf9. Council R1–R5 (Opus, full-file reads, E-READ-NOT-GREP). **R1** NAL-correct (figures/rules/premise-orders byte-match canonical OpenNARS; omitted Comparison/Analogy intentional — need `<->` copula CE64 can't carry). **R2** firewall+layer SOUND; the 3 truth-fns verified byte-identical to BOTH ndarray::hpc::nars AND forward(). **R3** SOUND + applied doc fix (predicate is a *typed placeholder*; compose_p won't synthesize the induced/abduced relation). **R4** SOUND + applied doc fix (EW64 fold must be **slot-0-anchored**, not a blind left-fold that None-cascades; EdgeRef→CE64 must honor family via OGIT class + 1-based local). **R5** FIX-NEEDED *at integration only* — kernel sound, but zero callers; the capstone (3-path glue / rung elevation / cranelift↔elixir) is unwired. + +**Applied:** R3+R4 doc tightenings + 1 redundant-link + fmt of test code (syllogism.rs now 14 tests / clippy / fmt / rustdoc all clean). **Spec'd** the roadmap → `.claude/specs/nal-syllogism-capstone-v1.md`. Highest-leverage next step (R5 #3): promote `notation()` → `const FIGURE_RULES` table + dual `jit_template()` / `elixir_clause()` emitters — the literal "NAL notation and Elixir complete each other" (one table, two backends, offline codegen). Next: PR + subscribe. + +--- + +## [Main thread / Opus, autoattended] NAL syllogism FIGURE resolution hardwired on CausalEdge64 (the capstone) + +**Branch:** claude/jolly-cori-clnf9. **Tests:** `causal-edge` syllogism 14 green (v2 default) / 13 (v1, the mantissa test gated); new file clippy- + fmt-clean (the 15 pre-existing `edge.rs` -D-warnings + fmt diffs are the documented v1/v2 mantissa minefield — untouched). User steered: "hardwire syllogism resolution like SPO 2³ … using causaledge64, wiring EW64"; "NAL notation = missing capstone glueing 3 reasoning methods + 10-rung ladder + JITson/cranelift vs elixir"; "34+ opennars vocabulary just needs wiring." + +**Did:** new `causal-edge::syllogism` — `Figure{Chain,ChainRev,SharedSubject,SharedObject}` resolved by integer SPO-palette term-sharing (the Pearl-2³ analogue); `CausalEdge64::figure()`/`syllogize()` emit the conclusion edge (outer terms + canonical NARS truth + signed mantissa + AND mask). Grounded by full reads (E-READ-NOT-GREP): nars_engine, cognitive_codebook, ndarray::hpc::nars, atoms, cognitive_shader, episodic_edges, causal-edge edge/tables. **Reverted** the speculative 3rd-copy syllogisms in `contract::exploration::NarsTruth` (mislabeled ind⇄abd vs canonical). Next: PR + 5-agent council review; then (gated) EW64→CE64 wiring in the driver. + +--- + +## [Main thread / Opus] 5-agent RESEARCH council — 8 semantics/embedding papers, firewall-filtered + +User: "use research council 5 agents [on 8 PDFs]; grep/sed/tail/head fragments forbidden; test the reading tools first." **Test caught a blocker:** the Read tool's PDF path needs poppler (absent) — every agent would have failed. Fixed: extracted PDFs→full-text `.txt` via pymupdf (pip), recovered an 8th that was a saved MHTML web page; verified Read works on `.txt`. THEN dispatched A1–A5 (Opus, read-only, **READ full text, never grep/head/tail**). All 5 returned full-read verdicts. + +**Result** (doc `research-council-semantics-papers-2026-06.md`, `E-RESEARCH-COUNCIL-PROPOSE-VALIDATE`): firewall cleanly filtered 8 LLM/float papers → 1 ADOPT-NOW (SemDiD→`head2head::WinnerCriterion::Repulsion`, cosine→Hamming), 3 integer validators (Legality / `⟨u,v⟩` / footprint), 1 shared novelty-gated-selection operator (head2head + EW64), 1 adversarial foundation-probe (Kozlowski: does hard-basin CAM-PQ discard entangled low-rank semantics?), 1 SKIP (segmentation = trap). The "PROPOSE/ADDRESS" doctrine was independently re-derived by 4/5 reviewers from 7 papers. Next: @jan picks the first build (SemDiD-adopt vs Kozlowski-probe vs Legality-validator). + +--- + ## [Main thread / Opus, autoattended] 5-agent dev council → D-ATOM-4/RawEdge shipped (① Heel-compose REFUTED, ② RawEdge built, ③ deferred) **Branch:** claude/jolly-cori-clnf9. **Cargo:** contract lib **550 green** (+5 counterfactual); default clippy clean. User: "use the 5 agent development council." Convened R1–R5 (Opus, read-only); consolidated + auto-resolved + built. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 97d2fbef1..19db72b5d 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,38 @@ +## 2026-06-01 — E-NARS-FIGURE-CAPSTONE — NAL syllogism resolution hardwired on CausalEdge64 like Pearl 2³: the figure = which SPO palette term two edges share → rule → conclusion edge + +**Status:** SHIPPED (`causal-edge::syllogism`, branch claude/jolly-cori-clnf9; 14 tests v2 / 13 v1, the new file clippy- + fmt-clean). User: "the syllogism resolution needs to be hardwired similar to SPO 2^3 rung decomposition … using causaledge64 and wiring EW64" + "NAL syllogism notation is the missing capstone for glueing all 3 reasoning methods with the 10-rung ladder and the JITson cranelift templates vs elixir." + +**The capstone:** `CausalEdge64::forward()` composes two edges POSITIONALLY with a *pre-set* inference type — it never asks WHICH TERM they share. NAL syllogism IS that question. `figure(other)` resolves it by integer SPO-palette equality — the hardwired analogue of the Pearl 2³ mask (O(1), branch-minimal, no float on the structural path): +- `o1==s2` → Chain → **Deduction** ⊢ s1→o2 +- `s1==o2` → ChainRev → **Deduction** ⊢ s2→o1 +- `s1==s2` → SharedSubject → **Induction** ⊢ o1→o2 +- `o1==o2` → SharedObject → **Abduction** ⊢ s1→s2 +- same statement (`s1==s2 ∧ o1==o2`) → None (that is Revision, not a syllogism). +`syllogize(other)` emits the conclusion `CausalEdge64` (outer terms + canonical NARS truth + signed v2 mantissa Ded +1 / Ind +2 / Abd −1 + AND-ed Pearl mask). Firewall: integer term-match PROPOSES the figure; the deterministic NARS truth-function ADDRESSES. Truth math is byte-identical to `ndarray::hpc::nars` (hardware) + `forward()` (protocol) — NOT a new truth type. + +**The "don't reinvent" catch (E-READ-NOT-GREP in action):** user flagged "we have 34+ opennars vocabulary, it just needs to be wired." Reading-first found it all already present — vocabulary (`cognitive_codebook::{NarsInference 10, NarsCopula 12}` with the `{M-->P,S-->M}` notation in comments + `CognitiveAddress` + fingerprints), the tested engine (`planner::nars_engine::nars_infer`, 9 rules / 17 tests), the canonical truth (`ndarray::hpc::nars::NarsTruth` + 7 truth-fns), the atoms (`atoms.rs` Operation lane: abduct/deduce/induce/synthesize), the wire (`causal_edge::InferenceType` signed mantissa). The speculative deduction/induction/abduction I had started adding to `contract::exploration::NarsTruth` were a 3rd copy AND mislabeled (induction⇄abduction swapped vs the canonical engine) — **REVERTED.** The gap was never the truth math; it was the FIGURE decomposition. Next (gated): wire EW64 `EdgeRef`→`CausalEdge64`→`syllogize` across the ≤4 hot edges in cognitive-shader-driver; cranelift/elixir dual-compile of the figure table. + +Cross-ref: `E-READ-NOT-GREP`, the firewall doctrine, CausalEdge64 v2 mantissa, `episodic_edges` (EW64), `atoms` Operation family, the Pearl 2³ mask. + +--- + +## 2026-06-01 — E-RESEARCH-COUNCIL-PROPOSE-VALIDATE — 8 LLM/float semantics papers, council-firewall-filtered: the "PROPOSE (float, offline) / ADDRESS (integer, hot)" doctrine independently re-derived from 7 of 8; 1 ADOPT-NOW, 3 integer validators, 1 adversarial foundation-probe + +**Status:** FINDING (5-agent research council, 2026-06-01; ALL papers READ IN FULL per `E-READ-NOT-GREP` — A1 read 1311 lines / A5 read 1517+1695, via Read not grep). Doc: `research-council-semantics-papers-2026-06.md`. + +**The corroboration:** all 8 papers are LLM/float-based; the council extracted only the deterministic/integer/offline kernel from 7 and cleanly SKIP'd the 1 with no quarantine seam (segmentation = unconditional firewall trap, float-all-the-way-down). **The workspace's "similarity PROPOSES (float, offline, upstream) / CAM ADDRESSES (integer, hot, deterministic)" doctrine was independently re-derived by 4 of 5 reviewers from 7 different papers** — strong external evidence the core architecture is right. + +**The slate (firewall-filtered):** +- **ADOPT-NOW (offline-buildable):** SemDiD (2506.23601) → a `head2head::WinnerCriterion::Repulsion` — repulsion-from-nearest-rival (VSA-overlap, cosine→Hamming) + ε-quality-floor + harmonic combiner; training-free, integer, self-contained in `contract::head2head`. *(A4)* +- **Shared operator (A4):** "retain a candidate iff its hypervector is far from incumbents under a quality floor" — mount on head2head (arbitration, SemDiD) AND EW64 admission (LaMAR's "novelty beats volume" — refines the just-merged `promote`/`coldest`). +- **3 integer VALIDATORS:** Legality Score (A1, prime-reduction purity, deepnsm-side, offline) · `⟨u,v⟩` cognitive-load (A2, CAM-PQ similarity self-explanation) · footprint `{→,←,∥,#}` (A5, aerial→DOLCE ordering validator). +- **ADVERSARIAL PROBE (highest-value foundation test):** Kozlowski (2508.10003) — non-orthogonality is *signal*; the hard 4096-basin + ρ=0.9973 CAM-PQ partition **may discard the entangled low-rank semantics**. Distinction (A3): role-disjointness (binding) is fine; *content* orthogonality is the challenge. Runnable test: antonym-direction interference vs CAM-PQ mis-addressing; pair with SAFARI's Weyl Semantic-Shift auditor over OGIT/DOLCE (tests #444 98.6%). +- **SKIP:** segmentation (2412.08671) — firewall trap. + +**Process:** the council read full text (no grep/head/tail) per `E-READ-NOT-GREP`; the reading-path TEST caught a missing-poppler blocker before dispatch (extracted PDFs→.txt via pymupdf). Cross-ref: `E-READ-NOT-GREP`, the firewall doctrine, `head2head`, `episodic_edges`, DeepNSM. + +--- + ## 2026-06-01 — E-READ-NOT-GREP — judgment-critical review agents must READ full files, not grep/sed/head/tail; fragments invalidate judgment **Status:** IRON RULE (process; user-stated 2026-06-01). Across the EW64 council + the 3 prior relayed sessions, every wrong framing came from narrating off grep/head fragments rather than reading the type. The council's R3 even found a grep-induced mis-citation (`edge.rs:750 concern_level` reads `direction()`, not `PlasticityState`). **RULE:** when a review/council/grounding agent's VERDICT depends on a type's semantics, its brief MUST instruct it to READ the relevant files in full (the Read tool), NOT grep/sed/head/tail. A fragment seen out of context produces a confident-but-wrong judgment — **grep is for LOCATING, reading is for JUDGING.** Baked into the agent-brief template (`autoattended-multiagent-pattern.md` §Rule 7). Cross-ref: `E-BASIN-NOT-EDGE-PLASTICITY` (the conflation grep-fragments produced). diff --git a/.claude/knowledge/research-council-semantics-papers-2026-06.md b/.claude/knowledge/research-council-semantics-papers-2026-06.md new file mode 100644 index 000000000..f54b101c6 --- /dev/null +++ b/.claude/knowledge/research-council-semantics-papers-2026-06.md @@ -0,0 +1,41 @@ +# Research council — 8 semantics/embedding papers, firewall-filtered (2026-06-01) + +**READ BY:** integration-lead, truth-architect; anyone mining external research into the substrate. +**Status:** SYNTHESIS (5-agent research council, all papers read IN FULL — no grep/head/tail, per `E-READ-NOT-GREP`). Each verdict is firewall-checked against the deterministic CAM-PQ substrate. +**Method:** 5 Opus reviewers (A1 NSM / A2 interpretable-embeddings / A3 embedding-structure / A4 sequence+decoding / A5 applications+firewall-critic) each read their assigned full-text papers and judged: mechanism, workspace relevance, firewall (does adoption pull LLM/float/language onto the hot path?), verdict. + +## The slate (8 papers) + +| # | paper (arXiv) | the extractable mechanism | verdict | firewall | +|---|---|---|---|---| +| 1 | 2505.11764 **Towards Universal Semantics / DeepNSM** (OSU) | **Legality Score** `α·(primes−molecules)/words` + lemma-containment circularity; prime-vs-**molecule** ontology | PROBE | gate **PASS** (integer, no LLM); the LLM machinery **CONFLICT** | +| 2 | 2410.03435 **Interpretable Text Embeddings (CQG-MBQA)** | **`cognitive load = ⟨u,v⟩`** (count of shared named axes) — self-explaining similarity; contrastive cluster-vs-hard-negative axis design (offline) | PROBE | metric **PASS** (integer hot-path); learned encoder **CONFLICT** | +| 3 | 2512.00852 **One Swallow / SAFARI** (NUS) | **Semantic-Shift basin-boundary detector** w/ Weyl-1912 bound `‖A_y‖₂·σ_max(A_x)` (no SVD, no labels) | PROBE | **PASS** (offline auditor; spectral-norm on ±1 fingerprints) | +| 4 | 2508.10003 **Semantic Structure in LLM Embeddings** (Kozlowski, U Chicago) | **cosine-proportional interference law**; meaning = continuous ~3-axis (Eval/Potency/Activity) low-rank manifold; **non-orthogonality is signal** | **PROBE (adversarial red-team)** | N-A as component; **CONFLICT as worldview** | +| 5 | 2510.18046 **LaMAR** (sequential recommenders) | **novelty-gated admission** — a near-duplicate signal *hurts*; diversity carries the gain | PROBE | abstracted (offline-frozen symbol + novelty gate) **PASS**; LLM gen **CONFLICT** | +| 6 | 2506.23601 **SemDiD** (semantic-guided diverse decoding) | **repulsion-from-nearest-rival + ε-quality-floor + harmonic (weakest-link) combiner**, k parallel arcs | **ADOPT-NOW** (cosine→Hamming) | **PASS** (training-free; swap float-cosine for VSA-overlap) | +| 7 | 2504.21074 **LLMs for Process Mining** | **footprint fitness** `(A×A)→{→,←,∥,#}`, quality = matching-cell fraction (frequency-free, integer) | PROBE (validator ADOPT-grade) | **SAFE iff quarantined** as proposer; TRAP if LLM-edges land as fact | +| 8 | 2412.08671 **Deep Semantic Segmentation** | neighbor-window (3×3) offset refinement | **SKIP** | **TRAP** — float/learned all the way down, no propose/validate seam | + +## The three unifying threads + +1. **The firewall is a *useful filter*, externally corroborated.** All 8 papers are LLM/float-based; the council extracted only the deterministic/integer/offline kernel from 7 and cleanly SKIP'd the 1 with no quarantine seam. **The "similarity PROPOSES (float, offline, upstream) / CAM ADDRESSES (integer, hot, deterministic)" doctrine was independently re-derived by 4 of 5 reviewers from 7 different papers** (A1 LLM-proposes-explication / Legality-validates; A2 LLM-authors-axes / ⟨u,v⟩-explains; A4 generate-arcs / repulsion-selects; A5 ARM-or-LLM-proposes-edges / footprint-validates). Strong outside evidence the core architecture is right. + +2. **One shared operator across the memory + inference axes (A4):** *"retain a candidate iff its hypervector is far enough from the current incumbents, under a quality floor."* Mount once on **head2head** (arc arbitration — SemDiD) and once on **EW64** (episodic-edge admission — LaMAR's "novelty beats volume"). Integer VSA-overlap vs an incumbent set; no learned weights, no float, no language. The EW64 side refines the just-merged `promote`/`coldest` (#447/#448): admission should gate on **novelty**, not recency alone. + +3. **Three deterministic integer VALIDATORS to adopt** (each turns a proposal into a check): **Legality** (A1, prime-reduction purity, DeepNSM-side), **⟨u,v⟩** (A2, similarity self-explanation, CAM-PQ-side), **footprint `{→,←,∥,#}`** (A5, ordering-relation validation, aerial→DOLCE boundary). + +## The adversarial finding (highest-value to weigh) + +**A3/Kozlowski (#4) is the strongest external challenge to the spine geometry.** Cross-model-replicated: semantic features are **meaningfully non-orthogonal** (whitening *reduces* human-rating fidelity ~20% — "non-orthogonality is a feature, not a bug"), and meaning collapses to a continuous ~3-axis low-rank manifold. A3's key distinction: **role disjointness** (SUBJECT vs PREDICATE binding slots) is standard, correct VSA — *not* challenged. But **content orthogonality** is: if the substrate treats concept-overlap as quantization noise, this says that overlap *is* the carrier of meaning, and the hard 4096-basin + ρ=0.9973 CAM-PQ partition **may be discarding the entangled low-rank structure that is the actual semantics.** A3's complement (#3 SAFARI) hands the *corroborating* tool: the Weyl Semantic-Shift auditor directly tests the #444 98.6%-locality (sharp `µ+3τ` spikes at class boundaries = clean partition; smeared = falsified). + +## Prioritized build/probe slate (firewall-filtered) + +- **ADOPT-NOW (offline-buildable):** **SemDiD → a head2head `WinnerCriterion::Repulsion`** — repulsion-from-nearest-rival (VSA-overlap) + ε-quality-floor + harmonic combiner. Self-contained in `contract::head2head`; integer; offline-testable. *(A4)* +- **ADOPT-grade VALIDATOR, offline:** **Legality Score** in `deepnsm` — prime/molecule counting + circularity; a float-free certifier of SPO prime-reduction. *(A1)* +- **PROBE (highest-value VALIDATION of the foundation):** the **Kozlowski antonym-direction interference test** — build antonym-difference directions in VSA space; does cross-direction cosine predict CAM-PQ mis-addressing? If yes, the basin model is lossy w.r.t. gradient semantics. Pair with the **SAFARI Weyl basin-coherence auditor** over the OGIT/DOLCE partition (tests #444). *(A3 — both)* +- **PROBE (refines shipped work):** **EW64 novelty-gated admission** — gate `promote` on dissimilarity-to-incumbents, not recency alone. Needs edge-fingerprint access (the co-addressed content), so it rides the gated SoA/hot-path seam, not pure-contract. *(A4/LaMAR)* +- **PROBE (interpretability):** **⟨u,v⟩ self-explanation** for CAM-PQ (list shared codebook IDs/primes behind a match) *(A2)*; **footprint validator** at the aerial→DOLCE landing *(A5)*. +- **SKIP:** segmentation (#8) — unconditional firewall trap. + +*Cross-ref:* EPIPHANIES `E-RESEARCH-COUNCIL-PROPOSE-VALIDATE`; `E-READ-NOT-GREP` (the council read full text); the firewall doctrine (`E-ENGLISH-BIFURCATES`, markov_soa SoC); `head2head`, `episodic_edges`, DeepNSM. Papers staged at `/tmp/papers/*.txt`. diff --git a/.claude/specs/atoms-styles-nal-planner-dto-unification-v1.md b/.claude/specs/atoms-styles-nal-planner-dto-unification-v1.md new file mode 100644 index 000000000..8bb65ebd8 --- /dev/null +++ b/.claude/specs/atoms-styles-nal-planner-dto-unification-v1.md @@ -0,0 +1,182 @@ +# Unifying atoms + thinking-styles + NAL into the planner DTO across ractor ↔ surrealdb(vart) ↔ lance-graph-planner (v1) + +**Status:** DESIGN (3-agent council C1 ractor / C2 surreal / C3 atoms-styles, full-file reads per `E-READ-NOT-GREP`; + vart keystone). Branch claude/jolly-cori-clnf9. The capstone `causal-edge::syllogism` (PR #450) is the reasoning kernel this wires up. + +## 0. The one-sentence thesis + +The 33 **atoms are the field-decomposition of a reasoning step**, not a separate thing to bolt on: a planner step is the 14-bit triple **(style, rung, rule)** + the emitted little-endian connectome edge; that one artifact is *emitted* by the planner (white-matter thinking), *dispatched* by ractor (grey-matter compute), and *persisted + scheduled* by surrealdb's **vart** versioned radix trie (white-matter memory). The **little-endian `CausalEdge64`/`EpisodicEdges64` bytes are the shared key** binding all three. + +## 1. The streamline — atoms ▸ hardwiring ▸ NAL ▸ grammar-styles are ONE basis + +The atom families ARE the step's fields (`atoms.rs:135`): +- **Pearl (3: see/do/imagine)** = the SPO 2³ causal mask already on `CausalEdge64` (`CausalMask`). `do`=Intervention, `imagine`=Counterfactual. +- **Rung (9: r1..r9)** = `cognitive_shader::RungLevel` (0..9; Counterfactual=6). +- **Operation (8: abduct/deduce/induce/synthesize/…)** = the NARS rules. **`Figure::rule()` (the shipped hardwiring) maps SPO term-sharing → `InferenceType` → an Operation atom**: Chain/ChainRev→`deduce` (dim18), SharedSubject→`induce` (dim19), SharedObject→`abduct` (dim17). +- **Sigma (5) / Presence (4) / Meta (4)** = modulation knobs (`FieldModulation`). + +So: **atom = one i4 lane; style = an i4×33 weighting over atoms (the molecule, identity = `ThinkingStyle`); persona = styles + thresholds** (`atoms.rs:10-16`, `recipe.rs`). The NAL rule is just the Operation slice of the style vector. grammar `GrammarStyleConfig` is the *parse policy* keyed by the same `ThinkingStyle` — keep it separate (it is not an i4 vector). + +### Duplication to collapse (C3) +- **5 style representations** → pick ONE basis: `atoms::I4x32` (33-lane vector) + `ThinkingStyle` (36 identities) + grammar-policy (separate). RETIRE: the 8-wide `nars_engine::StyleVector` (make it a Pearl-slice *view*) and the stale "34 styles" doc claim. +- **4 `InferenceType`/`NarsInference` enums** → ONE canonical: `causal-edge::edge::InferenceType` (8-variant, signed i4 mantissa, carries Intervention/Counterfactual = the Pearl-3 atoms). `grammar::inference` already has `.core()`; route `nars.rs` → causal-edge. Keep `cognitive_codebook::NarsInference.notation()` strings as the NAL-notation reference only. +- **Missing bridge:** `ThinkingStyle → I4x32` resolver (today `ThinkingStyleProvider::style_vector` returns a 23D crewai vec; there is no map to the 33 atoms). This single absence forks the style space 3 ways (23D/8D/33D). +- **The missing half of the hardwiring (downward bias):** `figure()` is detect-then-label (integer palette equality, no style — keep that firewall). The complement is a **style-biased try-order**: a style's `deduce/induce/abduct` lane signs reorder which figure `figure()` attempts first when several terms match. New, small, firewall-safe (it only reorders attempts; it never enters the truth math). + +### The genuine BLOCKER (the user's decision) +`atoms::I4x32` is **32 lanes**; the basis is **33** (`atoms.rs:39-43`, BLOCKED). `I4x32::pack/unpack` are `todo!()`, which blocks all of `recipe.rs`. Must be decided, not guessed. C3's surfaced option: fold the 33rd lane (`verbosity`, dim 32, a Meta knob) out-of-band so the carrier splits 3+9+5+8+4+3. + +## 2. The unified step word (the planner DTO field-set) + +```text +style : ThinkingStyle 6 bits → MetaWord.thinking ; resolves to I4x32 over 33 atoms +rung : RungLevel 4 bits (0..9) [Rung atom family] +rule : InferenceType i4 mantissa (signed) [Operation atom family; = Figure::rule()] + + emitted CausalEdge64 / EpisodicEdges64 (the connectome edge, LE bytes) +``` += 14 bits of identity + the edge. Carriers already exist: `MetaWord(u32)` (`thinking6+nars_f8+nars_c8+…`), `ShaderDispatch{style,rung}`, `plan::ThinkingContext{style,inference_type,…}`, `UnifiedStep`/`OrchestrationBridge`/`StepDomain` (the canonical cross-domain DTO; `PlannerAwareness` implements it). **Gaps:** `ThinkingContext` lacks `rung`; `MetaWord` lacks a rule field; `PlanResult` has **no edge-emission surface** to hand the connectome edge downstream. + +## 3. The three-system unification (one artifact, the LE-byte key) + +```text + lance-graph-planner (WHITE: thinking) ractor (GREY: compute) surrealdb/vart (WHITE: memory) + ─────────────────────────────────── ────────────────────── ────────────────────────────── + emit UnifiedStep{style,rung,rule} ──► ConsumerEnvelope::Plan(step) ──► persist edge → vart versioned ART + + CausalEdge64/EW64 (LE bytes) handle(&self, &mut State) keyed by LE edge bytes (radix) + ▲ emit CollapseGateEmission each commit = a vart VERSION + │ = Vec<(u16 target, u64 edge)> │ + │ next step (try_advance_phase) (the Baton; white matter as PAYLOAD) ▼ DatasetVersion tick + └────────────── KanbanMove ◄──── VersionScheduler::on_version(view, version, exec) ◄── LIVE / LanceVersionWatcher + "propose, don't dispose" (scheduler.rs; surreal proposes, ractor owner mutates) +``` + +- **ractor (C1):** grey-matter dispatch + supervision; it **moves batons, it does not weave them** — `CausalEdge64`/`EW64` ride as *payload* inside the envelope/emission, never as ractor state (BBB membrane bans `Vsa16kF32`). `handle(&self, …, &mut State)` structurally enforces the "no &mut self during compute" rule. **The one missing wire: the supervisor→child forward** (stub `supervisor.rs:198`); the DTO is one new closed `ConsumerEnvelope::Plan` arm (auto-`Message` via blanket impl); emission is `CollapseGateEmission` (zero-dep, Send, `wire_cost=13+10n`). Risks: `Send+'static`, bounded mailbox → `MailboxFull` backpressure at 32k–64k, one-for-one supervision must be overridden. +- **surrealdb/vart (C2 + keystone):** the IN-direction reactive seam `E-SUBSTRATE-IS-THE-SCHEDULER` is fully specified, zero shipped impl. `VersionScheduler::on_version(&V view, DatasetVersion, exec) -> Option` proposes (never `&mut`); `DemotionSink::demote` is the hot→cold exit; `ExecTarget::{Native,Jit,SurrealQl,Elixir}` routes the backend. Working primitive: `LanceVersionWatcher` (std-sync, no tokio). **Gaps:** `PlanResult` edge-emission surface (gap 1); a `LanceVersionScheduler` impl; the `WatchReceiver→DatasetVersion→on_version→try_advance_phase` adapter; a vart/surreal `DemotionSink` (OQ-11.6); unblock the surreal kv-lance fork dep (BLOCKED(C)). + +## 4. vart — the keystone + +`vart = "0.9.3"` is surrealdb's **versioned adaptive radix trie** = its `kv-mem` engine; AdaWorldAPI's `kvs/lance/` backend layers Lance (durable append-only) under a vart memtable (`memtable.rs`/`wal.rs`/`tx_buffer.rs`). vart is simultaneously, with no extra machinery: +1. **the cold/warm connectome store** — radix-keyed by the **LE `CausalEdge64`/`EpisodicEdges64` bytes** (the "append-never-renumber *gives* you a radix" of `E-EPISODIC-CLOSURE`, made concrete); +2. **the MVCC version source** — each transaction commit = a version = the `DatasetVersion` tick that drives the scheduler; +3. **the `DemotionSink` target** — demoted EW64 edges append into the vart cold tier, re-prefetchable (`E-EW64-IS-PREDICTIVE-PREFETCH`); +4. **surreal-native** — it unblocks the BLOCKED(C) kv-lance skeleton instead of inventing a store. + +The "32k reasoning-chained mailboxes with little-endian contract" is *literally vart's keyspace*: the LE edge bytes are the radix key; the MVCC versions are the reasoning chain's epochs; the snapshot reads are the lock-free grey-matter actor reads. + +## 5. Prioritized actions + +**A. Offline / unblocked NOW (testable like the syllogism PR):** +1. Add `rung: RungLevel` to `plan::ThinkingContext` → the step carries the full (style, rung, rule) triple. (Additive, no blocker.) +2. Collapse the 4 `InferenceType` enums onto `causal-edge::edge::InferenceType` via `.core()`-style maps; retire the 8-wide `StyleVector` to a Pearl-slice view + delete the "34 styles" doc. +3. Add the **style-biased figure try-order** (Operation-lane weights reorder `figure()`'s attempts; truth math untouched — firewall held). +4. Add a `PlanResult` edge-emission surface (expose emitted `CausalEdge64`/`EW64` as raw u64/LE bytes) so the DTO has something to persist. + +**B. Gated on the 32-vs-33 DECISION (user's call):** +5. Implement `I4x32::pack/unpack` + the `ThinkingStyle → I4x32` resolver → unblocks all of `recipe.rs` (style/persona composition). + +**C. Cross-repo seams (each a self-contained slice):** +6. **ractor:** the `ConsumerEnvelope::Plan` arm + the supervisor→child forward (the one stub). +7. **vart/surreal:** the vart-backed `DemotionSink` + `LanceVersionScheduler` + the watcher→scheduler adapter; prerequisite = unblock the surreal kv-lance fork dep (BLOCKED(C)). + +## 6. Firewall (held throughout) + +Figure detection = integer palette equality (PROPOSE); truth-function = deterministic (ADDRESS); style-bias = try-order only, never truth math. vart keys on integer LE bytes — no float, no language, on the hot path. The LE byte contract is the single shared grammar (ractor batons, vart radix keys, planner emissions, surreal WAL). + +Cross-ref: `E-NARS-FIGURE-CAPSTONE`, `E-EPISODIC-CLOSURE`, `E-SUBSTRATE-IS-THE-SCHEDULER`, `E-PLANNING-IS-WHITE-MATTER`, `E-EW64-IS-PREDICTIVE-PREFETCH`, the PROPOSE/ADDRESS firewall. + +## 7. vart — vendored + concrete API (added after the zipball grab) + +**Vendored now** at `/home/user/vart` (AdaWorldAPI/vart fork, `main`, v0.9.2; surrealdb pins crates.io `0.9.3`). "zipball now, Cargo path-dep vendor-import later, same as `/home/user/ndarray`." Cargo wiring is deferred to the gated surreal/vart slice (action C7). + +Concrete API (`vart/src/lib.rs`; the `Tree` + insert/get/snapshot lives in `art.rs`): +- **`Key` trait + `FixedSizeKey`** with `From/u16/u8` (via `to_be_bytes`) and `From<&[u8]>`. ⇒ a connectome word is a vart key *directly*: `FixedSizeKey::<8>::from(edge.0)` or `from_slice(&edge_key_bytes)`. +- **`KeyTrait = Key + Clone + Ord + Debug + From<&[u8]>`** — the trie is byte-ordered (lexicographic). +- **`TrieError::{VersionIsOld, SnapshotOlderThanRoot, RootIsNotUniquelyOwned}`** — versioned MVCC with monotonic version ordering + snapshot generations. +- **"immutable versioned"** = persistent / copy-on-write / structural-sharing. + +How it lands the architecture concretely: +- **Key = the addressing prefix (BE), value/WAL = LE.** vart orders lexicographically, so encode the *radix key* big-endian over the addressing fields (CausalEdge64 S→P→O, or EW64 family→local) so edges sharing a Subject/family **share a trie prefix** (range-scannable basins). Keep the connectome's frozen `to_le_bytes` as the stored **value**/WAL format. The BE-key vs LE-wire split is a deliberate, citable choice — decide the exact key projection when wiring C7. +- **vart version = DatasetVersion tick.** Each transaction commit = a monotonic vart version (`VersionIsOld` guards forward-only). The surreal `LIVE`/`LanceVersionWatcher` reads the new version → `VersionScheduler::on_version(snapshot_view, DatasetVersion(n), exec)` → next `KanbanMove`. `E-SUBSTRATE-IS-THE-SCHEDULER` is *literally* vart's version counter. +- **Immutable snapshot = lock-free grey-matter read.** A ractor mailbox actor reads a vart snapshot of the connectome with zero locking (structural sharing); the single white-matter writer commits a new version. This satisfies the data-flow rule ("no `&mut self` during compute; caches built once / interior-mutable") for free. +- **`DemotionSink` → `vart.insert(key, edge)`.** A demoted EW64 `EdgeRef` resolves to its connectome word and inserts into the vart cold tier under a new version; re-prefetch = a prefix/range scan of the basin (`E-EW64-IS-PREDICTIVE-PREFETCH`). +- Under surrealdb this rides the existing `kv-mem`/`kvs/lance` path (vart memtable over a Lance append-only WAL) — so C7 unblocks the BLOCKED(C) skeleton by adopting the surreal-native store rather than inventing one. + +## 8. DECISION LOG (jan, 2026-06-01) + +- **32-vs-33 carrier → RIDE TWO i4×32 HALVES (64 lanes).** The `I4x32` carrier becomes two 16-byte words (64 i4 lanes); the 33 atoms occupy lanes 0..33 with 31 spare for future families. No trimming, no out-of-band lane, room to grow. This UNBLOCKS `I4x32::pack/unpack` and all of `recipe.rs`. (Action B5 is now unblocked; the carrier type is a 2-word struct — name TBD by the plan, e.g. `I4x64`/`StyleVec64`.) +- **Drive ALL THREE threads** through the standard pipeline: specs → agents → review-plan → run (/// draft → review → fix → PR → subscribe → fix → repeat). Dependency order: **A (offline streamline foundation) → C6 (ractor seam) → C7 (vart/surreal seam)**; A unblocks both seams. + +## 9. I4x32D — the OGIT-inherited adjacent-best-practice RESOLVER (jan refinement) + +The two-i4×32-halves carrier is **`I4x32D`** (D = dual; 2 planes × 32 atoms). It is **not** merely a packed style weighting — it is a **resolver**. The i4 atom-vector is an *address* into the space of thinking templates: one i4×32 plane = 32 nibbles = 128 bits = exactly **2¹²⁸** distinct templates. A thinking step's atom-vector **resolves to the *adjacent* (nearest, by i4 distance) best-practice template *inherited from its OGIT class*** — CAM addressing into the ontology, never a per-instance copy (`I-VSA-IDENTITIES`: point, don't copy; `E-CODEBOOK-INHERITS-FROM-OGIT`). The dual planes are the instance/context weighting vs the OGIT-class-inherited best-practice reference; **adjacency between the two planes is the resolution**. + +Consequences for the plan (sharpens A3/A4): +- **A3 (carrier):** `I4x32D = 2 × I4x32` (64 i4 lanes). The 33 atoms occupy lanes; the dual structure is the *address space* + the *inherited reference plane*. pack/unpack operate per plane. +- **A4 (resolver):** `ThinkingStyle → I4x32D` is **not** a flat map — it RESOLVES to the nearest OGIT-inherited best-practice template. `recipe.rs::PersonaRecipe` IS a template = an inherited OGIT identity; the class supplies it by inheritance, the resolver snaps the instance to the *adjacent* one. This is the ADDRESS side of the firewall (integer i4 distance PROPOSES the neighborhood; the OGIT class ADDRESSES the identity). No similarity float on the hot path beyond the integer i4 distance; the template is pointed-to, not stored. +- This makes `recipe.rs` (PersonaRecipe = best-practice template) the OGIT-inherited codebook the resolver addresses into — closing atoms ↔ styles ↔ OGIT ontology with one CAM resolver. + +## 10. OGIT class grants Class + bitmask-attention FOR FREE (jan refinement) + +When `I4x32D` resolves a step to its OGIT class, the class grants **two inherited things at zero compute cost**: +1. **Class** = the best-practice thinking template (the i4×64 persona the resolver snaps to — §9). +2. **presence bitmask = the attention mask** — `view_angle::ViewAngle`'s class-presence bitmask *doubles as* the attention mask (`D-VIEW-1`: "the class presence bitmask doubles as the attention mask"), i.e. `ShaderDispatch.layer_mask` / `MetaFilter.thinking_mask` over the 8 Pearl planes + the active atom lanes — **inherited from the class, never computed per-instance** (`I-VSA-IDENTITIES`). + +So resolving the OGIT class = **template + attention in one inherited lookup**. "Attention for free" = the class IS the attention, by inheritance. Sharpens **A4**: the resolver returns `(I4x64 template, attention bitmask)`; the bitmask gates which atom lanes / Pearl planes the step attends to (it is the same `layer_mask` the shader already dispatches on). No per-step attention computation — the ontology grants it. + +## 11. PLAN ACCEPTED + flag decisions (Slice A, offline-verified) + +Plan agent verified all 6 sub-slices build offline (contract 6.9s / causal-edge 0.77s / planner 5.09s). Corrections folded: 5 inference enums (planner `nars_engine::Inference` is the live one); `recipe.rs` is an orphan (blocks nothing); the InferenceType collapse bridges by **mantissa value** (`to_mantissa`/`from_mantissa`), NOT by a contract→causal-edge type dep (zero-dep preserved). Run order: **A2 → A1 → A6 → A3 → A4 → A5**, then C6, then C7. + +Flag decisions (jan's pipeline = "run plan"; adopt the plan's defensible proposals, document prominently for veto): +- **A3 sign/scale:** ADOPT — carrier saturates only; per-group sign via `AtomGroup::is_signed` (ordinals unsigned; the 3 ± pairs deduce↔induce / authentic↔performance / exploration signed). Documented in the PR for sign-off. +- **A1 `nars_engine::Inference` (9-variant):** keep as-is; only swap its causal-edge cast to the mantissa path (kills deprecation warnings). No information-losing collapse of Analogy/Resemblance. +- **A5 single-match-invariance test:** HARD merge gate; `syllogize`/`syllogize_biased` share one private `conclude()` so truth math cannot drift. +- **A3.5 (wire `recipe.rs` + `StyleRegistry::register_recipe`):** DEFERRED (reopens the Cranelift JIT surface — its own slice). + +## 12. The full cognitive stack — facts proposer → reasoning ↔ representation ↔ meta-aware thinking (jan) + +The unification sits inside a 4-layer stack the DeepNSM grammar templates trigger: + +1. **Facts proposer — DeepNSM grammar templates.** `grammar/` (`inference.rs`, `tekamolo.rs`, `verb_table.rs`, `role_keys.rs`, `context_chain.rs`, `thinking_styles.rs`, `wechsel.rs`, `finnish.rs`, `free_energy.rs`, `disambiguator.rs`) — English-by-construction → SPO + roles + NARS *intent*. `inference.rs` states it outright: **"Grammar resolution IS reasoning, not pattern-matching"** — each NARS inference maps to a style cluster (Deduction→Analytical, Abduction→Meta, Synthesis→Creative, CounterfactualSynthesis→Creative, Extrapolation→Exploratory) and `.core()` → the canonical 5. These templates **express intent** (which reasoning the grammar implies) even where not yet wired; **later they trigger the pipeline: NARS resolution → syllogism resolution (`Figure`) → reasoning resolution.** +2. **Reasoning.** NARS micro-truth → `Figure::syllogize` (the capstone) → the `(style, rung, rule)` + edge artifact. The grammar's inference marker IS the `rule`; its style-cluster IS the `style`; A5's `FigureBias` is fed by the grammar+style Operation lanes. (`grammar::inference::NarsInference` is the 5th enum A1c bridges via `.core()`.) +3. **Representation.** The VSA / connectome projection — `CausalEdge64`/`EpisodicEdges64` (LE-byte substrate), the vart radix, BindSpace. All three paths (micro-truth / episodic basin / witness supporters) project here. +4. **Meta-aware thinking — the awareness chain.** The recursive meta layer that chains meta-reasoning over the **episodic basins (EW64)** + the proposed facts. It is **addressable via the rung A2 just wired**: `RungLevel::{Meta=7, Recursive=8, Transcendent=9}` are the meta-aware rungs; `MetaWord.awareness(4b)` + `MetaSummary{meta_confidence, brier, should_admit_ignorance}` (cognitive_shader) track the per-step meta-cognitive state; the atoms' **Meta** family + the `transcend`/`model_other` Operation lanes carry it. The awareness chain reads the episodic basin, meta-reasons over the facts, and feeds back down to bias the reasoning — **`reasoning ↔ representation ↔ meta-aware` is bidirectional.** + +**Consequence:** the `(style, rung, rule)` triple spans the whole stack — `style`+`rule` proposed by the grammar (layer 1), `rung` running from `Surface` (grammar object-level) up to `Transcendent` (meta-aware, layer 4). **A2's `rung` is the meta-aware handle**; the grammar sets the initial intent, the awareness chain elevates the rung as it reflects. This is why `rung` belongs on the planner DTO, not only the shader dispatch. + +## 13. THE DREAM — the closed self-driving loop (jan's north star) + +> nars resolution → syllogism resolution → mailbox orchestration in surrealdb kanban; AND surrealdb → active dispatch + kanban + Rubicon resolve on a meta level; producing **epiphanies, facts, and high signal separation**. + +```text +facts proposer (DeepNSM grammar, §12.1) + → NARS resolution (micro-truth) + → syllogism resolution (Figure::syllogize, the capstone) [reasoning] + → representation: CausalEdge64/EW64 → vart radix [the LE-byte connectome] + → mailbox orchestration (ractor) driven by the surreal/vart Rubicon KANBAN + ▲ │ + └── surrealdb ACTIVE: LIVE dispatch + VersionScheduler → ▼ + KanbanMove + Rubicon phase-resolve, ON A META LEVEL ────┘ (E-SUBSTRATE-IS-THE-SCHEDULER) + ⇒ PRODUCES: epiphanies · facts · high-signal separation ──(feed back as new facts/episodic basins)──┐ + └─────────────────────────────────────────────────────────────────────────────────────────────┘ +``` + +- **"mailbox orchestration in surrealdb kanban"** = **C6** (ractor mailboxes) dispatched by **C7** (the surreal/vart `VersionScheduler` → `KanbanMove`; the Rubicon `NextPhaseScheduler`: Planning→CognitiveWork→Evaluation→Commit, Libet anchor −550 ms). **vart's version-ticks ARE the kanban clock.** +- **"surrealdb → active dispatch … on a meta level"** = surreal is the ACTIVE pilot, not passive storage: LIVE queries over vart versions fire the next dispatch; the Rubicon resolve runs at the awareness/meta level (rung `Meta/Recursive/Transcendent` + the §12.4 awareness chain). "Propose, don't dispose" — surreal proposes the move, the ractor owner commits it. +- **"epiphanies · facts · high-signal separation"** = the meta-aware output: + - **facts** = validated SPO (the English-fact landing that survives resolution). + - **high-signal separation** = the meta discriminator — `MetaSummary{meta_confidence, brier, should_admit_ignorance}` + the research-council ADOPT items (SemDiD → `head2head::WinnerCriterion::Repulsion`; EW64 novelty-gated admission; head2head winner-select) separating high-signal arcs from noise. + - **epiphanies** = the highest-signal meta-insights the loop crystallizes — the substrate **writing its own `EPIPHANIES`** (the board this session maintains), closing the loop: a substrate that produces epiphanies about itself. + +**The path to the dream IS the slice sequence:** A (the `(style,rung,rule)`+edge artifact) → C6 (ractor dispatch of it) → C7 (surreal/vart active Rubicon kanban + the meta-level signal separation). The head2head/novelty-gate (research council) is the signal-separation operator; the awareness-chain rung is the meta level. No rewrite — these slices close the loop. + +## 14. Grammar vs business-grammar — DUAL-VIEW resolution (jan) + +The same input resolves under TWO grammar views at once, both OGIT-class-inherited (§10, "the class grants the view for free"): +1. **Semantic grammar** (DeepNSM, §12.1): English → SPO + NSM primes + NARS intent → the reasoning pipeline. The **MEANING**. +2. **Business grammar** (the OGIT-business sidecar, `E-OGIT-STAKES-LINCHPIN`): request class → `MappingRow` → `Marking::Financial` → the bookkeeping/FIBU savant. The **STAKES / domain**. + +**"Business is not here" (`atoms.rs`) holds:** business is NEVER an atom/lane — it is the *second grammar view*, a sidecar resolved through the OGIT class, never a weight in `I4x32D`. The atom basis stays purely semantic; the business grammar rides OGIT inheritance as a parallel `ViewAngle`. + +The dual = **two `ViewAngle`s over one OGIT class** (`D-VIEW-1`: the class-presence bitmask is the attention mask; head2head competes angles). The same `(style, rung, rule)` + connectome edge carries BOTH resolutions — semantic reasoning AND business interpretation — together. **head2head between the semantic angle and the business angle IS the dual-view resolution**, and it is the §13 "high-signal separation" operator: the winner is the high-signal interpretation. + +Firewall: the semantic grammar PROPOSES meaning (DeepNSM, upstream, float-OK); the business grammar ADDRESSES stakes (OGIT class → `MappingRow` → bookkeeping, deterministic, integer). Two views, one resolution, OGIT-inherited — never an atom. diff --git a/.claude/specs/nal-syllogism-capstone-v1.md b/.claude/specs/nal-syllogism-capstone-v1.md new file mode 100644 index 000000000..d7349f976 --- /dev/null +++ b/.claude/specs/nal-syllogism-capstone-v1.md @@ -0,0 +1,34 @@ +# NAL Syllogism Capstone — figure resolution on CausalEdge64 (v1) + +**Status:** KERNEL SHIPPED (`causal-edge::syllogism`, branch claude/jolly-cori-clnf9, f499546 + review fixes). Capstone INTEGRATION pending — this spec is the roadmap. +**Council:** 5-agent dev council (R1–R5, Opus, full-file reads per `E-READ-NOT-GREP`). R1/R2 SOUND (NAL-correct; firewall-clean; truth-fns byte-identical to `ndarray::hpc::nars` AND `forward()`). R3/R4 SOUND + doc fixes applied. R5 FIX-NEEDED **at the integration layer only** — the kernel is "a correct, well-documented O(1) kernel with zero callers." The capstone claim is real in shape, unwired in fact. + +## What shipped (the kernel) + +`CausalEdge64::figure(other) -> Option
` + `syllogize(other) -> Option`. `Figure{Chain,ChainRev,SharedSubject,SharedObject}` = which SPO palette term two edges share, by integer equality — the hardwired analogue of the Pearl 2³ mask. The conclusion edge carries outer terms + canonical NARS truth (Deduction/Induction/Abduction) + signed v2 mantissa (+1/+2/−1) + AND-ed Pearl mask. 14 tests v2 / 13 v1; clippy/fmt/rustdoc clean. + +**Scope boundary (R1, intentional):** the omitted Comparison/Exemplification/Analogy/Resemblance need a `<->` (Similarity) copula — `CausalEdge64` is *always* directed inheritance and has no copula field, so a `<->` conclusion is unrepresentable in this carrier. Correct to omit, not a gap. + +## The capstone claim (user) and where it stands (R5) + +> "NAL syllogism notation is the missing capstone for glueing all 3 reasoning methods with the 10-rung ladder and the JITson cranelift templates vs elixir. NAL notation and elixir complete each other." + +- **3-path glue** — *designed, not wired.* All three paths produce/consume `CausalEdge64`; `syllogize(self, other)` is the shared binary op. But Path2 (EW64) stores `EdgeRef{family,local}` (NOT edges) and Path3 (WitnessTable) is a column-primitive not yet wired to edges. The resolvers don't exist yet. +- **10-rung ladder** — *aspirational.* No code maps `InferenceType`→`RungLevel`/`Operation`-lane. The ladder a syllogism *does* ride today is `CausalMask` (Pearl Level 1/2/3, already AND-ed in the conclusion), not `RungLevel`. `atoms::I4x32::pack/unpack` is still `todo!()` (blocked on 32-vs-33 + per-group sign/scale) — no lane to write. +- **cranelift ↔ elixir** — *thesis, not code.* `notation()` returns 4 hard-coded strings consumed by nobody but tests. No emitter turns a figure into a `JitTemplate` (`jit.rs`) or an Elixir clause. The SHARED declarative source both should compile from does not exist yet — **this is the actual missing capstone piece**, not the `Figure` enum. + +## Roadmap (prioritized, firewall-clean, offline-first) + +- **[P1 — THE capstone] `FIGURE_RULES` declarative table + dual emitters (R5 #3).** Replace the 4 hard-coded `notation()` strings with one `const FIGURE_RULES: [FigureRule; 4]` carrying `{figure, premise_shape, conclusion_shape, rule: InferenceType, mantissa: i8}`. Add two *pure offline* emitters that BOTH read it: + - `fn jit_template(FigureRule) -> JitTemplate` — emit the JSON `jit.rs` accepts (a branch-free "match shared term → apply truth-fn" kernel; sibling of `cam_pq_cascade_template` in `jitson_kernel.rs`). + - `fn elixir_clause(FigureRule) -> String` — emit the function head whose **doubled binding** (`def figure(m, p, _, s)`, M bound twice) IS the middle-term unification `figure()` does by integer equality. *This is the literal "Elixir and NAL complete each other": one table, two backends.* No Elixir runtime needed to emit. +- **[P0 — unblocks Path2] EW64→`CausalEdge64` resolver seam.** In a driver (NOT zero-dep `episodic_edges.rs`): `resolve(EdgeRef, &basin, &class) -> CausalEdge64` honoring the EdgeRef grammar — `family==0` ⇒ row's own basin; `family∈1..=15` ⇒ `class.cross_family_palette[family]` (OGIT class); `local` is **1-based** (`basin[local-1]`). Then `syllogize_hot(EpisodicEdges64,…)` folding **slot-0-anchored** (`hot[0].syllogize(hot[k])`, k=1..n) — NOT a blind left-fold (syllogize is non-symmetric; a left-fold `None`-cascades). ≤3 conclusions. +- **[P1 — correctness upgrade] Re-express `nars_engine::combinatorial_entailment` on `syllogize`+`Figure::Chain` (R4).** Today it assumes the chain positionally and **never checks `o1==s2`** — delegating absorbs that safety check. A real bug-fix, not just DRY. +- **[P1 — DRY] Factor `forward()`'s 4 truth arms onto the `syllogism.rs` private truth-fns (R2/R4/R5 #4).** Deduction-truth is now byte-identical in 5 places; collapse the two intra-`causal-edge` copies so a future tweak changes one site. +- **[P0 — unblocks Path3] WitnessTable→premise resolution.** `w_slot → WitnessEntry.spo_fact_ref → CausalEdge64`, so Path3 supporters also reach `syllogize`. +- **[P2 — gated] `InferenceType→RungLevel`/`Operation`-lane map (R5 #5).** Deduction→Shallow, Induction/Abduction→Structural/Counterfactual, mirroring the Pearl levels. **Gated** on `atoms::I4x32::pack/unpack` being un-`todo!()` + the 32-vs-33 + per-group sign/scale BLOCKED decisions. +- **[P2] Driver integration tests** (EW64 fold + witness-backed premises) — current tests only exercise the pure kernel. + +## Firewall + +Integer palette equality PROPOSES the figure (deterministic, offline, no float on the structural path); the NARS truth-function ADDRESSES. The codegen (P1) is pure offline string work — no LLM, no float on the hot path. Cross-ref: `E-NARS-FIGURE-CAPSTONE`, the PROPOSE/ADDRESS doctrine, `E-READ-NOT-GREP`. diff --git a/crates/causal-edge/src/lib.rs b/crates/causal-edge/src/lib.rs index 4fd78e9ff..b6f22a559 100644 --- a/crates/causal-edge/src/lib.rs +++ b/crates/causal-edge/src/lib.rs @@ -56,6 +56,7 @@ pub mod layout; pub mod network; pub mod pearl; pub mod plasticity; +pub mod syllogism; pub mod tables; #[cfg(test)] @@ -64,6 +65,7 @@ mod v2_layout_tests; pub use edge::CausalEdge64; pub use pearl::CausalMask; pub use plasticity::PlasticityState; +pub use syllogism::{Figure, Syllogism}; // Re-export v2 layout types under cfg for downstream consumers #[cfg(feature = "causal-edge-v2-layout")] diff --git a/crates/causal-edge/src/syllogism.rs b/crates/causal-edge/src/syllogism.rs new file mode 100644 index 000000000..cd7839e40 --- /dev/null +++ b/crates/causal-edge/src/syllogism.rs @@ -0,0 +1,457 @@ +//! Hardwired NAL syllogism resolution on `CausalEdge64` — the **figure decomposition**. +//! +//! # The missing capstone +//! +//! `CausalEdge64::forward` composes two edges *positionally* and applies the +//! weight edge's **pre-set** inference type. It never asks *which term the two +//! edges share* — and that question is the whole of NAL syllogism. This module +//! adds the missing piece: given two premise edges, detect the **syllogistic +//! figure** by integer SPO-palette term-matching, select the matching NARS +//! rule, and emit the conclusion edge. +//! +//! This is hardwired exactly like **Pearl's 2³ causal mask** (`pearl.rs`): a +//! small, fixed, branch-minimal structural decomposition resolved by integer +//! equality — `O(1)`, no allocation, no float on the structural path. Where the +//! Pearl mask answers *"which SPO planes condition this edge?"*, the figure +//! answers *"which term bridges these two edges?"*. +//! +//! # The four figures (the `{premises} |- conclusion` notation) +//! +//! An edge `(s, p, o)` is read as the directed statement `s --[p]--> o` (the +//! subject inheres in / maps to the object; `p` labels the relation). Two +//! premises share exactly one **middle term** `M`; *where* `M` sits decides the +//! figure and therefore the rule (mirrors `RuleTables.syllogism` in OpenNARS and +//! the `{M-->P, S-->M} |- S-->P` notation carried by +//! `cognitive_codebook::NarsInference`): +//! +//! ```text +//! self = s1->o1 other = s2->o2 shared term figure rule +//! ──────────────────────────────────────────────────────────────────────────── +//! o1 == s2 (M = o1) {S->M, M->P} Chain Deduction ⊢ s1->o2 +//! s1 == o2 (M = s1) {M->P, S->M} ChainRev Deduction ⊢ s2->o1 +//! s1 == s2 (M = s1) {M->P, M->S} SharedSubject Induction ⊢ o1->o2 +//! o1 == o2 (M = o1) {P->M, S->M} SharedObject Abduction ⊢ s1->s2 +//! ``` +//! +//! # Firewall +//! +//! The figure is resolved by **integer palette equality** — it *proposes* which +//! rule applies (deterministic, no float, no language). The conclusion's truth +//! is then the canonical NARS truth-function, the same math as +//! `ndarray::hpc::nars` (the hardware) and `CausalEdge64::forward` (the +//! protocol). similarity never enters the hot path; addressing (palette index) +//! decides the figure. +//! +//! # Wiring EW64 +//! +//! The premises arrive as `EpisodicEdges64` (`EdgeRef` slots) from the hot +//! episodic tier. The driver (not this zero-dep kernel) resolves each +//! `EdgeRef` to its co-addressed `CausalEdge64` basin row — a **two-stage** +//! lookup that must honor the `EdgeRef` grammar: `family == 0` ⇒ the row's own +//! (inherited) basin; `family ∈ 1..=15` ⇒ `class.cross_family_palette[family]` +//! via the OGIT class (a naive `basin[local]` would conflate `cross(3,3)` with +//! `intra(3)`); and `local` is **1-based** (`basin[local - 1]`). Resolution +//! rides the driver because it needs the basin store + the OGIT class. +//! +//! **Pairing rule** (the relational/witness step): [`syllogize`] is *not* +//! symmetric — `a.syllogize(b) ≠ b.syllogize(a)` (figure order plus the +//! non-symmetric induction/abduction truth), so the fold is **slot-0-anchored**: +//! the strongest / most-recently-fired edge (slot 0, the MRU head) is `self`, +//! syllogized against each other hot edge — `hot[0].syllogize(hot[k])` for +//! `k = 1..n` (≤3 conclusions). A blind left-fold is wrong: a conclusion's +//! outer terms need not share a term with the next edge, so it would +//! `None`-cascade and silently drop most reasoning. +//! +//! [`syllogize`]: CausalEdge64::syllogize + +use crate::edge::{CausalEdge64, InferenceType}; +use crate::pearl::CausalMask; +use crate::plasticity::PlasticityState; + +/// The NAL syllogistic figure — which term two SPO edges share. +/// +/// The hardwired analogue of [`CausalMask`]: a +/// structural decomposition resolved by integer palette-index equality. Each +/// figure pins one NARS rule (see [`Figure::rule`]) and one conclusion shape +/// (see [`CausalEdge64::syllogize`]). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Figure { + /// `o1 == s2`: the forward chain `s1->o1, o1->o2 ⊢ s1->o2`. **Deduction.** + Chain, + /// `s1 == o2`: the reverse chain `s2->o2, o2->o1 ⊢ s2->o1`. **Deduction.** + /// + /// The same transitive chain as [`Chain`](Figure::Chain) with the premises + /// encountered in the opposite order; kept distinct so the conclusion shape + /// is explicit. + ChainRev, + /// `s1 == s2`: shared subject `M->o1, M->o2 ⊢ o1->o2`. **Induction.** + SharedSubject, + /// `o1 == o2`: shared object `s1->M, s2->M ⊢ s1->s2`. **Abduction.** + SharedObject, +} + +impl Figure { + /// The NARS rule this figure resolves to. + #[inline] + pub const fn rule(self) -> InferenceType { + match self { + Figure::Chain | Figure::ChainRev => InferenceType::Deduction, + Figure::SharedSubject => InferenceType::Induction, + Figure::SharedObject => InferenceType::Abduction, + } + } + + /// The canonical `{premise1, premise2} |- conclusion` NAL notation string, + /// in `S/P/M` term names (`M` = the shared middle term). + #[inline] + pub const fn notation(self) -> &'static str { + match self { + Figure::Chain => "{S-->M, M-->P} |- S-->P", + Figure::ChainRev => "{M-->P, S-->M} |- S-->P", + Figure::SharedSubject => "{M-->P, M-->S} |- S-->P", + Figure::SharedObject => "{P-->M, S-->M} |- S-->P", + } + } +} + +/// A resolved syllogism: the detected [`Figure`] plus the conclusion edge. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Syllogism { + /// Which term the premises shared. + pub figure: Figure, + /// The derived conclusion edge (conclusion SPO + composed NARS truth + + /// signed inference mantissa + AND-ed Pearl mask). + pub conclusion: CausalEdge64, +} + +impl CausalEdge64 { + /// Detect the NAL syllogistic figure shared with `other` by SPO palette + /// term-matching — the hardwired analogue of the Pearl 2³ mask. `O(1)`. + /// + /// Returns `None` when the two edges share no term (no valid syllogism), or + /// when they are the **same statement** (`s1==s2 && o1==o2`) — identical + /// statements are merged by NARS *revision* ([`CausalEdge64::learn`]), not a + /// syllogism. When several terms match, the figures are tried in the fixed + /// canonical order `Chain → ChainRev → SharedSubject → SharedObject` and the + /// first match wins. + #[inline] + pub fn figure(self, other: Self) -> Option
{ + let (s1, o1) = (self.s_idx(), self.o_idx()); + let (s2, o2) = (other.s_idx(), other.o_idx()); + // Same statement → revision, not syllogism. + if s1 == s2 && o1 == o2 { + return None; + } + if o1 == s2 { + Some(Figure::Chain) + } else if s1 == o2 { + Some(Figure::ChainRev) + } else if s1 == s2 { + Some(Figure::SharedSubject) + } else if o1 == o2 { + Some(Figure::SharedObject) + } else { + None + } + } + + /// Resolve the NAL syllogism between `self` and `other`: detect the + /// [`Figure`], apply the matching NARS truth-function, and emit the + /// conclusion `CausalEdge64`. + /// + /// The conclusion carries: + /// - **SPO**: the two outer terms (the middle term `M` is consumed), per the + /// figure table in the module docs. The predicate is a **typed placeholder** + /// carried from the premise contributing the conclusion's subject — it is + /// *not* the resolved relation. For the chain figures (Deduction) the true + /// relation is the composition `p1∘p2`, supplied by + /// [`CausalEdge64::forward`]'s `compose_p` table. For the shared-term + /// figures (Induction / Abduction) the conclusion relation is a *newly + /// induced / abduced* link that no current table composes — the carried + /// predicate is provisional there, and a downstream consumer must treat it + /// as unresolved (relation synthesis is a separate, later concern). + /// - **truth**: the canonical NARS rule for the figure (Deduction / + /// Induction / Abduction), identical to `ndarray::hpc::nars` and to + /// `forward`'s inline math. + /// - **inference mantissa**: the signed v2 mantissa for the rule + /// (Deduction `+1`, Induction `+2`, Abduction `−1`) — forward-chain + /// positive, backward-chain negative. + /// - **Pearl mask**: the bitwise AND of the two premise masks (only planes + /// active in *both* survive), matching `forward`. + /// - **plasticity**: `ALL_HOT` — a freshly derived conclusion is learnable. + /// + /// Returns `None` exactly when [`figure`](CausalEdge64::figure) does. + #[inline] + pub fn syllogize(self, other: Self) -> Option { + let figure = self.figure(other)?; + let (s1, p1, o1) = (self.s_idx(), self.p_idx(), self.o_idx()); + let (s2, p2, o2) = (other.s_idx(), other.p_idx(), other.o_idx()); + + // Conclusion SPO + which premise supplies the predicate (subject side). + let (cs, cp, co) = match figure { + Figure::Chain => (s1, p1, o2), + Figure::ChainRev => (s2, p2, o1), + Figure::SharedSubject => (o1, p1, o2), + Figure::SharedObject => (s1, p1, s2), + }; + + // Conclusion truth — canonical NARS rule for the figure (premise order + // self, other). Mirrors `ndarray::hpc::nars` and `forward`. + let (f1, c1) = (self.frequency(), self.confidence()); + let (f2, c2) = (other.frequency(), other.confidence()); + let (f, c) = match figure { + Figure::Chain | Figure::ChainRev => deduction_truth(f1, c1, f2, c2), + Figure::SharedSubject => induction_truth(f1, c1, f2, c2), + Figure::SharedObject => abduction_truth(f1, c1, f2, c2), + }; + + // Pearl mask: AND (only planes active in both survive) — as `forward`. + let mask = CausalMask::from_bits((self.causal_mask() as u8) & (other.causal_mask() as u8)); + + // Build the conclusion. `pack` writes the signed mantissa under the v2 + // layout via `InferenceType::to_mantissa` (Deduction→+1, Induction→+2, + // Abduction→−1), so the conclusion round-trips its rule + chain + // direction. Temporal is structural (dropped under v2); direction is + // neutral (recomputed downstream from composed palette dim0 signs). + #[allow(deprecated)] // v2 `pack` drops temporal; mantissa carried via to_mantissa + let conclusion = CausalEdge64::pack( + cs, + cp, + co, + (f.clamp(0.0, 1.0) * 255.0).round() as u8, + (c.clamp(0.0, 1.0) * 255.0).round() as u8, + mask, + 0, // direction: neutral; recomputed from composed palette downstream + figure.rule(), + PlasticityState::ALL_HOT, + 0, // temporal: structural under v2 (chain-position / AriGraph anchor) + ); + + Some(Syllogism { figure, conclusion }) + } +} + +// ─── Truth-functions (mirror `ndarray::hpc::nars` + `CausalEdge64::forward`) ── +// +// Kept private to this module. The formulas are byte-identical to the canonical +// `ndarray` hardware functions and to `forward`'s inline arms; the intentional +// mirror keeps `causal-edge` zero-dep (it cannot import `ndarray`). A later DRY +// pass may factor `forward`'s arms onto these. The hot-path u8→u8 table form +// lives in `tables.rs` (deduction shipped; induction/abduction tables follow). + +/// Deduction `A->B, B->C ⊢ A->C`: `f = f1·f2`, `c = f1·f2·c1·c2`. +#[inline] +fn deduction_truth(f1: f32, c1: f32, f2: f32, c2: f32) -> (f32, f32) { + let f = f1 * f2; + (f, f * c1 * c2) +} + +/// Induction `A->B, A->C ⊢ B->C`: `f = f2`, `c = w/(w+1)`, `w = f1·c1·c2`. +#[inline] +fn induction_truth(f1: f32, c1: f32, f2: f32, c2: f32) -> (f32, f32) { + let w = f1 * c1 * c2; + (f2, w / (w + 1.0)) +} + +/// Abduction `A->B, C->B ⊢ A->C`: `f = f1`, `c = w/(w+1)`, `w = f2·c1·c2`. +#[inline] +fn abduction_truth(f1: f32, c1: f32, f2: f32, c2: f32) -> (f32, f32) { + let w = f2 * c1 * c2; + (f1, w / (w + 1.0)) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Build an edge with the given SPO palette + truth (f,c as u8). Pearl mask + /// SPO, neutral direction, deduction stamp, all-hot. + fn edge(s: u8, p: u8, o: u8, f: u8, c: u8) -> CausalEdge64 { + #[allow(deprecated)] + CausalEdge64::pack( + s, + p, + o, + f, + c, + CausalMask::SPO, + 0, + InferenceType::Deduction, + PlasticityState::ALL_HOT, + 0, + ) + } + + #[test] + fn figure_chain_when_o1_eq_s2() { + // s1->o1(=M), M->o2 ⇒ Chain (deduction). + let a = edge(10, 1, 20, 200, 200); // 10 -> 20 + let b = edge(20, 2, 30, 200, 200); // 20 -> 30 (shares M=20) + assert_eq!(a.figure(b), Some(Figure::Chain)); + assert_eq!(Figure::Chain.rule(), InferenceType::Deduction); + } + + #[test] + fn figure_shared_subject_is_induction() { + let a = edge(10, 1, 20, 200, 200); // M=10 -> 20 + let b = edge(10, 2, 30, 200, 200); // M=10 -> 30 + assert_eq!(a.figure(b), Some(Figure::SharedSubject)); + assert_eq!(Figure::SharedSubject.rule(), InferenceType::Induction); + } + + #[test] + fn figure_shared_object_is_abduction() { + let a = edge(10, 1, 30, 200, 200); // 10 -> M=30 + let b = edge(20, 2, 30, 200, 200); // 20 -> M=30 + assert_eq!(a.figure(b), Some(Figure::SharedObject)); + assert_eq!(Figure::SharedObject.rule(), InferenceType::Abduction); + } + + #[test] + fn figure_chain_rev_when_s1_eq_o2() { + let a = edge(20, 1, 30, 200, 200); // M=20 -> 30 + let b = edge(10, 2, 20, 200, 200); // 10 -> M=20 + // o1(30)!=s2(10); s1(20)==o2(20) ⇒ ChainRev. + assert_eq!(a.figure(b), Some(Figure::ChainRev)); + assert_eq!(Figure::ChainRev.rule(), InferenceType::Deduction); + } + + #[test] + fn no_shared_term_is_none() { + let a = edge(10, 1, 20, 200, 200); + let b = edge(30, 2, 40, 200, 200); + assert_eq!(a.figure(b), None); + assert!(a.syllogize(b).is_none()); + } + + #[test] + fn identical_statement_is_revision_not_syllogism() { + // Same S and O ⇒ revision territory ⇒ None (even if p/truth differ). + let a = edge(10, 1, 20, 200, 200); + let b = edge(10, 9, 20, 100, 100); + assert_eq!(a.figure(b), None); + } + + #[test] + fn chain_conclusion_links_outer_terms() { + // 10 -> 20, 20 -> 30 ⊢ 10 -> 30. + let a = edge(10, 7, 20, 204, 204); // f=0.8 c=0.8 + let b = edge(20, 8, 30, 204, 204); + let syl = a.syllogize(b).expect("chain resolves"); + assert_eq!(syl.figure, Figure::Chain); + assert_eq!(syl.conclusion.s_idx(), 10, "conclusion subject = s1"); + assert_eq!(syl.conclusion.o_idx(), 30, "conclusion object = o2"); + } + + #[test] + fn shared_subject_conclusion_links_objects() { + // M->20, M->30 ⊢ 20 -> 30 (induction). + let a = edge(10, 1, 20, 200, 200); + let b = edge(10, 2, 30, 200, 200); + let syl = a.syllogize(b).unwrap(); + assert_eq!(syl.figure, Figure::SharedSubject); + assert_eq!(syl.conclusion.s_idx(), 20); + assert_eq!(syl.conclusion.o_idx(), 30); + } + + #[test] + fn shared_object_conclusion_links_subjects() { + // 10->M, 20->M ⊢ 10 -> 20 (abduction). + let a = edge(10, 1, 30, 200, 200); + let b = edge(20, 2, 30, 200, 200); + let syl = a.syllogize(b).unwrap(); + assert_eq!(syl.figure, Figure::SharedObject); + assert_eq!(syl.conclusion.s_idx(), 10); + assert_eq!(syl.conclusion.o_idx(), 20); + } + + #[test] + fn deduction_attenuates_confidence() { + // Strong premises: deduction confidence must be below both inputs. + let a = edge(10, 1, 20, 230, 230); // f=c≈0.90 + let b = edge(20, 2, 30, 230, 230); + let syl = a.syllogize(b).unwrap(); + assert!( + syl.conclusion.confidence() < a.confidence(), + "deduction must weaken confidence: {} !< {}", + syl.conclusion.confidence(), + a.confidence() + ); + // Deduction frequency = f1·f2 ≈ 0.9·0.9 = 0.81. + let expected_f = a.frequency() * b.frequency(); + assert!((syl.conclusion.frequency() - expected_f).abs() < 0.02); + } + + #[test] + fn induction_and_abduction_are_weak() { + let a = edge(10, 1, 20, 230, 230); + let ind = a.syllogize(edge(10, 2, 30, 230, 230)).unwrap(); + let abd = a.syllogize(edge(40, 2, 20, 230, 230)).unwrap(); + assert!( + ind.conclusion.confidence() < a.confidence(), + "induction weak" + ); + assert!( + abd.conclusion.confidence() < a.confidence(), + "abduction weak" + ); + } + + #[test] + fn pearl_mask_is_anded() { + // SPO premise ∧ PO premise ⇒ PO conclusion. + #[allow(deprecated)] + let a = CausalEdge64::pack( + 10, + 1, + 20, + 200, + 200, + CausalMask::SPO, + 0, + InferenceType::Deduction, + PlasticityState::ALL_HOT, + 0, + ); + #[allow(deprecated)] + let b = CausalEdge64::pack( + 20, + 2, + 30, + 200, + 200, + CausalMask::PO, + 0, + InferenceType::Deduction, + PlasticityState::ALL_HOT, + 0, + ); + let syl = a.syllogize(b).unwrap(); + assert_eq!(syl.conclusion.causal_mask(), CausalMask::PO); + } + + #[test] + fn notation_strings_are_canonical() { + assert_eq!(Figure::SharedObject.notation(), "{P-->M, S-->M} |- S-->P"); + assert_eq!(Figure::SharedSubject.notation(), "{M-->P, M-->S} |- S-->P"); + } + + // The signed inference mantissa is a v2-layout concept; assert it only when + // the (default) feature is on. + #[cfg(feature = "causal-edge-v2-layout")] + #[test] + fn conclusion_carries_signed_chain_direction() { + // Deduction / Induction are forward-chain (+); Abduction is backward (−). + let a = edge(10, 1, 20, 200, 200); + let chain = a.syllogize(edge(20, 2, 30, 200, 200)).unwrap(); + assert_eq!(chain.conclusion.inference_mantissa(), 1, "Deduction = +1"); + assert_eq!(chain.conclusion.inference_direction(), 1, "forward chain"); + + let ind = a.syllogize(edge(10, 2, 30, 200, 200)).unwrap(); + assert_eq!(ind.conclusion.inference_mantissa(), 2, "Induction = +2"); + + let abd = a.syllogize(edge(40, 2, 20, 200, 200)).unwrap(); + assert_eq!(abd.conclusion.inference_mantissa(), -1, "Abduction = −1"); + assert_eq!(abd.conclusion.inference_direction(), -1, "backward chain"); + } +} diff --git a/crates/cognitive-shader-driver/src/codec_bridge.rs b/crates/cognitive-shader-driver/src/codec_bridge.rs index c3cb75d08..8dba5759d 100644 --- a/crates/cognitive-shader-driver/src/codec_bridge.rs +++ b/crates/cognitive-shader-driver/src/codec_bridge.rs @@ -99,6 +99,7 @@ impl OrchestrationBridge for CodecResearchBridge { inference_type: InferenceType::Deduction, strategy: lance_graph_contract::nars::QueryStrategy::CamExact, semiring: lance_graph_contract::nars::SemiringChoice::HammingMin, + rung: lance_graph_contract::cognitive_shader::RungLevel::Surface, free_will_modifier: 1.0, exploratory: false, } diff --git a/crates/cognitive-shader-driver/src/cypher_bridge.rs b/crates/cognitive-shader-driver/src/cypher_bridge.rs index b703b1248..13782e17f 100644 --- a/crates/cognitive-shader-driver/src/cypher_bridge.rs +++ b/crates/cognitive-shader-driver/src/cypher_bridge.rs @@ -133,6 +133,7 @@ impl OrchestrationBridge for CypherBridge { inference_type: InferenceType::Deduction, strategy: lance_graph_contract::nars::QueryStrategy::CamExact, semiring: lance_graph_contract::nars::SemiringChoice::HammingMin, + rung: lance_graph_contract::cognitive_shader::RungLevel::Surface, free_will_modifier: 1.0, exploratory: false, } diff --git a/crates/lance-graph-contract/src/exploration.rs b/crates/lance-graph-contract/src/exploration.rs index f9a3c9e62..d7e4a95f7 100644 --- a/crates/lance-graph-contract/src/exploration.rs +++ b/crates/lance-graph-contract/src/exploration.rs @@ -120,6 +120,15 @@ impl NarsTruth { pub fn expectation(&self) -> f32 { self.confidence * (self.frequency - 0.5) + 0.5 } + + // NOTE: the syllogism truth-functions (deduction/induction/abduction/ + // exemplification/comparison/analogy/resemblance) are NOT redefined here. + // The canonical, tested implementation is `nars_engine::nars_infer` + // (crate lance-graph-planner). This `NarsTruth` is the *proposer-side* + // (offline, float) frequency/confidence carrier; it intentionally exposes + // only revision (evidence fusion) + expectation. Wiring the named + // OpenNARS vocabulary (`cognitive_codebook::NarsInference`) into executable + // truth-functions happens in the engine — see EPIPHANIES `E-NARS-ONE-ENGINE`. } /// An edge in the exploration frontier. diff --git a/crates/lance-graph-contract/src/nars.rs b/crates/lance-graph-contract/src/nars.rs index 3d571ebdb..cc8084c59 100644 --- a/crates/lance-graph-contract/src/nars.rs +++ b/crates/lance-graph-contract/src/nars.rs @@ -51,6 +51,73 @@ impl InferenceType { Self::Synthesis => QueryStrategy::BundleAcross, } } + + /// Signed v2 inference mantissa (i4, −8..+7) — the cross-crate rule key. + /// + /// This is the SAME rule space as `causal_edge::edge::InferenceType`, + /// bridged by VALUE — the contract stays **zero-dep** (no `causal-edge` + /// import). The mantissa is the shared little-endian grammar every + /// `CausalEdge64` consumer reads: forward-chain positive, backward-chain + /// negative. Mapping matches causal-edge `to_mantissa`: + /// Deduction `+1`, Induction `+2`, Abduction `−1`, Revision `+4`, + /// Synthesis `+5`. + pub const fn to_mantissa(self) -> i8 { + match self { + Self::Deduction => 1, + Self::Induction => 2, + Self::Abduction => -1, + Self::Revision => 4, + Self::Synthesis => 5, + } + } + + /// Inverse of [`to_mantissa`](Self::to_mantissa): the closest core rule for a + /// signed mantissa. Round-trips all 5 variants; negative magnitude is the + /// backward-chain Abduction direction; `0` = neutral → Deduction. Mantissas + /// `6`/`7` (causal-edge Intervention/Counterfactual/extension) collapse to + /// the nearest core rule (Synthesis), since the contract models only the 5. + pub fn from_mantissa(m: i8) -> Self { + let mag = m.unsigned_abs() & 0x7; + let forward = m >= 0; + match mag { + 0 => Self::Deduction, + 1 => { + if forward { + Self::Deduction + } else { + Self::Abduction + } + } + 2 => { + if forward { + Self::Induction + } else { + Self::Abduction + } + } + 3 => { + if forward { + Self::Synthesis + } else { + Self::Abduction + } + } + 4 => Self::Revision, + 5 => Self::Synthesis, + _ => Self::Synthesis, + } + } +} + +/// Bridge the grammar's extended inference set (`grammar::inference::NarsInference`, +/// 7 variants incl. Extrapolation / CounterfactualSynthesis) into the canonical +/// core 5, via the grammar enum's own `.core()`. Lets the DeepNSM grammar +/// proposer (§12.1) hand its inference *intent* to the reasoning pipeline with +/// an ergonomic `.into()`. +impl From for InferenceType { + fn from(g: crate::grammar::inference::NarsInference) -> Self { + g.core() + } } /// Semiring choice — how to combine evidence across paths. @@ -67,3 +134,45 @@ pub enum SemiringChoice { /// CAM-PQ ADC distance (compressed vector search). CamPqAdc, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mantissa_round_trips_all_five() { + for t in [ + InferenceType::Deduction, + InferenceType::Induction, + InferenceType::Abduction, + InferenceType::Revision, + InferenceType::Synthesis, + ] { + assert_eq!(InferenceType::from_mantissa(t.to_mantissa()), t, "{t:?}"); + } + } + + #[test] + fn mantissa_signs_match_causal_edge() { + // Forward-chain positive, backward-chain negative (the causal-edge scheme). + assert_eq!(InferenceType::Deduction.to_mantissa(), 1); + assert_eq!(InferenceType::Induction.to_mantissa(), 2); + assert_eq!(InferenceType::Abduction.to_mantissa(), -1); + assert_eq!(InferenceType::Revision.to_mantissa(), 4); + assert_eq!(InferenceType::Synthesis.to_mantissa(), 5); + // Neutral / extension mantissas fall back to a core rule. + assert_eq!(InferenceType::from_mantissa(0), InferenceType::Deduction); + assert_eq!(InferenceType::from_mantissa(6), InferenceType::Synthesis); + } + + #[test] + fn grammar_inference_bridges_via_core() { + use crate::grammar::inference::NarsInference as G; + assert_eq!(InferenceType::from(G::Deduction), InferenceType::Deduction); + assert_eq!(InferenceType::from(G::Extrapolation), InferenceType::Induction); + assert_eq!( + InferenceType::from(G::CounterfactualSynthesis), + InferenceType::Synthesis + ); + } +} diff --git a/crates/lance-graph-contract/src/plan.rs b/crates/lance-graph-contract/src/plan.rs index 658fbdefc..74d5fd561 100644 --- a/crates/lance-graph-contract/src/plan.rs +++ b/crates/lance-graph-contract/src/plan.rs @@ -4,6 +4,7 @@ //! consumers (ladybug-rs, n8n-rs) call. use crate::mul::{GateDecision, MulAssessment, SituationInput}; +use crate::cognitive_shader::RungLevel; use crate::nars::{InferenceType, SemiringChoice}; use crate::thinking::{FieldModulation, ThinkingStyle}; @@ -18,6 +19,8 @@ pub struct ThinkingContext { pub inference_type: InferenceType, pub strategy: crate::nars::QueryStrategy, pub semiring: SemiringChoice, + /// Semantic rung — depth of causation (0..9); the Rung atom family. + pub rung: RungLevel, pub free_will_modifier: f64, pub exploratory: bool, } diff --git a/crates/lance-graph-planner/src/api.rs b/crates/lance-graph-planner/src/api.rs index 1904cc84e..6ae146d5d 100644 --- a/crates/lance-graph-planner/src/api.rs +++ b/crates/lance-graph-planner/src/api.rs @@ -175,6 +175,7 @@ impl Planner { &crate::thinking::NarsInferenceType::Deduction, ), sigma_stage: crate::thinking::sigma_chain::SigmaStage::Omega, + rung: lance_graph_contract::cognitive_shader::RungLevel::Surface, free_will_modifier: 1.0, exploratory: matches!(style.cluster(), ThinkingCluster::Divergent), }; diff --git a/crates/lance-graph-planner/src/orchestration_impl.rs b/crates/lance-graph-planner/src/orchestration_impl.rs index cb508e817..c396aa4d9 100644 --- a/crates/lance-graph-planner/src/orchestration_impl.rs +++ b/crates/lance-graph-planner/src/orchestration_impl.rs @@ -148,6 +148,7 @@ fn resolve_thinking_minimal( InferenceType::Revision | InferenceType::Synthesis => SemiringChoice::NarsTruth, _ => SemiringChoice::Boolean, }, + rung: lance_graph_contract::cognitive_shader::RungLevel::Surface, free_will_modifier: 1.0, exploratory: false, } @@ -175,6 +176,7 @@ fn thinking_to_contract(p: &crate::thinking::ThinkingContext) -> ContractThinkin _ => QueryStrategy::CamExact, }, semiring: SemiringChoice::HammingMin, + rung: p.rung, free_will_modifier: p.free_will_modifier, exploratory: p.exploratory, } diff --git a/crates/lance-graph-planner/src/pipeline.rs b/crates/lance-graph-planner/src/pipeline.rs index fc05de862..edd4ed01d 100644 --- a/crates/lance-graph-planner/src/pipeline.rs +++ b/crates/lance-graph-planner/src/pipeline.rs @@ -590,6 +590,7 @@ mod tests { inference_type, strategy: QueryStrategy::CamExact, semiring: SemiringChoice::Boolean, + rung: lance_graph_contract::cognitive_shader::RungLevel::Surface, free_will_modifier: 1.0, exploratory: false, } diff --git a/crates/lance-graph-planner/src/thinking/mod.rs b/crates/lance-graph-planner/src/thinking/mod.rs index 460199d69..7d9ec43bd 100644 --- a/crates/lance-graph-planner/src/thinking/mod.rs +++ b/crates/lance-graph-planner/src/thinking/mod.rs @@ -23,6 +23,7 @@ pub use style::{FieldModulation, ThinkingCluster, ThinkingStyle}; use crate::mul::MulAssessment; use crate::plan::PlannerConfig; +use lance_graph_contract::cognitive_shader::RungLevel; /// Complete thinking context produced by orchestration. /// This is what the query planner receives as input alongside the query. @@ -40,6 +41,8 @@ pub struct ThinkingContext { pub semiring: SemiringChoice, /// Current sigma chain stage. pub sigma_stage: SigmaStage, + /// Semantic rung — depth of causation (0..9); the Rung atom family. + pub rung: RungLevel, /// Free will modifier (passed from MUL). pub free_will_modifier: f64, /// Whether we're in exploratory mode (from compass). @@ -80,6 +83,7 @@ pub fn orchestrate(query: &str, mul: &MulAssessment, _config: &PlannerConfig) -> strategy, semiring, sigma_stage, + rung: RungLevel::Surface, free_will_modifier: mul.free_will_modifier, exploratory: false, }