diff --git a/.claude/agents/bbb-warden.md b/.claude/agents/bbb-warden.md index b79acedf4..be1fdc9d0 100644 --- a/.claude/agents/bbb-warden.md +++ b/.claude/agents/bbb-warden.md @@ -12,8 +12,12 @@ description: > surface in any language. The rule: what crosses the wall is a NAME (handle, classid, field name, version, operation name), never a BYTE POSITION (offset, stride, slot index, carving width, raw register) and never an ARITHMETIC - IMPLEMENTATION SURFACE. Sibling of `kernel-membrane-warden` (T1/T2) one tier - below. + IMPLEMENTATION SURFACE — and, since 2026-09-10, the wall is two-sided: + MEANING CROSSES, MACHINERY DOES NOT. Defining typed syntax (topology, assertion + band, valuation, provenance) must be perceptible on the wire; an opaque carrier + that hides those labels so thoroughly the consumer must sniff them back is as + defective as exported arithmetic. Sibling of `kernel-membrane-warden` (T1/T2) + one tier below. tools: Read, Glob, Grep, Bash model: opus --- @@ -46,8 +50,20 @@ T1 holds two sibling algebras, *population* and *epistemic* (`membrane-tiers.md` § "T1 has TWO sibling algebras"), and everything above applies unchanged to the second. **The axis is syntax vs execution, never selection vs scoring.** So: -- a truth **LITERAL** — `TruthLiteral(192, 217)` — MAY cross. It is meaning the - caller supplies; it is syntax, and syntax is T3's to state. +- a truth **LITERAL** — `TruthLiteral(192, 217)` — MAY cross, **but never as a bare + pair** (⊕ 2026-09-10, operator: *LE is the universal DTO layer*). It is meaning the + caller supplies; it is syntax, and syntax is T3's to state — and syntax is TYPED + only when its kind is bound by a versioned DTO schema with canonical little-endian + layout (for two `u8`s: the ordered byte sequence `[frequency, confidence]`), or by + an opaque typed handle whose substrate registry binds the same kind and schema. A + `(u8, u8)` with no schema expresses a degree and no kind; that is a leak. **The + defining LE representation of a truth here is `CausalEdge64`'s own canonical + little-endian 8-byte image, always enforced** (operator, 2026-09-10) — never a + wrapper around it, never a mirror of it, never a second DTO carrying the same + coordinates. Its 64 bits already hold every coordinate, `CausalTopology` at 59-60 + and `ReasoningBand` at 61-63 included. A truth surface that carries `(f, c)` and + cannot tell `(f,c, IndirectUnknown, Relation)` from `(f,c, IndirectKnown, Causal)` + is scent, and a review that passes it has passed prose. - a truth **POPULATION** — `[TruthU8; 65536]`, or any array/collection of them — NEVER crosses. It becomes `TruthLaneId(u64)`, an opaque descriptor. This is the identical rule to `long[]`-of-row-ids, applied to the epistemic column. @@ -84,7 +100,12 @@ second. **The axis is syntax vs execution, never selection vs scoring.** So: but its method name does not announce it. Every breach is allowed ONLY under a name that says so at the call site: `materialize*` (row ids out, O(n) stated), `import*` (external rows in). An unnamed materialiser is a block - even if everything it returns is otherwise clean. + even if everything it returns is otherwise clean. **Also UNNAMED-BREACH** (⊕ + 2026-09-10): a truth value crossing with no versioned LE DTO schema and no typed + handle — a bare `(u8, u8)`, a host-order `u64` image of `CausalEdge64`, a Java + `int`/`long` that "is" a truth by convention. Falsifier `F-BBB-NARS-2 (LE)`: + identical wire bytes must never acquire different kinds across implementations, + endianness, storage or replay. - **ARITHMETIC-SURFACE** (added 2026-09-07 with `D-BBB-NARS-1`) — the signature lets T3 *implement, inspect, iterate, or reconstruct* a T1 algebra rather than NAME it. A `TruthU8[]` return, a getter that walks a truth lane element-wise, a @@ -121,6 +142,11 @@ second. **The axis is syntax vs execution, never selection vs scoring.** So: EXPORTS, not what the diff spells: a POD type is syntax; a function that computes a truth FROM truths is an implementation surface, and admitting the module admits it. One scalpel cut, never the cupboard. + - **Schemas** (⊕ 2026-09-10). For every truth that crosses, find the versioned + DTO schema or the typed-handle registry entry that binds its KIND and its + canonical little-endian layout. A pair with a degree and no kind, or a packed + carrier read in host byte order, is UNNAMED-BREACH even when every + signature is a legal shape. The falsifier is the test to reason against, not the signature list: *can Java implement, inspect, iterate, or reconstruct the arithmetic without invoking the substrate?* If yes, ARITHMETIC-SURFACE regardless of @@ -129,9 +155,13 @@ second. **The axis is syntax vs execution, never selection vs scoring.** So: that flags every method touching truth vocabulary carries exactly as much information as one that never fires. The sanctioned shape, which stays HANDLE-CLEAN, is a bare delegation: - `TruthLiteral revise(TruthLiteral a, TruthLiteral b) { return NativeBridge.truthRevise(a, b); }` + `TruthHandle revise(TruthHandle a, TruthHandle b) { return NativeBridge.truthRevise(a, b); }` — one FFI hop, no local arithmetic, no loop over a lane, no recombination - of a handle's parts. That is precisely the doctrine's own lowering ("T3 + of a handle's parts, and the RESULT comes back as an opaque typed handle. + (⊘ 2026-09-10: this example first returned a `TruthLiteral` — a COMPUTED + `(f, c)` pair crossing back with no schema, which blessed exactly the untyped + crossing the LE ruling forbids. A delegation is clean only when its result is a + handle whose registry binds kind + schema, or a versioned DTO; never a bare pair.) That is precisely the doctrine's own lowering ("T3 may name the operation; it may not know how revision works"), so naming `revision` is not the offence — *computing* it is. Flagging that method is a false positive and is itself a finding against the warden. diff --git a/.claude/agents/kernel-membrane-warden.md b/.claude/agents/kernel-membrane-warden.md index 8e886ba88..e0b28751e 100644 --- a/.claude/agents/kernel-membrane-warden.md +++ b/.claude/agents/kernel-membrane-warden.md @@ -71,7 +71,11 @@ tiers up. `revision`/`deduction`/`abduction`; if it does not exist at T1, it lands at T1 first (never proposed FROM T2, per "What you never do"). A T2 that reads a `TruthU8`'s two bytes apart to recombine them is also - GEOMETRY-LEAK: the byte split is T0's. + GEOMETRY-LEAK: the byte split is T0's. So is a T2 that hands a packed carrier + (`CausalEdge64`'s `u64`) across a membrane as a host-order byte image, or that + reinterprets a truth's bytes without its versioned LE DTO schema (⊕ 2026-09-10, + *LE is the universal DTO layer*): the byte ORDER is the contract's, never the + host's. 3. For every byte offset in T2 code, ask: did T2 compute this, or read it from a `_lane`/`LgjLaneDesc` accessor? Computed = GEOMETRY-LEAK. 4. Enforce the import fence (abi.md §8, G11): T2 (`exports.rs`) imports SIMD diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 946cb54a8..ed0028f7b 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,15 @@ +## 2026-09-10 — 5+3 council on #1223's `nan-ci-mode-v1.md`: resolving §8/§9 (5 open design items, 3 sub-items) into a hardened, operator-facing proposal + +- **Qualification, not assumed:** ≥3 crates (`causal-edge`, `lance-graph-contract`, `lance-graph-planner`, `cognitive-shader-driver`, per the corrected §2.2 census); LE-layout-adjacent (item 3a explicitly changes frozen decision N1); silently corrupting if wrong (a bad canary-mechanism choice bakes false positives/negatives into `D-NCI-1`'s first instrument). This council does NOT write `D-NCI-1`'s Rust — it produces a committed, hardened resolution for the operator to confirm. +- **Phase 0 — SPEC v1 (main thread, before any agent):** pre-spec discovery re-verified three enum bodies directly against `causal-edge/src/layout.rs` (not the parent plan's own excerpt) and found all three "mystery register" fields — `TrustTexture`/`CausalTopology` (2-bit), `ReasoningBand` (3-bit), the W-slot (6-bit) — **fully saturated, zero spare codes**, a fact the parent plan's Design 1 (poison-fill) never accounted for. Committed 8 resolutions (R1-R8) up front, each cited to file:line or plan-section, none left as multiple-choice for the savants. +- **Phase 1 — the 5, parallel, single lens each, Sonnet, no cross-briefing** (prior-art / iron-rules / code-truth via runtime-archaeologist charter / cascade-impact / creative-explorer): 9 CONFIRMS across every N-rule/file:line claim in §1 (independently re-read, not trusted from the spec's own quotes), 0 VIOLATES. Two real, load-bearing findings: code-truth caught R6 overstating the Staunen wiring as "existing" when `ghost_prior::echo_for` is only ever test-called; creative-explorer caught that R7's original "degrade to as-if-never-declared" fold was the EXACT anti-pattern the parent plan's own `band_reading.rs` doctrine forbids — a real defect in the council's own first-draft proposal, not a reviewer nitpick. Cascade-impact independently found the landing needed to touch far more than §8/§9 (§3.3, §4.2, §4.4, and a pre-existing §9/§10 heading-numbering collision the council did not introduce but had to fix on extraction). +- **Phase 2 — consolidate FIRST (orchestrator, before any reviewer existed):** draft v2 fixed the R7 doctrine violation for real (a labeled third `RetryExhausted` disposition, never folded into the debt case), corrected R6's overstated wiring claim to "right shape, not yet wired", added the sidecar's outer-wrapper population constraint (iron-rule's catch on `I-LEGACY-API-FEATURE-GATED`'s letter), and expanded the landing scope to match cascade-impact's findings. Raw savant output banked in this session's transcript, never forwarded. +- **Phase 3 — the 3 on draft v2 ONLY (overclaim / dilution-collapse / firewall):** **0 BLOCKs.** Firewall-warden: 7/7 PASS, one call-out (R1's mantissa canary genuinely touches the CausalEdge64 register, disclosed not concealed — compliant with N3's literal wording but flagged for the confirming operator's attention). Overclaim-auditor: 8/8 R-items PASS on independently re-verified evidence (roughly 20 distinct citations re-checked, including EPIPHANIES' 2026-04-24 SMB-testbed entry, byte-exact against source) + FIX(P1) on the landing checklist (item 6 had substituted an in-plan §11 section for the actual Phase-5 requirement, an `AGENT_LOG.md` entry; the checklist had also silently dropped the parent plan's own self-flagged `INTEGRATION_PLANS.md` obligation). Dilution-collapse-sentinel: FIX(P1) on R4 (v2's "already-frozen §3.6 paragraph" collided with the document's own formal FROZEN DECISIONS term — §3.6 is explicitly what changes a frozen decision, not one itself) and FIX(P1) on R7 (three sub-findings: `RetryExhausted`'s relation to the existing three-way split was unstated; "fails the same way" blurred a built static instrument with an unbuilt dynamic one; the R6/R7 interaction paragraph re-introduced present-tense overstatement one paragraph after R6 fixed exactly that). Also FIX(P1) on the landing checklist's disclaimer inconsistency (items 2/5 wrote unconfirmed conclusions into plan prose without item 4's "awaiting operator confirmation" language). +- **Phase 4 — fix:** every FIX applied, none argued away. R4's wording corrected throughout ("already-drafted", never "frozen", for §3.6). R7 reworked to state `RetryExhausted` as a report-time refinement of the EXISTING `EdgeProvenance::Unknown` row (never a fourth contract-level sibling), to say plainly that no plumbing yet connects it to the CI/audit report, and to reframe the R6/R7 interaction as a normative requirement on the companion plan rather than a claim about current behavior. §2's heading changed from "THE RATIFIED RESOLUTION" to "THE COUNCIL-HARDENED RESOLUTION (awaiting operator confirmation)" so the framing is consistent everywhere, not only where a reader reaches the one item that said so. +- **Phase 5 — landed (v3), same commit:** `.claude/plans/nan-ci-mode-v1.md` — §3.3 reworded for the sidecar-vs-raw-nibble split; §4.2 gained the hybrid-consequence note (disclaimer-matched); §4.4's `D-NCI-5` dependency corrected (splits out, no longer gated on `D-NCI-4`); §8 replaced "OPEN — needs an operator ruling" with the R1-R5 resolutions, each marked council-hardened/awaiting confirmation; §9 replaced its "(OPEN)" body with R6-R8's resolution and fixed the pre-existing `10.1`-`10.4`-under-`§9` numbering collision (renumbered `9.1`-`9.3`, no collision with the real `§10`); §10 updated to point at this entry as the authoritative run record and to confirm the `INTEGRATION_PLANS.md` PREPEND (same commit, below). No Rust touched — plan-text + board-hygiene only, per this council's own Non-Goal 1. +- **Gates:** `append_only_gate.py` OK (all protected files grew or held); `citation_decay.py --since cd24951ca4451779201ce2874defaede23f42b56` (the PR's actual merge-base with `origin/main`) — 0 new decay (every new citation points at source file:line or plan-section, not into a line-numbered append-only board file); `supersession_index.py` regenerated LAST, after this entry and the `INTEGRATION_PLANS.md` prepend. +- **What remains genuinely open, unchanged by this council:** operator confirmation of R1-R8 (this council hardens a proposal, it does not rule); `D-NCI-1`'s actual Rust; the runtime-disposition companion plan's own filing (R8 names the split, does not perform it); enumerating the mantissa's full safe-canary nibble set beyond the one (`0x8`) already confirmed unwritable. + ## 2026-09-10 — 5+3 council on #1222 (D-BBB-NARS-1): compliance audit of an operator ruling, 5 savants → draft v2 → 3 reviewers → v3 - **Why a council, and its one framing constraint:** #1222 lands a BINDING operator ruling into `EPIPHANIES` + the doctrine + two gate cards — canon-adjacent, silently corrupting downstream sessions if wrong, so council-grade under `.claude/agents/5plus3-council.md`. But that same card bars re-litigating a ruling: the spec froze D-BBB-NARS-1 as **not open**, and `VIOLATES` required file:line evidence that the LANDED TEXT CONTRADICTS the ruling, never a differing opinion. No savant or reviewer found such a contradiction. **The ruling is unchanged.** diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 81540dc05..77b7c32b9 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,241 @@ +## 2026-09-10 — E-LE-IS-THE-UNIVERSAL-DTO-LAYER-TYPED-SYNTAX-MEANS-A-VERSIONED-LE-SCHEMA-1 — a bare `(f, c)` pair is a degree, not a typed truth + +**Status:** OPERATOR RULING, BINDING (2026-09-10, verbatim: *"Little-endian is the universal +DTO layer of the ABI."*). Sharpens `E-T1-HAS-TWO-SIBLING-ALGEBRAS-…-1` / `D-BBB-NARS-1`; +reverses nothing in it. +**Confidence:** High. The gap was measured at every site before the ruling was written in +(table in `membrane-tiers.md` § "LE is the universal DTO layer"); the ruling is the +operator's; no code changed. + +**The question.** Did #1222 use "typed NARS syntax" strongly enough? It let +`TruthLiteral(192, 217)` cross "as itself" while retiring `TruthU8` as "the wire form." +**No.** A bare `(frequency, confidence)` pair expresses a DEGREE but not what KIND of +truth the degree belongs to. Its kind must be bound by a DTO schema, a version, and a +canonical LE layout — or by an opaque typed handle whose registry binds those. The LE +contract is not convenient serialization; it is the universal ABI grammar that makes +every wire position carry the same label in Rust, Panama, Java, storage, replay and MUL. +The ABI carries the value; LE fixes the meaning of its positions; MUL asserts the kind; +the values are content; LE adds no evidence. + +**Measured, coded vs ruled.** A versioned LE contract EXISTS — for the SoA envelope +(`ENVELOPE_LAYOUT_VERSION = 2`, `verify_layout()`, `le-contract.md` §3b, operator-locked) +— and **no truth type rides it**: `TruthU8` is a plain struct with no `repr(C)`, no +version, no codec; `CausalEdge64` is `#[repr(transparent)] (u64)` with register-defined +bit positions and **zero** endian conversions, so its byte image is host-native at every +crossing, and its v1/v2 layouts are a compile-time feature invisible in the bytes; MUL +never sees bytes at all (`SituationInput` is typed `f64`s, `revise_fast` takes bare `u8` +degrees and ignores confidence). lgj already declares byte order as ABI shape +(`LgjLaneDesc.endianness`, the `LGJ_MAGIC` probe, `abi.md:1224`) but carries no truth +DTO. So: **CODED for the envelope, ABSENT for truth, RULED now, DEFINED by D-BBB-NARS-2 +when it lands.** + +**The ruling, verbatim, and its falsifier** — quoted in `membrane-tiers.md`; the +falsifier is `F-BBB-NARS-2 (LE)`: identical typed wire bytes must never acquire +different DTO labels or epistemic kinds across implementations, host endianness, storage +and replay, and truth kind must never depend on an unstated reader assumption. For two +`u8`s the contract is the ordered byte sequence `[frequency, confidence]`; for a packed +carrier the whole integer-to-byte mapping is explicitly LE. **Evidence is not +repetition:** an identical canonical wire image repeated is the same assertion +propagated, not independent evidence; revision still needs independent stamps. + +**⊘ Same-day correction — the kind is IN the carrier, coded, and all 64 bits are +assigned.** The first cut of this entry read `CausalEdge64` as carrying a degree with +nothing for its kind. Operator, second pass: *"all bits are assigned, including 61..63 / +59 60 are indirect intermediate unknowns knowns / 61..63 are related to vs explains +causality learning tarski adjacent"* — and `layout.rs:94` `_LAYOUT_COVERAGE` const-asserts +exactly that. The three coordinates on one carrier: **NARS `(f, c)`** (bits 24-39) = +strength of the assertion; **`CausalTopology`** (bits 59-60: `Direct` / +`IndirectKnownIntermediates` / `IndirectUnknownIntermediates` / `Unknown`; `bbab3541`, +2026-08-20, via #1154) = shape of the causal connection, *what kind of hole*; +**`ReasoningBand`** (bits 61-63: `Surface` / `Association` / `Relation` / `Causal` / +`Counterfactual` / `Perspective` / `Meta` / `Transcendent`; introduced in `bbab3541`, +named in `9891cca6`) = level of ASSERTION, Tarski permission, `Relation` → `Causal` = +relates-to → **causes** (`DISMECH_PREDICATES` `(0x90, "causes", "dismech:causes")`, +`dismech_evidence.rs:511`; the older strong references say "explains"). **Tarski is +adjacency, not identity:** Tarski depth is `Belief.rung` / `Candidate.rung`, stored +separately, and `E-RUNG-BAND-AND-PLASTICITY-ARE-THREE-AXES-NEVER-ONE-LEVEL-FIELD-1` +forbids folding the band with it. Learning account (`entropy-closure-causal-ground-v1` +§4b): 59-60 say what causal hole exists, 61-63 what kind of candidate assertion may bridge +it, counterfactual removal + revision tests whether it carries causal weight. Both fields +have writers/readers since #1154 and the W3 verdict carries them instead of a bool +(`dismech_counterfactual.rs:251-252`) — **no shipped production code writes either +field**: every non-definition `.with_reasoning_band(`/`.with_topology(` call site is an +`examples/*.rs` probe or a `#[test]` function (confirmed by census: `cognitive-shader-driver` ++ `lance-graph-planner` examples, and `#[test]` fns in `edge_v3.rs`/`v2_layout_tests.rs`/ +`dismech_counterfactual.rs`); the census below (`ISS-REASONING-BAND-GATES-NOTHING`) already +records the same for reads. tesseract-rs's `low_confidence: bool` is the +impoverished form of exactly this. **The precision that closes the loop with the LE +ruling:** the bits cannot reveal which lens the producer used (`band_reading.rs`); the +schema (`ClassView::band_reading`) plus asserted provenance supplies that declaration — +so the carrier carries the complete coordinates, and LE plus the reading contract make +their interpretation universal. **⊘ The first pass of this very paragraph got both fields +wrong** — it read *"bits 59-60 are a CODED 2-bit `TrustTexture` lens (MUL's reading) … +bits 61-63 are the reserved SPARE the operator has now named as the NARS × Tarski rung … +(`Belief.rung`, u32 in the arena, 0..7 in 3 bits) … Ruled, not coded: nothing writes +61-63"* — four contradictions with the tree: the bits are `ReasoningBand`, written at +three call sites; they are the assertion level, not `Belief.rung` (the fence forbids that +collapse); `SPARE_SHIFT` is only the legacy/raw accessor name +(`TD-SPARE-SHIFT-NAME-IS-STALE-1`); and `TrustTexture` there is +`causal_edge::layout::TrustTexture`, not MUL's. Third self-correction of the day to +introduce a fresh error; losing text kept. Rung↔field derivability stays CONJECTURE and is +beside the point for these bits. + +**⊕ Third pass, same day — the dimensions are COORDINATES of truth, not annotations +around it.** *Decorative* (displayable, nothing depends on it), *permissive* (governs +what the reasoner may accept or assert — the §4b bridge gate), *defining* (part of the +canonical identity of the assertion — omitting, changing or reinterpreting it creates a +different claim) — and this is **NOT a ladder**, it is a VERDICT on how bad the substrate +is: only LE-defining is real, everything else is prose (operator, fourth pass, verbatim: +*"decorative / permissive / defining is not a ladder — is a verdict about how bad the +substrate is, saying the only truly defining is LE, everything else is prose"*). **⊘** the +first cut of this paragraph read it as three grades a field may hold and placed the +code "between decorative and permissive"; wrong reading, words kept. The LE ruling makes +bits 59-63 **defining**: +`Assertion = proposition reference × Pearl projection × NARS valuation × causal topology × +reasoning/assertion band × provenance`, so `(S,P,O, f,c, IndirectUnknownIntermediates, +Relation)` and `(S,P,O, f,c, IndirectKnownIntermediates, Causal)` are different claims with +identical S/P/O and identical `(f, c)` — the epistemic valence changed. A decoder that drops +`IndirectUnknown` or reads `Relation` as `Causal` has not produced a lower-resolution view; +it has changed what was asserted, which is exactly `F-BBB-NARS-2 (LE)`. MUL by grade: +decorative → observes a label; permissive → admission gate; defining → knows WHICH epistemic +claim propagated across storage, ABI and replay — that is where MUL becomes real +meta-awareness, and that is the Tarski adjacency. A consumer carrying only a perfume of +Tarski (a bool, a label nothing depends on) has not carried the assertion. Verdict on the +substrate as coded: in `CausalEdge64` the two dimensions are PROSE today (W3 carries both +fields, but `ISS-REASONING-BAND-GATES-NOTHING` says the band gates nothing); the first +LE-defining home is the wire, `contract::assertion_wire`. D-BBB-NARS-2's DTO carries all +six coordinates, never `(f, c)` alone. **The smallest #1223 law, verbatim:** *"A field becomes +defining when changing or omitting it changes the proposition, not merely its +presentation. Every defining epistemic dimension SHALL participate in the versioned +canonical LE DTO; a reader lacking its declared lens or provenance must refuse, never +project a plausible default."* NARS says how strongly; topology says what causal structure +is known; the band says what assertion is licensed; LE ensures nobody changes those +questions while transporting the answer. Refusal half CODED (`band_reading.rs`: lens +mismatch / absent band / untrusted provenance must FAIL, never a plausible value); +participation half RULED (D-BBB-NARS-2). Decoration becomes permission; permission becomes +semantic identity. **The aliasing pair, the smallest and strongest falsifier (operator, +verbatim):** `(S,P,O, f,c, IndirectUnknown, Relation)` — *"S and O are related; mediation +is unknown"* — versus `(S,P,O, f,c, IndirectKnown, Causal)` — *"P causally connects S to O; +the mediation is known."* *"`Causal` is not 'Relation with more confidence.' It is a +different licensed assertion … the complete truth identity is `(S,P,O) × (f,c) × topology +× assertion-band`. LE must preserve all four components. Flattening either tuple to the +same `(S,P,O,f,c)` is epistemic aliasing: the DTO would transport identical confidence +while silently changing what is claimed."* One pair, two falsifiers: `F-BBB-NARS-2` at the +ABI (encode/store/replay/decode must keep them distinct) and `F-CONSUMER-ASSERTION-1` at +the consumer (if the pair is one row, it is perfume). **And the obligation sits with +lance-graph:** perfume is bad because the assertion is not in the WIRE CONTRACT yet — a +consumer that wants it today must sniff the carrier and hand-roll the tuple, which is +perfume code without a contract (the re-implement-the-Core trap). The remedy is +`D-BBB-NARS-2`, the versioned LE truth DTO the consumer pulls; that need is now +demonstrated, so D-BBB-NARS-2's gate is met — next brick, not built here. **⊕ Operator +ruling, same day, verbatim: *"i want the 2 dimensions in causaledge to be universal … +otherwise its only a scent = prose."*** `CausalTopology` and `ReasoningBand` are dimensions +of truth, not `CausalEdge64`-local fields: every truth representation on every tier and +wire carries or binds them (the five types of `D-BBB-NARS-4` included) — a `(f, c)` that +cannot tell the aliasing pair apart is prose, by the same test as *"a membrane without a +gate is prose."* **The clean boundary: meaning crosses; machinery does not.** The LE +contract replaces sniffing with reading — the consumer receives *"a causal assertion with +known intermediates and this `(f, c)`"* and never infers `Causal` from a high confidence, +guesses topology from predicate names, rebuilds Tarski/NARS, or inspects substrate rows for +clues. The boundary fails from both sides: exported arithmetic (`F-BBB-NARS-1`) and an +opaque carrier that hides the labels too thoroughly (`F-BBB-NARS-2`) are one wall breached +from opposite directions. + +**⊕ Wired, same day (operator: *"CE64 already has it globally and we need to wire it, +period"*).** `lance_graph_contract::assertion_wire` is D-BBB-NARS-2 built: the DTO is the +EXISTING 16-byte edge facet `classid(4, LE) | CausalEdgeV3 payload(12)` — no new byte, no +new bit, no envelope bump (D-ACR-7 F7); `ASSERTION_WIRE_SCHEMA = 1` rides the envelope + +the ABI manifest, never the bytes; `AssertionTopology` / `AssertionBand` are the wire +vocabularies, mirrors of `causal_edge::layout::{CausalTopology, ReasoningBand}` fused +ordinal- and name-exact in `planner::cache::assertion_wire_parity` (both crates are +zero-dep and cannot import each other; the mirror is legal only because the fuse exists); +`AssertionWire::read(declared, provenance)` composes `band_reading::project_*` unchanged +and REFUSES — provenance → lens → presence — never defaults; `AssertionView`'s `==` is +claim identity. The aliasing pair is pinned on both sides: two wires differing ONLY at +byte 12 hi-2 and byte 13 lo-3 read `!=` and stay distinct through `to/from_le_bytes` +(contract), and two CE64 edges equal in S,P,O,(f,c) stay two claims through +`CE64 → V3 → wire → V3 → CE64` bit-exact (planner). No arithmetic anywhere in the module +(`D-BBB-NARS-1`); everything a G11 reader needs is reachable through the ONE module, so the +lgj allowlist grows by one entry — that admission is the next brick, in lance-graph-java. +Plan: `.claude/plans/assertion-wire-v1.md`. + +**⊕ Consumer falsifier, same pass — `F-CONSUMER-ASSERTION-1` (Tarski perfume).** A +consumer that uses the words truth / rung / causal or attaches `(f, c)` while the result +stays decorative has a *perfume of Tarski*. It is real only as a satisfaction relation — +*this typed property about this entity* →(witness + model)→ `(f, c)` — carried whole: +subject (alias, never PII) · predicate · object (concept id) · NARS `(f, c)` · +`CausalTopology` · `ReasoningBand` · witness. **Falsifier:** if topology, assertion band, +proposition identity, or provenance can be removed or changed without altering admission, +interpretation, or replay, it is perfume; likewise if `Relation` and `Causal` both land as +`supports = true`, or an unknown mediator becomes known without a new witness. The consumer +never executes NARS/Tarski arithmetic (`D-BBB-NARS-1`); it carries the typed proposition +and preserves the substrate's distinctions — otherwise `(f, c)` is confidence-flavoured +metadata and LE transports the perfume perfectly. Consumer pre-flight Q6 in +`ogar-consumer-preflight.md`; doctrine in `membrane-tiers.md` § "coordinates of truth". + +**Three corrections landed with it, all in-tree, no code.** (1) `bbb-warden`'s sanctioned +delegation returned a COMPUTED `TruthLiteral` — a bare pair crossing back — which blessed +the exact leak; it now returns an opaque typed handle. (2) `translator.rs` said `TruthU8` +"mirrors `CausalEdge64`'s `confidence_u8` + i4 mantissa" — wrong pairing: the truth is +`frequency_u8` (bits 24-31) + `confidence_u8` (bits 32-39); the i4 mantissa at bits 46-49 +is the `InferenceType` — provenance/type grammar, not half of the truth. (3) Every index +of the amended sentence — shape table, both warden cards, ledger L8, the ruled-vs-coded +section, the D-BBB-NARS-1/-2/-3 rows — updated in the same commit, so the stale-index +defect the council closed is not reopened. + +**What is deliberately NOT built:** no DTO struct, no opcode, no ABI symbol, no G11 +widening, no Java, no conversion. D-BBB-NARS-2/-3 stay Queued, *do not pre-build*. + +**⊘⊘ 2026-09-10, fourth pass — FOUR FABRICATIONS IN THIS ENTRY, operator-named, and the +code they produced is REMOVED.** The entry above converted descriptive operator remarks +into named doctrine artifacts. Corrected, each by the operator's own words: + +1. *"decorative / permissive / defining is not a ladder — is a verdict about how bad the + substrate is"* and *"i said we have these types of code"*. The three words name three + kinds of CODE this tree contains, measured by census. They are NOT a grade a field may + hold, NOT a ladder, and there is no MUL-per-grade column. The census: **defining 0, + permissive 0, decorative 1** (`dismech_counterfactual.rs:251-252`), everything else a + probe, a test or a doc comment — the verdict `ISS-REASONING-BAND-GATES-NOTHING` + already recorded on 2026-08-26. +2. *"medcare needs to sniff it"* was an EXAMPLE of bad hand-rolled implementation. I made + it `F-CONSUMER-ASSERTION-1` plus a Q6 decision tree in `ogar-consumer-preflight.md`. + Both removed; that file is restored to its pre-arc state. +3. *"CE64 already has it globally and we need to wire it, period"* meant USE the existing + dimensions at the sites that lack them. I built a 734-line `contract::assertion_wire` + module with a schema constant, two mirror enums, a view struct, and a 227-line + cross-crate fuse to police the mirror I had just created — first on the WRONG carrier + (the V3 facet, which drops the in-edge S/P/O the aliasing pair requires). Operator: + *"we already have causaledge64"*, *"not a wrapper — just wiring"*. **All of it is + deleted**, with its plan file and its `TYPE_DUPLICATION_MAP` rows. +4. The `d7e8ec5` NARS × Tarski rung claim, already regraded above, came from reading a + stale `SPARE_SHIFT` doc comment and never looking three hundred lines down the same + file at the enum. + +**The ruling that stands (operator, 2026-09-10):** *"we should always enforce CE64 as +defining LE."* `CausalEdge64`'s canonical little-endian 8-byte image IS the defining +truth representation, always — never a wrapper, never a mirror, never a second DTO. The +pair `(S,P,O, f,c, IndirectUnknown, Relation)` vs `(S,P,O, f,c, IndirectKnown, Causal)` +is readable from the shipping carrier today via `topology()` and `reasoning_band()`. So +enforcement is wiring plus the census, not a type. What survives from this arc: the LE +ruling verbatim, the measured bit facts, the aliasing pair, the smallest law, meaning- +crosses-machinery-does-not, and the census as the worklist. + +**⊘ Precision, same day (codereview finding, confirmed against source) — "defining LE" +is a RULING about status, not a claim that CE64 has an LE codec.** `CausalEdge64` is +`#[repr(transparent)] (u64)` with **zero** `to_le_bytes`/`from_le_bytes` — unlike +`CausalEdgeV3`, which HAS the explicit byte-serialization boundary. CE64's "little-endian +image" is its in-register u64 value, trivially LE-equivalent on any host because nothing +ever serializes it — that is exactly the "host-native, zero endian conversions" measurement +already recorded above, not a contradiction of it. The ruling makes this host-native image +the DEFINING one by fiat, not by adding a codec. **And the version gate is real, not +optional:** `topology()`/`reasoning_band()` read bits 59-63 under the `causal-edge-v2-layout` +feature (default ON); under `default-features = false` (the documented v1-compat opt-out) +those same bits carry the v1 temporal field and the accessors are fixed stubs +(`Direct`/`Surface`) — reading them without knowing which layout produced the edge is +exactly the unstated-reader-assumption `F-BBB-NARS-2` forbids. "Readable from the shipping +carrier today" holds only under the default feature; a consumer on the v1-compat opt-out has +no kind to read at these bits at all (`translator.rs`'s own doc comment now says this). + ## 2026-09-07 — E-T1-HAS-TWO-SIBLING-ALGEBRAS-THE-AXIS-IS-SYNTAX-VS-EXECUTION-1 — the membrane is a behavior membrane, not a selection pipeline **Status:** OPERATOR RULING, BINDING (2026-09-07). Ruled after a three-agent audit of the diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index da5bfce3d..8620f0f47 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,60 @@ +## 2026-09-10 — CORRECTION: the `assertion_wire` entry below is stale — the module was DELETED as fabrication + +The `## 2026-09-10 — assertion_wire — the versioned canonical LE truth DTO +(D-BBB-NARS-2) → .claude/plans/assertion-wire-v1.md` entry further down this +file describes `contract::assertion_wire` as built and **"In PR (#1223)."** +It no longer exists. Same day, after that entry landed, operator: *"we +already have causaledge64"*, *"not a wrapper — just wiring"*, *"we should +always enforce CE64 as defining LE."* Commit `2c66c01` deleted the module +(`assertion_wire.rs`, 734 lines), its cross-crate fuse +(`assertion_wire_parity.rs`, 227 lines), and its own plan file +(`assertion-wire-v1.md`) as fabrication — `CausalEdge64`'s existing +`topology()`/`reasoning_band()` accessors ARE the defining LE representation; +nobody asked for a second DTO type. Full record: `EPIPHANIES.md` +"⊘⊘ 2026-09-10, fourth pass — FOUR FABRICATIONS IN THIS ENTRY" (under +`E-LE-IS-THE-UNIVERSAL-DTO-LAYER-TYPED-SYNTAX-MEANS-A-VERSIONED-LE-SCHEMA-1`); +`STATUS_BOARD.md`'s D-BBB-NARS-2 row already carries the matching retraction. +That commit corrected both of those but missed this file and +`LATEST_STATE.md` (corrected separately, same day) — the entry below is +retracted by this one rather than edited in place, per this file's +append-only convention. + +## 2026-09-10 — the NaN CI mode → `.claude/plans/nan-ci-mode-v1.md` + +**Status: PROPOSAL.** `D-NCI-1..5` are unbuilt (`D-NCI-5` splits into its own +PR per the council resolution below). Thesis: a field nobody wrote decodes +as its default value today, so the substrate cannot tell "never stamped" +from "stamped with the default"; the plan makes that distinction visible in +a CI/verbose build without changing a single stored byte in release. + +§8 (5 open design items) and §9 (3 sub-items) were run through a 5+3 +hardening council the same day (run record: `.claude/board/AGENT_LOG.md`, +this date) and now carry council-hardened resolutions — a canary mechanism +split by field saturation (a sidecar for fully-saturated register fields, +raw-nibble reuse for the mantissa), the `#[cfg]`+hot-plug hybrid for the +certification switch, and a labeled third `RetryExhausted` disposition for +the runtime side that does not fold "tried and gave up" into "never +declared." **Every resolution is explicitly awaiting operator confirmation +— the council hardens a proposal, it does not rule.** No Rust was written +landing this; §8/§9's text + this entry + the `AGENT_LOG.md` entry are one +plan-text-and-board-hygiene commit. + +## 2026-09-10 — `assertion_wire` — the versioned canonical LE truth DTO (D-BBB-NARS-2) → `.claude/plans/assertion-wire-v1.md` + +Built in #1223 on the operator's *"CE64 already has it globally and we need to +wire it, period."* Frozen F1–F8: the DTO is the EXISTING 16-byte edge facet +(`classid(4, LE) | CausalEdgeV3 payload(12)`), no new bit, no envelope bump; +schema version rides the envelope + ABI manifest, never the bytes; reading is +fallible and refusing (composes D-ACR-7's `band_reading` unchanged); the two +defining vocabularies (`AssertionTopology`, `AssertionBand`) are mirrors of +`causal_edge::layout` FUSED in the planner; no arithmetic; one G11 fence entry. +Gates G1–G6 pre-registered (the aliasing pair on both sides, refusal, the +4×8 sweep fuse, the unstated lift, width/schema). Non-goals: the Java-side +admission (next brick, other repo), D-BBB-NARS-3, consumer migration. + +Status: **In PR (#1223)** — 9 contract + 4 planner tests green; clippy +`-D warnings` clean on both crates. + ## 2026-09-06 — `NodeGuid::new` repurpose AUDIT (5+3 council, READ-ONLY) → `.claude/plans/nodeguid-new-repurpose-audit-v1.md` SPEC v1 of a council convened to AUDIT — not implement — whether diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index ed0ea4309..92e6b9dcd 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,35 @@ +## 2026-09-10 — CORRECTION: `contract::assertion_wire` (D-BBB-NARS-2) was DELETED as fabrication — the entry below is stale + +The entry immediately below reports a shipped contract-inventory delta for a +module that no longer exists. Same day, after it landed, operator: *"we +already have causaledge64"*, *"not a wrapper — just wiring"*, *"we should +always enforce CE64 as defining LE."* Commit `2c66c01` deleted +`crates/lance-graph-contract/src/assertion_wire.rs` (734 lines — +`AssertionWire`, `AssertionView`, `AssertionTopology`, `AssertionBand`, +`ASSERTION_WIRE_SCHEMA`), `cache::assertion_wire_parity.rs` (227 lines), and +`.claude/plans/assertion-wire-v1.md`, as fabrication — nobody asked for a +second DTO type; `CausalEdge64`'s existing `topology()`/`reasoning_band()` +accessors ARE the defining LE representation. Full record: `EPIPHANIES.md` +"⊘⊘ 2026-09-10, fourth pass — FOUR FABRICATIONS IN THIS ENTRY" (under +`E-LE-IS-THE-UNIVERSAL-DTO-LAYER-TYPED-SYNTAX-MEANS-A-VERSIONED-LE-SCHEMA-1`). +**Current contract inventory correction:** `lance_graph_contract` carries NO +`assertion_wire` module. The rulings the entry below banked (LE is the +universal DTO layer; `CausalTopology`/`ReasoningBand` are defining +coordinates; the aliasing pair; meaning-crosses-machinery-does-not) stand — +only the module they motivated does not. `STATUS_BOARD.md`'s D-BBB-NARS-2 row +already carries the matching retraction ("RESCOPED and the code REMOVED"); +treat D-BBB-NARS-2 as **Queued**, not built. The entry below is retracted by +this one rather than edited in place, per this file's append-only +convention. + +## 2026-09-10 — D-BBB-NARS-2 built: `contract::assertion_wire`, the versioned canonical LE truth DTO (#1223, in PR) + +- **Contract inventory — delta:** new module `lance_graph_contract::assertion_wire` — `AssertionWire([u8; 16])` (`repr(transparent)`, align 1; `from_le_bytes`/`to_le_bytes`/`as_le_bytes`/`from_parts(classid, payload)`; coordinate readers `classid`, `target`, `frequency_u8`, `confidence_u8`, `causal_mask_bits`, `direction_bits`, `inference_mantissa`, `plasticity_bits`, `w_slot`, `topology_raw`, `band_raw`; `read(declared, provenance) -> Result`, `read_truth_raw`), `AssertionView`, `AssertionTopology` (4), `AssertionBand` (8), `ASSERTION_WIRE_SCHEMA = 1`, `ASSERTION_WIRE_BYTES = 16`, the byte-offset consts; re-exports of `band_reading::{BandPresence, BandReadError, BandReading, EdgeProvenance, TruthLens, WitnessKind}`. No new layout, no envelope bump, no arithmetic. +- **Planner:** `cache::assertion_wire_parity` (cfg(test)) — the cross-crate fuse: byte positions and both vocabularies against `causal_edge` over a 4×8 sweep; the aliasing pair end to end; the unstated-lift refusal. 4 tests. +- **Rulings banked (operator, 2026-09-10, all in `E-LE-IS-THE-UNIVERSAL-DTO-LAYER-TYPED-SYNTAX-MEANS-A-VERSIONED-LE-SCHEMA-1`):** LE is the universal DTO layer; `CausalTopology` + `ReasoningBand` are defining, universal coordinates of truth; the defining-field law; the aliasing pair; *meaning crosses, machinery does not*; `F-CONSUMER-ASSERTION-1` (Tarski perfume); *"CE64 already has it globally and we need to wire it, period."* +- **Not resident:** the lance-graph-java G11 admission (`ALLOWED` + lists + manifest schema export), D-BBB-NARS-3, any consumer migration, `TD-SPARE-SHIFT-NAME-IS-STALE-1`. +- Plan: `.claude/plans/assertion-wire-v1.md`. STATUS_BOARD: D-BBB-NARS-2 → **In PR**. + ## 2026-09-07 — `SpogTenants::merged_rows` + D-SPG-5 shipped (F9 migration complete on the consumer side) - **Contract inventory — delta:** `contract::spog_tenants::SpogTenants::merged_rows(&self) -> Vec` — `merge()` in row form (global `seq`, stamp at value slot 0, edges reserved-and-zeroed), the exact sibling of `AlphaTunnel::merged_rows`, so a tenant aufstellung, a tunnel and one overlay are ONE table to any writer. This is the row builder D-SPG-6's sealed batch is built from. Test `merged_rows_is_merge_in_row_form` (two shadows, the two per-shadow zeros must NOT leak). diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 281da6395..24e41e3e6 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -7,10 +7,10 @@ NARS lowers through the `plan_eval` membrane that already exists. | D-id | scope | status | gate / falsifier | |---|---|---|---| -| D-BBB-NARS-1 | NARS truth arithmetic remains substrate-owned. G11/T3 may carry only typed NARS **syntax** and **opaque substrate handles**. NARS execution lowers through the existing bulk plan-evaluation membrane; no Java-side arithmetic and no materialized truth population crosses Panama. `TruthU8` is the canonical SUBSTRATE representation; cross-membrane results are handles (`TruthLaneId(u64)`). Any required G11 expansion SHALL expose syntax/vocabulary only, never an arithmetic implementation surface — one scalpel cut, never the cupboard | **Ruled 2026-09-07** (operator). Doctrine landed; no code. `lgj_score_*` explicitly REJECTED — extend the plan language, not the ABI surface | **F-BBB-NARS-1:** fail if Java can implement, inspect, iterate, or reconstruct NARS truth arithmetic without invoking the substrate, OR if a truth population crosses G11/Panama other than as an opaque handle | -| D-BBB-NARS-2 | The syntax/vocabulary contract the G11 fence would admit, **if one turns out to be needed at all**. ⊘ **Scope corrected pre-merge by the 5+3 council, 2026-09-10** — the original read *"split the POD/vocabulary half of `lance_graph_contract::nars` from its arithmetic half"*, which presumes an arithmetic half that **does not exist**: measured, `crates/lance-graph-contract/src/nars.rs` exports three POD enums (`InferenceType`, `QueryStrategy`, `SemiringChoice`) plus `InferenceType::default_strategy()` (enum→enum), `InferenceType::from_mantissa(i8)` (decode) and one `From` impl — no function computes a truth FROM truths, and the arithmetic (`arm_to_truth_u8`) lives in a different crate. So the first step is an AUDIT, not a split: re-measure, and if the module is still arithmetic-free, the fence may be widenable as-is | Queued — gates on a consumer actually needing it; **do not pre-build** | the admitted module contains no arithmetic (a function that computes a truth from truths); `ALLOWED` grows in all three spellings in ONE commit | -| D-BBB-NARS-4 | **Truth-type LADDER — one canonical per tier, named lifts, no casts.** ⊘ **Reframed pre-merge 2026-09-10** (operator question: are these distinct truths, or one truth at several precisions?); the row first read *"four truth types coexist … Converge them onto the ruled canonical type"* — the count was wrong and the verb was wrong. Measured: **five** NARS `(frequency, confidence)` types, and every one carries the SAME two axes — `lance-graph-contract/src/exploration.rs:89` (f32×2), `lance-graph-planner/src/nars/truth.rs:10` (f32×2, the belief-arena type), `ndarray::hpc::nars::NarsTruth` (f32×2, aliased `Truth` at `lance-graph-planner/src/cache/triple_model.rs:42` — what `nars_engine.rs` executes against), `holograph/src/width_16k/schema.rs:104` (u16×2 packed), `lance-graph-arm-discovery/src/translator.rs:28` (`TruthU8`, u8×2, ruled canonical at T0). Same axes ⇒ these are ONE kind of truth over **three scalar widths** — u8 (register), u16 (storage), f32 (compute; three of the five types share it) — so **five representations, three widths, not five kinds**. ⊘ 2026-09-10, same day (CodeRabbit, Major): this sentence first said *"four precisions"* — a leftover from the four-type count — and the falsifier below first promised a *"lossless-upward lift per tier boundary"* while prescribing `f32 → u16`/`u8` packing, which is downward quantization and lossy; both corrected, losing text kept here. The genuinely distinct truths (grammatical validity, causal-trajectory provenance) are NOT on this ladder and enter it only by a named constructor — the precedent is tesseract-rs `sentence_nars_truth`. **Same axes does NOT prove same semantics:** ndarray clamps confidence to `[0, 0.9999]`; the u16 and u8 mappings have not been read side by side. So step 1 is an AUDIT of the five mappings, never a merge | Queued — discovered by the 5+3 council on #1222, reframed on operator question; **do not pre-build**, it gates on a consumer needing a cross-tier bridge | one NAMED conversion per direction per boundary, each carrying its own honesty grade: **widening** `u8 → f32` / `u16 → f32` is round-trip-exact (the integer is recovered) but the f32 is a rounded rational (`x/255` is not representable), so it is never called lossless; **packing** `f32 → u16` / `f32 → u8` is a named quantizer and lossy by construction; **dequantizing** `u16 → f32` is the named inverse of the pack, not an identity. *Lossless* is claimed for no mapping until the audit proves that specific one. Zero bare `as` casts between any two of the five, **or** the doctrine states why a given tier legitimately holds two. A bare cast, or an unaudited *lossless*, is an automatic fail | -| D-BBB-NARS-3 | `Truth(…)` as a named `plan_eval` operation (Revision / Deduction / Abduction / …), returning `TruthLaneId`, never an array | Queued — gates on D-BBB-NARS-2 | one crossing per behavioral expression regardless of operand count (the bulk-or-lifecycle law, lgj `docs/abi.md` §6); a `[TruthU8]` return is an automatic fail | +| D-BBB-NARS-1 | NARS truth arithmetic remains substrate-owned. G11/T3 may carry only typed NARS **syntax** and **opaque substrate handles**. NARS execution lowers through the existing bulk plan-evaluation membrane; no Java-side arithmetic and no materialized truth population crosses Panama. `TruthU8` is the canonical SUBSTRATE representation; cross-membrane results are handles (`TruthLaneId(u64)`). Any required G11 expansion SHALL expose syntax/vocabulary only, never an arithmetic implementation surface — one scalpel cut, never the cupboard | **Ruled 2026-09-07** (operator). Doctrine landed; no code. `lgj_score_*` explicitly REJECTED — extend the plan language, not the ABI surface. **⊕ Amended 2026-09-10** (operator, *LE is the universal DTO layer*): "typed NARS syntax" SHALL mean a versioned DTO schema with canonical little-endian layout, or a typed handle whose registry binds kind + schema — a bare `(f, c)` pair is a degree, not a typed truth. Second falsifier **F-BBB-NARS-2 (LE)** added; `E-LE-IS-THE-UNIVERSAL-DTO-LAYER-…-1`. ⊕ same day, third pass: the typed truth is the six-coordinate ASSERTION (proposition reference × Pearl projection × NARS valuation × `CausalTopology` × `ReasoningBand` × provenance) — bits 59-63 are DEFINING, not optional metadata; a decoder that drops or reinterprets them has changed the claim and fails F-BBB-NARS-2. **Meaning crosses; machinery does not** (operator, same day): the boundary fails from BOTH sides — exported arithmetic (F-BBB-NARS-1) and an opaque carrier that hides topology/band so thoroughly the consumer must sniff them back (F-BBB-NARS-2) are one wall breached from opposite directions | **F-BBB-NARS-1:** fail if Java can implement, inspect, iterate, or reconstruct NARS truth arithmetic without invoking the substrate, OR if a truth population crosses G11/Panama other than as an opaque handle. **F-BBB-NARS-2 (LE), smallest instance — the aliasing pair** (operator, 2026-09-10): `(S,P,O, f,c, IndirectUnknown, Relation)` and `(S,P,O, f,c, IndirectKnown, Causal)` must never encode, store, replay or decode to the same thing; `Causal` is not `Relation` with more confidence, and flattening either to `(S,P,O,f,c)` is epistemic aliasing | +| D-BBB-NARS-2 | The syntax/vocabulary contract the G11 fence would admit, **if one turns out to be needed at all**. ⊘ **Scope corrected pre-merge by the 5+3 council, 2026-09-10** — the original read *"split the POD/vocabulary half of `lance_graph_contract::nars` from its arithmetic half"*, which presumes an arithmetic half that **does not exist**: measured, `crates/lance-graph-contract/src/nars.rs` exports three POD enums (`InferenceType`, `QueryStrategy`, `SemiringChoice`) plus `InferenceType::default_strategy()` (enum→enum), `InferenceType::from_mantissa(i8)` (decode) and one `From` impl — no function computes a truth FROM truths, and the arithmetic (`arm_to_truth_u8`) lives in a different crate. So the first step is an AUDIT, not a split: re-measure, and if the module is still arithmetic-free, the fence may be widenable as-is | Queued — gates on a consumer actually needing it; **do not pre-build**. **⊘ Superseded 2026-09-10, operator — RESCOPED and the code REMOVED.** The scope is not a DTO to mint: *"we should always enforce CE64 as defining LE"*, *"not a wrapper — just wiring"*, *"CE can speak for itself."* `CausalEdge64`'s canonical little-endian image IS the defining truth representation; bits 59-60 (`CausalTopology`) and 61-63 (`ReasoningBand`) already exist and the aliasing pair is readable today via `topology()` / `reasoning_band()`. So this D-id is the WIRING worklist that the census names: **defining 0, permissive 0, decorative 1** — `dismech_counterfactual.rs:251-252` is the only production read and nothing branches on either value (`ISS-REASONING-BAND-GATES-NOTHING`); and three of the four crates carrying a NARS `(f, c)` pair (`lance-graph-contract`, `holograph`, `lance-graph-arm-discovery`) have no `causal-edge` dependency at all, so the dimensions are structurally absent, not merely unused. ⊘ This cell previously read *"In PR — #1223 … Built: `lance_graph_contract::assertion_wire` — `AssertionWire([u8; 16])` … `ASSERTION_WIRE_SCHEMA` … `AssertionTopology`/`AssertionBand` … fused by `planner::cache::assertion_wire_parity`"*; that module, its fuse, its plan and its duplication-map rows are DELETED as a fabrication (operator: *"we already have causaledge64"*). Losing text kept here, code gone. | the admitted module contains no arithmetic (a function that computes a truth from truths); `ALLOWED` grows in all three spellings in ONE commit | +| D-BBB-NARS-4 | **Truth-type LADDER — one canonical per tier, named lifts, no casts.** ⊘ **Reframed pre-merge 2026-09-10** (operator question: are these distinct truths, or one truth at several precisions?); the row first read *"four truth types coexist … Converge them onto the ruled canonical type"* — the count was wrong and the verb was wrong. Measured: **five** NARS `(frequency, confidence)` types, and every one carries the SAME two axes — `lance-graph-contract/src/exploration.rs:89` (f32×2), `lance-graph-planner/src/nars/truth.rs:10` (f32×2, the belief-arena type), `ndarray::hpc::nars::NarsTruth` (f32×2, aliased `Truth` at `lance-graph-planner/src/cache/triple_model.rs:42` — what `nars_engine.rs` executes against), `holograph/src/width_16k/schema.rs:104` (u16×2 packed), `lance-graph-arm-discovery/src/translator.rs:28` (`TruthU8`, u8×2, ruled canonical at T0). Same axes ⇒ these are ONE kind of truth over **three scalar widths** — u8 (register), u16 (storage), f32 (compute; three of the five types share it) — so **five representations, three widths, not five kinds**. ⊘ 2026-09-10, same day (CodeRabbit, Major): this sentence first said *"four precisions"* — a leftover from the four-type count — and the falsifier below first promised a *"lossless-upward lift per tier boundary"* while prescribing `f32 → u16`/`u8` packing, which is downward quantization and lossy; both corrected, losing text kept here. The genuinely distinct truths (grammatical validity, causal-trajectory provenance) are NOT on this ladder and enter it only by a named constructor — the precedent is tesseract-rs `sentence_nars_truth`. **Same axes does NOT prove same semantics:** ndarray clamps confidence to `[0, 0.9999]`; the u16 and u8 mappings have not been read side by side. So step 1 is an AUDIT of the five mappings, never a merge. ⊕ 2026-09-10, operator: *"i want the 2 dimensions in causaledge to be universal … otherwise its only a scent = prose"* — `CausalTopology` + `ReasoningBand` are dimensions of truth, not `CausalEdge64`-local, so the audit records for EACH of the five types where its topology and band live (carried, or bound by its declared schema); a type that carries `(f, c)` alone and cannot tell `(f,c, IndirectUnknown, Relation)` from `(f,c, IndirectKnown, Causal)` is scent, and converging onto it would converge onto prose | Queued — discovered by the 5+3 council on #1222, reframed on operator question; **do not pre-build**, it gates on a consumer needing a cross-tier bridge | one NAMED conversion per direction per boundary, each carrying its own honesty grade: **widening** `u8 → f32` / `u16 → f32` is round-trip-exact (the integer is recovered) but the f32 is a rounded rational (`x/255` is not representable), so it is never called lossless; **packing** `f32 → u16` / `f32 → u8` is a named quantizer and lossy by construction; **dequantizing** `u16 → f32` is the named inverse of the pack, not an identity. *Lossless* is claimed for no mapping until the audit proves that specific one. Zero bare `as` casts between any two of the five, **or** the doctrine states why a given tier legitimately holds two. A bare cast, or an unaudited *lossless*, is an automatic fail | +| D-BBB-NARS-3 | `Truth(…)` as a named `plan_eval` operation (Revision / Deduction / Abduction / …), returning `TruthLaneId`, never an array | Queued — gates on D-BBB-NARS-2. ⊕ 2026-09-10: `TruthLaneId` is clean under the LE ruling only because its substrate registry binds the lane's truth KIND and schema — that binding is now part of this gate, not an assumption — AND only while the defining labels (topology, band) stay perceptible through the typed syntax beside the handle: a handle that hides them forces the consumer to rediscover them, which is as defective as exporting the arithmetic (operator, 2026-09-10) | one crossing per behavioral expression regardless of operand count (the bulk-or-lifecycle law, lgj `docs/abi.md` §6); a `[TruthU8]` return is an automatic fail | ## spog-alpha-channel-v1 (D-ids minted 2026-09-07 with the spec) diff --git a/.claude/board/SUPERSESSION-INDEX.md b/.claude/board/SUPERSESSION-INDEX.md index 2fdd26b94..439ea916f 100644 --- a/.claude/board/SUPERSESSION-INDEX.md +++ b/.claude/board/SUPERSESSION-INDEX.md @@ -56,7 +56,7 @@ coverage instead. | `persist_cycle` | BLOCKED | — | 11 | 8 | 5 | | `CognitiveMarkers` | REPURPOSE | `Commit` | 1 | 0 | 0 | | `DominoCascade` | REPURPOSE | `Commit` | 7 | 1 | 0 | -| `GateDecision` | REPURPOSE | — | 25 | 27 | 24 | +| `GateDecision` | REPURPOSE | — | 25 | 28 | 25 | | `GateState` | REPURPOSE | — | 14 | 1 | 1 | | `MergeMode` | REPURPOSE | — | 8 | 13 | 12 | | `ResonanceDto` | REPURPOSE | `PerturbationDto` | 2 | 12 | 7 | @@ -64,7 +64,7 @@ coverage instead. | `CollapseGateEmission` | RETIRE | — | 5 | 14 | 12 | | `ThinkingStyle` | RETIRE-toward-contract | — | 52 | 28 | 24 | -## Table 2 — plans naming a ruled symbol without citing the ruling (73) +## Table 2 — plans naming a ruled symbol without citing the ruling (74) Route is **mechanical triage, not a verdict**: `ARCHIVE?` = the plan's own status says it shipped; `RESCOPE` = it targets a symbol marked RETIRE; `READ` = neither signal fires @@ -91,6 +91,7 @@ a licence to act on it. | **READ** | `mul-consumer-build-gate-v1` | `GateDecision` | GATE RUN — 2026-08-27. Discharges D-MCAL-6 a | 3/7 | | **READ** | `mul-consumer-census-v1` | `GateDecision` | MEASUREMENT COMPLETE — 2026-08-27. Measureme | 1/2 | | **READ** | `mul-ewa-trust-propagation-v1` | `GateDecision` | PROPOSED — PLAN/BOARD ONLY. Measure-before-c | 1/3 | +| **READ** | `nan-ci-mode-v1` | `GateDecision` | PROPOSAL — **`D-NCI-1..5` are unbuilt. No Na | 2/8 | | **READ** | `persistence-artifact-backed-commit-v1` | `persist_cycle` | RATIFIED (operator ruling 2026-08-09). Phase | 0/0 | | **READ** | `post-teardown-buildup-survey-v1` | `StepMask` | SURVEY, read-only, plan-only (no code, no te | 5/5 | | **READ** | `r2il-bpe-typed-genetic-recombination-v1` | `GateDecision` | PROPOSAL, §7's three falsifiers now RUN (see | 1/1 | @@ -155,5 +156,5 @@ a licence to act on it. - **ARCHIVE?**: 0 - **RESCOPE**: 55 -- **READ**: 18 +- **READ**: 19 - ruled symbols tracked: 14 diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index b669c4d94..c81423d87 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -1,3 +1,40 @@ +## TD-PLANNER-CLIPPY-RED-ON-BASE-1 (2026-09-10) — OPEN + +**`cargo clippy -p lance-graph-planner --lib --tests -- -D warnings` and +`--all-targets` are red on `main` in files #1223 did not touch** — measured while +gating `cache::assertion_wire_parity` (which is itself clean): three +`clippy::chunks_exact_to_as_chunks` hits under `--tests` (the 1.98 lint #1194 swept at +ten sites; these are new sites in planner test targets), plus two example targets under +`--all-targets` (`examples/probe_r2il_frontier_phase2.rs`: *this operation has no +effect*; `examples/probe_nxg_roll_1.rs:105` `chunks_exact(2)`). Same shape as +`TD-SUPERVISOR-CLIPPY-RED-ON-BASE-1` / `TD-SIGKER-CLIPPY-RED-ON-BASE-1`: the planner +is NOT a gated tier in `style.yml` (only contract, deepnsm, deepnsm-v2, callcenter are), +so CI never sees it. Fix once, then arm a planner clippy step. Not done in #1223: not +this arc's code, and a lint sweep in a doctrine + one-module PR would bury the diff. + +## TD-SPARE-SHIFT-NAME-IS-STALE-1 (2026-09-10) — OPEN, doc-only + +**`crates/causal-edge/src/layout.rs:67-77` still names bits 61-63 `SPARE_SHIFT` and +doc-comments them *"Spare: 3-bit reserved for sprint-12+ … Candidates: Rubicon-commit +marker, Markov-decay quantum, I-NOISE-FLOOR-JIRAK threshold"* — while the same file's +`ReasoningBand` (`:353-373`) has occupied those bits since `bbab3541` (introduced as +`TextureBand`) and `9891cca6` (named), and `edge.rs` writes them (`with_reasoning_band`, +`:1057`; callers `dismech_counterfactual.rs:547` and two probes).** The constant's own +comment half-admits it (*"Same three bits also carry an ADDITIVE … view, +`ReasoningBand`"*) but keeps the word *reserved* and the candidate list, so a reader who +stops at the constant — as #1223's first pass did — concludes the field is free and +proposes a new occupant. Operator, 2026-09-10: *"all bits are assigned, including +61..63"*; `SPARE_SHIFT` is the legacy/raw accessor name, not unclaimed design space. +(`band_reading.rs`'s `BandPresence::Absent` — *"the three bits are spare for this +class"* — is a per-class declaration that no band was stamped, not a statement about +the layout; do not read it the same wrong way.) Fix shape: rename `SPARE_SHIFT` → +`BAND_SHIFT` with the old name kept as a `#[deprecated]` alias +(I-LEGACY-API-FEATURE-GATED), drop the candidate list, say `ReasoningBand` in the +first line. Not done in #1223: that PR is doctrine + doc-comment only, and +`causal-edge` is workspace-excluded so a change there gets no CI +(`TD-CAUSAL-EDGE-IS-EXCLUDED-SO-CI-NEVER-LINTS-IT`). Source: the ⊘ in +`E-LE-IS-THE-UNIVERSAL-DTO-LAYER-TYPED-SYNTAX-MEANS-A-VERSIONED-LE-SCHEMA-1`. + ## TD-VERSIONED-GRAPH-DIFF-LOCKSTEP-AND-NO-REMOVALS-1 (2026-09-05) — OPEN **`VersionedGraph::diff` (`crates/lance-graph/src/graph/versioned.rs`) has two diff --git a/.claude/knowledge/membrane-tiers.md b/.claude/knowledge/membrane-tiers.md index 5b20a7067..d0f2784a5 100644 --- a/.claude/knowledge/membrane-tiers.md +++ b/.claude/knowledge/membrane-tiers.md @@ -148,7 +148,7 @@ What crosses is decided separately, and by shape: | shape | crosses? | as | |---|---|---| -| a truth LITERAL, `TruthLiteral(192, 217)` | **yes** — it is meaning supplied by the caller, syntax, T3's to state | itself | +| a truth LITERAL, `TruthLiteral(192, 217)` | **yes — but never as a bare pair** (⊘ 2026-09-10, § "LE is the universal DTO layer" below): it is meaning the caller supplies, syntax, T3's to state — and its KIND is bound by a versioned DTO schema with canonical little-endian layout, or by an opaque typed handle whose registry binds the same kind and schema | a versioned typed DTO (schema + version + canonical LE byte order), or a typed handle — never `(u8, u8)` on its own | | a truth POPULATION, `[TruthU8; 65536]` | **never** | `TruthLaneId(u64)` — an opaque 8-byte descriptor | This is the same rule `bbb-warden` already enforces for masks (*"a `long[]` of row ids is a @@ -159,6 +159,160 @@ at an 8-byte payload** (VM-confirmed, `valhalla-lab/docs/three-truths.md`), so a the membrane about where the wall is. **Valhalla carries the noun; Panama carries the verb; lance-graph owns the reality.** +### LE is the universal DTO layer — "typed syntax" means a versioned LE schema (operator, 2026-09-10) + +**Frozen meaning.** *Little-endian is the universal DTO layer of the ABI.* This is +stronger than "LE is convenient serialization": LE is the canonical wire grammar that +guarantees every byte and bit position carries the SAME DTO label in Rust, Panama, +Java, storage, replay, and MUL interpretation. The ABI carries the value. The LE DTO +contract fixes the universal meaning of its positions. MUL may then assert what +epistemic KIND the value expresses. The particular `(frequency, confidence)` values +are content. LE adds no evidence and no confidence; it makes the labels and the +truth-kind interpretation universal. Without a versioned canonical LE DTO contract, +an ABI can transport bits but cannot guarantee every reader assigns them the same +epistemic meaning. + +**The gap this closes.** The shape table above first let `TruthLiteral(192, 217)` +cross "as itself" while retiring `TruthU8` as "the wire form." A bare `(f, c)` pair +expresses a DEGREE but not what KIND of truth that degree belongs to. Degree without +kind is not typed syntax. So the ruling is sharpened, not reversed — `TruthU8` stays +the canonical T0 substrate representation, and what crosses is now defined: + +> **Typed epistemic syntax crossing G11/Panama SHALL be bound to a versioned DTO +> schema with canonical little-endian field/bit interpretation. The LE contract is +> the universal ABI grammar that fixes the meaning of every wire position. Bare +> `(frequency, confidence)` fields or host-native layouts are not independently +> typed truth. An opaque handle is clean only when its substrate registry binds the +> same truth kind and schema.** + +For two `u8` fields the operative contract is the canonical ordered byte sequence +`[frequency, confidence]` — individual bytes have no endianness, the ORDER is the +contract. For a packed multi-byte carrier such as `CausalEdge64`, the complete +integer-to-byte mapping must be explicitly little-endian at every crossing. + +> **F-BBB-NARS-2 (LE).** Fail if identical typed wire bytes can acquire different +> DTO labels or epistemic kinds across implementations, host endianness, storage +> and replay; fail if truth kind depends on an unstated reader assumption rather +> than the DTO schema or typed-handle registry. + +> **Evidence is not repetition.** Repetition of an identical canonical wire image is +> propagation of the same assertion, not automatically independent evidence. NARS +> revision still requires independent evidential provenance/stamps. + +**This extends, and does not restate, the LE contract that already exists.** +`.claude/v3/soa_layout/le-contract.md` §3b (operator-locked 2026-07-02) is +two-level: every tenant carries its own facet LE contract, and the SoA envelope +carries the register-file descriptor (`ColumnDescriptor` offsets/widths, +`verify_layout()`, `ENVELOPE_LAYOUT_VERSION = 2` at `soa_envelope.rs:54`). lgj +already declares byte order as ABI shape: `LgjLaneDesc.endianness: u32 // 0 = little` +(`abi.rs:383`), `LGJ_MAGIC` doubles as an endianness probe (`abi.md:87-93`), and +`abi.md:1224` says it outright — *"Java can discover the ABI's SHAPE instead of +declaring it — sizes, alignments, pointer width, byte order. A wire encoding is +exactly such a shape."* The truth column now inherits that grammar; it did not have +it before. + +**Measured 2026-09-10 — what is coded vs what this rules (no code changed):** + +| question | answer | evidence | +|---|---|---| +| Any truth DTO with schema + version + canonical LE encode/decode? | **CODED for the envelope, ABSENT for truth.** No truth type rides the envelope contract | `ENVELOPE_LAYOUT_VERSION` exists; zero `to_le_bytes`/`from_le_bytes` in `translator.rs` or `causal-edge/src/edge.rs` | +| Is `TruthU8` a wire DTO? | **No — substrate value only.** Plain `#[derive(Copy)]` struct, no `repr(C)`, no version, no codec; without `repr(C)` Rust does not even guarantee field order | `translator.rs:34-40` | +| `CausalEdge64` byte order at crossings? | **Host-native.** `#[repr(transparent)] (u64)`; bit positions are register-defined (`FREQ_SHIFT=24`, `CONF_SHIFT=32`, `INFER_SHIFT=46`) and endianness-agnostic in-register, but the 8-byte image at any crossing is whatever the host writes — **0** endian conversions in the file. Its v1/v2 layouts are a compile-time feature, invisible in the bytes: exactly what a versioned schema exists to make visible. **⊘ Corrected same day, twice — the KIND is not absent from this carrier, and it is CODED, not ruled.** Operator, second pass: *"all bits are assigned, including 61..63"* — `layout.rs:94` `_LAYOUT_COVERAGE` const-asserts all 64 bits covered exactly once. Bits 59-60 = `CausalTopology` (`Direct` / `IndirectKnownIntermediates` / `IndirectUnknownIntermediates` / `Unknown`, `layout.rs:239-252` — *"indirect intermediate unknowns knowns"*), an additive view ordinal-identical with the older `TrustTexture` reading of the same bits (`bbab3541`, 2026-08-20, via #1154); bits 61-63 = `ReasoningBand` (`Surface` / `Association` / `Relation` / `Causal` / `Counterfactual` / `Perspective` / `Meta` / `Transcendent`, `layout.rs:353-373`; introduced as `TextureBand` in `bbab3541`, named `ReasoningBand` in `9891cca6`) — the **level of ASSERTION, Tarski permission** (`E-RUNG-BAND-AND-PLASTICITY-ARE-THREE-AXES-NEVER-ONE-LEVEL-FIELD-1`; `entropy-closure-causal-ground-v1` §4), `Relation` → `Causal` = relates-to → *causes* (`DISMECH_PREDICATES` `(0x90, "causes", "dismech:causes")`, `dismech_evidence.rs:511`). Writers `with_topology()` / `with_reasoning_band()` (`edge.rs:1009`, `:1057`), readers `topology()` / `reasoning_band()` (`:952`, `:979`), consumed by the W3 verdict (`dismech_counterfactual.rs:251-252`). `SPARE_SHIFT` is the legacy/raw accessor name, not unclaimed design space (`TD-SPARE-SHIFT-NAME-IS-STALE-1`). The first pass of this row read *"bits 61-63 are the reserved SPARE that the operator has now assigned as the NARS × Tarski rung (RULED 2026-09-10, unwritten in code)"* and called `TrustTexture` *"MUL's reading"* — both wrong. The `(f, c)` bytes have no kind *of their own*; the kind rides beside them in the same carrier, and which LENS a producer wrote is declared per class (`ClassView::band_reading`, `band_reading.rs`), never inferred from the bits | `edge.rs:160-176`, `layout.rs:52-77`, `:94`, `:239-252`, `:353-373`; `band_reading.rs` | +| Can `TruthLiteral`'s kind be inferred from its enclosing typed AST? | **No.** 0 code sites; the doctrine had it crossing as an untyped pair | this file, `bbb-warden.md` | +| Can MUL determine the same kind from the same bytes, host-independent? | **No — MUL never sees bytes.** `SituationInput` is typed `f64`s; `revise_fast(f1: u8, _c1: u8, f2: u8, _c2: u8)` takes bare degrees and ignores confidence. Kind is whatever the caller labelled | `mul.rs:12-30`, `nars_engine.rs:459` | + +**CE64 IS the defining LE — always enforced (operator, 2026-09-10, verbatim: +*"we should always enforce CE64 as defining LE"*).** `CausalEdge64`'s canonical +little-endian 8-byte image is THE defining representation of a truth in this +workspace. Not a wrapper around it, not a mirror of it, not a second DTO carrying +the same coordinates: **the edge itself**, whose 64 bits already hold every +coordinate of an assertion, with `_LAYOUT_COVERAGE` (`layout.rs:94`) asserting all +64 are assigned exactly once. + +| coordinate | bits | +|---|---| +| proposition reference (S, P, O — palette256) | 0-23 | +| NARS valuation (frequency, confidence) | 24-39 | +| Pearl projection (`CausalMask`) | 40-42 | +| direction triad | 43-45 | +| inference mantissa (provenance grammar, not truth) | 46-49 | +| plasticity | 50-52 | +| W-slot (witness corpus root) | 53-58 | +| `CausalTopology` | 59-60 | +| `ReasoningBand` | 61-63 | + +The two dimensions are UNIVERSAL for the same reason (*"i want the 2 dimensions in +causaledge to be universal … otherwise its only a scent = prose"*): every truth +representation on every tier carries or binds them, and a `(f, c)` that cannot tell +the aliasing pair apart is prose, by the same test as property 1 above. + +**The smallest law (operator, verbatim — BINDING):** + +> A field becomes **defining** when changing or omitting it changes the +> proposition, not merely its presentation. Every defining epistemic dimension +> SHALL participate in the versioned canonical LE DTO; a reader lacking its +> declared lens or provenance must **refuse**, never project a plausible default. + +The refusal half is already CODED for the reading contract — `band_reading.rs` +(D-ACR-7): *"a lens mismatch, an absent band, or untrusted provenance must FAIL, +never return a plausible value"*, `EdgeProvenance::Unknown` refuses, +`BandPresence::Absent` refuses. + +**The aliasing pair — the smallest and strongest falsifier (operator, verbatim):** + +```text +(S,P,O, f,c, IndirectUnknown, Relation) "S and O are related; mediation is unknown." +(S,P,O, f,c, IndirectKnown, Causal) "P causally connects S to O; the mediation is known." +``` + +*The `(f,c)` values are identical, but the truths are not. `Causal` is not "Relation +with more confidence." It is a different licensed assertion. Likewise, `IndirectKnown` +is not a cosmetic refinement of `IndirectUnknown`. Therefore the complete truth +identity is `(S,P,O) × (f,c) × topology × assertion-band`. LE must preserve all four +components. Flattening either tuple to the same `(S,P,O,f,c)` is **epistemic +aliasing**: the DTO would transport identical confidence while silently changing what +is claimed.* + +**Tarski is adjacency, not identity** (operator): `ReasoningBand` controls the level +at which a claim may be asserted (`Relation` → `Causal`, the authoritative predicate +being `(0x90, "causes")`, `dismech_evidence.rs:511`) while remaining distinct from +Tarski derivation depth, which is `Belief.rung` / `Candidate.rung`, stored separately. +`E-RUNG-BAND-AND-PLASTICITY-ARE-THREE-AXES-NEVER-ONE-LEVEL-FIELD-1` forbids the fold. + +**Meaning crosses; machinery does not** (operator). The LE contract replaces +*sniffing* with *reading*: the consumer receives "a causal assertion with known +intermediates and this `(f, c)`" and never infers `Causal` from a high confidence, +guesses topology from predicate names, rebuilds Tarski/NARS logic, or inspects +substrate rows for semantic clues. The wall is two-sided: exported arithmetic +(`F-BBB-NARS-1`) and an opaque carrier hiding the labels so thoroughly the consumer +must sniff them back (`F-BBB-NARS-2`) are one boundary breached from opposite +directions. + +**Census 2026-09-10 — the substrate is entirely DECORATIVE on these two dimensions.** +*Decorative / permissive / defining names three kinds of CODE this tree contains, and +the census is the verdict on how bad the substrate is. It is NOT a grade a field may +legitimately hold and NOT a ladder to climb* (operator correction; an earlier cut of +this section presented it as three grades with a MUL column, which was a fabrication +and is removed). Measured across every site: + +| kind of code | count | where | +|---|---|---| +| **defining** — the value is the identity | 0 | nowhere | +| **permissive** — something branches on it | 0 | nothing matches or compares either value outside a test | +| **decorative** — read and carried, nothing depends | 1 | `dismech_counterfactual.rs:251-252` copies both into a verdict struct | +| tests, probes, doc comments | the rest | four probe examples, `v2_layout_tests.rs`, and `recipe_vocab.rs` stating it does not write one | + +`ISS-REASONING-BAND-GATES-NOTHING` (2026-08-26) recorded the same verdict a month +earlier in the same words. And three of the four crates carrying a NARS pair cannot +even name the types: `lance-graph-contract`, `holograph` and +`lance-graph-arm-discovery` have no `causal-edge` dependency; only +`lance-graph-planner` does. So enforcement is not a new type. It is closing that +census: a truth that crosses anything is CE64's LE image, and the sites above are the +worklist. + +**What this does NOT do.** No DTO struct, no opcode, no ABI symbol, no G11 import, +no Java, no conversion, no new carrier of any kind. + ### The G11 widening rule: one scalpel cut, never the cupboard Do **not** import `lance_graph_contract::nars` through the G11 fence merely @@ -190,7 +344,21 @@ hits**, so there is no Java surface to run it against. **The gate that will hold column is `ApiSurfaceTest`'s forbidden-type list plus a G11 allowlist entry, and it is gated on D-BBB-NARS-2/-3** — which are Queued and marked *do not pre-build*. Until then this half of the membrane is enforced by review, and saying otherwise would be the exact -defect this arc keeps finding. +defect this arc keeps finding. **⊕ 2026-09-10, later the same day:** D-BBB-NARS-2 is now +BUILT on the lance-graph side — `contract::assertion_wire` (plan `assertion-wire-v1.md`), +the versioned LE truth DTO with its own refusing reader and the two fused vocabularies — +so the G11 allowlist entry it was gated on is now admissible; the entry itself (lgj +`ALLOWED` + `CLAUDE.md` + `Cargo.toml` + the manifest's schema export) is the +lance-graph-java brick and is still not resident. D-BBB-NARS-3 stays Queued. +**⊘ Still later the same day — retracted.** `assertion_wire` was fabrication: nobody +asked for a new DTO type, and the first cut was built on the wrong carrier (the V3 +facet, which drops the in-edge S/P/O the aliasing pair requires). Operator: *"we +already have causaledge64"*, *"not a wrapper — just wiring"*. The module, its fuse, and +its plan are DELETED (commit `2c66c01`); D-BBB-NARS-2 is **Queued**, not built, exactly +as this paragraph originally said before the retracted addendum. The G11 allowlist +entry is NOT admissible on that gate — it remains gated on the WIRING (using CE64's +existing `topology()`/`reasoning_band()` directly), never on a type that does not exist. +Losing text kept above; code and plan gone. **2. The named epistemic primitives are not at T1.** `revision`/`deduction`/`abduction`/ `induction` are CODED, but only inside `crates/lance-graph-planner/src/cache/nars_engine.rs:194-207` @@ -216,6 +384,22 @@ that alias. `TruthU8` occurs outside its own crate in exactly one file, a test. conversion path bridges them. So "T0 owns every resulting `TruthU8`" is the direction of travel, not the current state — the convergence is tracked as **D-BBB-NARS-4**. +**4. The LE DTO contract is CODED for the envelope and ABSENT for truth.** +`ENVELOPE_LAYOUT_VERSION = 2` + `verify_layout()` exist and are operator-locked; +no truth type — not `TruthU8`, not `CausalEdge64`, not any `NarsTruth` — carries a +version, a `repr(C)` layout, or an LE codec (see § "LE is the universal DTO layer"). +`CausalEdge64`'s byte image is host-native at every crossing today. Ruled 2026-09-10; +defined by D-BBB-NARS-2 when it lands; nothing built here. +**⊘ Superseded, same day, later still (operator, verbatim: *"we should always +enforce CE64 as defining LE"*).** The measurements above stand — no `repr(C)`, no +explicit `to_le_bytes`/`from_le_bytes`, host-native at every crossing — but "ABSENT +for truth… defined by D-BBB-NARS-2 when it lands" is no longer the status. The gap +is closed by RULING, not by a new codec: `CausalEdge64`'s existing host-native +8-byte image IS declared the defining LE representation, always, going forward. +D-BBB-NARS-2 will NOT build a new type to close this — see § "CE64 IS the defining +LE — always enforced" above. What remains open is wiring (using `topology()` / +`reasoning_band()` at the sites that lack them) and the census, never a codec. + ### The ruling and its falsifier > **D-BBB-NARS-1.** NARS truth arithmetic remains substrate-owned. G11/T3 may @@ -307,7 +491,7 @@ reject the old spelling once closed. `[OPEN]` until the gate rejects it. | L5 | `Engine.LaneWindow.setU64` — raw word write | `importRows` (named breach) is the only sanctioned writer | ApiSurfaceTest (internal.ffm already fenced from public) | CLOSED | | L6 | any future `byte[]` / `[u8;12]` rail array in a public signature | a named `Reading` value type OGAR emits per ClassView (Valhalla), read zero-copy | ApiSurfaceTest byte[]-fence (this PR) | CLOSED (forward guard) | | L7 | any future array return not named `materialize*`/`import*` | a named terminal | ApiSurfaceTest array-return naming rule (this PR) | CLOSED (forward guard) | -| L8 | any future truth POPULATION in a public signature — `TruthU8[]`, a truth lane, a collection of them — or any T3 body that computes a truth FROM truths | the `TruthLaneId(u64)` opaque descriptor for the population; a named `Truth(…)` `plan_eval` operation for the arithmetic | **OPEN — review-note only** (`bbb-warden` step 4 + ARITHMETIC-SURFACE). The structural gate (ApiSurfaceTest forbidden-type entry + G11 allowlist) is gated on D-BBB-NARS-2/-3 | OPEN (forward guard, ungated) | +| L8 | any future truth POPULATION in a public signature — `TruthU8[]`, a truth lane, a collection of them — or any T3 body that computes a truth FROM truths — **or any truth crossing as a bare `(u8, u8)` / host-order packed image with no versioned LE DTO schema binding its kind** (⊕ 2026-09-10) | the `TruthLaneId(u64)` opaque descriptor for the population; a named `Truth(…)` `plan_eval` operation for the arithmetic | **OPEN — review-note only** (`bbb-warden` step 4 + ARITHMETIC-SURFACE). The structural gate (ApiSurfaceTest forbidden-type entry + G11 allowlist) is gated on D-BBB-NARS-2/-3 | OPEN (forward guard, ungated) | Provenance: the two fixes that produced this doctrine — the 7.5→1.1 ms `lgj_hop` (T1 doing T0's job badly: gathered a contiguous lane; fixed inside diff --git a/.claude/plans/nan-ci-mode-v1.md b/.claude/plans/nan-ci-mode-v1.md new file mode 100644 index 000000000..c47fd13c1 --- /dev/null +++ b/.claude/plans/nan-ci-mode-v1.md @@ -0,0 +1,996 @@ +# PLAN v1 — the NaN CI mode: making dormant ABI absence visible + +> **Status:** PROPOSAL — **`D-NCI-1..5` are unbuilt. No NaN mode exists.** No +> board rows, no minted D-ids; the `D-NCI-*` labels below are **this +> document's own** proposed deliverable names, not entries on +> `STATUS_BOARD.md`. §3.6's `#[cfg]`-vs-hot-plug choice changes a frozen +> decision (N1), so it is a ruling, not an implementation detail. +> +> **§8 and §9 were run through a 5+3 hardening council (2026-09-10) and now +> carry COUNCIL-HARDENED resolutions — still awaiting operator confirmation, +> never a substitute for it.** 5 savants + 3 brutal reviewers, one full +> streamline→attack→fix→ratify cycle, 0 BLOCKs, several real fixes applied +> (a defect in the council's own first-draft R7 was caught and repaired, not +> argued away). See §8/§9 for the resolutions and +> `.claude/board/AGENT_LOG.md` for the run record. No Rust was written; this +> is a plan-text + board-hygiene commit only. +> +> **One PREREQUISITE is pushed to this branch +> (`claude/lance-graph-1218-plans-z8hzqr`) — it is NOT on `main`, and it is +> not part of the mode.** `6e5e674` fixed `NarsEngine::revise_fast`, which was +> indexing `tables.deduction` — the wrong NARS rule, not merely dropping its +> confidence arguments. It now delegates to `NarsTables::revise`; +> `deduce_fast` names the other rule explicitly; `NarsEngine::with_c_levels` +> lets a caller buy a real confidence resolution. Three disable-verified +> tests (the can-fire / can-stay-silent pair plus the rule pin). It has +> **zero callers**, so it changed no production behaviour — it made a dead +> function correct and named the limitation the live threshold code had +> already run into. It matters here only as §4.5's confidence prerequisite, +> and **only** for the NARS revision path — `contract::revision` has no +> numerics at all (see §4.5's correction block). +> +> **§2.2's writer census was corrected after external review.** The original +> table named only two builders (`with_topology` / `with_reasoning_band`) and +> reported 0 non-test writers of bits 59-63 — true as far as it goes, but an +> undercount: `edge_v3::rehydrate` (`edge_v3.rs:263-291`) also writes bits +> 53-63, via `set_w_slot` / `set_truth` / `set_spare`, and it is a real, +> non-test-gated `pub fn`, not scaffolding. Checked both ways: `rehydrate` +> PRESERVES a value already carried in the V3 payload, it does not originate +> one; and every call site of `rehydrate` in this tree today sits inside a +> `#[cfg(test)] mod tests`. So the dormancy thesis is unchanged — the reason +> is one layer deeper than the original census stated. Full table: §2.2. +> +> **CI-verified at `7a5790e`:** 10/10 green — `format`, `clippy`, `test`, +> `test-with-coverage`, `member-tests`, `linux-build`, `regenerate-and-diff`, +> `citation-decay`, `added-plans-have-dids`, `no-shrink`. Cited because a plan +> that names a landed prerequisite owes the sha the claim was checked at. +> +> **Operator framing (2026-09-10, verbatim in substance):** the biggest debt is +> a large fraction of the ABI reading as *absent* — dormant and invisible. +> Run absence as a **debug/verbose mode during CI**, brutal and loud; certify +> per wire against the LE contract; the entry tax is paid once at the boundary +> and the wire stays cheap. +> +> **Thesis in one line:** a field nobody wrote is `0x00`, `0x00` currently +> decodes as a legitimate value, and therefore **the substrate cannot tell +> "never stamped" from "stamped with the default"** — anywhere. This plan makes +> that distinction visible in CI **without changing a single stored byte**. + +## §1 FROZEN DECISIONS (cite-or-VIOLATES; not re-opened on taste) + +| # | Frozen | Source | +|---|---|---| +| N1 | **Storage never changes.** Release builds are byte-identical to today, in what they store AND in what they decode. Only a CI/verbose build differs, and only in what it OBSERVES. ⊕ The hot-plug variant (§3.6) would relax the *decode* half — release could be flipped per activation. That variant is NOT chosen here; if it is, N1 is restated, not quietly broken. | operator, 2026-09-10 | +| N2 | **OBSERVE and FAIL, never branch-and-continue.** The mode may count, report, and abort. It may never take a different code path and proceed. The moment it changes control flow it becomes the pattern `I-LEGACY-API-FEATURE-GATED` forbids. | this plan §3.2 | +| N3 | **No new CE64 bit, no `ENVELOPE_LAYOUT_VERSION` bump, no new address type.** Inherited verbatim from `D-ACR-7` F7. | `dacr7-band-reading-contract-v1.md` §1 F7 | +| N4 | **A guard needs BOTH a can-it-fire and a can-it-STAY-SILENT test on non-trivial input.** | `CLAUDE.md` falsifiability rule | +| N5 | **A detector that fires on most reads is a census, not a gate.** Enforcement is an allowlist that SHRINKS, never a global assert that would be red forever and therefore ignored. Corollary of N4 applied to the instrument itself. | this plan §4.2 | +| N6 | **The mode is not switched off after certification.** It costs nothing in release either way; "off" buys only the loss of the ratchet. | this plan §7.3 | +| N7 | **Absence-detection is not correctness.** A wire can be fully wired, fully non-absent, and still carry the wrong value. This plan measures ONE axis and says so. | this plan §5 | + +## §2 INPUT INVENTORY (measured 2026-09-10; file:line where verified) + +### 2.1 The primary constructor cannot express the tail + +`CausalEdge64::pack` under the default `causal-edge-v2-layout` writes bits +0..52 and stops: + +- `crates/causal-edge/src/edge.rs:225-234` — the v2 arm writes S/P/O, freq, + conf, causal mask, direction, mantissa, plasticity, then + `// v2: temporal is IGNORED. Bits 52-63 are reclaimed ... silently drop it.` + followed by `let _ = temporal;`. +- `pack_v2` (`edge.rs:835-844`) takes no W-slot, no truth, no band either. + +**Consequence:** every edge built by either constructor is born with +`w_slot = 0`, `topology = 0`, `band = 0`. Those are not "unset" — they decode +as the *legitimate* values `Direct` / `Crystalline` / `Surface`. The named +builders that write bits 59-63 as fresh input — `with_topology()` +(`edge.rs:1009`) and `with_reasoning_band()` (`edge.rs:1057`) — have 0 +callers outside `v2_layout_tests.rs`. A second writer exists too, +`edge_v3::rehydrate`, and it PRESERVES rather than originates (§2.2 has the +corrected census). `layout.rs:70-72` states that nothing DERIVES the band +automatically — that claim is unaffected either way. + +### 2.2 The census: writers, readers, contract callers + +> **Corrected after external review.** The first version of this table +> counted only two named builders and reported 0 non-test writers for bits +> 59-63; that undercounted a second, real writer function. Two questions +> were bundled under one column header and need to stay apart: (1) does a +> non-test-gated `pub fn` exist that writes the bits at all, and (2) is that +> function ever REACHED from a path outside `#[cfg(test)]`? The table below +> answers both, separately, for every dormant field this plan tracks — +> W-slot (53-58), truth/topology (59-60), spare/band (61-63). + +| layer | (1) writer exists, non-test-gated? | (2) reached outside `#[cfg(test)]`? | +|---|---|---| +| bits 53-58 (W-slot) via `with_w_slot` / `with_routing` (`edge.rs:988,1069`) | yes, but **0** callers outside `v2_layout_tests.rs` | n/a — never called | +| bits 59-60 / 61-63 via `with_topology` / `with_reasoning_band` (`edge.rs:1009,1057`) | yes, but **0** callers outside `dismech_counterfactual.rs`'s own `#[cfg(test)] mod tests` | n/a — never called | +| bits 53-63 (all three) via `edge_v3::rehydrate`'s `set_w_slot` / `set_truth` / `set_spare` (`edge_v3.rs:279,288-289`) | **yes** — a real, non-test-gated function; but it PRESERVES a payload-resident value, it does not originate one | **no** — every call site (`cognitive-shader-driver/src/edge_v3_compare.rs:68-69`, `lance-graph-planner/src/cache/stage26_v3_parity.rs:294-295`) sits inside that file's own `#[cfg(test)] mod tests` | +| readers of bits 59-63 | — | **1**, reachable — `lance-graph-planner/src/dismech_counterfactual.rs:251-252`, via the raw accessors, not the contract projection | +| readers of bits 53-58 (W-slot) | — | **1 live filter, unreachable today** — `cognitive-shader-driver/src/mailbox_soa.rs:355`'s `apply_edges` drops any delivery whose `edge.w_slot() != self.w_slot`; `apply_edges` itself has zero callers outside its own `#[cfg(test)] mod tests` (`mailbox_soa.rs:1089,1120`) | +| callers of the `band_reading` surface (`BandReading`, `EdgeProvenance`, `project_truth`, `project_band`, `admits`, `admits_band`, `BandDeclarations`) | — | **0, anywhere in the tree** | +| classes overriding `ClassView::band_reading` | — | **0** — one impl, the default returning `ZERO_FALLBACK` (`class_view.rs:1231-1237`) | + +`BandReading::ZERO_FALLBACK` is `{Trust, Absent}` (`band_reading.rs:230-234`), +so `project_band` would refuse `BandAbsent` for **every class in the tree +today**. The read contract is armed and fail-closed; nothing has ever opted in. + +Note the one reader reads a field no production path writes: on any chain whose +edges came through `pack`, it reports the constant `(Direct, Surface)`. + +**The `apply_edges` row is the thesis made concrete, not abstract.** A real +delivery filter compares `edge.w_slot()` against a real mailbox's own +`w_slot` and silently drops on mismatch — the module's own doc comment says +so directly (`mailbox_soa.rs:341-355`: "Mismatched edges are silently +dropped in `apply_edges`"). Because no production path originates a +non-zero W (the row above), and because `apply_edges` is not yet called +from outside its own tests, the comparison is latent rather than live +today — but it is the exact failure this plan exists to make visible: a +`CausalEdge64::ZERO`-derived baton's `w_slot() == 0` is indistinguishable +from a real mailbox that also happens to be `w_slot == 0`, the moment +`apply_edges` gains a live caller. + +### 2.3 The mantissa already aliases absence to a legitimate value + +- `InferenceType::to_mantissa` (`edge.rs:65-82`) emits only + `{1, 2, -1, 4, 5, 6, -6, 7}` — **never `-8`**. +- `InferenceType::from_mantissa` (`edge.rs:90-94`) does + `let mag = m.unsigned_abs() & 0x7;` then `0 => Self::Deduction` with the + comment `// 0 = Identity/neutral -> treat as Deduction`. + +So on that field `0` means **three** things at once: "Identity/neutral", +"never stamped", and "the `-8` bucket". `-8` is unwritable by construction and +aliased on read. + +### 2.4 Carriers that erase the tail + +- `SpoHead` (`lance-graph-planner/src/cache/nars_engine.rs:28-38`) mirrors + CE64 at 8 bytes but carries `temporal: u8`, which is **dead under v2**: + `from_causal_edge` hardcodes `temporal: 0` (`:513`, with a doc explaining the + v2 sentinel) and `to_causal_edge` (`:469`) feeds it to `pack`'s ignored + argument. It never carries topology or band. It is a **v1-shaped mirror of a + v2 carrier** — it did not follow the reclaim its own model made. +- `CausalEdgeV3::from_v1_tail_unstated` (`crates/causal-edge/src/edge_v3.rs`) + zeroes bytes 8 and 9, producing a register indistinguishable from one stamped + `Direct` / `Surface`. This is the transitive half of the v1 provenance trap + `band_reading`'s module doc already names (council BLOCK 1). + +### 2.5 The convention is already "zero means absent" — by hand + +- `causal_witness::elected` (`causal_witness.rs:428-437`): *"Zero maps to + `None` because `0` is the register's own zero-fallback sentinel for + 'unbound' ... it is never 'offset zero, meaning self'."* A legitimate value + was **forfeited** because there was no sentinel. +- `probe_witness_presence_2bit.rs`'s `presence_2bit` puts `v < 0` in *before*, + `v > 0` in *after*, and `v == 0` in **neither**. +- `CausalEdgeV3::anaphora()` returns `Option` with `0 = none`. + +Three shipped sites already treat zero as absence, each by local convention, +none by contract. + +### 2.6 The instrument family already exists + +Four probes in this tree are the right idiom, so this is a fifth in a shipped +family, not new architecture: + +`probe_witness_presence_2bit.rs`, `probe_mask_algebra_invariance.rs`, +`probe_copula_group_mask.rs`, `sigma_probe_masked_traverse.rs`. + +The last states the discipline verbatim: *"changes no library code: it calls +`mxm` directly for the unmasked baseline and `masked_traverse` for the masked +result, and reports per-call rows."* + +### 2.7 The i4 SIMD surface (for §3.4 cost) + +- `I8x16::from_i4_packed_u64` (ndarray `src/simd_int_ops.rs`, W1a primitive) + unpacks 16 packed nibbles into 16 **sign-extended i8 lanes**; its own tests + pin `0x8 -> -8` and `0x7 -> +7`. +- `masked_sum_i32` (`:1117`), `masked_strided_group_sum` (`:1208`), + `mask_ternlog_assign` (`:1015`) already take mask words. +- `cmp_gt` / `cmpgt_mask` / `movemask` / `mask_blend` are on the polyfill + SURFACE (`src/simd.rs`), which is a re-export catalog over per-arch backends + (`simd_avx512` / `simd_avx2` / `simd_neon` / `simd_wasm` / `simd_scalar`). +- `src/simd_scalar.rs` is a **backend of that same surface**, not an alternative + path — its own module doc: *"Mirrors the API of `simd_avx512`, `simd_avx2`, + and `simd_neon::aarch64_simd` so consumer code reading + `use crate::simd::F32x16` compiles and runs uniformly across all supported + targets."* Dispatch is **compile-time** (`#[cfg]`), so the W1a tests run + *"against exactly one backend per build"*. +- `nibble_above_threshold` (ndarray `src/nibble.rs:227`) is an AVX2 compare + over packed nibbles — but **unsigned** (Minecraft light levels), returning a + materialised `Vec`. + + +## §3 THE DESIGN + +### 3.1 What "NaN" means here + +**A decode-time verdict, not a stored bit pattern.** In a CI/verbose build, a +read of a field that was never written returns/reports `Absent` instead of the +value the bits happen to spell. In a release build the identical read returns +the identical value it returns today. + +This is `debug_assert!` generalised to the decode boundary. The precedent is +shipped: `BandReading::project_truth` already carries +`debug_assert!((truth_raw as usize) < TRUTH_STATES, ...)`, documented as *"the +G7' compile-time/precondition pin (F9-exempt, stated)"*. + +### 3.2 The iron rule that keeps it legal (N2) + +`I-LEGACY-API-FEATURE-GATED` forbids *"the same function name silently +producing different semantics under different feature flags"* — and a decode +mode looks exactly like that. The distinction that makes it legitimate: + +> Release returns a value. CI returns **"this was never written"** and stops. +> CI is strictly MORE informative, never DIFFERENTLY informative. + +Operationally: the mode may `count`, `log`, `collect`, and `panic`/fail. It may +not `if absent { ... } else { ... }` and continue. A reviewer checking this +plan's output checks exactly that one property. + +### 3.3 Two designs, different reach — this plan authorises only the first + +**Design 1 — POISON-FILL (no encoding change). THIS PLAN.** +In CI builds, never-written fields are marked absent instead of decoding as +zero. **The mechanism splits in two, by field shape — a 5+3 council finding +(2026-09-10, §8/§9 resolution below), not stated when this section was first +drafted:** + +- **Saturated fields** (`TrustTexture`/`CausalTopology` 2-bit, + `ReasoningBand` 3-bit, W-slot 6-bit — every bit pattern in each field's own + width already names a legitimate variant or slot, zero spare codes) have no + VALUE available to poison-fill with. These are marked via a **CI-build-only, + out-of-band sidecar** — a `#[cfg(feature = "nan-ci-mode")]`-gated tracker + populated by an OUTER constructor/builder that calls the existing, + UNMODIFIED setters and separately records touch state. The setters + themselves (`with_topology` / `with_reasoning_band` / `with_w_slot`) never + change body or behavior in any build. +- **The mantissa** (partial exception): `to_mantissa` never emits 8 of the 16 + possible raw nibbles (`-8` among them) — a real, disclosed reuse of + already-unused value-space within the existing field width, checked before + `from_mantissa`'s masking, the same point `BandReading::project_truth`'s + `debug_assert!` already occupies. +- **Every other byte-range field** (frequency/confidence, S/P/O): a literal + in-value canary fill applies as originally described below — these are read + as raw bytes, never decoded through an exhaustive enum match, so a chosen + canary risks only a documented, avoidable VALUE collision, not the + saturated fields' guaranteed STRUCTURAL impossibility. + +**⊘ "Avoidable" is not yet "avoided" (codereview finding, confirmed valid — +open, needs an implementation-time decision before `D-NCI-1`).** `pack`/ +`pack_v2` accept unrestricted `u8` for S, P, O, frequency and confidence, so a +literal canary is, today, just a `u8` value picked without a stated exclusion +rule — a legitimate producer emitting that exact byte would be misread as +absent. This plan does not yet name the sentinel(s) or a producer-side +invariant that rules them out (§6 gate G7 below makes this a pre-registered +requirement rather than an implementation afterthought); the choice itself +(which byte, and whether it is a true exclusion or a measured-and-accepted +low-probability collision) is `D-NCI-1`'s to make, with a collision test as +its own evidence, not this plan's to pre-decide in the abstract. + +`pack()` fills the plain byte-range fields with the canary, likewise +`CausalEdge64::ZERO`, `Default::default()`, `from_v1_tail_unstated`, and the +equivalent SoA/tenant initialisers; the three saturated fields and the +mantissa use their own mechanisms above. Anything still reading absent was +**never stamped by this producer**. Classic poisoned-memory technique, +generalised to fields that have no free value to poison with. + +- Reach: producer-side gaps — which is what an *ABI* debt is. +- Works on **every** field — the plain byte-range fields directly, the + saturated fields via the sidecar, the mantissa via its disclosed reuse. +- Zero storage change, zero encoding change, zero release cost — the sidecar + is a CI-only type that never exists in a release build. +- Cannot answer "was this ever stamped by anyone, ever" for a **persisted** row: + a stored zero and a never-written zero are the same byte on disk, and the + sidecar does not survive a round-trip to storage either. + +**⊘ Boundary named, not yet enforced (codereview finding, confirmed valid — +open, needs an implementation-time decision before `D-NCI-1`).** The canary +write itself is an in-memory mutation of the plain byte-range fields at +construction time, in a CI build — it happens BEFORE the read-side observer +runs, and N1's own wording ("Release builds are byte-identical... Only a +CI/verbose build differs, and only in what it OBSERVES") does not on its face +authorize a CI-build WRITE-side change; it was written with the read-side +compare in mind. Concretely: if a CI-built binary's canary-poisoned object is +ever serialized — a saved test fixture, a CI-produced Lance snapshot compared +across runs, anything that outlives the process that poisoned it — the canary +bytes leak into what is supposed to be release-shaped data. **This plan does +not yet resolve that boundary; `D-NCI-1` owes one of:** (a) restrict poisoned +objects to construction-and-immediate-read within one CI process, with a +debug assertion refusing any serialization call on a still-poisoned object +before that boundary is crossed, or (b) treat N1 as scoped to release-vs-CI +*decode* only (as its own text literally says) and accept that a CI build's +in-memory representation may differ, so long as nothing CI produces is ever +consumed outside that same CI run. Either resolution is compatible with N6 +("not switched off after certification") and N2 (observe-and-fail only); the +plan currently asserts neither explicitly, and should before `D-NCI-1` lands. + +**Design 2 — SWAPPED ENCODING. NAMED, NOT AUTHORISED HERE.** +Reassign `0b0000 -> NaN` and `0b1000 -> 0` on signed-i4 fields, making the +default byte the absence sentinel in storage, permanently. Real value set +becomes `{-7..-1, 0, +1..+7}` — 15 values, symmetric, median exactly 0. + +- Reach: persisted corpora, not just freshly-constructed registers. +- Costs: reinterprets every existing byte (same accessor, different value — + `I-LEGACY-API-FEATURE-GATED` at corpus scale); legitimate legacy zeros become + absent; two ndarray W1a tests re-pin; NaN arithmetic must be defined. +- **Deliberately out of scope.** Reach for it only when certifying a stored + corpus becomes the live question, and give it its own plan and its own + version gate. + +### 3.4 Cost, honestly split + +**Release: zero.** The mode is `#[cfg]`-ed out. No lane predicate, no branch, +no extra op in any kernel. The whole SIMD question is priced at zero because +nothing runs. + +**CI, Design 1:** a canary fill in the affected constructors and a compare at +the affected reads. On the i4 lanes this lands where the tree already unpacks +to i8 (`from_i4_packed_u64`), so a canary compare is an ordinary i8 compare and +`masked_sum_i32` / `masked_strided_group_sum` already accept the resulting +mask — **no new reduction kernel**. At most one new surface function would be +an unpack-plus-presence returning `(I8x16, mask)`, in the module that already +owns the unpack. Not required for Design 1's first wave. + +**No scalar arm, and no scalar cross-check** (operator, 2026-09-10). The +polyfill surface is transparent in the Valhalla/Panama sense — Panama's vector +surface lowers to the best available ISA with a guaranteed fallback, Valhalla's +value types flatten a wrapper onto a register, and `ndarray::simd` does both, +at **compile time** rather than by JIT. A consumer writes `use ndarray::simd::*` +once; ndarray fills that surface with SIMD per target. Consequences for this +plan, stated so a later session does not re-derive them: + +- **Nothing here authors a scalar path.** `simd_scalar` already mirrors the API + (§2.7); a hand-written scalar arm beside it would be a second implementation + of a backend that exists. +- **Nothing here authors a scalar cross-check either.** Dispatch is + compile-time and one backend runs per build, so the scalar backend *is* the + cross-check when CI builds a non-x86 target. A cross-check written into this + plan would test the polyfill, which is not this plan's subject. +- **"Per backend" is not a unit of cost for a caller.** A new surface function + is authored once on the surface; where its arch implementations live is + ndarray's internal structure, and no consumer of the polyfill ever sees it. + Earlier drafts of this section priced backends as a plan cost — that was + wrong, and it is corrected here rather than deleted. + +### 3.5 The collapse taxonomy — the product, not a by-product + +The mode's output is not a boolean. Each finding lands in one of five classes, +and each class has a **different fix**. All five are already instanced in the +tree: + +| mode | measured instance | fix | +|---|---|---| +| **Silent-zero** — absent becomes 0, nobody says so | bits 59-63 through `pack` (`edge.rs:225-234`) | sentinel or declaration | +| **Silent-alias** — absent acquires a *different legitimate identity* | `from_mantissa(-8)` -> `& 0x7` -> 0 -> `Deduction`, which `to_mantissa` re-emits as `+1` | close the alias (free here: `-8` is unwritable) | +| **False-assert** — absent becomes a positive claim | `quorum::AxisProjection::nars_frequency` maps `position = 0` to `8/15 = 0.5333`, and `nars_frequency_range` (`quorum.rs:357-365`) *asserts* `mid > 0.5` | **arithmetic**, not a sentinel — see §5.2 | +| **Declared-collapse** — collapses to 0 and the contract says so | `causal_witness::elected` -> `None`; `anaphora()` -> `Option` | acceptable — or reclaim the forfeited value | +| **Refusal** — never collapses; the read errors | `BandReading::project_band` -> `BandAbsent` | **the target state** | + +Silent-alias is the worst of the four defect classes: the value does not +vanish, it **becomes something else**, so the downstream reader gets a +confident wrong answer rather than an empty one. + +### 3.6 ALTERNATIVE — the switch as a hot-plug property (operator, 2026-09-10) + +> *"you can put even NaN switch into the contract via hotplug.rs — which would +> be a little brutal."* Recorded as a live alternative to the `#[cfg]` design +> above. **Not chosen here**; choosing it changes N1 and §3.4, so it is a +> ruling, not an implementation detail. + +**Why it fits, better than a new surface would.** `hotplug::Activation` +**already carries the reading** — `read_modes`, ruled `D-BLOCKS-HOTPLUG-1` +(operator, 2026-09-07): *"how a row addressed under each hot-plugged concept is +READ: which tail the key carries, which value tenants materialise, how the edge +block is carved."* "What does a field nobody wrote decode to" is a **reading** +decision, so it belongs in the struct that already answers that question. + +Four properties come free: + +1. **Per consumer, per classid — which is §4.1's certification unit exactly.** + A consumer that has certified its wires activates without the mode; one that + has not gets it on. §4.2's allowlist stops being a separate file and becomes + a property of the activation. +2. **Fail-closed by construction.** `Activation` deliberately has **no + `Default`** — *"an activation is something an authority RESOLVED; there is + no meaningful empty one."* So there is no accidental green activation + carrying no NaN policy. +3. **Drift machinery already exists.** `ActivationDrift` / + `verify_against_mirror` / `mirror_disagreement` — a policy mismatch between + consumer and authority bangs once, on the path that already bangs. +4. **Zero-dep forces the right vocabulary.** The contract cannot see + `causal-edge`, so the switch must be expressed in raw/contract terms — + the same discipline `band_reading` gets by taking raw ordinals. + +**What it costs, stated plainly.** + +- **N1's decode half.** The mode becomes a runtime property, so a *release* + binary can be flipped. That is strictly more powerful — a live deploy becomes + certifiable, not only CI — and it is why the operator calls it brutal. But + §3.4's "Release: zero" stops being unconditional and must be re-priced. +- **The re-pricing is smaller than it looks.** An activation is resolved + ONCE, at plug time (`Activation::read_mode_for`), so the policy does not have + to be read per decode: the consumer already holds a resolved `ReadMode` and + branches on a value it has. That is the dispatch cost the tree already pays, + not a new per-read branch — but this is a *reasoned expectation, not a + measurement*, and it needs one before N1 is relaxed on its strength. +- **N2 is unchanged and non-negotiable.** Runtime or not, the mode may observe + and fail; the moment it takes a different branch and continues, it is the + defect this plan exists to find. + +**The hybrid, if the trade is unwelcome.** Hot-plug carries the POLICY (which +classids are certified, what absence means for them); `#[cfg]` carries the +ENFORCEMENT (whether a violation aborts). Release resolves the policy once at +activation and pays nothing further; CI adds the abort. This keeps N1 intact +and still lands the certification unit where the reading already lives. + +## §4 THE CAMPAIGN + +### 4.1 The certification unit is a WIRE, not a crate + +A "wire" is one `(producer, field, consumer)` path with a declared LE reading. +It maps onto surfaces that already exist: + +- `BandReading` per `(classid, rail)` — the declaration +- `ColumnDescriptor` / `SoaEnvelope::verify_layout` — the byte range +- `EdgeProvenance` — the epoch the raw ordinal was written under + +A wire is **certified** when: a producer stamps it, a class declares it, a +consumer projects it through the contract (not a raw accessor), **that path has +been observed to execute at least once** (a positive read count, not merely the +absence of canary reads), and the CI mode reports zero canary reads across those +observed reads. Progress is then a countable fraction, not a feeling. + +**⊘ Gap closed (codereview finding, confirmed valid):** "zero canary reads" alone +is necessary but not sufficient — a dead or never-exercised consumer path ALSO +reports zero canary reads, for the same reason a light switch nobody has flipped +reports no failures. Certifying that would be certifying silence, not +correctness (the same `can-fire`/`can-stay-silent` pairing N4 already requires +of every guard in this plan, applied here to the certification criterion +itself). The fix is the added clause above: `D-NCI-3`'s allowlist-seeding census +and `D-NCI-4`'s per-wire certification both need a read-count, not just a +canary-count, before marking a wire green. + +### 4.2 Enforcement is an allowlist that SHRINKS (N5) + +Day one, the mode will fire on a large fraction of reads. A gate that is red +everywhere carries exactly as much information as one that never fires, and it +will be routed around within a week. + +So the enforcement shape is: + +1. **Wave 0 — census only.** The mode runs, reports, and **does not fail**. Its + output is a table: wire, field, canary-read count, collapse class. +2. **The allowlist is seeded from that census** — every currently-absent wire is + listed as a known exception, with the class from §3.5. +3. **CI fails on any read NOT in the allowlist.** New unwired sites are red + immediately; existing ones are documented debt. +4. **Each certification removes an entry.** The allowlist only ever shrinks; + growing it requires the same review as any other debt admission. + +> **Council-hardened, awaiting operator confirmation (2026-09-10) — this +> mechanics description assumes §3.6's `#[cfg]` design, not the hybrid.** +> If §8 item 3a's hybrid is confirmed, the allowlist described above STOPS +> being a bare shrinking file and BECOMES a property of the `Activation` +> resolved once at plug-time (per §3.6's own already-drafted consequence: +> "§4.2's allowlist stops being a separate file and becomes a property of +> the activation"). Steps 1-4 above would then read as: Wave 0 populates +> per-classid certification state on `Activation` rather than a +> free-standing table; step 3's enforcement is the `#[cfg]`-gated abort +> half of the hybrid, resolved against that state once per activation, not +> re-read per decode. Not rewritten as fact here because the hybrid is not +> yet operator-confirmed — see §8 item 3a. + +### 4.3 The first deliverable is the real number + +The first Wave-0 run **is** the debt measurement, per field, per wire, free — +a COUNT. §4.5 turns it into a RANKING, which is the more useful object. +The operator's working figure for the ABI is large; this plan does not restate +it as measured, because it has not been measured here. Everything in §2 points +the same direction — no production PATH ever writes bits 53-63 (the one real +writer, `edge_v3::rehydrate`, is itself reachable only from test harnesses, +per §2.2's corrected census), `pack` zeroing 53-63, zero callers of the +entire `band_reading` surface, zero `ClassView` overrides — but the number +comes from the run, not from the plan. + +### 4.4 Proposed deliverables (this document's own labels) + +| id | deliverable | depends on | +|---|---|---| +| `D-NCI-1` | The mode itself: a `#[cfg]`-gated canary constant + fill in the affected constructors, and a read-side observer. Observe-and-fail only (N2). | — | +| `D-NCI-2` | Wave-0 census run + the report table (wire / field / count / collapse class). | D-NCI-1 | +| `D-NCI-3` | The allowlist, seeded from D-NCI-2, plus the CI job that fails outside it. | D-NCI-2 | +| `D-NCI-4` | First certified wire, end to end: stamp -> declare -> project -> zero canary reads. Also the first caller of `admits_band` / `project_band`, which today have none. | D-NCI-3 | +| `D-NCI-5` | `SpoHead` reclaim: it is a v1-shaped mirror (dead `temporal` byte) of a v2 carrier. Preservation fix under `I-LEGACY-API-FEATURE-GATED`, not a feature. | **none — splits into its own PR, council-hardened 2026-09-10 (§8 item 4)** | + +`D-NCI-1..3` are the instrument. `D-NCI-4` is the first repayment. +**`D-NCI-5` no longer depends on `D-NCI-4`** — it needs none of the mode's +machinery, is the same class of fix as the five Sprint-11 +`I-LEGACY-API-FEATURE-GATED` catches, and ships as its own independent PR, +before or alongside `D-NCI-1`, per the council resolution at §8 item 4. +Nothing beyond `D-NCI-4` is planned here on purpose — the census decides the +order, and pre-deciding it would be the plan overruling its own measurement. + +### 4.5 Ranking by consequence — "if 0 were NaN" is a COUNTERFACTUAL, literally + +> Operator, 2026-09-10: *"wiring NaN mode into revision and recalculate a CE64's +> known-unknowns — 'if 0 would be NaN' kind of counterfactual probing and +> revision."* + +> ⊘⊘ **CORRECTED 2026-09-10 on a re-read of both modules, and on the operator's +> caution — *"0 vs NaN is only a sidestep via `ogar-loco`; it never replaces the +> whole."* Three errors in the first version of this section, kept visible +> rather than deleted:** +> +> 1. **A HOMONYM collapsed in my own prose.** This section cited +> `NarsEngine::revise_fast` — **NARS truth revision** (u8 frequency / +> confidence, `NarsTables`) — while being framed around the revision +> **docket**, which is `contract::revision::GadamerRevision`. They are +> unrelated. `contract::revision` is **pure set algebra over +> `EvidenceMask`**: nine `RevisionKind`s from six booleans, and +> `EvidentialEffect` is *"deliberately coarser than numerical confidence"*. +> **It has no numerics at all**, so it never had a confidence axis to be +> blind on. The `revise_fast` fix (`6e5e674`) is real and unblocks the NARS +> path; the commit message's claim that it "unblocks §4.5's stated +> blindness" over-reached to this one. This repo has a standing record of +> exactly this trap — the two-`GateDecision` collision in +> `probe_revision_kanban_hinge`, `TrustTexture` as a four-way homonym in +> `D-ACR-7` §2.4. +> 2. **`contract::counterfactual` does not RUN counterfactuals.** Its whole v3 +> is `todo!()` — `CounterfactualMailbox::{new, poll, cancel}` and +> `revise_if_minority_wins` — and `AwarenessRevise` is an explicitly +> **BLOCKED placeholder** whose doc says the canonical `awareness.revise` +> signature is *"not confirmed on the current contract surface"*. Only v2 is +> real: the 4-bit mantissa deposit and the spawn gate. The module that +> actually runs one is `planner::dismech_counterfactual::counterfactual_replay` +> — cited correctly below, but framed as if the contract module were the +> runner. It is not. +> 3. **The routing was missing, and it is the point.** See "Where it actually +> attaches" at the end of this section. + +A count says how many fields are unstamped. It does not say which absences +**change an answer**. That second question is not a new mechanism: it is the +shape `dismech_counterfactual::counterfactual_replay` already implements — +*"the SAME W1 replay with one edge cut ... a thresholded verdict ... a +load-bearing edge moves the chain's truth ACROSS the threshold; a redundant one +moves it and stays on the same side."* + +Substitute the cut and the shape carries over unchanged: + +| | factual arm | counterfactual arm | verdict | +|---|---|---|---| +| shipped (`counterfactual_replay`) | chain as recorded | chain with step `i` removed | was that EDGE load-bearing | +| **this probe** | chain read as stored (`0` = a value) | chain read with `0` = **absent** | were those ABSENCES load-bearing | + +Both arms go through the same replay, so a divergence can only come from the +reading — never from two implementations drifting apart, which is the property +`dismech_replay` was built to guarantee. + +**It respects the direction ruling.** Nothing here feeds bits into revision. +Revision and counterfactual stay complete thinking; the probe runs the *same* +script twice over *two readings of the same bytes*. The scripts are untouched. + +**What the verdict buys:** + +- `Necessary` — the chain's conclusion moves when the unstamped fields are + treated as absent. **The absence is load-bearing**: this wire's silence is + already changing answers, and it ranks first. +- `Dispensable` — the conclusion holds either way. The wire is unwired and + nothing downstream depends on it: real debt, low priority. + +That is the ordering §4.2's allowlist wants, derived rather than argued — and +it repairs §7.4's weakness, because a small census of *load-bearing* absences +is worth more than a large census of inert ones. + +**Scope and honesty:** + +- This is a **probe, not a gate**. `counterfactual_replay` has no production + caller today (measured: tests only), and replaying every chain is not a CI + budget. It belongs after `D-NCI-2`, not inside `D-NCI-1`. +- **"`0` = absent" here means canary-detected absent, never a blanket + reinterpretation of every stored zero (codereview finding, confirmed + valid).** §3.3's own Design 1 already discloses the limit this probe must + respect: "a stored zero and a never-written zero are the same byte on + disk" for an ALREADY-PERSISTED row — poison-fill cannot tell them apart + there, and nothing here changes that. The probe's counterfactual arm is + legitimate ONLY over freshly-constructed, CI-instrumented objects where the + canary (or the saturated-field sidecar) makes "never written" a real, + distinct signal from "written as zero" — never over rows loaded from + storage, where `0` remains a value, full stop. A future implementation + scopes the probe's input to canary/sidecar-tagged objects explicitly; it + does not run this arm against persisted corpora. +- **The confidence axis is available, but only if the caller buys it.** + ⊘ This bullet first read *"blind on the confidence axis as things stand — + `revise_fast(f1, _c1, f2, _c2)` discards BOTH confidences ... fix that + first"*. The fix landed the same day, and it was a bigger defect than the + dropped arguments: `revise_fast` was indexing `tables.deduction` — the wrong + NARS rule — and the deduction table has no confidence axis at all, which is + why both arguments were `_`-prefixed. It now delegates to + `NarsTables::revise`, and `deduce_fast` names the other rule explicitly. + + **The blindness did not vanish; it MOVED**, and the new location is the one + that matters here. `revise` selects its table by quantizing `c1`/`c2` into + `c_levels` buckets, and `NarsEngine::new` builds **one** bucket: equal + weights, so the frequency is a plain mean and `c_out` is the constant 170 — + the same fixed point `DEFAULT_FREQUENCY_BAR` documents. **A ranking built on + `new` is still frequency-only.** Use `NarsEngine::with_c_levels` (cost + `c_levels² × 128 KB`) or state in the result that the axis was never + consulted. Pinned two-sided: + `revise_fast_honors_confidence_at_multiple_levels` / + `revise_fast_confidence_is_inert_at_one_c_level`. +- `DEFAULT_FREQUENCY_BAR` already carries the right warning for whoever tunes + this: confidence saturates to a fixed point under `NarsTables::build(1)`, so a + confidence-based verdict would be *"a vacuous threshold — every chain on the + same side of every bar."* The same trap is one substitution away here. + +**Where it actually attaches — a sidestep, never a replacement.** + +`GadamerRevision::revise(prior, encounter, ancestry)` consumes **masks**: +`independent_roots`, `resistance`, `contradictions`, `proposed_claims`. There +is no field in it a CE64 bit could be written into, and nothing here proposes +one. So "recalculate a CE64's known-unknowns through revision" cannot mean +modifying `revise` — it means **constructing a different `EncounterEvidence`**: +does an absent field still count as an independent root contacted, a +resistance met, a contradiction live? + +That construction is **upstream of the docket**, and its home is `ogar-loco` +— the operator-ruled planning/execution target (2026-09-05, *"every planning +is in migration to ogar-loco and ogar-r2il"*), where `recipe_vocab` already +lowers the 34 NARS recipes to loco ops and `dismech_replay` / +`dismech_counterfactual` already reference it. + +So the shape is: + +```text +loco program A: read as stored (0 = a value) → masks → docket → verdict +loco program B: read with 0 = absent → masks → docket → verdict + ↑ + THE SAME docket, unmodified, run twice +``` + +**The NaN reading is one more loco program, run BESIDE the docket.** It does +not enter `revise`, does not add a field to `EncounterEvidence`, does not +substitute for the counterfactual attack, and does not become the thinking. It +changes what the thinking is handed — and if this section is ever read as +licence to put a NaN branch inside `revision.rs` or `counterfactual.rs`, it has +been read wrong. + +## §5 NON-GOALS (each with its why) + +1. **The swapped encoding (Design 2).** Named in §3.3, not authorised. It is a + storage reinterpretation and needs its own plan, its own version gate, and a + persisted-corpus question that is not yet live. +2. **The `/15` divisor family.** Six sites carry a `15` derived from the + asymmetric `-8..+7` range, and they split into **two kinds**: + - *offset-and-scale* `(x + 8) / 15`: `quorum.rs:137`, + `mul.rs:898`, `mul.rs:1422` (allostatic load, **duplicated**) + - *max-distance* `|d| / 15`: `recipe_substrate.rs:237`, `:255`, and the + `logical_dissonance` pin in + `tests/d_pop_2_producer_reaches_consumers.rs:252` + + Both become `14` **for different reasons** (span 15->14 steps; max distance + 15->14). Two consequences worth stating so a later session does not + grep-and-replace its way to a right answer for the wrong reason: + - The **max-distance sites are not buggy today.** `/15` is correct for the + current range; they become wrong only *after* the range narrows. So the + order is: change the range first, then all six follow. + - The **clamp couples to the divisor.** `AxisProjection::settled` clamps to + `-8` and `position_clamps_to_i4_range` (`quorum.rs:350-353`) pins it. + Divisor to `/14` with the clamp still at `-8` yields `(-8+7)/14 = -0.071`, + a negative frequency. Same commit or neither. + + This is a sibling arithmetic fix. A detector does not touch it. +3. **The i4 dequant overshoot.** `quantize_f32_to_i4` uses + `scale = abs_max / 7.0` (ndarray `hpc/quantized.rs:671-673`) while + `dequantize_i4_to_f32` maps `0x8 -> -8 -> -8*scale`, i.e. about + `-1.143 * abs_max` — outside the codec's own declared `min_val = -abs_max`. + Same family as (2), same fix direction, separate change. +4. **Correctness.** This measures whether a field was written, never whether + the value is right (N7). A wire can be certified green and still assert the + wrong thing. +5. **Any consumer-repo work.** This plan is lance-graph-internal. Downstream + consumers consume `main`; their side is a separate, later question. + + +## §6 PRE-REGISTERED GATES (decided BEFORE any code) + +| # | Gate | Falsifier | +|---|---|---| +| G1 | **Release is byte-identical.** A release build with and without the mode's code present produces identical output on the golden paths. | Any observable difference in a release build = N1 violated, revert. | +| G2 | **Observe-only.** No `cfg`-gated branch changes control flow; the mode's only effects are counting, reporting, and failing. | A reviewer finds an `if ` with a non-failing `else` arm = N2 violated, block. | +| G3 | **Can-fire.** At least one wire reports a NON-zero absence horizon. `project_band` refuses today, so a positive control exists on day one. | If every row reports total collapse, the probe is broken, not the substrate. | +| G4 | **Can-stay-silent.** `frequency_u8` / `confidence_u8` survive a CE64 round-trip and must report **present**. | If those report absent, the probe is wrong. | +| G5 | **The allowlist shrinks.** Every PR after D-NCI-3 either leaves the allowlist unchanged or removes entries. | An addition without an explicit debt admission = block. | +| G6 | **No new CE64 bit, no `ENVELOPE_LAYOUT_VERSION` bump** (N3). | Any layout constant moves = out of scope, split the PR. | +| G7 | **Byte-range canaries are collision-audited, not merely disclosed** (codereview finding, added post-council). Each plain byte-range field (S/P/O, frequency, confidence) that adopts a literal in-value canary names its exact sentinel value(s) and a producer-side exclusion invariant, backed by a test proving the sentinel is distinguishable from every value a real producer emits. | A canary shipped without a stated exclusion invariant, or without a collision test, is not ready — block until named. | +| G8 | **The sidecar has one stated ownership rule per edge instance** (codereview finding, added post-council), covering `Copy`, array/`Vec` storage, `ZERO`/`Default` resets, and direct setter calls that bypass any outer constructor. | A sidecar read that can return another edge's touch state, or silently under-reports touch state on a bypassed path, is a defect in the mechanism itself — block until the ownership rule is written down. | + +G3 and G4 together are the N4 pair applied to the instrument itself, and they +are the two that a vacuous version of this work would skip. + +## §7 RISKS + +### 7.1 It looks exactly like the pattern the iron rule forbids + +`I-LEGACY-API-FEATURE-GATED` was written against feature-gated semantic +divergence, and Sprint-11 caught that pattern five times. A decode mode is +adjacent to it by construction. The only thing separating them is N2, and N2 is +a property a reviewer must actually check rather than assume. **If N2 ever +softens, this plan becomes the defect it was written to find.** + +### 7.2 A red-everywhere gate gets ignored + +Covered by N5/§4.2, restated as a risk because it is the likeliest failure +mode: shipping enforcement before the census produces a job that is red on +day one and disabled by day ten. + +### 7.3 Turning it off after certification (operator's own last clause) + +The plan's one disagreement with its own framing. Certification is not a finish +line; it is a ratchet. In release the mode already costs nothing, so "off" buys +only the loss of the ratchet, and new unwired sites reappear silently. + +This repository has receipts on both halves of that failure: + +- the supersession index went stale **within the hour** of first landing, + because regeneration was manual — *"a generated artifact with no staleness + gate is a hand-maintained artifact with extra steps"* (`CLAUDE.md`); +- two `tesseract-core` fixtures sat red for 13 days, invisible three ways, + because nothing re-ran them where anyone looked. + +Recommendation: keep the mode, keep it CI-only, drive the allowlist to zero and +leave the job armed. + +### 7.4 The census could be smaller than expected + +If Wave 0 reports a low absence rate, the premise weakens and the campaign +should be re-scoped rather than pushed. That outcome is a legitimate result of +D-NCI-2, not a failure of it — and pre-committing to it here is what stops the +measurement from becoming a formality. + +### 7.5 Design 2's trap, recorded now so it is not rediscovered later + +If Design 2 is ever taken up: under the swap, a **stale reader** still doing +naive two's-complement decode produces + +- absent (`0b0000` -> 0) -> `(0 + 7) / 14 = 0.5` — perfectly neutral, + perfectly plausible, **invisible**; +- a real zero (`0b1000` -> -8) -> `(-8 + 7) / 14 = -0.071` — out of range, + **loud**. + +The two failure directions are asymmetric in the worst way: absence fails +silent at the one value nobody questions. Any Design-2 plan must guard the +formula with the absence check, not merely re-base the divisor. + +## §8 COUNCIL-HARDENED RESOLUTION (awaiting operator confirmation) + +> 5+3 hardening council, 2026-09-10 (run record: `.claude/board/AGENT_LOG.md`). +> **None of R1-R5 below is a ruling.** Each is a committed, savant-verified, +> reviewer-attacked proposal — the council's job was to harden a specific +> resolution well enough that the operator's confirmation is a yes/no, not a +> re-derivation. Nothing here authorizes writing `D-NCI-1`'s Rust code +> (Non-Goal, this council's own spec §3 item 1). + +1. **R1 — The canary value(s): two mechanisms, by field shape.** + `TrustTexture`/`CausalTopology` (2-bit), `ReasoningBand` (3-bit), and the + W-slot (6-bit) are each independently re-verified **fully saturated** — + every bit pattern already names a legitimate variant or slot, zero spare + codes in any of them (checked against the complete enum bodies in + `layout.rs`, not a truncated excerpt, by two separate agents across the + council's two phases). A poison-fill VALUE therefore does not exist for + these three fields. Resolution: a **CI-build-only, out-of-band sidecar** + (`#[cfg(feature = "nan-ci-mode")]`-gated, e.g. + `TouchedTail { topology_set: bool, band_set: bool, w_set: bool }`), + populated by an OUTER constructor/builder that calls the EXISTING, + UNMODIFIED setters and separately records touch state — never by adding a + branch inside `with_topology` / `with_reasoning_band` / `with_w_slot` + themselves, which keeps `I-LEGACY-API-FEATURE-GATED` satisfied on its + letter (same function, same body, same behavior, always). This is a new + TYPE, which N3 does not forbid (N3 forbids new CE64 bits / layout-version + bumps / new address types; a tracker held entirely OUTSIDE the 64-bit + register is none of those, and it never exists in a release build). + *Considered and rejected:* a global aggregate counter + (`tenant_counter.rs:29-44`'s already-shipped `LazyLock<[AtomicU64; N]>` + pattern) — it can only answer "was this setter called anywhere this run", + not "was THIS edge's field set before THIS read", producing false + negatives on edges built before the first call and false positives on + every edge after. **The mantissa** is a partial exception: `to_mantissa` + never emits 8 of its 16 possible raw nibbles (`-8` confirmed among them), + so a raw-nibble canary checked before `from_mantissa`'s + `unsigned_abs() & 0x7` masking is available and disclosed as touching the + register directly — reuse of already-unused value-space, not a new bit. + **Every plain byte-range field** (frequency/confidence, S/P/O) has no + saturation problem — read as raw bytes rather than decoded through an + exhaustive enum match, a chosen canary risks only a documented, avoidable + VALUE collision there, never the saturated fields' structural + impossibility. See §3.3 for the mechanics text. + + **⊘ Ownership left unspecified (codereview finding, confirmed valid — open, + needs an implementation-time decision before `D-NCI-1`).** `CausalEdge64` + is a plain public `Copy` `#[repr(transparent)]` value with public setters; + the sidecar as described has no stated rule for WHICH edge instance a + sidecar entry belongs to once that guarantee is exercised. Concretely, + unaddressed here: (a) a `Copy` of an edge — does the copy's sidecar entry + move, alias the original's, or start fresh (correctly reporting the copy's + own fields as untouched, even though the bits were copied touched)? (b) an + array/`Vec` of edges, or `CausalEdge64::ZERO`/ + `Default::default()` used as a reset — same question, at scale. (c) a + DIRECT call to `with_topology`/`with_reasoning_band`/`with_w_slot` that + bypasses whatever "outer constructor" the sidecar is populated through — + nothing in the type system forces a caller through that constructor, so a + direct call risks a stale sidecar entry silently under-reporting touch + state (a false absence, the OPPOSITE direction from a poisoned false + presence, and arguably worse: it would suppress a real ABI-debt finding). + The mechanism needs a keying scheme (edge identity, not edge VALUE — two + edges with identical bits are not the same provenance) before `D-NCI-1` + can implement it soundly; naming that scheme is implementation work, not a + plan-text decision, but its ABSENCE is a real gap this plan should not + paper over. +2. **R2 — Failure granularity: aggregate during Wave 0, per-test after.** + Wave 0 (census-only, N5) reports one aggregate table, no test fails — + matching §4.2 step 1 exactly. Once the allowlist exists (post `D-NCI-3`), + a NEW violation fails the specific test/call site that produced it, not + the whole job — confirmed orthogonal to N5's shrink-only rule (the + allowlist's membership direction and a failure's reporting granularity are + independent), and consistent with `G3`/`G4`'s own per-instrument phrasing. +3. **R3 — Scope of the first wave: three crates.** `causal-edge` + + `lance-graph-contract` + `cognitive-shader-driver` — the third crate + carries the one artifact with real production shape today, + `apply_edges`'s live `w_slot` filter (`mailbox_soa.rs:355`), even though it + currently lacks a live caller. Certifying that wire first (as `D-NCI-4`'s + target) demonstrates the mode's value on the wire most likely to matter + once `apply_edges` gains a caller, rather than one dormant on both ends. +3a. **R4 — `#[cfg]` vs hot-plug vs hybrid: THE HYBRID.** Chosen over pure + `#[cfg]` because the hybrid captures every real win hot-plug offers + (per-classid certification matching §4.1 exactly; `Activation`'s + fail-closed-by-construction with no `Default`; the existing + `ActivationDrift`/`verify_against_mirror` drift machinery) at zero cost to + N1 — hot-plug carries the POLICY, `#[cfg]` carries the ENFORCEMENT, and + release resolves the policy once at activation with no new per-read + branch. Chosen over pure hot-plug because relaxing N1 is a capability this + campaign has not measured the re-pricing for (§3.6 already says so). This + restates the already-drafted §3.6 paragraph — §3.6 is explicitly not + itself a frozen decision (N1 is; §3.6's choice among its alternatives is + what changes one), so nothing here treats it as pre-settled. **Build-time + constraint, confirmed novel composition** (no prior pairing of hotplug + with a `#[cfg]`-gated enforcement half exists in this tree, and + `ActivationDrift` carries no `#[non_exhaustive]`): whoever builds + `D-NCI-1` under this hybrid must NOT add a new `ActivationDrift` variant — + the certification signal belongs on a new field or sibling type. +4. **R5 — `D-NCI-5` (`SpoHead`) splits into its own PR.** It needs none of + `D-NCI-1..4`'s machinery — it is the same class of fix as the five + Sprint-11 `I-LEGACY-API-FEATURE-GATED` catches. Ships independently, + before or alongside `D-NCI-1` (§4.4 updated). This PR is the FIRST-EVER + `STATUS_BOARD.md` / `LATEST_STATE.md` / `PR_ARC_INVENTORY.md` entry any + `D-NCI-*` id has had (confirmed zero prior hits in all three files) — + whoever ships it adds the `STATUS_BOARD.md` row in the same commit. +5. **Where the runtime disposition of absence lives — R6/R7/R8, resolved at + §9.** Absence is not only debt; at runtime it is *surprise* — the one + surprise the current Staunen cannot see (`nars/ghost_prior.rs:152-157` + `GhostEcho::Staunen`; Staunen has no primitive carrier, not among the 17 + `AXIS_LABELS`, `qualia.rs:28-46`). + +## §9 THE RUNTIME DISPOSITION — resolved, splits into its own companion plan + +> Council-hardened 2026-09-10, same run as §8. **R8's conclusion: this +> section's content SPLITS out of `nan-ci-mode-v1`.** The CI-side disposition +> (§4's campaign, `D-NCI-1..5`) and the runtime-side disposition below share +> ONE detector but need nothing from each other to ship, and the runtime side +> needs Staunen/free-energy wiring this plan's own deliverables don't touch. +> What follows is the RATIFIED CONTENT for that companion plan, kept here +> until it is filed, so the resolution is not lost between council and +> filing. **Awaiting operator confirmation, same as §8** — this is not a +> ruling. + +The CI mode and a runtime absence-signal are **the same detection with two +dispositions**, and neither this plan nor its companion forks the detector to +accommodate the second: + +| context | absence means | response | +|---|---|---| +| CI | debt — a producer that never stamps | fail the build (§4.2 allowlist) | +| runtime | surprise — unhydrated | raise Staunen -> gather | + +### 9.1 The existing three-way split, and where R7's new state attaches + +Conflating "never wired" with "not yet hydrated" would be a defect: the first +must fail CI, the second must retry, and an eternal retry on the first is worse +than silence. `band_reading` already separates all three, and its module doc +states the distinction verbatim — *"`None` = never declared; `Some(band: +Absent)` = **explicitly** declared band-free. Folding the two would make 'opted +out' and 'never considered' indistinguishable to a migration audit."* + +| contract state | meaning | disposition | +|---|---|---| +| `BandPresence::Absent` (declared) | the class carries no band, on purpose | **not** a surprise — a fact. No Staunen. | +| `EdgeProvenance::Unknown` -> `BandReadError::UnknownProvenance` | origin unstated; the bits are not readable | **the unhydrated case** -> Staunen -> gather | +| `BandDeclarations::get` -> `None` (never declared) | nobody ever considered this wire | **the debt case** -> CI fail | + +So the placement question has a cheap answer: **the trigger lives in the read +contract that already refuses, and the refusal variant already carries the +reason.** Nothing new is minted — no new bit (N3), no new qualia axis, no new +error type. + +The 2-bit field already encodes degrees of epistemic murk under either lens — +`TrustTexture` = `Crystalline / Solid / Fuzzy / Murky`, `CausalTopology` = +`Direct / IndirectKnown / IndirectUnknown / Unknown`. Absence is the limiting +case of the same axis: `Unknown` is *"I do not know the path"*; a refused read +is *"I was never told"*. It reads as a fifth state **without costing a bit**, +because it is the absence of the field rather than a value in it. + +**R7's `RetryExhausted` (§9.2) is NOT a fourth contract-level sibling in the +table above — it is a report-time refinement of the `EdgeProvenance::Unknown` +row**, marking the moment that row's "still resolving" phase is deemed to +have run out of chances. This table does not yet represent that refinement's +crossing from the runtime context into a CI-facing outcome; whoever files the +companion plan adds that row, rather than silently folding the refinement +into one of the three existing rows. + +### 9.2 R6/R7 — resolved: route via free energy, with a labeled third disposition on exhaustion + +`mul::GateDecision::Hold` returns `None` from `advance_on_gate`, and the owner +is HELD and re-polled — mechanically *"stay, gather more"*. That is the +hydration response, already shipped. + +**R6 — does the refusal raise Staunen directly, or via free energy: VIA FREE +ENERGY.** Staunen has no primitive carrier today, and a direct write would +either widen a fixed-width qualia column or overload an existing axis's +semantics — exactly the kind of new-surface cost this plan avoids everywhere +else (Design 1 over Design 2, the hybrid over new bits, the sidecar over new +fields). The PATTERN is precedented (`EPIPHANIES.md`, 2026-04-24, "SMB as +cognitive-stack testbed": a missing required property already routes to free +energy rather than a hard fail — the same pattern, in an unrelated +subsystem, not a literal reuse). **What is not yet true, checked rather than +assumed:** `ghost_prior::echo_for` — the function that actually raises +`GhostEcho::Staunen` — is exercised only by its own test module; +`cognitive-shader-driver/src/driver.rs`'s live `FreeEnergy::compose` call +never feeds it. R6 names the right mechanism and the right precedent for its +shape; the specific wiring from a `BandReadError::UnknownProvenance` refusal +through `kl` into `echo_for` is new plumbing the companion plan must build, +not something already connected that a refusal merely joins. + +**R7 — retry policy: `N_RETRY_CYCLES = 3`** (a POLICY PIN needing its own +later measurement, following `DEFAULT_FREQUENCY_BAR`'s own precedent), +counted in mailbox cycles (`MailboxSoA::current_cycle: u32`), not wall time. +The one real count-based give-up precedent in this tree +(`supervisor.rs::ESCALATION_CRASH_COUNT = 10`) pairs its count with a +wall-time backoff interval (100ms -> 30s) because a ractor respawn crosses +an async I/O boundary; a mailbox hydration retry does not — it is already +paced by the substrate's own cycle cadence — so only the give-up THRESHOLD is +borrowed from that precedent's shape, deliberately not its backoff mechanism. +**After `N_RETRY_CYCLES` failed attempts, the disposition becomes a labeled +THIRD state, `RetryExhausted`** — never folded into either `never-declared` +(the doctrine quoted in §9.1 forbids exactly that fold) or `still-hydrating`. +It still fails the CI/audit report (the practical "stop looping forever" +outcome R7 wants), but the report LABELS it separately from the debt case, so +a reviewer can tell "nobody ever touched this wire" apart from "this wire +tried to hydrate and gave up" — the two mechanisms are structurally different +(D-NCI-1..3's static census/allowlist vs. this dynamic runtime signal) and +**not yet connected**; building that connection is itself companion-plan +work, not something the existing instrument already does. + +**R6/R7 interaction, stated as a requirement on the companion plan, not a +claim about current behavior:** the companion plan MUST allow Staunen to +fire on every still-unresolved read within the retry window, not only once +`RetryExhausted` is reached — a read that has not yet happened is surprising +each time it is observed, and `N_RETRY_CYCLES` governs only when the +CI/audit disposition stops calling it "not yet"; it must never gate whether +Staunen may fire earlier. + +### 9.3 R8 — this section files as its own plan + +Splits per the reasoning at the top of this section. When filed, the +companion plan carries §9.1-§9.2 above, forward-referenced from here; §8 +item 5's cross-reference is updated to name that file once it exists. + +## §10 BOARD HYGIENE + +Per `CLAUDE.md`'s Mandatory Board-Hygiene Rule, a PR carrying a new integration +plan owes a PREPEND to `.claude/board/INTEGRATION_PLANS.md` — discharged in the +same commit as the 2026-09-10 council landing (§8/§9 above). + +The council run itself (5 savants, 3 reviewers, verdict counts, the v1→v2→v3 +change ledger) is recorded in `.claude/board/AGENT_LOG.md`, per +`.claude/agents/5plus3-council.md`'s Phase-5 requirement — that PREPEND, not a +section inside this plan, is the authoritative run record. diff --git a/crates/causal-edge/src/tables.rs b/crates/causal-edge/src/tables.rs index e44c36a7f..8d08519e2 100644 --- a/crates/causal-edge/src/tables.rs +++ b/crates/causal-edge/src/tables.rs @@ -52,7 +52,9 @@ impl NarsTables { /// Build all lookup tables. /// /// `c_levels`: number of confidence quantiles for revision tables. - /// Use 16 for full precision (32 MB), 1 for fast path (128 KB). + /// Total footprint is `(c_levels² + 1) × 128 KB` — the deduction table is + /// always allocated, independent of `c_levels`. Use 16 for full precision + /// (~32.1 MB), 1 for fast path (256 KB, not 128 KB). pub fn build(c_levels: usize) -> Self { let c_levels = c_levels.clamp(1, 16); diff --git a/crates/lance-graph-arm-discovery/src/translator.rs b/crates/lance-graph-arm-discovery/src/translator.rs index 09738a6ad..32775fe9e 100644 --- a/crates/lance-graph-arm-discovery/src/translator.rs +++ b/crates/lance-graph-arm-discovery/src/translator.rs @@ -22,8 +22,31 @@ use crate::rule::{CandidateRule, Item}; /// needed before confidence approaches saturation. pub const NARS_PERSONALITY_K: u32 = 1; -/// Quantised NARS truth — the canonical, float-free **substrate** representation -/// (mirrors the `CausalEdge64` `confidence_u8` + i4 mantissa fields). `255` = 1.0. +/// Quantised NARS truth — the canonical, float-free **substrate** representation. +/// `255` = 1.0. Mirrors the `CausalEdge64` truth pair — `frequency_u8` (bits 24-31) +/// + `confidence_u8` (bits 32-39). Under the default `causal-edge-v2-layout` +/// feature, the pair's KIND rides beside it, coded: `CausalTopology` at bits +/// 59-60 and `ReasoningBand` at bits 61-63. **Under `default-features = false` +/// (the v1-compat opt-out)**, those accessors are fixed stubs — `topology()` +/// always `Direct`, `reasoning_band()` always `Surface` — bits 59-63 keep the +/// v1 temporal layout underneath and are not readable as a KIND at all; a +/// consumer on that feature set has no dimension to read here. This struct +/// carries the strength ONLY, so it is not a truth this workspace can define +/// with: the defining LE representation is `CausalEdge64`'s own little-endian +/// image (v2 layout), always (operator ruling, 2026-09-10). (Two earlier +/// comments here were wrong: one +/// called bits 61-63 a spare field ruled as a Tarski rung — they have been +/// `ReasoningBand` since `9891cca6`, only the `SPARE_SHIFT` name is stale; the other +/// paired `confidence_u8` with the i4 mantissa — the i4 mantissa at bits 46-49 is the +/// `InferenceType`, provenance/type grammar, not half of the truth value.) +/// +/// **This is a substrate value, not a wire DTO.** It carries no `repr(C)`, no schema +/// version, and no little-endian codec, so it binds a DEGREE and not a KIND. Per the +/// operator ruling of 2026-09-10 (*LE is the universal DTO layer of the ABI*), a truth +/// crosses a membrane only as a versioned DTO whose canonical LE layout fixes the +/// meaning of every position — for this pair, the ordered byte sequence +/// `[frequency, confidence]` — or as an opaque typed handle whose registry binds the +/// same kind and schema. That DTO is defined by D-BBB-NARS-2, not here. /// /// Not "the wire form": that wording was retired by `D-BBB-NARS-1` (2026-09-07), /// which separates two claims the workspace had been conflating. This type is diff --git a/crates/lance-graph-planner/src/cache/nars_engine.rs b/crates/lance-graph-planner/src/cache/nars_engine.rs index dbf54a10a..6a0f629c4 100644 --- a/crates/lance-graph-planner/src/cache/nars_engine.rs +++ b/crates/lance-graph-planner/src/cache/nars_engine.rs @@ -448,19 +448,89 @@ impl NarsEngine { pub fn new(distances: SpoDistances) -> Self { Self { distances, - tables: NarsTables::build(1), // fast path: 1 c-level = 128 KB + tables: NarsTables::build(1), // fast path: 1 c-level = 256 KB (128 KB revision + 128 KB deduction) + consecutive_confident: 0, + history: Vec::new(), + } + } + + /// Like [`new`](Self::new) but with an explicit revision-table confidence + /// resolution. + /// + /// `c_levels` is clamped to `1..=16` by `NarsTables::build`. Memory is + /// `(c_levels² + 1) × 128 KB` — the revision tables plus the always-present + /// 128 KB deduction table: **1 → 256 KB** (the `new` default, confidence + /// inert — NOT 128 KB; the deduction table is allocated unconditionally), + /// **4 → ~2.1 MB**, **16 → ~32.1 MB** (full precision). Pick deliberately; + /// the default is the fast path, not the accurate one. + pub fn with_c_levels(distances: SpoDistances, c_levels: usize) -> Self { + Self { + distances, + tables: NarsTables::build(c_levels), consecutive_confident: 0, history: Vec::new(), } } /// Hot path: NARS revision via lookup table. O(1), no float. + /// + /// Revision merges two truths asserted about the SAME statement from + /// independent evidence: the frequency is a CONFIDENCE-WEIGHTED average + /// (`(f1·w1 + f2·w2) / (w1 + w2)`, `w = c/(1−c)`) and the confidence + /// ACCUMULATES (`c = ws/(ws+1)`). Both halves need `c1`/`c2`, so both are + /// read — see [`causal_edge::tables::NarsTables::revise`]. + /// + /// # ⚠ The confidence axis is only as fine as `c_levels` + /// + /// `revise` selects its table by quantizing `c1`/`c2` into + /// [`NarsTables::c_levels`] buckets. [`NarsEngine::new`] builds + /// `c_levels = 1` (the 128 KB fast path), and with ONE bucket every + /// confidence maps to the same table: the weights are equal, so the + /// frequency is a plain mean and `c_out` is the constant **170**. That is + /// the fixed point `dismech_counterfactual::DEFAULT_FREQUENCY_BAR` + /// already documents — *"measured across a weak 3-chain, a strong + /// 4-chain, and a mixed one, the terminal confidence was 170 in every + /// case"* — and it is why a confidence-based threshold there would be + /// vacuous. + /// + /// **To have confidence actually discriminate, construct with + /// [`NarsEngine::with_c_levels`].** Pinned two-sided by + /// `revise_fast_honors_confidence_at_multiple_levels` (it does) and + /// `revise_fast_confidence_is_inert_at_one_c_level` (it cannot, at the + /// default). + /// + /// ⊘ Before 2026-09-10 this indexed `tables.deduction` — the wrong NARS + /// rule, not merely a dropped argument. Deduction (`A→B, B→C ⊢ A→C`, + /// `f = f1·f2`) and revision are different inferences; the deduction + /// table has no confidence axis at all, which is why `c1`/`c2` were + /// `_`-prefixed. Corrected in place, and recorded rather than deleted. #[inline] - pub fn revise_fast(&self, f1: u8, _c1: u8, f2: u8, _c2: u8) -> (u8, u8) { - let packed = self.tables.deduction[f1 as usize * 256 + f2 as usize]; + pub fn revise_fast(&self, f1: u8, c1: u8, f2: u8, c2: u8) -> (u8, u8) { + let packed = self.tables.revise(f1, c1, f2, c2); (unpack_f(packed), unpack_c(packed)) } + /// Hot path: NARS DEDUCTION via lookup table. O(1), no float. + /// + /// `A→B ⟨f1,c1⟩`, `B→C ⟨f2,c2⟩` ⊢ `A→C ⟨f,c⟩` with the standard NARS + /// deduction truth function `f = f1·f2`, `c = f·c1·c2`. The table + /// (`tables.rs`) only bakes in `f1`/`f2` — it has no confidence axis, so + /// it stores the packed frequency twice, `f_out` doubling as a + /// CONSERVATIVE UPPER BOUND on confidence (since `c ≤ f` always holds + /// when `c1, c2 ≤ 1`). `deduce_fast` finishes the computation by scaling + /// that upper bound down by the caller's own `c1`/`c2` — returning the + /// bare table lookup as "confidence" would silently read a + /// zero-confidence premise (`c1 = 0` or `c2 = 0`, e.g. two totally + /// unevidenced but high-frequency premises) as maximally confident, + /// which is exactly backwards for the case that matters most. + #[inline] + pub fn deduce_fast(&self, f1: u8, c1: u8, f2: u8, c2: u8) -> (u8, u8) { + let packed = self.tables.deduce(f1, f2); + let frequency = unpack_f(packed); + let confidence = (u32::from(frequency) * u32::from(c1) * u32::from(c2) / (255 * 255)) as u8; + (frequency, confidence) + } + /// Hot path: SpoHead → CausalEdge64 for protocol transport. /// /// Maps the SpoHead's `inference` byte to the causal-edge `InferenceType`, @@ -1316,4 +1386,115 @@ mod tests { "should not stop after low-confidence entry" ); } + + // ── revise_fast: the rule, and the confidence axis ── + + /// Revision is not deduction. Before 2026-09-10 `revise_fast` indexed the + /// DEDUCTION table, so this is the two-sided pin on the rule itself: + /// revision of two agreeing truths must PRESERVE their frequency, where + /// deduction multiplies it away. + #[test] + fn revise_fast_uses_revision_not_deduction() { + let engine = NarsEngine::new(SpoDistances::new_zero()); + let (f_rev, _) = engine.revise_fast(200, 128, 200, 128); + let (f_ded, _) = engine.deduce_fast(200, 255, 200, 255); + + // Deduction: 200*200/255 = 156. Anti-vacuity — the two rules must be + // far apart on this input, or the test proves nothing. + assert_eq!(f_ded, 156, "deduction fixture drifted"); + assert!( + f_rev.abs_diff(f_ded) > 40, + "revision and deduction must be far apart here (rev={f_rev}, ded={f_ded})" + ); + // Two independent witnesses both saying 200 revise to ~200, never 156. + assert!( + f_rev >= 199 && f_rev <= 201, + "agreeing witnesses must preserve frequency, got {f_rev}" + ); + } + + /// Codex/CodeRabbit P2 finding on PR #1223: `deduce_fast` used to return + /// the deduction table's raw packed byte as "confidence" — that byte is + /// `f_out` doubling as a conservative UPPER BOUND (`tables.rs`), not the + /// real NARS deduction confidence `c = f·c1·c2`. So two premises with + /// ZERO evidential confidence but maximal frequency came back reporting + /// confidence 255: maximally confident from maximally unevidenced + /// premises. Two-sided — zero confidence must zero the output; full + /// confidence at the same frequencies must still reach the upper bound + /// (proving the fix scales rather than always returning zero). + #[test] + fn deduce_fast_confidence_scales_with_premise_confidence_not_just_frequency() { + let engine = NarsEngine::new(SpoDistances::new_zero()); + + // f1 = f2 = 255 -> f_out = 255 (max frequency, the upper bound). + let (f_max_freq, c_zero_conf) = engine.deduce_fast(255, 0, 255, 0); + assert_eq!( + f_max_freq, 255, + "fixture drifted: f1=f2=255 must deduce f=255" + ); + assert_eq!( + c_zero_conf, 0, + "zero-confidence premises must not deduce full confidence, got {c_zero_conf}" + ); + + let (_, c_full_conf) = engine.deduce_fast(255, 255, 255, 255); + assert_eq!( + c_full_conf, 255, + "max-confidence premises at the same frequencies must still reach the upper bound" + ); + } + + /// CAN-FIRE half: with a real confidence axis, `c1`/`c2` change the answer + /// — in BOTH the frequency (the weights) and the confidence (accumulation). + #[test] + fn revise_fast_honors_confidence_at_multiple_levels() { + const LEVELS: usize = 8; + let engine = NarsEngine::with_c_levels(SpoDistances::new_zero(), LEVELS); + + // Anti-vacuity: the two fixtures must land in DIFFERENT c-buckets, or + // this measures nothing. Bucket index is `c * LEVELS / 256`. + let bucket = |c: u8| c as usize * LEVELS / 256; + assert_ne!(bucket(20), bucket(240), "fixtures share a bucket"); + + // Disagreeing witnesses (f=40 vs f=240) with LOPSIDED confidence: + // the confident one must pull the revised frequency toward itself. + let (f_lo_wins, _) = engine.revise_fast(40, 240, 240, 20); + let (f_hi_wins, _) = engine.revise_fast(40, 20, 240, 240); + assert!( + f_lo_wins < f_hi_wins, + "the more confident witness must dominate (lo={f_lo_wins}, hi={f_hi_wins})" + ); + assert!( + f_hi_wins - f_lo_wins > 20, + "the pull must be substantial, not a rounding artefact" + ); + + // Confidence accumulates: two strong witnesses out-confide two weak. + let (_, c_weak) = engine.revise_fast(128, 20, 128, 20); + let (_, c_strong) = engine.revise_fast(128, 240, 128, 240); + assert!( + c_strong > c_weak, + "revised confidence must grow with input confidence \ + (weak={c_weak}, strong={c_strong})" + ); + } + + /// CAN-STAY-SILENT half, and the honest limitation: at the `new` default + /// (`c_levels = 1`) there is exactly ONE bucket, so confidence CANNOT + /// discriminate and `c_out` is the constant 170. Delegating to `revise` + /// fixes the RULE; it does not conjure a resolution the table lacks. + #[test] + fn revise_fast_confidence_is_inert_at_one_c_level() { + let engine = NarsEngine::new(SpoDistances::new_zero()); + assert_eq!(engine.tables.c_levels, 1, "`new` is the 128 KB fast path"); + + let a = engine.revise_fast(40, 0, 240, 255); + let b = engine.revise_fast(40, 255, 240, 0); + assert_eq!( + a, b, + "one bucket cannot discriminate — this is a documented limitation, \ + not a passing confidence check" + ); + assert_eq!(a.1, 170, "the fixed point DEFAULT_FREQUENCY_BAR documents"); + } }