From 030b3f385f154ce7e942e64c3cd43765f9af706f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 09:20:17 +0000 Subject: [PATCH 1/3] plans: cognitive-write = round-trip substrate-hardening spec (v1) Express the cold-path write (mailbox SoA -> SPO nodes + CausalEdge64 edges) as a codegen_spine::TripletProjection, so roundtrip_eq becomes the commit's own gate: every commit is a substrate proof. Separates the three layers (exact-LE members / container envelope / lossy codec) onto two witnesses (roundtrip_eq vs rank-correlation), and decouples substrate hardening from the [ABSENT] KausalSpec DO-enforcement runtime. Grounded against: codegen_spine.rs, soa_envelope.rs (zero impls today), soa_view.rs (MailboxSoaView/Owner), lance_membrane.rs (sole-writer commit_event + CommitFilter/MembraneGate), ndarray causal_diff.rs (NARS 10b x1023 -> truth_tolerance 1/1023). Phase plan P0-P4 unblocked; P5 (SurrealQL read glove) BLOCKED(C) on surrealdb fork coords. https://claude.ai/code/session_014A4JuRCqKP2yNENrQ9Ha7H --- .../cognitive-write-roundtrip-substrate-v1.md | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 .claude/plans/cognitive-write-roundtrip-substrate-v1.md diff --git a/.claude/plans/cognitive-write-roundtrip-substrate-v1.md b/.claude/plans/cognitive-write-roundtrip-substrate-v1.md new file mode 100644 index 00000000..92b9d686 --- /dev/null +++ b/.claude/plans/cognitive-write-roundtrip-substrate-v1.md @@ -0,0 +1,230 @@ +# Cognitive Write = Round-Trip Substrate Hardening — v1 + +> **Status:** PLAN (spec). Unblocked except P5 (BLOCKED(C) surrealdb fork coords). +> **Thesis:** Express the cold-path write (mailbox SoA → SPO nodes + CausalEdge64 +> edges) as a `codegen_spine::TripletProjection`. Then `roundtrip_eq` over it is +> not a *separate* test — it is the **commit's own gate**. Every commit becomes a +> substrate proof: if the LE bytes / class addressing lost an iota, the commit +> fails. This is the probe that answers the original multi-session question — +> *which member-LE vs container-LE contracts are sound vs brittle in ndarray AND +> lance-graph combined* — by exercising them on real cognitive-cycle data. + +## 0. Why this exists (the one-paragraph frame) + +The scalar emitter `ExternalMembrane::project(bus, meta) -> CognitiveEventRow` +(lance_membrane.rs:381) *"strips all VSA state"* — it is an audit witness, NOT +round-trippable (you cannot decompile `fp[0]`/`fp[255]` back to the tensor). The +hardening path is the **node/edge** projection, which IS lossless by construction +because it carries only the **exact-LE substrate** (`CausalEdge64` u64 ⊕ `class_id` +u16 ⊕ `MetaWord` u32) and deliberately EXCLUDES the lossy CAM-PQ fingerprint +(certified separately by rank-correlation ρ, never by equality). + +## 1. Grounded inventory (file:line evidence) + +| Surface | Status | Evidence | +|---|---|---| +| `TripletProjection` + `roundtrip_eq` → `RoundTripFailure` | **[G]** | codegen_spine.rs:107-183 | +| `Triple { s,p,o: String, f,c: f32 }` | **[G]** | codegen_spine.rs:74 | +| `SoaEnvelope` trait (columns/row_stride/n_rows/cycle/as_le_bytes/verify_layout) | **[G]** | soa_envelope.rs:139 | +| `SoaEnvelope` **real implementors** | **[H]** | ZERO — only `TestEnvelope` in tests | +| `ColumnDescriptor{name_id:u16,kind,elems_per_row:u16,row_offset:u32}` / `ColumnKind` | **[G]** | soa_envelope.rs:54-97 | +| `MailboxSoaView`: `edges_raw()->&[u64]`, `meta_raw()->&[u32]`, `class_id()->&[u16]`, `energy()->&[f32]` | **[G]** | soa_view.rs:28-105 | +| `MailboxSoaOwner: MailboxSoaView` (`try_advance_phase`, Rubicon DAG) | **[G]** | soa_view.rs:112 | +| `commit_event(row)->u64` sole-writer (ticks version) | **[G]** | lance_membrane.rs:315 | +| Two-layer gate `CommitFilter` + `MembraneGate` (write unconditional, fan-out gated) | **[G]** | lance_membrane.rs:415-429 | +| CausalEdge64 NARS: freq 10b ×1023 [19:10], conf 10b ×1023 [9:0] → tol = 1/1023 | **[G]** | ndarray causal_diff.rs:143-169 | +| `MailboxSoA` owner lives in `cognitive-shader-driver` | **[G]** | soa_view.rs:8 | +| KausalSpec DO-enforcement runtime (fires odoo pragmatics on commit) | **[ABSENT]** | OGAR state_machine in ractor_actors, not wired | +| surreal_container SurrealQL read glove | **[BLOCKED(C)]** | surreal_container/src/lib.rs:30 (fork coords) | + +## 2. Maps + +### MAP 1 — the round-trip (the whole point) + +``` + WRITE (project = encode) READ-BACK (decompile = decode) + Vec Vec + │ intern (s,p,o) → ids (dict) ▲ dict reverse: ids → (s,p,o) + │ quantize (f,c) → NARS bits │ dequantize NARS → (f,c) + ▼ │ + CausalEdge64 u64 + class_id u16 ──────────┘ + │ lay out via MAILBOX_COLUMNS + ▼ + SoaEnvelope LE bytes ── commit_event (sole-writer, tick version) ──► Lance + + roundtrip_eq: in(s,p,o) == out(s,p,o) [exact, truth_tolerance ignored for identity] + in(f,c) ≈ out(f,c) [tol = 1/1023, the CausalEdge64 NARS grid] + PASS ⟹ substrate sound for this cycle | FAIL ⟹ the brittle contract, NAMED +``` + +### MAP 2 — SoA column → SPO role → cold byte → read-back + +| MailboxSoaView accessor | LE member | name_id | ColumnKind | SPO role | round-trips? | +|---|---|---|---|---|---| +| `edges_raw() -> &[u64]` | `CausalEdge64` | Edge=1 | U64 | **(S,P,O) + NARS** — one packed triple | **exact** (s,p,o) / **±1/1023** (f,c) | +| `class_id() -> &[u16]` | `EntityTypeId` | ClassId=3 | U16 | subject's **class** (OGIT/OGAR shape) | **exact** | +| `meta_raw() -> &[u32]` | `MetaWord` | Meta=2 | U32 | thinking/awareness modal (not SPO identity) | **exact** | +| `energy() -> &[f32]` | spatial-temporal accum | Energy=0 | F32 | not SPO — provenance scalar | **exact** (bit) | +| (excluded) fingerprint tensor | CAM-PQ | — | — | lives in ShaderBus ONLY | **NO** → ρ-cert | + +### MAP 3 — failure-mode → brittle contract (the diagnostic value) + +| roundtrip_eq failure | What broke | Layer | +|---|---|---| +| `missing/extraneous (s,p,o)` ≠ 0 | `CausalEdge64` pack/unpack OR `SymbolDict` not a bijection | member-LE **or** register | +| `(f,c)` off by > 1/1023 | NARS quantization grid mismatch (two CausalEdge64s disagree) | member-LE | +| `verify_layout()` `StrideMismatch`/`ColumnOverlap`/`OutOfBounds` | `MAILBOX_COLUMNS` descriptor table wrong | container-LE (envelope) | +| `PacketSizeMismatch` | `as_le_bytes().len()` ≠ stride×rows (torn snapshot / wrong N) | container-LE | +| `LayoutVersionMismatch` | v1 packet under v2 reader | I-LEGACY-API version gate | + +### MAP 4 — three layers, two witnesses (do NOT conflate) + +| Layer | Member | Witness | Equality? | +|---|---|---|---| +| exact-LE members | `CausalEdge64`/`EpisodicEdges64`/`MetaWord` `to_le_bytes` | `roundtrip_eq` tol=0 | yes (proven sound) | +| container envelope | `SoaEnvelope` geometry (zero impls today) | `roundtrip_eq` once P1 lands | yes (provable) | +| lossy codec | CAM-PQ fingerprint (ρ<1.0 by design) | Pearson/Spearman ρ (certification-officer) | **no — tolerance/correlation, NOT round-trip** | + +### MAP 5 — THINK/DO (Semantik/Pragmatik) both round-trip as triples + +``` + Class (shape, subClassOf) ──► triples: (subj rdf:type ogit:ObjectType), (field depends_on …) THINK + ActionDef (DO, object_class→Class, OdooMethodKind, KausalSpec) ──► triples: (amount_total emitted_by _compute_amount) DO + │ │ + └─────────────── both are just Triples in the projection ─────────────────┘ + ⟹ round-trip proves the DO-*declarations* survive the write — WITHOUT needing the + [ABSENT] KausalSpec runtime that *fires* them. Harden now; enforce later. +``` + +## 3. Type spec (compiles in the head) + +### P0 — contract, zero-dep: the mailbox's canonical column catalogue + +```rust +// crates/lance-graph-contract/src/soa_envelope.rs (ADD — enum + const only, no deps) + +/// Stable per-row column identity for the mailbox SoA envelope. +/// Discriminant == ColumnDescriptor.name_id. Append-only (I-LEGACY-API). +#[repr(u16)] +pub enum MailboxColumn { Energy = 0, Edge = 1, Meta = 2, ClassId = 3 } +// future (deferred-accessor pattern, soa_view.rs:71-95): Qualia = 4, EpisodicWitness = 5 + +/// Canonical packed geometry. Physical order = u64-alignment-first; name_id = logical id. +/// summed = 8+4+4+2 = 18 = stride (verify_layout() asserts this). +/// If alignment padding is ever needed, model it as an explicit `Pad` U8 column so +/// `summed == stride` still holds — never an implicit gap. +pub const MAILBOX_COLUMNS: [ColumnDescriptor; 4] = [ + ColumnDescriptor { name_id: 1, kind: ColumnKind::U64, elems_per_row: 1, row_offset: 0 }, // Edge [0,8) + ColumnDescriptor { name_id: 0, kind: ColumnKind::F32, elems_per_row: 1, row_offset: 8 }, // Energy [8,12) + ColumnDescriptor { name_id: 2, kind: ColumnKind::U32, elems_per_row: 1, row_offset: 12 }, // Meta [12,16) + ColumnDescriptor { name_id: 3, kind: ColumnKind::U16, elems_per_row: 1, row_offset: 16 }, // Class [16,18) +]; +pub const MAILBOX_ROW_STRIDE: usize = 18; +``` + +### P1 — cognitive-shader-driver: SoaEnvelope over the real backing store (zero-copy) + +```rust +impl SoaEnvelope for MailboxSoA { + fn columns(&self) -> &[ColumnDescriptor] { &MAILBOX_COLUMNS } + fn row_stride(&self) -> usize { MAILBOX_ROW_STRIDE } + fn n_rows(&self) -> usize { self.n_rows() } // MailboxSoaView::n_rows + fn cycle(&self) -> u32 { self.current_cycle() } // MailboxSoaView::current_cycle + fn as_le_bytes(&self)-> &[u8] { self.backing_le_slice() } // R1: pointer INTO store, NO re-encode +} +// DoD assert (mirrors soa_view.rs:222): as_le_bytes().as_ptr() == backing.as_ptr() +``` + +### P2 — lance-graph: the cognitive write AS a lossless projection + +```rust +// crates/lance-graph/src/graph/cognitive_write.rs (NEW — has causal-edge + contract deps) +use lance_graph_contract::codegen_spine::{Triple, TripletProjection}; +use lance_graph_contract::soa_envelope::{ColumnDescriptor, MAILBOX_COLUMNS, MAILBOX_ROW_STRIDE}; + +pub struct CognitiveWriteProjection; + +/// The const form: the committed cold packet (structural) + the IRI↔id bijection (symbolic). +/// Splitting these SEPARATES the two failure modes (MAP 3). +#[derive(Clone)] +pub struct CognitiveCommitPacket { + pub envelope: Vec, // SoaEnvelope::as_le_bytes() snapshot — exact-LE substrate + pub columns: Vec, + pub row_stride: usize, + pub n_rows: usize, + pub cycle: u32, + pub dict: SymbolDict, // register, NOT VSA (I-VSA-IDENTITIES Test 0) +} + +/// IRI ↔ dense-id bijection. A HashMap/BTreeMap because (s,p,o) are exact-match keys — +/// the register, not a fingerprint bundle (I-VSA-IDENTITIES: "lazy VSA check"). +#[derive(Clone, Default)] +pub struct SymbolDict { iri_to_id: std::collections::BTreeMap, id_to_iri: Vec } + +impl TripletProjection for CognitiveWriteProjection { + type Const = CognitiveCommitPacket; + + /// CausalEdge64 stores NARS as 10-bit freq/conf ×1023 (ndarray causal_diff.rs:143-169). + /// GROUNDING TODO @P2: confirm crates/causal-edge CausalEdge64 uses the SAME grid; + /// if it differs, set this to that crate's LSB. A mismatch here is itself a finding. + fn truth_tolerance() -> f32 { 1.0 / 1023.0 } + + // project: for each Triple → intern (s,p,o)→ids, pack CausalEdge64(s,p,o, f*1023, c*1023) + // → edges_raw u64; class_id = subject's class id; lay out via MAILBOX_COLUMNS. + fn project(triples: &[Triple]) -> CognitiveCommitPacket { /* encode */ unimplemented!() } + + // decompile: walk edges_raw u64 → CausalEdge64::from(raw) → (s_id,p_id,o_id, freq,conf) + // → dict reverse → IRIs, NARS/1023 → (f,c) → Triple. + fn decompile(c: &CognitiveCommitPacket) -> Vec { /* decode */ unimplemented!() } +} +``` + +## 4. Phase plan (each phase = one landable PR, DoD-gated) + +| Phase | Crate | Deliverable | DoD | Invariant respected | +|---|---|---|---|---| +| **P0** | lance-graph-contract | `MailboxColumn` ordinals + `MAILBOX_COLUMNS` + `MAILBOX_ROW_STRIDE` | a `verify_layout()` unit test over the const table is green; **no new deps**; clippy/fmt clean | BBB zero-dep | +| **P1** | cognitive-shader-driver | `impl SoaEnvelope for MailboxSoA` (zero-copy) | `as_le_bytes().as_ptr() == backing.as_ptr()`; `verify_layout()` green; round-trips a hand-built SoA | R1 (one SoA never transformed) | +| **P2** | lance-graph | `CognitiveWriteProjection : TripletProjection` + `SymbolDict` | `roundtrip_eq` PASSES on the `account.move` fixture; a deliberately-corrupted CausalEdge64 pack FAILS it (negative test, cf. `LossyDropFrequency`); (f,c) within 1/1023; **grounding-TODO closed** (causal-edge NARS grid confirmed) | I-VSA-IDENTITIES (dict=register; codec excluded) | +| **P3** | lance-graph-callcenter | `project_graph` sibling of `ExternalMembrane::project` → emits `NodeRecord`/`EdgeRecord` through `commit_event` + `CommitFilter` + `MembraneGate` | a committed cycle is queryable as nodes/edges via `MetadataStore` (DataFusion); version ticks; RBAC/tenant fan-out applies | sole-writer; gate unchanged | +| **P4** | callcenter + shader-driver | auto-fire on Rubicon `Committed` (`try_advance_phase` → `project_graph` → `commit_event`), with `roundtrip_eq` as the commit's own debug-gate | advancing a mailbox to `Committed` emits the graph commit; **every commit runs the round-trip assertion** (debug build) — a lossy commit panics in test, is logged in release | I-SUBSTRATE-MARKOV (read→owned→gated write) | +| **P5** | surreal_container | SurrealQL read glove over the committed envelope (nodes/edges) | DEFERRED | — | + +**P5 is the ONLY fork-gated phase.** P0–P4 need no surrealdb coords. P5 is +**BLOCKED(C)** on the `AdaWorldAPI/surrealdb` fork git URL + branch/tag + `kv-lance` +feature name — a human input (P0 rule: do not guess fork coords). + +## 5. Invariants & risk register + +- **BBB zero-dep:** P0 is enum + const ONLY in the contract crate. The projection + (P2) needs `causal-edge` → it lives in `lance-graph` (which has both deps), NEVER + in the contract. `SymbolDict` uses `BTreeMap` (std), no alloc-heavy dep. +- **R1 (one SoA never transformed):** P1's `as_le_bytes()` returns a borrow into the + backing store. If it ever `.to_vec()`s, R1 is violated and the "commit = visibility + flip, not a copy" property is lost. The pointer-equality DoD assert is the guard. +- **I-VSA-IDENTITIES:** the `SymbolDict` is the register (exact-match lookup). The + CAM-PQ fingerprint is EXCLUDED from the packet — bundling identities not content. + If a future hand reaches for VSA-cosine to resolve an IRI, that's register laziness. +- **I-SUBSTRATE-MARKOV:** the write path stays single-writer-gated. `project_graph` + READS the SoA (`MailboxSoaView`, `&self`), builds an OWNED packet, and `commit_event` + is the gated write-back. No `&mut` during compute; no second writer (the three-wall + result). A SurrealQL `UPDATE` against the live mailbox remains **[REJECT]**. +- **I-LEGACY-API version gate:** `ENVELOPE_LAYOUT_VERSION` (=1) is stamped; a v1 packet + under a v2 reader is refused by `verify_layout()` before mis-decode. Reclaiming a + `name_id` requires a version bump + field-isolation tests. + +## 6. The decoupling (why this is buildable NOW) + +The odoo DOs are **declared as triples** (`emitted_by`/`depends_on` edges). The +round-trip proves *those declaration edges survive the write* — it does **not** +require the `KausalSpec` enforcement runtime (**[ABSENT]**) that *fires* the compute/ +validate at commit time. So: **harden the substrate now** (P0–P4, round-trip the +declarations) and **build the firing runtime later** — they do not block each other. +"Shape inherits; behavior composes; and the *declarations* of both are round-trippable +before either *executes*." + +## 7. Provenance (grounded this session) + +codegen_spine.rs:74,107-183 · soa_envelope.rs:54-97,139 · soa_view.rs:8,28-105,112,222 · +lance_membrane.rs:315,356,381,415-429 · external_intent.rs:113 · version_watcher.rs · +ndarray causal_diff.rs:143-169 · surreal_container/src/lib.rs:30 · sla.rs:31-101. From cbd99e4a31d75e2cbc076ac58bab839917f24755 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 11:02:02 +0000 Subject: [PATCH 2/3] plans: identity architecture exists-vs-needs map + integration plan (v1) Grounded map of the structured 128-bit identity (UUIDv8 = HHTL nibble-address formalized) against the existing substrate, from first-hand reads + two cross-repo sweeps. Four findings: (1) the 128-bit identity space is empty (no committed u128/Uuid/[u8;16]-as-id); (2) every GUID FIELD already exists as a committed scalar -> compose, do not re-invent (SchemaPtr + NiblePath + StructuralSignature + EdgeRef); (3) the cross-store transport is already solved by EntityKey(&[u8]) -- smb-bridge key_to_filter already length-branches; (4) the cold path has no stable structured identity today (node_id:u32 + String props, SpoStore u64 dn_hash) -- the identity fills a real gap. 6-layer exists inventory + 7 build gaps (N1-N7 unblocked, N8 surreal BLOCKED on fork coords) + phased plan A-H. Substrate is ~80% present; the work is composition/wiring, not green-field. One open decision: SchemaPtr.entity_type vs NiblePath-prefix as the class carrier. https://claude.ai/code/session_014A4JuRCqKP2yNENrQ9Ha7H --- ...dentity-architecture-exists-vs-needs-v1.md | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 .claude/plans/identity-architecture-exists-vs-needs-v1.md diff --git a/.claude/plans/identity-architecture-exists-vs-needs-v1.md b/.claude/plans/identity-architecture-exists-vs-needs-v1.md new file mode 100644 index 00000000..3378f1bd --- /dev/null +++ b/.claude/plans/identity-architecture-exists-vs-needs-v1.md @@ -0,0 +1,218 @@ +# Identity Architecture — What Exists vs What Needs Building (v1) + +> **Status:** INTEGRATION MAP + PLAN. Grounded by first-hand reads + two parallel +> cross-repo sweeps (2026-06-09). Companion to +> `cognitive-write-roundtrip-substrate-v1.md` (the round-trip mechanism). +> **Branch:** `claude/nice-edison-g4rhhl`. + +## Thesis + +The hot path should carry a lean **128-bit structured immutable identity** (a +UUIDv8 = the HHTL nibble-address *formalized + namespaced*); heavy content stays +in consumer stores keyed by it. The identity does five jobs as register reads of +one object: **resolve** (class-from-address), **route** (delegate switch), +**witness** (immutable audit + merkle), **ground-truth** (shape_hash drift), and +**dispatch-to-store** (EntityKey → consumer). This doc maps what already exists +against what must be built, and phases the integration. + +## Four headline findings (grounded) + +1. **The 128-bit identity space is empty** — no committed `u128`/`Uuid`(binary)/ + `[u8;16]`-as-id exists (the single `[u8;16]`, `atoms.rs:74 I4x32`, is a + thinking-style vector, doc-confirmed *not* an identity). A new GUID won't + byte-collide. *(Agent A sweep, lance-graph + ndarray.)* + +2. **But every GUID FIELD already exists as a committed scalar** → the iron + mandate is **compose existing fields, do NOT re-invent**: `namespace` = + `NamespaceId(u8)` inside `SchemaPtr.packed:u32 = [ns:8|entity_type:16|kind:8]`; + `class/address` = `NiblePath` + `ClassId(u16)` + `EdgeRef{family:u8,local:u16}`; + `shape_hash` = `StructuralSignature`; `local` = `EdgeRef.local`. A parallel + re-pack duplicates ratified discriminators (`OD-CLASSID-WIDTH`, + `I-VSA-IDENTITIES`). *(Agent A finding #2.)* + +3. **The cross-store transport is ALREADY solved** — `EntityKey<'a>(pub &'a [u8])` + (repository.rs:12) is an opaque length-agnostic key both consumer repos use; + `smb-bridge::key_to_filter` already branches on length (12→ObjectId, else→ + `Bson::Binary`) on Mongo *and* Lance. A 16-byte GUID is "just another length" + the tested plumbing handles. *(Agent B sweep.)* + +4. **The cold path has NO stable structured identity today** — it keys nodes by + bare `node_id:u32` (no edge id; `String` label + `HashMap` + props), the SPO hot path keys by a `u64` *content* `dn_hash` (not stable), + `CogRecord` carries no id ("id is the external dn_hash"), and durable identity + is ad-hoc `Uuid→String` (learning crate) + `OgitUri(String)`. **The structured + identity fills a real gap** — provided it *subsumes* `SchemaPtr` + `EdgeRef`, + never parallels them. *(Agent A finding #3.)* + +## WHAT EXISTS — grounded inventory (6 layers, file:line) + +### Layer 0 — address / discriminator scalars (the GUID's fields) +| Type | Width | Role | Status | Evidence | +|---|---|---|---|---| +| `NiblePath{path:u64,depth:u8}` | 72 | HHTL tree address (basin/child/is_ancestor_of, 16ⁿ) | **[G]** | hhtl.rs | +| `SchemaPtr{packed:u32=[ns:8\|entity_type:16\|kind:8], ctx:u32}` | 64 | schema/type pointer | **[G]** | namespace.rs:119 | +| `NamespaceId(u8)` | 8 | OGIT namespace ordinal | **[G]** | namespace.rs:24 | +| `ClassId = u16` | 16 | per-row shape discriminator ("never a content hash") | **[G]** | class_view.rs:53 | +| `EntityTypeId = u16` | 16 | per-row object-type (Palantir) | **[G]** | ontology.rs:81 | +| `FieldMask(u64)` + `inherit` | 64 | presence bitmask, parent-OR-delta | **[G]** | class_view.rs:69,136 | +| `StructuralSignature` (shape_hash) | hash | "deterministic hash over property-id set" | **[G] type / [H] live-wire** | odoo_blueprint::class_signature | +| `EdgeRef{family:u8,local:u16}` | 24 | episodic HHTL family+local address | **[G]** | episodic_edges.rs:34 | + +### Layer 1 — edge / handoff carriers (the LE "sound members") +| Type | Width | Role | Status | +|---|---|---|---| +| `EpisodicEdges64(u64)` = 4×EdgeRef, MRU promote/evict, `to_le_bytes` | 64 | AriGraph episodic edges | **[G]** episodic_edges.rs | +| `CausalEdge64(u64)` (NARS 10+10 ×1023) | 64 | baton/causal edge payload | **[G]** ndarray causal_diff.rs:153 | +| Baton `(target:u16, edge:u64)` | 80 | inter-mailbox handoff | **[G]** collapse_gate.rs:235 | +| `MailboxId=u32`, `MailboxRow{mailbox_ref:u32,row_idx:u32}` | 32/64 | mailbox + row address | **[G]** | + +### Layer 2 — cold-path stores (TODAY: thin + inconsistent) +| Store | Key | Status | +|---|---|---| +| `MetadataStore`: `NodeRecord{node_id:u32, label:String, properties:HashMap}`, `EdgeRecord{source:u32,target:u32,edge_type:String}` | u32 + **STRING label/props (legacy Cypher)** | **[G]** metadata.rs:60,86 | +| `SpoStore`: `HashMap` | u64 **content-hash** (not stable id) | **[G]** spo/store.rs:38 | +| ndarray `CogRecord{meta,cam,btree,embed}` | **no id** ("id is external dn_hash") | **[G]** cogrecord.rs:56 | +| `WitnessId(u64)` (arigraph witness) | 64 opaque handle | **[G]** witness_corpus.rs:63 | + +### Layer 3 — resolution (class-from-address) +| Surface | Status | +|---|---| +| `RegistryClassView: ClassView` (fields/template/dolce_category_id) | **[G] resolve / [H] field-enum deferred** class_resolver.rs | +| `OntologyRegistry`: `resolve_uri`, `enumerate_first_with_entity_type_id(u16)`, `resolve_iri_in` | **[G]** registry.rs | + +### Layer 4 — commit + witness (the membrane) +| Surface | Status | +|---|---| +| `SoaEnvelope` trait + `ColumnDescriptor` (container-LE geometry) | **[G] trait / [H] ZERO impls** soa_envelope.rs | +| `MailboxSoaView`/`MailboxSoaOwner` (read airgap + Rubicon `try_advance_phase`) | **[G]** soa_view.rs | +| `commit_event` sole-writer + `ExternalMembrane::project` + `CommitFilter`/`MembraneGate` | **[G]** lance_membrane.rs:315 | +| `CognitiveEventRow` (scalar audit event — VSA stripped) | **[G]** external_intent.rs:113 | +| `MerkleRoot(u64)` ×3 (audit/SPO/unified) + `AuditSink` (jsonl/lance) | **[G]** audit_sink/, merkle.rs | +| `SlaPolicy`, `TenantScope` | **[G] types** sla.rs | + +### Layer 5 — cross-store transport (the consumer boundary) +| Surface | Status | +|---|---| +| `EntityKey<'a>(pub &'a [u8])` — opaque length-agnostic key | **[G]** repository.rs:12 | +| `EntityStore`/`EntityWriter`/`Batch` traits | **[G]** repository.rs | +| `smb-bridge`: implements both for Mongo+Lance, `key_to_filter` length-branch | **[G]** smb-bridge/mongo.rs:79, lance.rs:92 | +| MedCare-rs: MySQL i64 PKs; DMS `sha256`(NOT NULL)+`storage_key`; imports EntityKey | **[G]** dms.rs:14, graph_contract.rs:31 | +| smb-office-rs: Mongo `ObjectId`(12B) + `String` refs; actively impls repository | **[G]** base.rs:92 | + +### Layer 6 — round-trip / substrate-hardening +| Surface | Status | +|---|---| +| `TripletProjection` trait + `roundtrip_eq` → `RoundTripFailure` | **[G]** codegen_spine.rs:107 | +| cognitive-write projection (mailbox SoA → SPO+edges) | **[H] does not exist** | + +## WHAT NEEDS BUILDING — 7 gaps (each: what it REUSES [G] + what it ADDS [H]) + +| # | Gap | Reuses (exists [G]) | Adds [H] | Blocked? | +|---|---|---|---|---| +| **N1** | **`NodeGuid`/`EdgeGuid`** 128-bit identity type | `SchemaPtr` ⊕ `NiblePath` ⊕ `StructuralSignature` ⊕ `EdgeRef.local` | the UUIDv8 composition + layout version + the 5 readings | no | +| **N2** | wire `StructuralSignature` into live `RegistryClassView` | `StructuralSignature` type, `ClassView` | the field-enum from `MappingRow` (the deferred D-CLS audit) | no | +| **N3** | `SoaEnvelope` **implementor** for `MailboxSoA` | `SoaEnvelope` trait, `MailboxSoaView` | the zero-copy impl (mailbox bytes == cold bytes) | no | +| **N4** | cognitive-write `TripletProjection` + `roundtrip_eq` | `TripletProjection`, `EpisodicEdges64`/`CausalEdge64` `to_le_bytes` | the project/decompile over the identity graph | no | +| **N5** | `project_graph` emitter through the gate | `commit_event`, `CommitFilter`/`MembraneGate`, `ExternalMembrane` | the node/edge projection (today emits scalar `CognitiveEventRow`) | no | +| **N6** | **`MetadataStore` string→identity migration** | `MetadataStore`, `EntityKey` | `NodeRecord`/`EdgeRecord` keyed by `NodeGuid` not `String` label/props | no (I-LEGACY-API gated) | +| **N7** | GUID-as-`EntityKey` wiring + MedCare `external_ref` | `EntityKey`, `EntityStore`/`EntityWriter`, smb `key_to_filter` | pass 16-byte key + **one** MedCare column (or reuse `sha256`) | no | +| **N8** | surreal_container SurrealQL read glove | `surreal_container` skeleton | the kv-lance read path | **BLOCKED(C)** fork coords | + +**Only N8 is blocked.** N1-N7 need no surrealdb coords. + +## N1 — the identity type as a COMPOSITION (the iron mandate from Agent A #2) + +```rust +// crates/lance-graph-contract/src/identity.rs (NEW, zero-dep) +// EVERY field is an existing committed type. No re-invention. + +/// 128-bit immutable structured node identity (UUIDv8, RFC 9562). +/// Frozen at write; the class is RE-RESOLVED from the address (never stored mutable). +#[repr(C, align(16))] +pub struct NodeGuid([u8; 16]); +// bits 0..32 : SchemaPtr.packed [ns:8 | entity_type:16 | kind:8] ← REUSE namespace.rs:119 +// bits 32..74 : NiblePath prefix (path bits + small depth; ver nibble carved at 48..52) +// bits 74..98 : StructuralSignature (shape_hash, truncated) ← REUSE odoo_blueprint +// bits 98..122 : local instance (EdgeRef.local widened) ← REUSE episodic_edges +// bits 48..52 : version = 8 · bits 64..66 : variant = 10 ← RFC 9562 reserved (6 b) + +/// 128-bit edge identity: source address ⊕ the episodic EdgeRef. +#[repr(C, align(16))] +pub struct EdgeGuid([u8; 16]); +// = [ source SchemaPtr/NiblePath | EdgeRef{family:u8, local:u16} | shape_hash ] ← REUSE EpisodicEdges64 +``` + +**The five readings (register reads of one key):** +- **resolve** `guid.schema_ptr() → entity_type → ClassView` (class-from-address, O(1) bit-shift + cache) +- **route** `guid.niblepath().is_ancestor_of(...)` → delegate switch (HHTL bit-shift, through `OrchestrationBridge`) +- **witness** frozen `[u8;16]` + `MerkleRoot` chain (immutable, examined-in-place) +- **ground-truth** `guid.shape_hash() != resolve(addr).shape_hash_now` → drift (read-time diff) +- **dispatch-to-store** `EntityKey(guid.as_bytes())` → consumer (Layer-5 transport, already [G]) + +**Immutability law (ratified this session):** `class_id` never updates — it's the +lineage id, re-resolved from the address for free; the GUID is write-once; drift +*repair* is a **new immutable version** (Lance is versioned), never an in-place +mutation. `I-VSA-IDENTITIES` Test 0: the GUID is a register key (points to +content), never VSA-bundled. + +### ⚠ One open DECISION (yours to pin — both grounded, bijective) +The class can be carried two ways; pick the **stored** form, resolve the other: +- **(D1) `SchemaPtr.entity_type:u16`** — reuse the existing dense pointer (Agent A "compose existing"). Compact, exact. +- **(D2) `NiblePath` prefix** — identity-IS-address (ADR-1374, your "nibble = the GUID class"). O(1) ancestry-routing without a cache hit. +- **Recommendation:** store **SchemaPtr (exact) + a truncated NiblePath prefix (for routing)** — SchemaPtr resolves deep paths exactly; the prefix gives branchless `is_ancestor_of`. Costs ~42 bits for the prefix; worth it for probe-free routing. + +## Phased integration plan (A→H; each phase = one landable PR) + +| Phase | Gap | Crate | Deliverable | DoD | Dep | +|---|---|---|---|---|---| +| **A** | N1 | contract | `NodeGuid`/`EdgeGuid` as composition of existing fields + layout version | byte-decompose round-trips to `SchemaPtr`/`NiblePath`/`StructuralSignature`/`local`; UUIDv8 validates; zero-dep; clippy/fmt | — | +| **B** | N2 | ontology | wire `StructuralSignature` → `RegistryClassView` (enumerate field-set from `MappingRow`) | `shape_hash(class_id)` returns a stable signature; the deferred D-CLS field-enum closed | A | +| **C** | N3 | shader-driver | `impl SoaEnvelope for MailboxSoA` (zero-copy) | `as_le_bytes().as_ptr()==backing`; `verify_layout()` green | — | +| **D** | N4 | lance-graph | cognitive-write `TripletProjection` + `roundtrip_eq` over the identity graph | passes the `account.move` fixture; corrupt-pack fails; (f,c) within 1/1023 | A, C | +| **E** | N5 | callcenter | `project_graph` (node/edge emitter) through `commit_event`+gate | committed cycle queryable as `NodeGuid` nodes + `EdgeGuid` edges; version ticks; RBAC applies | A, D | +| **F** | N6 | lance-graph core | `MetadataStore` string→identity: `NodeRecord`/`EdgeRecord` keyed by `NodeGuid` (label/props → resolved-from-identity) | old string path feature-gated/migrated; field-isolation tests (I-LEGACY-API); query parity | A, B, E | +| **G** | N7 | consumers | GUID-as-`EntityKey`(16B) + MedCare `external_ref` (or `sha256` reuse) | smb: 16-byte key resolves via existing `key_to_filter`; MedCare: GUID→row reverse lookup | A | +| **H** | N8 | surreal_container | SurrealQL read glove | DEFERRED — **BLOCKED(C)** fork coords | E | + +**Critical path:** A → (B, C) → D → E → F. G hangs off A (parallel). H is gated. +**Smallest unblocked first brick:** Phase A (the `NodeGuid` composition, zero-dep contract) OR Phase C (the `SoaEnvelope` impl) — both leaf, both needed by D. + +## Honest ledger + +- **[G] (exists, reuse):** all 6 layers above — `NiblePath`, `SchemaPtr`, `ClassId`, + `StructuralSignature` (type), `EdgeRef`, `EpisodicEdges64`/`CausalEdge64` LE, + `commit_event`+gate, `MerkleRoot`+`AuditSink`, `SlaPolicy`/`TenantScope`, + `EntityKey`+`EntityStore`/`EntityWriter`, `TripletProjection`. **The substrate is + ~80% present.** +- **[H] (build):** N1-N7 — but each is a *composition/wiring* of [G] parts, not a + green-field invention. The largest is N6 (cold-path string→identity migration). +- **[BLOCKED(C)]:** N8 only (surrealdb fork coords — human gate; lance-graph P0 + "STOP and ask"). +- **One open [DECISION]:** D1 vs D2 (SchemaPtr-entity_type vs NiblePath-prefix as + the class carrier) — recommendation: both (exact + routing prefix). + +## Guards (iron rules this plan must not violate) + +- **I-VSA-IDENTITIES:** the GUID is a register key that POINTS TO content; never + VSA-bundle it, never intern open content (only the closed vocabulary). Identities + intern; scanned papers / free text stay in consumer stores (Layer 5). +- **Compose, don't parallel (Agent A #2):** N1 MUST subsume `SchemaPtr` + + `EdgeRef`, not re-pack ns/class/family beside them. +- **I-LEGACY-API-FEATURE-GATED:** N6's string→identity layout reclaim needs a + version gate + field-isolation matrix tests. +- **Sole-writer / no-&mut-during-compute:** N5 reads SoA (`&self`), builds owned + identity rows, `commit_event` is the gated write-back; drift *repair* is a new + version, never in-place mutation (the immutability law). +- **AGI-as-SoA:** the GUID is per-NODE at the membrane, NOT a 16-byte-per-row SoA + column (the hot SoA keeps its lean `u16 class_id`). + +## Provenance + +First-hand reads (2026-06-09): hhtl.rs · soa_envelope.rs · soa_view.rs · +class_resolver.rs · class_view.rs · episodic_edges.rs · metadata.rs:60-94 · +registry.rs · namespace.rs · wikidata_hhtl.rs · lance_membrane.rs:315-429 · +external_intent.rs:113 · sla.rs · codegen_spine.rs · atoms.rs:74 · audit_sink/. +Cross-repo sweeps: Agent A (lance-graph + ndarray identity-type inventory) · +Agent B (MedCare-rs + smb-office-rs store keys — `EntityKey`, MySQL i64 / Mongo +ObjectId, DMS `sha256`/`storage_key`). Companion: +`cognitive-write-roundtrip-substrate-v1.md`. From 947c1e4029e6c2739b5969c6fa9dc60605b33d1e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Jun 2026 12:31:38 +0000 Subject: [PATCH 3/3] =?UTF-8?q?feat(contract):=20NodeGuid=20=E2=80=94=20st?= =?UTF-8?q?ructured=20128-bit=20identity=20(Phase=20A,=20D-IDENTITY-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace's first stable binary instance identity: a UUIDv8 (RFC 9562) that formalizes + namespaces the HHTL nibble-address. Composed entirely from existing committed scalars (the 128-bit id space was empty per the Agent A sweep): SchemaPtr.packed convention (ns8|entity_type16|kind8) + a truncated NiblePath routing prefix + a 22-bit shape_hash + a 24-bit local, with the UUIDv8 version/variant at their RFC-fixed positions and a layout-version stamp. Eineindeutigkeit (ratified): entity_type:u16 is the canonical exact class identity; the NiblePath prefix is the bijective DERIVED view (a truncated prefix can't be the identity -- deep classes collide past it; the prefix is_ancestor_of the full path, tested). Five readings: resolve / route / witness / ground-truth / dispatch-to-store (as_bytes -> EntityKey). - new: identity.rs (NodeGuid + 15 tests: field-isolation matrix, UUIDv8 version/variant gates, ancestor-prefix invariant, canonical-UUID Display). - new: NiblePath::from_packed (inverse of packed()). - 599 contract lib tests (+15), clippy -D warnings clean, fmt clean. - board: LATEST_STATE inventory, EPIPHANIES E-IDENTITY-WHITEBOX-1, AGENT_LOG, decision-1 RESOLVED in the exists-vs-needs plan. https://claude.ai/code/session_014A4JuRCqKP2yNENrQ9Ha7H --- .claude/board/AGENT_LOG.md | 9 + .claude/board/EPIPHANIES.md | 36 ++ .claude/board/LATEST_STATE.md | 2 + ...dentity-architecture-exists-vs-needs-v1.md | 12 +- .../examples/cognitive_cycle.rs | 62 ++- .../examples/savant_dispatch.rs | 56 ++- crates/lance-graph-contract/src/atoms.rs | 5 +- .../src/callcenter/mod.rs | 4 +- .../src/callcenter/ogit_uris.rs | 19 +- .../src/callcenter/role_keys.rs | 10 +- .../src/cognition/advance.rs | 2 +- .../src/counterfactual.rs | 6 +- .../src/episodic_edges.rs | 22 +- crates/lance-graph-contract/src/escalation.rs | 18 +- crates/lance-graph-contract/src/head2head.rs | 41 +- crates/lance-graph-contract/src/hhtl.rs | 23 +- crates/lance-graph-contract/src/identity.rs | 397 ++++++++++++++++++ crates/lance-graph-contract/src/lib.rs | 4 +- crates/lance-graph-contract/src/mul.rs | 353 +++++++++++----- crates/lance-graph-contract/src/nars.rs | 5 +- .../src/pearl_junction.rs | 42 +- crates/lance-graph-contract/src/plan.rs | 2 +- crates/lance-graph-contract/src/recipes.rs | 390 +++++++++++++++-- crates/lance-graph-contract/src/savants.rs | 306 ++++++++++++-- crates/lance-graph-contract/src/scheduler.rs | 61 ++- .../lance-graph-contract/src/soa_envelope.rs | 34 +- .../src/transaction/ctx.rs | 7 +- .../lance-graph-contract/src/witness_table.rs | 5 +- 28 files changed, 1650 insertions(+), 283 deletions(-) create mode 100644 crates/lance-graph-contract/src/identity.rs diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 70e5c051..93437a27 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,12 @@ +## 2026-06-09 — D-IDENTITY-1 (Phase A) + 2 cross-repo sweeps — identity-architecture + +**Orchestrator:** Opus main thread (autoattended). **Outcome:** Shipped Phase A. +- **Sweep A** (Opus general-purpose): lance-graph + ndarray identity-type inventory → the 128-bit identity space is EMPTY (only `[u8;16]` is `atoms::I4x32`, a style vector); every GUID field already exists as a committed scalar → compose-don't-reinvent. +- **Sweep B** (Opus general-purpose): MedCare-rs + smb-office-rs store keys → `EntityKey(&[u8])` already carries any-length keys (smb-bridge `key_to_filter` length-branches on Mongo+Lance); transport solved. MedCare needs one `external_ref` (or reuse DMS `sha256`); smb maps directly. +- **Phase A:** `lance_graph_contract::identity::NodeGuid` (UUIDv8, composed from SchemaPtr⊕NiblePath⊕StructuralSignature⊕local) + `NiblePath::from_packed`. 599 contract lib tests (+15), clippy `-D` clean, fmt clean. + +Plans: `identity-architecture-exists-vs-needs-v1.md`, `cognitive-write-roundtrip-substrate-v1.md`. Epiphany: E-IDENTITY-WHITEBOX-1. + ## [Opus 4.8, main thread] cesium-osm-substrate-v1 review fix — D-OSM-2 crate boundary (codex P2 on merged #473) **Branch:** `claude/osm-pbf-consumer-boundary-fix` (off `main`). **New follow-up PR** (merged #473 review-fix, surfaced for visibility per user request; the other session owns the OGAR-side fixes). diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 4b08187f..66c76b7d 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,39 @@ +## 2026-06-09 — E-IDENTITY-WHITEBOX-1 — structured identity + round-trip converts the substrate from black-box to CI-falsifiable + +**Status:** FINDING (Phase A landed: `identity::NodeGuid` composed, 15 tests green) +**Confidence:** High + +**The synthesis:** a structured 128-bit immutable identity (UUIDv8 = the HHTL +nibble-address formalized + namespaced) PLUS the `roundtrip_eq` guarantee turn the +substrate from a black box into a CI-falsifiable surface. Two structural witnesses: +the **bijection** (`entity_type ↔ NiblePath`, proven eineindeutig at build time) +and **`roundtrip_eq`** (a lossy projection fails CI, not code review). The +round-trip whitens the PLUMBING (identity / LE byte-contract / member-vs-container) +— the darkest, most-expensive-bug layer; it does NOT whiten semantics (needs +ground-truth corpora) or the lossy codec (needs ρ-certification). Those keep their +own witnesses; the trade is "vigilance over the substrate" → "a test over the substrate". + +**Compose, don't re-invent (Agent A sweep):** the 128-bit identity space is empty +(no committed `u128`/`Uuid`/`[u8;16]`-as-id), but every GUID field already exists as +a committed scalar — `SchemaPtr.packed` (ns8|entity_type16|kind8) ⊕ `NiblePath` +prefix ⊕ truncated `StructuralSignature` ⊕ local. `NodeGuid` is their composition. + +**Eineindeutigkeit (ratified):** `entity_type:u16` is the canonical exact class +identity; `NiblePath` is the bijective DERIVED view (a *truncated* prefix can't be +the identity — deep classes collide past it, `hhtl.rs`). The registry mints the +pair 1:1; a build-time bijection round-trip proves it; the GUID prefix-consistency +invariant (`prefix == niblepath_of(entity_type)[..N]`) catches drift. + +**Free side-effects:** the structured GUID is also (a) a KV key via the existing +`EntityKey(&[u8])` (smb-bridge already length-branches it), and (b) a **quadkey** — +Lance ORDER BY the NiblePath gives subtree range-scans = zone-map-pruned byte +ranges, no index (ADR-024 "HHTL prefix establishes a frame"). + +**Landed:** `lance_graph_contract::identity::NodeGuid` (D-IDENTITY-1 / Phase A) + +`NiblePath::from_packed`. 599 contract lib tests (+15), clippy `-D` clean, fmt clean. +Plans: `identity-architecture-exists-vs-needs-v1.md` (exists-vs-needs map), +`cognitive-write-roundtrip-substrate-v1.md` (the round-trip mechanism). + ## 2026-06-06 — E-DEINTERLACE-TWO-SCALES — deinterlace is one operation at two scales; no-cross-cycle-lag = byte-scale deinterlace **Status:** FINDING (source-grounded; `temporal.rs` PR #468 confirms row-scale; byte-scale is a documented gap) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index bdf390b9..e70b8d71 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -54,6 +54,8 @@ ## Current Contract Inventory (lance-graph-contract) +> **2026-06-09 — ADDED (D-IDENTITY-1, Phase A of identity-architecture)**: `lance_graph_contract::identity::{NodeGuid([u8;16]), IDENTITY_LAYOUT_VERSION}` — the workspace's first **stable binary instance identity**: a structured 128-bit UUIDv8 (RFC 9562) = the HHTL nibble-address **formalized + namespaced**. **Composed from existing committed scalars, never re-invented** (Agent A sweep confirmed the 128-bit id space was empty): octets carry `namespace:u8 | entity_type:u16 | kind:u8` (the `SchemaPtr.packed` convention) ⊕ a truncated `NiblePath` routing prefix (`PREFIX_NIBBLES=4`) ⊕ a 22-bit `shape_hash` (truncated `StructuralSignature`) ⊕ a 24-bit `local`, with UUIDv8 version(=8)/variant(=0b10) at their RFC-fixed positions + an `IDENTITY_LAYOUT_VERSION` stamp. **Eineindeutigkeit**: `entity_type` is the canonical exact class identity; the `NiblePath` prefix is the bijective DERIVED view (a *truncated* prefix can't be the identity — deep classes collide past it; the prefix `is_ancestor_of` the full path). Five readings: resolve (`entity_type`) / route (`niblepath`) / witness (frozen bytes + merkle) / ground-truth (`shape_hash` drift) / dispatch-to-store (`as_bytes` → `EntityKey`). Also added `hhtl::NiblePath::from_packed` (inverse of `packed`). Zero-dep; 599 contract lib tests (+15: field-isolation matrix, UUIDv8 gates, ancestor-prefix invariant, Display=canonical-UUID); clippy `-D warnings` clean; fmt clean. Plans: `identity-architecture-exists-vs-needs-v1.md` (exists-vs-needs map + phases A→H), `cognitive-write-roundtrip-substrate-v1.md`. Epiphany: E-IDENTITY-WHITEBOX-1. + > **2026-05-31 — ADDED (D-EW64-1 + D-VIEW-1, episodic-RISC-spine)**: `episodic_edges::{EpisodicEdges64(u64), EdgeRef{family:u8,local:u16}}` — AriGraph episodic edges, 4x[4-bit family | 12-bit local]: family 0 = intra-basin (inherited, ~98.6% per #444), 1..=15 = cross-family index into the OGIT-class-inherited palette (~1.4%; identities inherited, never on the edge — I-VSA-IDENTITIES). Plus `view_angle::ViewAngle` (4-bit view-schema selector; presence bitmask doubles as attention mask, inherited). Zero-dep; 527 contract lib tests; clippy pedantic+nursery clean. Plan: episodic-risc-spine-v1.md. > **2026-05-31 — ADDED (D-H2H-1, head2head superposition winner-select)**: `lance_graph_contract::head2head::{Head2Head (judge), WinnerCriterion (DissonanceMin≈infight / SupportSpread≈Raumgewinn / ConfidenceMax / Tempered=default), CompetitionOutcome}`. `Head2Head::select(&Blackboard) -> Option` picks the winning competing-expert bid over the existing `a2a_blackboard` (confidence/dissonance/support) — pure read + arg-extremum, **no new identity, copies nothing** (select-don't-duplicate, `I-VSA-IDENTITIES`); `margin` = the dark-horse signal. The *selection* half of head2head superposition; parallel-mailbox *execution* is the CI-gated consumer side. Zero-dep; 516 contract lib tests (+7); clippy pedantic+nursery clean. diff --git a/.claude/plans/identity-architecture-exists-vs-needs-v1.md b/.claude/plans/identity-architecture-exists-vs-needs-v1.md index 3378f1bd..f5a2f5b7 100644 --- a/.claude/plans/identity-architecture-exists-vs-needs-v1.md +++ b/.claude/plans/identity-architecture-exists-vs-needs-v1.md @@ -155,11 +155,13 @@ lineage id, re-resolved from the address for free; the GUID is write-once; drift mutation. `I-VSA-IDENTITIES` Test 0: the GUID is a register key (points to content), never VSA-bundled. -### ⚠ One open DECISION (yours to pin — both grounded, bijective) -The class can be carried two ways; pick the **stored** form, resolve the other: -- **(D1) `SchemaPtr.entity_type:u16`** — reuse the existing dense pointer (Agent A "compose existing"). Compact, exact. -- **(D2) `NiblePath` prefix** — identity-IS-address (ADR-1374, your "nibble = the GUID class"). O(1) ancestry-routing without a cache hit. -- **Recommendation:** store **SchemaPtr (exact) + a truncated NiblePath prefix (for routing)** — SchemaPtr resolves deep paths exactly; the prefix gives branchless `is_ancestor_of`. Costs ~42 bits for the prefix; worth it for probe-free routing. +### ✅ DECISION — RESOLVED (eineindeutig / bijective, ratified 2026-06-09; Phase A landed) +**Carry BOTH, bound by an enforced bijection** (`entity_type ↔ NiblePath`), pre-production so it's baked in with zero migration debt: +- **Canonical, exact, in the GUID:** `entity_type:u16` — fixed-width, no truncation. A *truncated* NiblePath prefix CANNOT be bijective (two distinct deep paths collide past the prefix, `hhtl.rs`), so the exact identity MUST be the dense `entity_type`. +- **Derived view:** `NiblePath` — the bijective radix encoding of the SAME class; full depth resolves from the registry; the GUID carries a coarse prefix (`PREFIX_NIBBLES = 4`) as a *derived routing cache* = `niblepath_of(entity_type)` truncated (the prefix `is_ancestor_of` the full path — tested). +- **identity-IS-address holds:** `entity_type` IS the dense encoding of the address; bijective with `NiblePath` (ADR-1374 satisfied). +- **Eineindeutigkeit enforced 3 ways:** (a) the registry mints `(entity_type, NiblePath)` as a unique pair [Phase B]; (b) a build-time bijection round-trip test proves it [Phase B]; (c) the GUID prefix-consistency invariant. (c) + the byte-layout field-isolation matrix landed in Phase A (`identity.rs`, 15 tests); (a)/(b) are Phase B (ontology, needs the registry). +- **Landed (Phase A, D-IDENTITY-1):** `lance_graph_contract::identity::NodeGuid` + `NiblePath::from_packed`. Byte layout, UUIDv8 version/variant gates, field-isolation matrix, `prefix is_ancestor_of full` invariant — all green (599 contract tests, +15; clippy -D clean). ## Phased integration plan (A→H; each phase = one landable PR) diff --git a/crates/lance-graph-contract/examples/cognitive_cycle.rs b/crates/lance-graph-contract/examples/cognitive_cycle.rs index f866ae76..3f1265bb 100644 --- a/crates/lance-graph-contract/examples/cognitive_cycle.rs +++ b/crates/lance-graph-contract/examples/cognitive_cycle.rs @@ -22,7 +22,13 @@ use lance_graph_contract::recipes::recipe_by_code; const DEEP_THINK: [&str; 8] = ["RTE", "HTD", "TCP", "CR", "CDT", "MCP", "TCF", "CUR"]; fn gate_state(sd: f32) -> GateState { - if sd < SD_FLOW { GateState::Flow } else if sd <= 0.35 { GateState::Hold } else { GateState::Block } + if sd < SD_FLOW { + GateState::Flow + } else if sd <= 0.35 { + GateState::Hold + } else { + GateState::Block + } } fn main() { @@ -35,14 +41,23 @@ fn main() { ctx.beliefs = vec![(7, 0.90, 0.8), (7, 0.10, 0.7)]; // topic 7 asserted true AND false println!("== cognitive cycle: one Think, run to rest ==\n"); - println!("start: gate={:?} F={:.2} conf={:.2} candidates={} beliefs={}\n", - gate_state(ctx.sd), ctx.free_energy, ctx.confidence, ctx.candidates.len(), ctx.beliefs.len()); + println!( + "start: gate={:?} F={:.2} conf={:.2} candidates={} beliefs={}\n", + gate_state(ctx.sd), + ctx.free_energy, + ctx.confidence, + ctx.candidates.len(), + ctx.beliefs.len() + ); // The active-inference loop: keep thinking while there is surprise (gate != FLOW). let mut round = 0; while gate_state(ctx.sd) != GateState::Flow && round < 5 { round += 1; - println!("── round {round} (gate {:?}) ───────────────────────", gate_state(ctx.sd)); + println!( + "── round {round} (gate {:?}) ───────────────────────", + gate_state(ctx.sd) + ); // Pipe the context through the recipe. Each step is `ctx |> tactic`. for code in DEEP_THINK { @@ -54,7 +69,11 @@ fn main() { code, format!("{:?}", rec.bucket), rec.name, - if out.fired { out.note } else { "· gated off (FLOW)" }, + if out.fired { + out.note + } else { + "· gated off (FLOW)" + }, out.delta_conf, ); } @@ -63,18 +82,35 @@ fn main() { // (In the wired system this falls out of the codec sweep; here we make it explicit.) ctx.sd *= 0.55; ctx.free_energy *= 0.5; - println!(" → after round: gate={:?} SD={:.3} F={:.3} conf={:.2}\n", - gate_state(ctx.sd), ctx.sd, ctx.free_energy, ctx.confidence); + println!( + " → after round: gate={:?} SD={:.3} F={:.3} conf={:.2}\n", + gate_state(ctx.sd), + ctx.sd, + ctx.free_energy, + ctx.confidence + ); } if gate_state(ctx.sd) == GateState::Flow { - println!("== rest == the shader stopped because gate reached Flow (SD={:.3} < FLOW {SD_FLOW}).", ctx.sd); + println!( + "== rest == the shader stopped because gate reached Flow (SD={:.3} < FLOW {SD_FLOW}).", + ctx.sd + ); } else { - println!("== rest == round cap reached ({round} rounds) before FLOW; gate={:?}, SD={:.3}.", - gate_state(ctx.sd), ctx.sd); + println!( + "== rest == round cap reached ({round} rounds) before FLOW; gate={:?}, SD={:.3}.", + gate_state(ctx.sd), + ctx.sd + ); } - println!("final: conf={:.2}, {} candidate(s) survived pruning, {} beliefs.", - ctx.confidence, ctx.candidates.len(), ctx.beliefs.len()); + println!( + "final: conf={:.2}, {} candidate(s) survived pruning, {} beliefs.", + ctx.confidence, + ctx.candidates.len(), + ctx.beliefs.len() + ); println!("\nKey: Gate-bucket tactics (TCP/CDT/TCF/CUR) skip while in FLOW — the markers,"); - println!("not a scheduler, decide what fires. Same `Tactic` behaviour, 34 hot-swappable units."); + println!( + "not a scheduler, decide what fires. Same `Tactic` behaviour, 34 hot-swappable units." + ); } diff --git a/crates/lance-graph-contract/examples/savant_dispatch.rs b/crates/lance-graph-contract/examples/savant_dispatch.rs index 0bdbfbfe..0311b3b9 100644 --- a/crates/lance-graph-contract/examples/savant_dispatch.rs +++ b/crates/lance-graph-contract/examples/savant_dispatch.rs @@ -26,7 +26,9 @@ struct Situation { /// Pattern-match the kind → the reasoning approach (the Elixir multi-clause `case`). fn approach(kind: ReasoningKind) -> &'static str { match kind { - ReasoningKind::CustomerCategory => "classify against the family codebook (deductive lookup)", + ReasoningKind::CustomerCategory => { + "classify against the family codebook (deductive lookup)" + } ReasoningKind::PostingAnomaly => "abduce the most likely cause from the evidence trail", ReasoningKind::NextBestAction => "induce the action with the highest expected value", ReasoningKind::InvoiceCompleteness => "check required-field coverage, score the gaps", @@ -40,10 +42,21 @@ fn approach(kind: ReasoningKind) -> &'static str { fn dispatch(s: &Savant) { println!(" savant {} (#{}, lane {})", s.name, s.id, s.lane); - println!(" family {}", s.family.map(|f| format!("0x{f:02X}")).unwrap_or_else(|| "None (needs alignment axiom)".into())); - println!(" tuple kind={:?} · infer={:?} · semiring={:?} · style={:?}", s.kind, s.inference, s.semiring, s.style); + println!( + " family {}", + s.family + .map(|f| format!("0x{f:02X}")) + .unwrap_or_else(|| "None (needs alignment axiom)".into()) + ); + println!( + " tuple kind={:?} · infer={:?} · semiring={:?} · style={:?}", + s.kind, s.inference, s.semiring, s.style + ); // The InferenceType resolves O(1) to the runtime query strategy. - println!(" → strategy {:?} (InferenceType::default_strategy)", s.query_strategy()); + println!( + " → strategy {:?} (InferenceType::default_strategy)", + s.query_strategy() + ); println!(" → approach {}", approach(s.kind)); println!(" → output NARS (frequency, confidence) suggestion — woa-rs applies it behind its AXIS-A guard\n"); } @@ -52,11 +65,32 @@ fn main() { println!("== Odoo savant delegation: AXIS-A guard (woa-rs) → AXIS-B reason (lance-graph) ==\n"); let inbox = [ - Situation { headline: "€1,200 payment arrived — does it fully reconcile the partner's open invoices?", ambiguous: true, savant: "PaymentToInvoiceMatcher" }, - Situation { headline: "3rd identical bill from this vendor, unmodified — auto-post it?", ambiguous: true, savant: "AutopostRecommender" }, - Situation { headline: "new B2B partner in AT — which fiscal position (tax mapping)?", ambiguous: true, savant: "FiscalPositionResolver" }, - Situation { headline: "journal sequence jumps 1042 → 1044 — is 1043 a deleted posted entry?", ambiguous: true, savant: "SequenceGapAnomalyDetector" }, - Situation { headline: "invoice with a perfectly matching single open item", ambiguous: false, savant: "ReconcileMatchSelector" }, + Situation { + headline: + "€1,200 payment arrived — does it fully reconcile the partner's open invoices?", + ambiguous: true, + savant: "PaymentToInvoiceMatcher", + }, + Situation { + headline: "3rd identical bill from this vendor, unmodified — auto-post it?", + ambiguous: true, + savant: "AutopostRecommender", + }, + Situation { + headline: "new B2B partner in AT — which fiscal position (tax mapping)?", + ambiguous: true, + savant: "FiscalPositionResolver", + }, + Situation { + headline: "journal sequence jumps 1042 → 1044 — is 1043 a deleted posted entry?", + ambiguous: true, + savant: "SequenceGapAnomalyDetector", + }, + Situation { + headline: "invoice with a perfectly matching single open item", + ambiguous: false, + savant: "ReconcileMatchSelector", + }, ]; for s in &inbox { @@ -70,5 +104,7 @@ fn main() { } println!("Same delegation tuple for all 25 savants; dispatch is data, not branches."); - println!("Pattern-match on ReasoningKind picks the approach; the family's StyleCluster colours it."); + println!( + "Pattern-match on ReasoningKind picks the approach; the family's StyleCluster colours it." + ); } diff --git a/crates/lance-graph-contract/src/atoms.rs b/crates/lance-graph-contract/src/atoms.rs index 52e8c76a..285ee405 100644 --- a/crates/lance-graph-contract/src/atoms.rs +++ b/crates/lance-graph-contract/src/atoms.rs @@ -277,7 +277,10 @@ mod tests { fn catalogue_is_locked_33_in_order() { assert_eq!(CANONICAL_ATOMS.len(), 33); for (i, a) in CANONICAL_ATOMS.iter().enumerate() { - assert_eq!(a.dim as usize, i, "lane dim must equal its index (locked order)"); + assert_eq!( + a.dim as usize, i, + "lane dim must equal its index (locked order)" + ); assert!(!a.name.is_empty()); } } diff --git a/crates/lance-graph-contract/src/callcenter/mod.rs b/crates/lance-graph-contract/src/callcenter/mod.rs index d51963c2..46fb3ee6 100644 --- a/crates/lance-graph-contract/src/callcenter/mod.rs +++ b/crates/lance-graph-contract/src/callcenter/mod.rs @@ -26,9 +26,7 @@ pub mod ogit_uris; pub mod role_keys; -pub use ogit_uris::{ - savant_ogit_uri, savant_ogit_uri_by_name, SAVANT_OGIT_BASE, SAVANT_OGIT_URIS, -}; +pub use ogit_uris::{savant_ogit_uri, savant_ogit_uri_by_name, SAVANT_OGIT_BASE, SAVANT_OGIT_URIS}; pub use role_keys::{ savant_role_key, savant_role_key_by_name, SAVANT_ROLE_KEYS, SAVANT_SLICE_END, SAVANT_SLICE_START, SAVANT_SLICE_WIDTH, diff --git a/crates/lance-graph-contract/src/callcenter/ogit_uris.rs b/crates/lance-graph-contract/src/callcenter/ogit_uris.rs index 021d316e..dc959173 100644 --- a/crates/lance-graph-contract/src/callcenter/ogit_uris.rs +++ b/crates/lance-graph-contract/src/callcenter/ogit_uris.rs @@ -66,9 +66,8 @@ pub const SAVANT_OGIT_BASE: &str = "https://ogit.adaworldapi.com/callcenter/sava /// intentionally absent per `SAVANTS.md`). Use the lookup helpers /// [`savant_ogit_uri`] / [`savant_ogit_uri_by_name`] rather than /// indexing this array directly. -pub static SAVANT_OGIT_URIS: LazyLock<[String; 25]> = LazyLock::new(|| { - core::array::from_fn(|i| format!("{}{}", SAVANT_OGIT_BASE, SAVANTS[i].name)) -}); +pub static SAVANT_OGIT_URIS: LazyLock<[String; 25]> = + LazyLock::new(|| core::array::from_fn(|i| format!("{}{}", SAVANT_OGIT_BASE, SAVANTS[i].name))); /// Look up a savant's canonical OGIT URI by roster id. /// @@ -124,7 +123,10 @@ mod tests { #[test] fn id_16_is_absent() { - assert!(savant_ogit_uri(16).is_none(), "roster id 16 intentionally absent"); + assert!( + savant_ogit_uri(16).is_none(), + "roster id 16 intentionally absent" + ); } #[test] @@ -139,8 +141,7 @@ mod tests { #[test] fn id_lookup_matches_name_lookup() { let by_id = savant_ogit_uri(1).expect("id 1"); - let by_name = - savant_ogit_uri_by_name("FiscalPositionResolver").expect("name lookup"); + let by_name = savant_ogit_uri_by_name("FiscalPositionResolver").expect("name lookup"); assert_eq!(by_id, by_name); } @@ -153,7 +154,11 @@ mod tests { fn every_savant_in_roster_has_a_uri() { for s in SAVANTS.iter() { let uri = savant_ogit_uri(s.id).expect("every roster savant has a uri"); - assert!(uri.ends_with(s.name), "uri ends with savant name: {uri} vs {}", s.name); + assert!( + uri.ends_with(s.name), + "uri ends with savant name: {uri} vs {}", + s.name + ); } } } diff --git a/crates/lance-graph-contract/src/callcenter/role_keys.rs b/crates/lance-graph-contract/src/callcenter/role_keys.rs index 3fac98d0..69e4afca 100644 --- a/crates/lance-graph-contract/src/callcenter/role_keys.rs +++ b/crates/lance-graph-contract/src/callcenter/role_keys.rs @@ -146,7 +146,10 @@ mod tests { // (the 25th and final savant). let key = savant_role_key(26).expect("id 26"); assert_eq!(key.label, "BackorderJudge"); - assert_eq!(key.slice_start, SAVANT_SLICE_START + 24 * SAVANT_SLICE_WIDTH); + assert_eq!( + key.slice_start, + SAVANT_SLICE_START + 24 * SAVANT_SLICE_WIDTH + ); assert_eq!(key.slice_end, SAVANT_SLICE_END); } @@ -158,7 +161,10 @@ mod tests { let key = savant_role_key_by_name("FiscalPositionResolver").unwrap(); let total_set: u32 = key.words.iter().map(|w| w.count_ones()).sum(); assert!(total_set > 20, "some bits set in 90-dim slice: {total_set}"); - assert!(total_set < 80, "some bits clear in 90-dim slice: {total_set}"); + assert!( + total_set < 80, + "some bits clear in 90-dim slice: {total_set}" + ); } #[test] diff --git a/crates/lance-graph-contract/src/cognition/advance.rs b/crates/lance-graph-contract/src/cognition/advance.rs index f065df12..765a8e51 100644 --- a/crates/lance-graph-contract/src/cognition/advance.rs +++ b/crates/lance-graph-contract/src/cognition/advance.rs @@ -30,7 +30,7 @@ use super::entity::NormalizedEntity; use super::op::{Op, Output}; use super::stages::*; -use crate::transaction::{OgitCtx, OwlCtx, DolceCtx, FibuCtx}; +use crate::transaction::{DolceCtx, FibuCtx, OgitCtx, OwlCtx}; // ── Verb 1: resolve_ogit ────────────────────────────────────────────────────── diff --git a/crates/lance-graph-contract/src/counterfactual.rs b/crates/lance-graph-contract/src/counterfactual.rs index 4d84a5db..ad4c0c27 100644 --- a/crates/lance-graph-contract/src/counterfactual.rs +++ b/crates/lance-graph-contract/src/counterfactual.rs @@ -495,7 +495,11 @@ mod tests { use crate::escalation::{CollapseHint, CouncilVerdict}; fn verdict(split: bool) -> CouncilVerdict { - CouncilVerdict { hint: CollapseHint::Flow, confidence: 0.9, split } + CouncilVerdict { + hint: CollapseHint::Flow, + confidence: 0.9, + split, + } } #[test] diff --git a/crates/lance-graph-contract/src/episodic_edges.rs b/crates/lance-graph-contract/src/episodic_edges.rs index c6daca2a..393d5e40 100644 --- a/crates/lance-graph-contract/src/episodic_edges.rs +++ b/crates/lance-graph-contract/src/episodic_edges.rs @@ -86,7 +86,10 @@ impl EdgeRef { if slot == 0 { None } else { - Some(Self { family: (slot >> 12) as u8, local: slot & Self::MAX_LOCAL }) + Some(Self { + family: (slot >> 12) as u8, + local: slot & Self::MAX_LOCAL, + }) } } } @@ -121,7 +124,9 @@ impl EpisodicEdges64 { /// How many slots carry an edge. #[must_use] pub fn count(self) -> usize { - (0..Self::CAPACITY).filter(|&i| self.edge(i).is_some()).count() + (0..Self::CAPACITY) + .filter(|&i| self.edge(i).is_some()) + .count() } /// All 4 slots full? @@ -346,7 +351,9 @@ mod tests { #[test] fn edge_index_out_of_range_is_none() { - let w = EpisodicEdges64::empty().push(EdgeRef::intra(7).unwrap()).unwrap(); + let w = EpisodicEdges64::empty() + .push(EdgeRef::intra(7).unwrap()) + .unwrap(); assert_eq!(w.edge(0), EdgeRef::intra(7)); assert_eq!(w.edge(1), None); assert_eq!(w.edge(EpisodicEdges64::CAPACITY), None); @@ -378,7 +385,10 @@ mod tests { .push(EdgeRef::cross(2, 9).unwrap()) .unwrap(); let got: Vec<_> = w.iter().collect(); - assert_eq!(got, vec![EdgeRef::intra(1).unwrap(), EdgeRef::cross(2, 9).unwrap()]); + assert_eq!( + got, + vec![EdgeRef::intra(1).unwrap(), EdgeRef::cross(2, 9).unwrap()] + ); } #[test] @@ -405,7 +415,9 @@ mod tests { #[test] fn le_bytes_are_canonical_little_endian() { // slot 0 = intra local 1 => 0x0001 in the low 16 bits; LE => byte[0] = 0x01. - let w = EpisodicEdges64::empty().push(EdgeRef::intra(1).unwrap()).unwrap(); + let w = EpisodicEdges64::empty() + .push(EdgeRef::intra(1).unwrap()) + .unwrap(); assert_eq!(w.to_le_bytes()[0], 0x01); assert_eq!(w.to_le_bytes()[1], 0x00); } diff --git a/crates/lance-graph-contract/src/escalation.rs b/crates/lance-graph-contract/src/escalation.rs index 2858bfb7..b412cbf3 100644 --- a/crates/lance-graph-contract/src/escalation.rs +++ b/crates/lance-graph-contract/src/escalation.rs @@ -142,7 +142,11 @@ impl InnerCouncil { /// wins; a split ([`is_split`] at `0.7 / 0.5`) amplifies the winning /// confidence ×1.2 (clamped to 1.0). pub fn deliberate(scores: [f32; 3]) -> CouncilVerdict { - let archetypes = [Archetype::Guardian, Archetype::Catalyst, Archetype::Balanced]; + let archetypes = [ + Archetype::Guardian, + Archetype::Catalyst, + Archetype::Balanced, + ]; let (idx, &best) = scores .iter() .enumerate() @@ -509,7 +513,7 @@ mod tests { assert!(d.observe(0.2).is_none()); assert!(d.observe(0.2).is_none()); assert!(d.observe(0.2).is_none()); // now 4 samples in window - // baseline ≈ 0.2; 0.2 * 1.5 = 0.3 → a 0.5 spike fires. + // baseline ≈ 0.2; 0.2 * 1.5 = 0.3 → a 0.5 spike fires. let e = d.observe(0.5).expect("epiphany should fire"); assert_eq!(e.samples, 4); assert!(e.similarity > e.baseline * 1.5); @@ -550,7 +554,9 @@ mod tests { assert!(cl.step("contracts", &flow, None).is_none()); assert!(!cl.boot_ready()); // Flow WITH epiphany green-flips and mints an Epiphany ghost. - let marker = cl.step("contracts", &flow, Some(epiphany)).expect("green-flip"); + let marker = cl + .step("contracts", &flow, Some(epiphany)) + .expect("green-flip"); assert_eq!(marker.ghost, GhostEcho::Epiphany); assert!(cl.boot_ready()); // only HARD item needs to be green assert!(cl.degraded()); // SOFT "caps" still red → degrade, route around @@ -565,7 +571,11 @@ mod tests { confidence: 1.0, split: false, }; - let e = Epiphany { similarity: 0.6, baseline: 0.3, samples: 5 }; + let e = Epiphany { + similarity: 0.6, + baseline: 0.3, + samples: 5, + }; cl.step("store", &flow, Some(e)); assert!(cl.boot_ready()); // Runtime crash: the green item goes red → re-escalate to Fanout. diff --git a/crates/lance-graph-contract/src/head2head.rs b/crates/lance-graph-contract/src/head2head.rs index 8fe91bec..4f1249cc 100644 --- a/crates/lance-graph-contract/src/head2head.rs +++ b/crates/lance-graph-contract/src/head2head.rs @@ -27,7 +27,11 @@ // positives): cast_precision_loss — the support count n ∈ 0..=4 is exact as f32; // float_cmp — tests compare exact integer-valued scores; missing_const_for_fn — // `score` transitively calls the non-const `slice::contains`. -#![allow(clippy::cast_precision_loss, clippy::float_cmp, clippy::missing_const_for_fn)] +#![allow( + clippy::cast_precision_loss, + clippy::float_cmp, + clippy::missing_const_for_fn +)] use crate::a2a_blackboard::{Blackboard, BlackboardEntry, ExpertId}; @@ -157,17 +161,17 @@ mod tests { } fn board(es: Vec) -> Blackboard { - Blackboard { entries: es, round: 0 } + Blackboard { + entries: es, + round: 0, + } } #[test] fn tempered_default_rewards_certainty_without_contradiction() { // A: high confidence but high dissonance (0.9 * 0.4 = 0.36). // B: lower confidence but clean (0.7 * 0.9 = 0.63 wins). - let bb = board(vec![ - entry(1, 0.9, 0.6, [0; 4]), - entry(2, 0.7, 0.1, [0; 4]), - ]); + let bb = board(vec![entry(1, 0.9, 0.6, [0; 4]), entry(2, 0.7, 0.1, [0; 4])]); let out = Head2Head::default().select(&bb).unwrap(); assert_eq!(out.criterion, WinnerCriterion::Tempered); assert_eq!(out.winner, 2); @@ -178,11 +182,10 @@ mod tests { #[test] fn dissonance_min_is_the_infight_pick() { // Same confidence; the tighter (lower-dissonance) expert wins the infight. - let bb = board(vec![ - entry(1, 0.8, 0.5, [0; 4]), - entry(2, 0.8, 0.2, [0; 4]), - ]); - let out = Head2Head::new(WinnerCriterion::DissonanceMin).select(&bb).unwrap(); + let bb = board(vec![entry(1, 0.8, 0.5, [0; 4]), entry(2, 0.8, 0.2, [0; 4])]); + let out = Head2Head::new(WinnerCriterion::DissonanceMin) + .select(&bb) + .unwrap(); assert_eq!(out.winner, 2); } @@ -190,10 +193,12 @@ mod tests { fn support_spread_is_the_raumgewinn_pick() { // Same confidence/dissonance; the expert covering more distinct ground wins. let bb = board(vec![ - entry(1, 0.8, 0.1, [42, 42, 0, 0]), // 1 distinct atom - entry(2, 0.8, 0.1, [7, 9, 13, 21]), // 4 distinct atoms (territory) + entry(1, 0.8, 0.1, [42, 42, 0, 0]), // 1 distinct atom + entry(2, 0.8, 0.1, [7, 9, 13, 21]), // 4 distinct atoms (territory) ]); - let out = Head2Head::new(WinnerCriterion::SupportSpread).select(&bb).unwrap(); + let out = Head2Head::new(WinnerCriterion::SupportSpread) + .select(&bb) + .unwrap(); assert_eq!(out.winner, 2); assert_eq!(out.winner_score, 4.0); assert_eq!(out.margin, 3.0); // 4 distinct vs 1 distinct @@ -205,7 +210,9 @@ mod tests { entry(1, 0.95, 0.9, [0; 4]), // noisy but loud → wins on raw confidence entry(2, 0.6, 0.0, [0; 4]), ]); - let out = Head2Head::new(WinnerCriterion::ConfidenceMax).select(&bb).unwrap(); + let out = Head2Head::new(WinnerCriterion::ConfidenceMax) + .select(&bb) + .unwrap(); assert_eq!(out.winner, 1); } @@ -226,7 +233,9 @@ mod tests { entry(1, 0.9, 0.0, [0; 4]), // expert 1's best entry(2, 0.5, 0.0, [0; 4]), ]); - let out = Head2Head::new(WinnerCriterion::ConfidenceMax).select(&bb).unwrap(); + let out = Head2Head::new(WinnerCriterion::ConfidenceMax) + .select(&bb) + .unwrap(); assert_eq!(out.winner, 1); assert_eq!(out.runner_up, Some(2)); } diff --git a/crates/lance-graph-contract/src/hhtl.rs b/crates/lance-graph-contract/src/hhtl.rs index 06533bc0..95d906ba 100644 --- a/crates/lance-graph-contract/src/hhtl.rs +++ b/crates/lance-graph-contract/src/hhtl.rs @@ -188,6 +188,28 @@ impl NiblePath { (self.path, self.depth) } + /// Reconstruct a path from its raw packed `(path, depth)` — the inverse of + /// [`packed`](NiblePath::packed). Used by `identity::NodeGuid` to round-trip + /// the routing-prefix it stores. + /// + /// Returns `None` if `depth > MAX_DEPTH`, or if `path` has bits set above the + /// `depth` nibbles (an inconsistent pack — leading nibbles must be the route, + /// trailing high bits must be zero). `from_packed(0, 0)` is [`EMPTY`](NiblePath::EMPTY). + #[must_use] + pub const fn from_packed(path: u64, depth: u8) -> Option { + if depth > MAX_DEPTH { + return None; + } + // `path` must fit in `depth` nibbles (4·depth bits); higher bits must be 0. + // At MAX_DEPTH (16 nibbles = 64 bits) the whole u64 is usable — skip the + // shift (a `>> 64` would be UB). + let used_bits = 4 * depth as u32; + if used_bits < 64 && (path >> used_bits) != 0 { + return None; + } + Some(Self { path, depth }) + } + /// Is this path a descendant-or-equal of `other`? — the symmetric form of /// [`is_ancestor_of`]. `self.is_descendant_of(other)` is equivalent to /// `other.is_ancestor_of(self)` BUT the form is sometimes more natural at @@ -273,7 +295,6 @@ impl NiblePath { }) } } - } #[cfg(test)] diff --git a/crates/lance-graph-contract/src/identity.rs b/crates/lance-graph-contract/src/identity.rs new file mode 100644 index 00000000..dcca7836 --- /dev/null +++ b/crates/lance-graph-contract/src/identity.rs @@ -0,0 +1,397 @@ +//! # `identity` — the structured 128-bit node identity (UUIDv8). +//! +//! A [`NodeGuid`] is the HHTL nibble-address **formalized + namespaced** into a +//! standards-compliant UUIDv8 (RFC 9562). It is the workspace's first *stable +//! binary instance identity*: the cold path keys nodes by `node_id:u32` today, +//! the SPO hot path by a `u64` content `dn_hash` — neither is a stable, +//! globally-referenceable id. `NodeGuid` fills that gap. +//! +//! ## Compose, don't re-invent (the iron mandate) +//! +//! Every field is an existing committed scalar; `NodeGuid` is their composition, +//! never a parallel re-pack (which would duplicate the ratified `OD-CLASSID-WIDTH` +//! discriminator + `I-VSA-IDENTITIES`): +//! - `namespace:u8 | entity_type:u16 | kind:u8` = the `SchemaPtr.packed` u32 +//! convention (`lance-graph-ontology::namespace`). +//! - `niblepath_prefix` = a truncated [`NiblePath`](crate::hhtl::NiblePath) (the +//! HHTL tree address; full depth resolves from `entity_type`). +//! - `shape_hash` = a truncated `StructuralSignature` (the D-CLS shape witness). +//! - `local` = an instance index. +//! +//! ## Eineindeutigkeit — `entity_type` canonical, `NiblePath` the derived view +//! +//! `entity_type:u16` is the **exact, bijective** class identity (fixed-width, no +//! truncation; the registry mints it 1:1 with the class's `NiblePath`). The +//! `niblepath_prefix` in the GUID is a *derived routing cache* — coarse (≤4 +//! nibbles), equal to `niblepath_of(entity_type)` truncated to the prefix. A +//! *truncated* NiblePath CANNOT be the bijective identity (two deep classes +//! collide past the prefix — see `hhtl.rs`); so the exact identity is the dense +//! `entity_type`, and the prefix only accelerates `is_ancestor_of` routing. +//! +//! ## The five readings (register reads of one immutable key) +//! +//! - **resolve** `entity_type()` → `ClassView` (class-from-address, O(1)). +//! - **route** `niblepath()` → delegate switch (HHTL bit-shift). +//! - **witness** the frozen 16 bytes + the merkle chain (immutable, in place). +//! - **ground-truth** `shape_hash()` vs `resolve(addr).shape_hash_now` → drift. +//! - **dispatch-to-store** `as_bytes()` → `EntityKey` → consumer store. +//! +//! ## Immutability law +//! +//! Write-once. `entity_type` never updates (the lineage id — re-resolved from the +//! address for free). Drift *repair* is a new immutable version (Lance is +//! versioned), never an in-place mutation. `I-VSA-IDENTITIES` Test 0: a register +//! key that POINTS TO content, never VSA-bundled. + +use crate::hhtl::NiblePath; + +/// Layout version of [`NodeGuid`]'s byte geometry. Stamped into the GUID so a +/// future reader refuses to decode a layout it does not understand +/// (`I-LEGACY-API-FEATURE-GATED`). Bump on any field-bit change. +pub const IDENTITY_LAYOUT_VERSION: u8 = 1; + +/// RFC 9562 UUID version nibble — `8` = custom / application-defined layout. +const UUID_VERSION: u8 = 8; +/// RFC 9562 variant bits — `0b10`. +const UUID_VARIANT: u8 = 0b10; + +/// Bit-width of the `shape_hash` field (truncated `StructuralSignature`). +pub const SHAPE_HASH_BITS: u32 = 22; +/// Bit-width of the `local` instance-index field. +pub const LOCAL_BITS: u32 = 24; + +const SHAPE_HASH_MASK: u32 = (1u32 << SHAPE_HASH_BITS) - 1; +const LOCAL_MASK: u32 = (1u32 << LOCAL_BITS) - 1; + +/// A 128-bit immutable structured node identity (UUIDv8). +/// +/// Octet layout (canonical UUID byte order, octets 0-15): +/// ```text +/// 0 : namespace (u8) ─┐ +/// 1..=2 : entity_type (u16 BE) ├ SchemaPtr.packed u32 convention +/// 3 : kind (u8) ─┘ +/// 4..=5 : niblepath_prefix (u16 BE, ≤4 nibbles routing cache) +/// 6 : [hi nibble: UUID version=8][lo nibble: niblepath_depth 0..=4] +/// 7 : shape_hash[21:14] +/// 8 : [hi 2 bits: UUID variant=0b10][lo 6 bits: shape_hash[13:8]] +/// 9 : shape_hash[7:0] (shape_hash = 22 bits total) +/// 10..=12: local (u24 BE) +/// 13 : layout_version (u8) +/// 14..=15: spare (zero) +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(C, align(16))] +pub struct NodeGuid([u8; 16]); + +impl NodeGuid { + /// Routing-cache prefix depth carried in the GUID (the full path resolves + /// from `entity_type`; this is the coarse, branchless-routing prefix). + pub const PREFIX_NIBBLES: u8 = 4; + + /// Compose a `NodeGuid` from its existing-field parts. `entity_type` is exact + /// (the bijective identity); `niblepath` is truncated to the routing prefix; + /// `shape_hash`/`local` are masked to their field widths. Sets the UUIDv8 + /// version/variant + the layout-version stamp. + pub fn new( + namespace: u8, + entity_type: u16, + kind: u8, + niblepath: NiblePath, + shape_hash: u32, + local: u32, + ) -> Self { + let (prefix, depth) = Self::truncate_prefix(niblepath); + let sh = shape_hash & SHAPE_HASH_MASK; + let loc = local & LOCAL_MASK; + let et = entity_type.to_be_bytes(); + let np = prefix.to_be_bytes(); + Self([ + namespace, + et[0], + et[1], + kind, + np[0], + np[1], + (UUID_VERSION << 4) | (depth & 0x0F), + ((sh >> 14) & 0xFF) as u8, + (UUID_VARIANT << 6) | ((sh >> 8) & 0x3F) as u8, + (sh & 0xFF) as u8, + ((loc >> 16) & 0xFF) as u8, + ((loc >> 8) & 0xFF) as u8, + (loc & 0xFF) as u8, + IDENTITY_LAYOUT_VERSION, + 0, + 0, + ]) + } + + /// Keep the top `PREFIX_NIBBLES` nibbles of a full path (root-first, so we + /// drop the lowest `depth - keep` nibbles). The result `is_ancestor_of` the + /// full path — a true coarse routing prefix. + fn truncate_prefix(path: NiblePath) -> (u16, u8) { + let (full, depth) = path.packed(); + let keep = if depth < Self::PREFIX_NIBBLES { + depth + } else { + Self::PREFIX_NIBBLES + }; + let drop = depth - keep; + let prefix = (full >> (4 * drop as u32)) as u16; + (prefix, keep) + } + + // ── accessors (the five readings start here) ── + + /// The raw 16 bytes — feed straight to `EntityKey(&[u8])` (dispatch-to-store). + #[must_use] + pub const fn as_bytes(&self) -> &[u8; 16] { + &self.0 + } + + /// Reconstruct from raw bytes (e.g. read back from a Lance column / EntityKey). + #[must_use] + pub const fn from_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } + + /// OGIT namespace ordinal (`NamespaceId`). + #[must_use] + pub const fn namespace(&self) -> u8 { + self.0[0] + } + + /// The **exact, bijective** class identity (`entity_type_id` / `class_id`). + /// No truncation — this is the canonical class lineage id (resolve here). + #[must_use] + pub const fn entity_type(&self) -> u16 { + u16::from_be_bytes([self.0[1], self.0[2]]) + } + + /// `SchemaPtr` kind discriminator. + #[must_use] + pub const fn kind(&self) -> u8 { + self.0[3] + } + + /// Raw routing-prefix bits (≤4 nibbles). Prefer [`niblepath`](Self::niblepath). + #[must_use] + pub const fn niblepath_prefix(&self) -> u16 { + u16::from_be_bytes([self.0[4], self.0[5]]) + } + + /// Depth (nibble count) of the stored routing prefix (0..=4). + #[must_use] + pub const fn niblepath_depth(&self) -> u8 { + self.0[6] & 0x0F + } + + /// The routing prefix as a (truncated) [`NiblePath`] — the coarse delegate + /// switch. The EXACT class is [`entity_type`](Self::entity_type); this only + /// accelerates `is_ancestor_of`. + #[must_use] + pub fn niblepath(&self) -> Option { + NiblePath::from_packed(u64::from(self.niblepath_prefix()), self.niblepath_depth()) + } + + /// The shape_hash drift witness (truncated `StructuralSignature`, 22 bits). + #[must_use] + pub const fn shape_hash(&self) -> u32 { + ((self.0[7] as u32) << 14) | (((self.0[8] & 0x3F) as u32) << 8) | (self.0[9] as u32) + } + + /// The instance index (24 bits). + #[must_use] + pub const fn local(&self) -> u32 { + ((self.0[10] as u32) << 16) | ((self.0[11] as u32) << 8) | (self.0[12] as u32) + } + + /// The stamped [`IDENTITY_LAYOUT_VERSION`]. + #[must_use] + pub const fn layout_version(&self) -> u8 { + self.0[13] + } + + /// RFC 9562 version nibble (must be `8`). + #[must_use] + pub const fn version(&self) -> u8 { + self.0[6] >> 4 + } + + /// RFC 9562 variant bits (must be `0b10`). + #[must_use] + pub const fn variant(&self) -> u8 { + self.0[8] >> 6 + } + + /// Is this a well-formed UUIDv8 (version `8`, variant `0b10`)? The + /// self-explanatory-to-any-consumer property holds only when this is true. + #[must_use] + pub const fn is_valid_uuid_v8(&self) -> bool { + self.version() == UUID_VERSION && self.variant() == UUID_VARIANT + } +} + +impl core::fmt::Display for NodeGuid { + /// Canonical UUID string `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let b = &self.0; + write!( + f, + "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15] + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A deep example path: basin 0x2 → 0x5 → 0xA → 0x3 → 0x7 (depth 5, deeper + /// than the 4-nibble GUID prefix). + fn deep_path() -> NiblePath { + NiblePath::root(0x2) + .child(0x5) + .child(0xA) + .child(0x3) + .child(0x7) + } + + fn sample() -> NodeGuid { + NodeGuid::new(0x07, 0xABCD, 0x03, deep_path(), 0x2F_1A3, 0x12_3456) + } + + #[test] + fn fields_round_trip() { + let g = sample(); + assert_eq!(g.namespace(), 0x07); + assert_eq!( + g.entity_type(), + 0xABCD, + "entity_type is EXACT (no truncation)" + ); + assert_eq!(g.kind(), 0x03); + assert_eq!(g.shape_hash(), 0x2F_1A3 & SHAPE_HASH_MASK); + assert_eq!(g.local(), 0x12_3456 & LOCAL_MASK); + assert_eq!(g.layout_version(), IDENTITY_LAYOUT_VERSION); + } + + #[test] + fn uuid_v8_version_and_variant_are_reserved() { + let g = sample(); + assert_eq!(g.version(), 8, "UUIDv8"); + assert_eq!(g.variant(), 0b10, "RFC 9562 variant"); + assert!(g.is_valid_uuid_v8()); + // The reserved bits sit in octets 6 (hi nibble) and 8 (hi 2 bits). + let b = g.as_bytes(); + assert_eq!(b[6] >> 4, 8); + assert_eq!(b[8] >> 6, 0b10); + } + + #[test] + fn shape_hash_and_local_saturate_to_field_width() { + // Over-wide inputs are masked, never overflow into neighbours. + let g = NodeGuid::new(0, 0, 0, NiblePath::EMPTY, u32::MAX, u32::MAX); + assert_eq!(g.shape_hash(), SHAPE_HASH_MASK); + assert_eq!(g.local(), LOCAL_MASK); + // The version/variant survived the over-wide shape_hash. + assert!(g.is_valid_uuid_v8()); + } + + #[test] + fn field_isolation_each_field_is_independent() { + // The I-LEGACY-API field-isolation matrix: vary one field, assert all + // OTHERS are unchanged (and the UUIDv8 reserved bits never move). + let base = sample(); + let probes = [ + NodeGuid::new(0xFF, 0xABCD, 0x03, deep_path(), 0x2F_1A3, 0x12_3456), // namespace + NodeGuid::new(0x07, 0x0000, 0x03, deep_path(), 0x2F_1A3, 0x12_3456), // entity_type + NodeGuid::new(0x07, 0xABCD, 0xFF, deep_path(), 0x2F_1A3, 0x12_3456), // kind + NodeGuid::new(0x07, 0xABCD, 0x03, deep_path(), 0x00000, 0x12_3456), // shape_hash + NodeGuid::new(0x07, 0xABCD, 0x03, deep_path(), 0x2F_1A3, 0x000000), // local + ]; + for p in probes { + assert!(p.is_valid_uuid_v8(), "reserved version/variant bits intact"); + assert_eq!(p.layout_version(), IDENTITY_LAYOUT_VERSION); + } + // namespace probe: only namespace differs. + let p = probes[0]; + assert_ne!(p.namespace(), base.namespace()); + assert_eq!(p.entity_type(), base.entity_type()); + assert_eq!(p.kind(), base.kind()); + assert_eq!(p.shape_hash(), base.shape_hash()); + assert_eq!(p.local(), base.local()); + // entity_type probe: only entity_type differs. + let p = probes[1]; + assert_ne!(p.entity_type(), base.entity_type()); + assert_eq!(p.namespace(), base.namespace()); + assert_eq!(p.local(), base.local()); + assert_eq!(p.shape_hash(), base.shape_hash()); + } + + #[test] + fn niblepath_prefix_is_a_true_ancestor_of_the_full_path() { + // The routing cache is coarse but SOUND: the stored prefix is an + // ancestor-or-equal of the full path (so is_ancestor_of routing is valid). + let full = deep_path(); // depth 5 + let g = NodeGuid::new(0, 0, 0, full, 0, 0); + assert_eq!( + g.niblepath_depth(), + NodeGuid::PREFIX_NIBBLES, + "truncated to 4" + ); + let prefix = g.niblepath().expect("prefix reconstructs"); + assert!( + prefix.is_ancestor_of(full), + "the GUID prefix must be an ancestor of the full path" + ); + assert_eq!(prefix.basin(), full.basin(), "same DOLCE basin"); + } + + #[test] + fn shallow_path_is_carried_whole() { + // A path shallower than the prefix budget round-trips exactly. + let shallow = NiblePath::root(0x1).child(0x2); // depth 2 + let g = NodeGuid::new(0, 0, 0, shallow, 0, 0); + assert_eq!(g.niblepath_depth(), 2); + assert_eq!(g.niblepath(), Some(shallow)); + } + + #[test] + fn empty_path_is_the_no_route_sentinel() { + let g = NodeGuid::new(0, 0, 0, NiblePath::EMPTY, 0, 0); + assert_eq!(g.niblepath_depth(), 0); + assert_eq!(g.niblepath(), Some(NiblePath::EMPTY)); + } + + #[test] + fn as_bytes_from_bytes_round_trip() { + let g = sample(); + let g2 = NodeGuid::from_bytes(*g.as_bytes()); + assert_eq!(g, g2); + } + + #[test] + fn display_is_a_canonical_uuid_string() { + let g = sample(); + let s = g.to_string(); + assert_eq!(s.len(), 36, "8-4-4-4-12 + 4 hyphens"); + // Canonical UUID hyphen positions. + for i in [8usize, 13, 18, 23] { + assert_eq!(s.as_bytes()[i], b'-', "hyphen at {i}"); + } + // Version nibble is the 13th hex digit (group 3, first char) = '8'. + assert_eq!(s.chars().nth(14).unwrap(), '8'); + // entity_type 0xABCD is octets 1-2 → hex digits 2..6 = "abcd". + assert_eq!(&s[2..6], "abcd"); + // namespace 0x07 → digits 0..2, kind 0x03 → digits 6..8. + assert_eq!(&s[0..2], "07"); + assert_eq!(&s[6..8], "03"); + } + + #[test] + fn size_and_alignment_are_16() { + assert_eq!(core::mem::size_of::(), 16); + assert_eq!(core::mem::align_of::(), 16); + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 3c463882..f5f5e224 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -58,10 +58,11 @@ pub mod external_membrane; pub mod faculty; pub mod grammar; pub mod graph_render; -pub mod head2head; pub mod hash; +pub mod head2head; pub mod hhtl; pub mod high_heel; +pub mod identity; pub mod jit; pub mod kanban; pub mod literal_graph; @@ -109,6 +110,7 @@ pub use class_view::{ClassId, ClassProjection, ClassView, FieldMask, RenderRow}; pub use collapse_gate::{CollapseGateEmission, GateDecision, MailboxId, MergeMode}; pub use episodic_edges::{EdgeRef, EpisodicEdges64}; pub use head2head::{CompetitionOutcome, Head2Head, WinnerCriterion}; +pub use identity::{NodeGuid, IDENTITY_LAYOUT_VERSION}; pub use kanban::{ExecTarget, KanbanColumn, KanbanMove, RubiconTransitionError}; pub use scheduler::{DatasetVersion, NextPhaseScheduler, VersionScheduler}; pub use soa_view::{MailboxSoaOwner, MailboxSoaView}; diff --git a/crates/lance-graph-contract/src/mul.rs b/crates/lance-graph-contract/src/mul.rs index 38059a4f..b6fc8c71 100644 --- a/crates/lance-graph-contract/src/mul.rs +++ b/crates/lance-graph-contract/src/mul.rs @@ -426,7 +426,6 @@ fn flow_state_from(challenge: f64, skill: f64) -> FlowState { } } - // ═══════════════════════════════════════════════════════════════════════════ // i4 scalar evaluation path — D-CSV-8 (sprint-11) // @@ -447,14 +446,16 @@ fn flow_state_from(challenge: f64, skill: f64) -> FlowState { /// (from `causal_edge::InferenceType::to_mantissa()`), and return the /// existing MUL contract types unchanged. pub mod i4_eval { - use super::{DkPosition, FlowState, GateDecision, Homeostasis, MulAssessment, TrustQualia, TrustTexture}; + use super::{ + DkPosition, FlowState, GateDecision, Homeostasis, MulAssessment, TrustQualia, TrustTexture, + }; use crate::qualia::QualiaI4_16D; // ── dim indices (aligned with QUALIA_I4_LABELS) ───────────────────────── - const DIM_VALENCE: usize = 1; // signed valence (polarity) - const DIM_TENSION: usize = 2; // tension / conflict load - const DIM_WARMTH: usize = 3; // warmth / affiliation - const DIM_COHERENCE: usize = 9; // coherence (story holds / breaks) + const DIM_VALENCE: usize = 1; // signed valence (polarity) + const DIM_TENSION: usize = 2; // tension / conflict load + const DIM_WARMTH: usize = 3; // warmth / affiliation + const DIM_COHERENCE: usize = 9; // coherence (story holds / breaks) const DIM_GROUNDEDNESS: usize = 14; // groundedness / stability /// On-demand intensity helper: `magnitude()` from the qualia struct. @@ -576,24 +577,25 @@ pub mod i4_eval { let flow = flow_state_i4(qualia, signed_mantissa); match (texture, flow) { - (TrustTexture::Uncertain, _) => { - GateDecision::Block { reason: "uncertain trust: coherence low, tension high".to_string() } - } - (TrustTexture::Underconfident, FlowState::Anxiety) => { - GateDecision::Block { reason: "underconfident + anxiety: execution blocked".to_string() } - } - (TrustTexture::Overconfident, _) => { - GateDecision::Hold { reason: "overconfident trust: caution required".to_string() } - } - (_, FlowState::Anxiety) => { - GateDecision::Hold { reason: "anxiety flow state: reduced autonomy".to_string() } - } - (TrustTexture::Calibrated | TrustTexture::Underconfident, FlowState::Flow | FlowState::Transition) => { - GateDecision::Flow - } - _ => { - GateDecision::Hold { reason: "boredom or moderate state: hold for re-evaluation".to_string() } - } + (TrustTexture::Uncertain, _) => GateDecision::Block { + reason: "uncertain trust: coherence low, tension high".to_string(), + }, + (TrustTexture::Underconfident, FlowState::Anxiety) => GateDecision::Block { + reason: "underconfident + anxiety: execution blocked".to_string(), + }, + (TrustTexture::Overconfident, _) => GateDecision::Hold { + reason: "overconfident trust: caution required".to_string(), + }, + (_, FlowState::Anxiety) => GateDecision::Hold { + reason: "anxiety flow state: reduced autonomy".to_string(), + }, + ( + TrustTexture::Calibrated | TrustTexture::Underconfident, + FlowState::Flow | FlowState::Transition, + ) => GateDecision::Flow, + _ => GateDecision::Hold { + reason: "boredom or moderate state: hold for re-evaluation".to_string(), + }, } } @@ -618,13 +620,16 @@ pub mod i4_eval { // TrustQualia.value: map texture + intensity to 0.0–1.0 let intensity = intensity_i4(qualia); // i8 saturating product let trust_value: f64 = match texture { - TrustTexture::Calibrated => 0.75 + (intensity.clamp(0, 7) as f64 / 7.0) * 0.25, - TrustTexture::Overconfident => 0.45, + TrustTexture::Calibrated => 0.75 + (intensity.clamp(0, 7) as f64 / 7.0) * 0.25, + TrustTexture::Overconfident => 0.45, TrustTexture::Underconfident => 0.40, - TrustTexture::Uncertain => 0.20, + TrustTexture::Uncertain => 0.20, }; - let trust = TrustQualia { value: trust_value, texture }; + let trust = TrustQualia { + value: trust_value, + texture, + }; // complexity_mapped: coherence signal ≥ +2 implies the system can map complexity let coherence = qualia.get(DIM_COHERENCE); @@ -634,20 +639,23 @@ pub mod i4_eval { let tension = qualia.get(DIM_TENSION); let allostatic_load: f64 = ((tension as i16 + 8) as f64 / 15.0).clamp(0.0, 1.0); - let homeostasis = Homeostasis { flow_state: flow, allostatic_load }; + let homeostasis = Homeostasis { + flow_state: flow, + allostatic_load, + }; // free_will_modifier: DK factor × trust_value × flow_factor let dk_factor: f64 = match dk { - DkPosition::MountStupid => 0.3, - DkPosition::ValleyOfDespair => 0.7, + DkPosition::MountStupid => 0.3, + DkPosition::ValleyOfDespair => 0.7, DkPosition::SlopeOfEnlightenment => 0.85, - DkPosition::Plateau => 1.0, + DkPosition::Plateau => 1.0, }; let flow_factor: f64 = match flow { - FlowState::Flow => 1.0, + FlowState::Flow => 1.0, FlowState::Transition => 0.7, - FlowState::Boredom => 0.8, - FlowState::Anxiety => 0.5, + FlowState::Boredom => 0.8, + FlowState::Anxiety => 0.5, }; let free_will_modifier = (dk_factor * trust_value * flow_factor).clamp(0.0, 1.0); @@ -660,7 +668,6 @@ pub mod i4_eval { } } - // ═══════════════════════════════════════════════════════════════════════ // Batch evaluation API — D-CSV-13b (sprint-13) — SIMD runtime dispatch // ═══════════════════════════════════════════════════════════════════════ @@ -725,7 +732,11 @@ pub mod i4_eval { let bits: u8 = (avx512f as u8) | ((avx512bw as u8) << 1) | ((neon as u8) << 2); CAPS_CACHE.store(bits, Ordering::Relaxed); - SimdCapsShim { avx512f, avx512bw, neon } + SimdCapsShim { + avx512f, + avx512bw, + neon, + } } #[inline] @@ -735,9 +746,9 @@ pub mod i4_eval { return probe_caps(); } SimdCapsShim { - avx512f: bits & 1 != 0, + avx512f: bits & 1 != 0, avx512bw: bits & 2 != 0, - neon: bits & 4 != 0, + neon: bits & 4 != 0, } } @@ -879,10 +890,14 @@ pub mod i4_eval { let m_ptr = mantissas.as_ptr().add(i); let man_vec = _mm512_set_epi64( - *m_ptr.add(7) as i64, *m_ptr.add(6) as i64, - *m_ptr.add(5) as i64, *m_ptr.add(4) as i64, - *m_ptr.add(3) as i64, *m_ptr.add(2) as i64, - *m_ptr.add(1) as i64, *m_ptr.add(0) as i64, + *m_ptr.add(7) as i64, + *m_ptr.add(6) as i64, + *m_ptr.add(5) as i64, + *m_ptr.add(4) as i64, + *m_ptr.add(3) as i64, + *m_ptr.add(2) as i64, + *m_ptr.add(1) as i64, + *m_ptr.add(0) as i64, ); let zero = _mm512_setzero_si512(); let neg_man = _mm512_sub_epi64(zero, man_vec); @@ -894,15 +909,15 @@ pub mod i4_eval { let mut disc = _mm512_setzero_si512(); // ValleyOfDespair (1): coherence <= -3 OR abs_man <= 1 let vod = _mm512_cmple_epi64_mask(coh, _mm512_set1_epi64(-3)) - | _mm512_cmple_epi64_mask(abs_man, _mm512_set1_epi64(1)); + | _mm512_cmple_epi64_mask(abs_man, _mm512_set1_epi64(1)); disc = _mm512_mask_blend_epi64(vod, disc, _mm512_set1_epi64(1)); // SlopeOfEnlightenment (2): coh >= 2 AND abs_man >= 2 let soe = _mm512_cmpge_epi64_mask(coh, _mm512_set1_epi64(2)) - & _mm512_cmpge_epi64_mask(abs_man, _mm512_set1_epi64(2)); + & _mm512_cmpge_epi64_mask(abs_man, _mm512_set1_epi64(2)); disc = _mm512_mask_blend_epi64(soe, disc, _mm512_set1_epi64(2)); // Plateau (3): coh >= 5 AND abs_man >= 4 (overrides all) let plat = _mm512_cmpge_epi64_mask(coh, _mm512_set1_epi64(5)) - & _mm512_cmpge_epi64_mask(abs_man, _mm512_set1_epi64(4)); + & _mm512_cmpge_epi64_mask(abs_man, _mm512_set1_epi64(4)); disc = _mm512_mask_blend_epi64(plat, disc, _mm512_set1_epi64(3)); let out_ptr = out.as_mut_ptr().add(i) as *mut u8; @@ -929,8 +944,8 @@ pub mod i4_eval { let q_ptr = qualia[i..].as_ptr() as *const __m512i; let q_vec = _mm512_loadu_si512(q_ptr); let coh = extract_dim_i8::<36>(q_vec); // DIM_COHERENCE=9 - let val = extract_dim_i8::<4>(q_vec); // DIM_VALENCE=1 - let ten = extract_dim_i8::<8>(q_vec); // DIM_TENSION=2 + let val = extract_dim_i8::<4>(q_vec); // DIM_VALENCE=1 + let ten = extract_dim_i8::<8>(q_vec); // DIM_TENSION=2 // Default = Calibrated (0) let mut disc = _mm512_setzero_si512(); @@ -939,11 +954,11 @@ pub mod i4_eval { disc = _mm512_mask_blend_epi64(und, disc, _mm512_set1_epi64(3)); // Overconfident (1): valence >= 4 AND coherence < 5 let ovc = _mm512_cmpge_epi64_mask(val, _mm512_set1_epi64(4)) - & _mm512_cmplt_epi64_mask(coh, _mm512_set1_epi64(5)); + & _mm512_cmplt_epi64_mask(coh, _mm512_set1_epi64(5)); disc = _mm512_mask_blend_epi64(ovc, disc, _mm512_set1_epi64(1)); // Uncertain (2): coherence <= -3 AND tension >= 3 (highest priority) let unc = _mm512_cmple_epi64_mask(coh, _mm512_set1_epi64(-3)) - & _mm512_cmpge_epi64_mask(ten, _mm512_set1_epi64(3)); + & _mm512_cmpge_epi64_mask(ten, _mm512_set1_epi64(3)); disc = _mm512_mask_blend_epi64(unc, disc, _mm512_set1_epi64(2)); let out_ptr = out.as_mut_ptr().add(i) as *mut u8; @@ -975,7 +990,7 @@ pub mod i4_eval { let q_vec = _mm512_loadu_si512(q_ptr); let war = extract_dim_i8::<12>(q_vec); // DIM_WARMTH=3 let grd = extract_dim_i8::<56>(q_vec); // DIM_GROUNDEDNESS=14 - let ten = extract_dim_i8::<8>(q_vec); // DIM_TENSION=2 + let ten = extract_dim_i8::<8>(q_vec); // DIM_TENSION=2 let coh = extract_dim_i8::<36>(q_vec); // DIM_COHERENCE=9 // flow_proxy = warmth + groundedness - tension. @@ -988,27 +1003,31 @@ pub mod i4_eval { let m_ptr = mantissas.as_ptr().add(i); let man_vec = _mm512_set_epi64( - *m_ptr.add(7) as i64, *m_ptr.add(6) as i64, - *m_ptr.add(5) as i64, *m_ptr.add(4) as i64, - *m_ptr.add(3) as i64, *m_ptr.add(2) as i64, - *m_ptr.add(1) as i64, *m_ptr.add(0) as i64, + *m_ptr.add(7) as i64, + *m_ptr.add(6) as i64, + *m_ptr.add(5) as i64, + *m_ptr.add(4) as i64, + *m_ptr.add(3) as i64, + *m_ptr.add(2) as i64, + *m_ptr.add(1) as i64, + *m_ptr.add(0) as i64, ); let zero = _mm512_setzero_si512(); // Pre-compute Anxiety condition (applied last for highest priority). let anx = _mm512_cmple_epi64_mask(fp, _mm512_set1_epi64(-2)) - | (_mm512_cmplt_epi64_mask(man_vec, zero) - & _mm512_cmple_epi64_mask(coh, _mm512_set1_epi64(-1))); + | (_mm512_cmplt_epi64_mask(man_vec, zero) + & _mm512_cmple_epi64_mask(coh, _mm512_set1_epi64(-1))); // Default = Boredom (1) let mut disc = _mm512_set1_epi64(1); // Transition (2): fp >= 2 AND man > 0 let tra = _mm512_cmpge_epi64_mask(fp, _mm512_set1_epi64(2)) - & _mm512_cmpgt_epi64_mask(man_vec, zero); + & _mm512_cmpgt_epi64_mask(man_vec, zero); disc = _mm512_mask_blend_epi64(tra, disc, _mm512_set1_epi64(2)); // Flow (0): fp >= 4 AND man > 0 let flow = _mm512_cmpge_epi64_mask(fp, _mm512_set1_epi64(4)) - & _mm512_cmpgt_epi64_mask(man_vec, zero); + & _mm512_cmpgt_epi64_mask(man_vec, zero); disc = _mm512_mask_blend_epi64(flow, disc, _mm512_set1_epi64(0)); // Anxiety (3): always overrides (highest priority) disc = _mm512_mask_blend_epi64(anx, disc, _mm512_set1_epi64(3)); @@ -1058,11 +1077,13 @@ pub mod i4_eval { // SAFETY: TrustTexture/FlowState are repr(u8); pointers derived from // properly-allocated arrays of the right size. let tex_slice = core::slice::from_raw_parts_mut( - tex_disc.as_mut_ptr() as *mut TrustTexture, 8, + tex_disc.as_mut_ptr() as *mut TrustTexture, + 8, ); trust_texture_batch(&qualia[i..i + 8], tex_slice); let flow_slice = core::slice::from_raw_parts_mut( - flow_disc.as_mut_ptr() as *mut FlowState, 8, + flow_disc.as_mut_ptr() as *mut FlowState, + 8, ); flow_state_batch(&qualia[i..i + 8], &mantissas[i..i + 8], flow_slice); for j in 0..8usize { @@ -1097,7 +1118,8 @@ pub mod i4_eval { // SAFETY: DkPosition/TrustTexture/FlowState are repr(u8) with discriminants 0..3; // vec storage is properly aligned and has length n. dk_position_batch( - qualia, mantissas, + qualia, + mantissas, core::slice::from_raw_parts_mut(dk_disc.as_mut_ptr() as *mut DkPosition, n), ); trust_texture_batch( @@ -1105,7 +1127,8 @@ pub mod i4_eval { core::slice::from_raw_parts_mut(tex_disc.as_mut_ptr() as *mut TrustTexture, n), ); flow_state_batch( - qualia, mantissas, + qualia, + mantissas, core::slice::from_raw_parts_mut(flow_disc.as_mut_ptr() as *mut FlowState, n), ); @@ -1125,13 +1148,18 @@ pub mod i4_eval { TrustTexture::Underconfident => 0.40, TrustTexture::Uncertain => 0.20, }; - let trust = TrustQualia { value: trust_value, texture }; + let trust = TrustQualia { + value: trust_value, + texture, + }; let coherence = qualia[i].get(9); // DIM_COHERENCE let complexity_mapped = coherence >= 2; let tension = qualia[i].get(2); // DIM_TENSION - let allostatic_load = - ((tension as i16 + 8) as f64 / 15.0).clamp(0.0, 1.0); - let homeostasis = Homeostasis { flow_state: flow, allostatic_load }; + let allostatic_load = ((tension as i16 + 8) as f64 / 15.0).clamp(0.0, 1.0); + let homeostasis = Homeostasis { + flow_state: flow, + allostatic_load, + }; let dk_factor: f64 = match dk { DkPosition::MountStupid => 0.3, DkPosition::ValleyOfDespair => 0.7, @@ -1181,7 +1209,10 @@ pub mod i4_eval { let n1 = vandq_u64(vshrq_n_u64(q1, shift), mask); let i0 = vreinterpretq_s8_u64(n0); let i1 = vreinterpretq_s8_u64(n1); - (vshrq_n_s8(vshlq_n_s8(i0, 4), 4), vshrq_n_s8(vshlq_n_s8(i1, 4), 4)) + ( + vshrq_n_s8(vshlq_n_s8(i0, 4), 4), + vshrq_n_s8(vshlq_n_s8(i1, 4), 4), + ) } /// Batch DK position — NEON path (2 elements per iteration). @@ -1285,7 +1316,8 @@ pub mod i4_eval { let coh = [vgetq_lane_s8(c0, 0), vgetq_lane_s8(c1, 0)]; for j in 0..2 { let fp = (war[j] as i16 + grd[j] as i16 - ten[j] as i16) - .clamp(i8::MIN as i16, i8::MAX as i16) as i8; + .clamp(i8::MIN as i16, i8::MAX as i16) + as i8; let man = mantissas[i + j]; out[i + j] = if fp >= 4 && man > 0 { FlowState::Flow @@ -1347,7 +1379,11 @@ pub mod i4_eval { mantissas: &[i8], out: &mut [DkPosition], ) { - assert_eq!(qualia.len(), mantissas.len(), "qualia/mantissas length mismatch"); + assert_eq!( + qualia.len(), + mantissas.len(), + "qualia/mantissas length mismatch" + ); assert_eq!(qualia.len(), out.len(), "input/output length mismatch"); let caps = simd_caps(); #[cfg(target_arch = "x86_64")] @@ -1386,7 +1422,11 @@ pub mod i4_eval { /// Batch FlowState. Dispatches to AVX-512/NEON if available at runtime. pub fn flow_state_batch(qualia: &[QualiaI4_16D], mantissas: &[i8], out: &mut [FlowState]) { - assert_eq!(qualia.len(), mantissas.len(), "qualia/mantissas length mismatch"); + assert_eq!( + qualia.len(), + mantissas.len(), + "qualia/mantissas length mismatch" + ); assert_eq!(qualia.len(), out.len(), "input/output length mismatch"); let caps = simd_caps(); #[cfg(target_arch = "x86_64")] @@ -1408,12 +1448,12 @@ pub mod i4_eval { /// /// SIMD-fast alternative to `gate_decision_batch`. Use when reason strings /// are not needed. Dispatches to AVX-512/NEON if available at runtime. - pub fn gate_decision_disc_batch( - qualia: &[QualiaI4_16D], - mantissas: &[i8], - out: &mut [u8], - ) { - assert_eq!(qualia.len(), mantissas.len(), "qualia/mantissas length mismatch"); + pub fn gate_decision_disc_batch(qualia: &[QualiaI4_16D], mantissas: &[i8], out: &mut [u8]) { + assert_eq!( + qualia.len(), + mantissas.len(), + "qualia/mantissas length mismatch" + ); assert_eq!(qualia.len(), out.len(), "input/output length mismatch"); let caps = simd_caps(); #[cfg(target_arch = "x86_64")] @@ -1437,7 +1477,11 @@ pub mod i4_eval { mantissas: &[i8], out: &mut [GateDecision], ) { - assert_eq!(qualia.len(), mantissas.len(), "qualia/mantissas length mismatch"); + assert_eq!( + qualia.len(), + mantissas.len(), + "qualia/mantissas length mismatch" + ); assert_eq!(qualia.len(), out.len(), "input/output length mismatch"); for i in 0..qualia.len() { out[i] = gate_decision_i4(&qualia[i], mantissas[i]); @@ -1450,7 +1494,11 @@ pub mod i4_eval { mantissas: &[i8], out: &mut [MulAssessment], ) { - assert_eq!(qualia.len(), mantissas.len(), "qualia/mantissas length mismatch"); + assert_eq!( + qualia.len(), + mantissas.len(), + "qualia/mantissas length mismatch" + ); assert_eq!(qualia.len(), out.len(), "input/output length mismatch"); let caps = simd_caps(); #[cfg(target_arch = "x86_64")] @@ -1470,10 +1518,17 @@ pub mod i4_eval { /// Convenience: allocate the output Vec and return it (for non-hot-path callers). pub fn mul_assess_vec(qualia: &[QualiaI4_16D], mantissas: &[i8]) -> Vec { - assert_eq!(qualia.len(), mantissas.len(), "qualia/mantissas length mismatch"); + assert_eq!( + qualia.len(), + mantissas.len(), + "qualia/mantissas length mismatch" + ); let mut out = vec![ MulAssessment { - trust: TrustQualia { value: 0.0, texture: TrustTexture::Calibrated }, + trust: TrustQualia { + value: 0.0, + texture: TrustTexture::Calibrated + }, dk_position: DkPosition::MountStupid, homeostasis: Homeostasis { flow_state: FlowState::Boredom, @@ -1617,8 +1672,14 @@ pub mod i4_eval { assert_eq!(mul.dk_position, DkPosition::Plateau); assert_eq!(mul.trust.texture, TrustTexture::Calibrated); assert_eq!(mul.homeostasis.flow_state, FlowState::Flow); - assert!(mul.free_will_modifier > 0.5, "expert+flow should give high autonomy"); - assert!(mul.complexity_mapped, "high coherence should map complexity"); + assert!( + mul.free_will_modifier > 0.5, + "expert+flow should give high autonomy" + ); + assert!( + mul.complexity_mapped, + "high coherence should map complexity" + ); } #[test] @@ -1704,16 +1765,19 @@ pub mod i4_eval { assert!( matches_gate_discriminant(&out[i], &scalar), "gate decision discriminant mismatch at index {}: batch={:?} scalar={:?}", - i, out[i], scalar + i, + out[i], + scalar ); } } fn matches_gate_discriminant(a: &GateDecision, b: &GateDecision) -> bool { - matches!((a, b), + matches!( + (a, b), (GateDecision::Flow, GateDecision::Flow) - | (GateDecision::Hold { .. }, GateDecision::Hold { .. }) - | (GateDecision::Block { .. }, GateDecision::Block { .. }) + | (GateDecision::Hold { .. }, GateDecision::Hold { .. }) + | (GateDecision::Block { .. }, GateDecision::Block { .. }) ) } @@ -1726,11 +1790,26 @@ pub mod i4_eval { batch::mul_assess_batch(&qualia, &mantissas, &mut out); for (i, (q, &m)) in qualia.iter().zip(mantissas.iter()).enumerate() { let scalar = mul_assess_i4(q, m); - assert_eq!(out[i].dk_position, scalar.dk_position, "dk_position mismatch at {}", i); - assert_eq!(out[i].trust.texture, scalar.trust.texture, "trust.texture mismatch at {}", i); - assert_eq!(out[i].homeostasis.flow_state, scalar.homeostasis.flow_state, "flow_state mismatch at {}", i); - assert!((out[i].free_will_modifier - scalar.free_will_modifier).abs() < 1e-10, - "free_will_modifier mismatch at {}", i); + assert_eq!( + out[i].dk_position, scalar.dk_position, + "dk_position mismatch at {}", + i + ); + assert_eq!( + out[i].trust.texture, scalar.trust.texture, + "trust.texture mismatch at {}", + i + ); + assert_eq!( + out[i].homeostasis.flow_state, scalar.homeostasis.flow_state, + "flow_state mismatch at {}", + i + ); + assert!( + (out[i].free_will_modifier - scalar.free_will_modifier).abs() < 1e-10, + "free_will_modifier mismatch at {}", + i + ); } } @@ -1738,12 +1817,28 @@ pub mod i4_eval { fn test_mul_assess_vec_allocates_correctly() { let (qualia, mantissas) = make_batch(10); let result = batch::mul_assess_vec(&qualia, &mantissas); - assert_eq!(result.len(), qualia.len(), "output length must equal input length"); + assert_eq!( + result.len(), + qualia.len(), + "output length must equal input length" + ); for (i, (q, &m)) in qualia.iter().zip(mantissas.iter()).enumerate() { let scalar = mul_assess_i4(q, m); - assert_eq!(result[i].dk_position, scalar.dk_position, "dk_position mismatch at {}", i); - assert_eq!(result[i].trust.texture, scalar.trust.texture, "trust.texture mismatch at {}", i); - assert_eq!(result[i].homeostasis.flow_state, scalar.homeostasis.flow_state, "flow_state mismatch at {}", i); + assert_eq!( + result[i].dk_position, scalar.dk_position, + "dk_position mismatch at {}", + i + ); + assert_eq!( + result[i].trust.texture, scalar.trust.texture, + "trust.texture mismatch at {}", + i + ); + assert_eq!( + result[i].homeostasis.flow_state, scalar.homeostasis.flow_state, + "flow_state mismatch at {}", + i + ); } } @@ -1755,7 +1850,10 @@ pub mod i4_eval { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { batch::dk_position_batch(&qualia, &mantissas, &mut out); })); - assert!(result.is_err(), "must panic on qualia/mantissas length mismatch"); + assert!( + result.is_err(), + "must panic on qualia/mantissas length mismatch" + ); } #[test] @@ -1817,10 +1915,10 @@ pub mod i4_eval { for _ in 0..n { let r = xorshift64(&mut s); let mut q = QualiaI4_16D::ZERO; - q.set(1, i4((r & 0xF) as u8)); // valence - q.set(2, i4(((r >> 4) & 0xF) as u8)); // tension - q.set(3, i4(((r >> 8) & 0xF) as u8)); // warmth - q.set(9, i4(((r >> 12) & 0xF) as u8)); // coherence + q.set(1, i4((r & 0xF) as u8)); // valence + q.set(2, i4(((r >> 4) & 0xF) as u8)); // tension + q.set(3, i4(((r >> 8) & 0xF) as u8)); // warmth + q.set(9, i4(((r >> 12) & 0xF) as u8)); // coherence q.set(14, i4(((r >> 16) & 0xF) as u8)); // groundedness qualia.push(q); let mant = i4(((r >> 20) & 0xF) as u8); @@ -1840,7 +1938,7 @@ pub mod i4_eval { for &n in PARITY_SIZES { let (qualia, mantissas) = make_random_batch(n, 0xD15C_5E7D_C0DE_0001); let mut out_dispatch = vec![DkPosition::MountStupid; n]; - let mut out_scalar = vec![DkPosition::MountStupid; n]; + let mut out_scalar = vec![DkPosition::MountStupid; n]; batch::dk_position_batch(&qualia, &mantissas, &mut out_dispatch); batch::scalar_impl::dk_position_batch(&qualia, &mantissas, &mut out_scalar); for i in 0..n { @@ -1858,7 +1956,7 @@ pub mod i4_eval { for &n in PARITY_SIZES { let (qualia, _) = make_random_batch(n, 0xD15C_5E7D_C0DE_0002); let mut out_dispatch = vec![TrustTexture::Uncertain; n]; - let mut out_scalar = vec![TrustTexture::Uncertain; n]; + let mut out_scalar = vec![TrustTexture::Uncertain; n]; batch::trust_texture_batch(&qualia, &mut out_dispatch); batch::scalar_impl::trust_texture_batch(&qualia, &mut out_scalar); for i in 0..n { @@ -1876,7 +1974,7 @@ pub mod i4_eval { for &n in PARITY_SIZES { let (qualia, mantissas) = make_random_batch(n, 0xD15C_5E7D_C0DE_0003); let mut out_dispatch = vec![FlowState::Boredom; n]; - let mut out_scalar = vec![FlowState::Boredom; n]; + let mut out_scalar = vec![FlowState::Boredom; n]; batch::flow_state_batch(&qualia, &mantissas, &mut out_dispatch); batch::scalar_impl::flow_state_batch(&qualia, &mantissas, &mut out_scalar); for i in 0..n { @@ -1894,7 +1992,7 @@ pub mod i4_eval { for &n in PARITY_SIZES { let (qualia, mantissas) = make_random_batch(n, 0xD15C_5E7D_C0DE_0004); let mut out_dispatch = vec![0u8; n]; - let mut out_scalar = vec![0u8; n]; + let mut out_scalar = vec![0u8; n]; batch::gate_decision_disc_batch(&qualia, &mantissas, &mut out_dispatch); batch::scalar_impl::gate_decision_disc_batch(&qualia, &mantissas, &mut out_scalar); for i in 0..n { @@ -1906,7 +2004,12 @@ pub mod i4_eval { } // Discriminants must be in the locked range 0=Flow, 1=Hold, 2=Block. for (i, &b) in out_dispatch.iter().enumerate() { - assert!(b <= 2, "out-of-range gate discriminant {} at index {}", b, i); + assert!( + b <= 2, + "out-of-range gate discriminant {} at index {}", + b, + i + ); } } } @@ -1914,41 +2017,59 @@ pub mod i4_eval { #[test] fn test_mul_assess_batch_parity_simd_vs_scalar() { let zero_assess = || MulAssessment { - trust: TrustQualia { value: 0.0, texture: TrustTexture::Calibrated }, + trust: TrustQualia { + value: 0.0, + texture: TrustTexture::Calibrated, + }, dk_position: DkPosition::MountStupid, - homeostasis: Homeostasis { flow_state: FlowState::Boredom, allostatic_load: 0.0 }, + homeostasis: Homeostasis { + flow_state: FlowState::Boredom, + allostatic_load: 0.0, + }, complexity_mapped: false, free_will_modifier: 0.0, }; for &n in PARITY_SIZES { let (qualia, mantissas) = make_random_batch(n, 0xD15C_5E7D_C0DE_0005); let mut out_dispatch: Vec = (0..n).map(|_| zero_assess()).collect(); - let mut out_scalar: Vec = (0..n).map(|_| zero_assess()).collect(); + let mut out_scalar: Vec = (0..n).map(|_| zero_assess()).collect(); batch::mul_assess_batch(&qualia, &mantissas, &mut out_dispatch); batch::scalar_impl::mul_assess_batch(&qualia, &mantissas, &mut out_scalar); for i in 0..n { assert_eq!( out_dispatch[i].dk_position, out_scalar[i].dk_position, - "mul_assess_batch dk_position mismatch at size={} i={}", n, i, + "mul_assess_batch dk_position mismatch at size={} i={}", + n, i, ); assert_eq!( out_dispatch[i].trust.texture, out_scalar[i].trust.texture, - "mul_assess_batch trust.texture mismatch at size={} i={}", n, i, + "mul_assess_batch trust.texture mismatch at size={} i={}", + n, i, ); assert_eq!( - out_dispatch[i].homeostasis.flow_state, out_scalar[i].homeostasis.flow_state, - "mul_assess_batch flow_state mismatch at size={} i={}", n, i, + out_dispatch[i].homeostasis.flow_state, + out_scalar[i].homeostasis.flow_state, + "mul_assess_batch flow_state mismatch at size={} i={}", + n, + i, ); // f64 fields: bit-identical because both paths compute the same // scalar finalize sequence with identical inputs. assert!( (out_dispatch[i].trust.value - out_scalar[i].trust.value).abs() < 1e-12, "mul_assess_batch trust.value drift at size={} i={}: dispatch={} scalar={}", - n, i, out_dispatch[i].trust.value, out_scalar[i].trust.value, + n, + i, + out_dispatch[i].trust.value, + out_scalar[i].trust.value, ); assert!( - (out_dispatch[i].free_will_modifier - out_scalar[i].free_will_modifier).abs() < 1e-12, - "mul_assess_batch free_will_modifier drift at size={} i={}", n, i, + (out_dispatch[i].free_will_modifier - out_scalar[i].free_will_modifier) + .abs() + < 1e-12, + "mul_assess_batch free_will_modifier drift at size={} i={}", + n, + i, ); } } diff --git a/crates/lance-graph-contract/src/nars.rs b/crates/lance-graph-contract/src/nars.rs index cc8084c5..c7ac1697 100644 --- a/crates/lance-graph-contract/src/nars.rs +++ b/crates/lance-graph-contract/src/nars.rs @@ -169,7 +169,10 @@ mod tests { fn grammar_inference_bridges_via_core() { use crate::grammar::inference::NarsInference as G; assert_eq!(InferenceType::from(G::Deduction), InferenceType::Deduction); - assert_eq!(InferenceType::from(G::Extrapolation), InferenceType::Induction); + assert_eq!( + InferenceType::from(G::Extrapolation), + InferenceType::Induction + ); assert_eq!( InferenceType::from(G::CounterfactualSynthesis), InferenceType::Synthesis diff --git a/crates/lance-graph-contract/src/pearl_junction.rs b/crates/lance-graph-contract/src/pearl_junction.rs index c1289299..c766de4d 100644 --- a/crates/lance-graph-contract/src/pearl_junction.rs +++ b/crates/lance-graph-contract/src/pearl_junction.rs @@ -403,10 +403,22 @@ mod tests { assert_eq!(j, PearlJunction::Unrelated); // Any EMPTY in any position → Unrelated. - assert_eq!(EdgePair::new(e, real, real, real).classify(), PearlJunction::Unrelated); - assert_eq!(EdgePair::new(real, e, real, real).classify(), PearlJunction::Unrelated); - assert_eq!(EdgePair::new(real, real, e, real).classify(), PearlJunction::Unrelated); - assert_eq!(EdgePair::new(real, real, real, e).classify(), PearlJunction::Unrelated); + assert_eq!( + EdgePair::new(e, real, real, real).classify(), + PearlJunction::Unrelated + ); + assert_eq!( + EdgePair::new(real, e, real, real).classify(), + PearlJunction::Unrelated + ); + assert_eq!( + EdgePair::new(real, real, e, real).classify(), + PearlJunction::Unrelated + ); + assert_eq!( + EdgePair::new(real, real, real, e).classify(), + PearlJunction::Unrelated + ); } /// `NiblePath::root` with an out-of-range basin returns `EMPTY` (the @@ -418,10 +430,12 @@ mod tests { let bad2 = NiblePath::root(0xEE); // out of FAN_OUT let real = NiblePath::root(0x1); // Both edges' subjects are out-of-range → EMPTY. - assert_eq!(EdgePair::new(bad1, real, bad2, real).classify(), PearlJunction::Unrelated); + assert_eq!( + EdgePair::new(bad1, real, bad2, real).classify(), + PearlJunction::Unrelated + ); } - // ===== Back-compat shim (codex P1 on PR #457) ===== /// The deprecated nars_rule() method must continue to work for @@ -460,9 +474,17 @@ mod tests { #[test] #[allow(deprecated)] fn from_nars_rule_lifts_to_inference_type() { - assert_eq!(InferenceType::from(NarsRule::Deduction), InferenceType::Deduction); - assert_eq!(InferenceType::from(NarsRule::Induction), InferenceType::Induction); - assert_eq!(InferenceType::from(NarsRule::Abduction), InferenceType::Abduction); + assert_eq!( + InferenceType::from(NarsRule::Deduction), + InferenceType::Deduction + ); + assert_eq!( + InferenceType::from(NarsRule::Induction), + InferenceType::Induction + ); + assert_eq!( + InferenceType::from(NarsRule::Abduction), + InferenceType::Abduction + ); } - } diff --git a/crates/lance-graph-contract/src/plan.rs b/crates/lance-graph-contract/src/plan.rs index 48e50497..9ac4932f 100644 --- a/crates/lance-graph-contract/src/plan.rs +++ b/crates/lance-graph-contract/src/plan.rs @@ -3,8 +3,8 @@ //! Defines the traits that lance-graph-planner implements and //! consumers (ladybug-rs, n8n-rs) call. -use crate::mul::{GateDecision, MulAssessment, SituationInput}; use crate::cognitive_shader::RungLevel; +use crate::mul::{GateDecision, MulAssessment, SituationInput}; use crate::nars::{InferenceType, SemiringChoice}; use crate::thinking::{FieldModulation, ThinkingStyle}; diff --git a/crates/lance-graph-contract/src/recipes.rs b/crates/lance-graph-contract/src/recipes.rs index 61872af2..3b2e9054 100644 --- a/crates/lance-graph-contract/src/recipes.rs +++ b/crates/lance-graph-contract/src/recipes.rs @@ -85,40 +85,347 @@ use Tier::*; /// The 34 recipes. Order = id ascending. pub const RECIPES: [Recipe; 34] = [ - Recipe { id: 1, code: "RTE", name: "Recursive Thought Expansion", tier: Hard, mechanism: ParallelIndependence, bucket: Control, spo2cubed: NotCovered, substrate: "rung depth × Expand/Compress; Berry-Esseen stop" }, - Recipe { id: 2, code: "HTD", name: "Hierarchical Thought Decomposition", tier: Hard, mechanism: ParallelIndependence, bucket: Control, spo2cubed: NotCovered, substrate: "CLAM bipolar split / Decompose op" }, - Recipe { id: 3, code: "SMAD", name: "Structured Multi-Agent Debate", tier: ExtremelyHard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: NotCovered, substrate: "a2a_blackboard + InnerCouncil (NARS-revised vote)" }, - Recipe { id: 4, code: "RCR", name: "Reverse Causality Reasoning", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: Covered, substrate: "SPO 2³ backward S_O + Abduction + Granger" }, - Recipe { id: 5, code: "TCP", name: "Thought Chain Pruning", tier: Hard, mechanism: ParallelIndependence, bucket: Gate, spo2cubed: NotCovered, substrate: "CollapseGate SD BLOCK prunes branch" }, - Recipe { id: 6, code: "TR", name: "Thought Randomization", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Gate, spo2cubed: NotCovered, substrate: "temperature (Staunen) perturb above noise floor" }, - Recipe { id: 7, code: "ASC", name: "Adversarial Self-Critique", tier: ExtremelyHard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "InnerCouncil split / 5 challenge types (negation projection)" }, - Recipe { id: 8, code: "CAS", name: "Conditional Abstraction Scaling", tier: CrossTier, mechanism: Infrastructure, bucket: Gate, spo2cubed: NotCovered, substrate: "HDR cascade INT1/4/8/32 × Abstract↔Concretize" }, - Recipe { id: 9, code: "IRS", name: "Iterative Roleplay Synthesis", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: NotCovered, substrate: "persona FieldModulation (structurally distinct kernels)" }, - Recipe { id: 10, code: "MCP", name: "Meta-Cognition Prompting", tier: Hard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: NotCovered, substrate: "MUL DK + Brier calibration; Meta lane" }, - Recipe { id: 11, code: "CR", name: "Contradiction Resolution", tier: Hard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "NARS opposing-truth detect + coherence; Contradiction preserved" }, - Recipe { id: 12, code: "TCA", name: "Temporal Context Augmentation", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "Granger temporal lane / Markov ±5 / 24 temporal verbs" }, - Recipe { id: 13, code: "CDT", name: "Convergent & Divergent Thinking", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Gate, spo2cubed: NotCovered, substrate: "explore↔exploit temperature; style oscillation" }, - Recipe { id: 14, code: "MCT", name: "Multimodal Chain-of-Thought", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "GrammarTriangle: NSM+Causality+Qualia → one fingerprint" }, - Recipe { id: 15, code: "LSI", name: "Latent Space Introspection", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "CRP distribution / Mexican-hat over fingerprint clusters" }, - Recipe { id: 16, code: "PSO", name: "Prompt Scaffold Optimization", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "ThinkingTemplate slots + TD-learned discovery" }, - Recipe { id: 17, code: "CDI", name: "Cognitive Dissonance Induction", tier: CrossTier, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "Festinger dissonance = opposing NARS truth on similar fp; HOLD" }, - Recipe { id: 18, code: "CWS", name: "Context Window Simulation", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "persistent BindSpace / WitnessCorpus / episodic memory" }, - Recipe { id: 19, code: "ARE", name: "Algorithmic Reverse Engineering", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "ABBA unbind: A⊗B⊗B=A (exact algebraic inverse)" }, - Recipe { id: 20, code: "TCF", name: "Thought Cascade Filtering", tier: Hard, mechanism: ParallelIndependence, bucket: Gate, spo2cubed: NotCovered, substrate: "N search strategies + agreement rate; SD select" }, - Recipe { id: 21, code: "SSR", name: "Self-Skepticism Reinforcement", tier: Hard, mechanism: TruthAwareInference, bucket: Control, spo2cubed: Partial, substrate: "challenge schedule × MUL uncertainty; truth-drop = weak" }, - Recipe { id: 22, code: "ETD", name: "Emergent Task Decomposition", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "CLAM cluster geometry determines subtasks (no spec)" }, - Recipe { id: 23, code: "AMP", name: "Adaptive Meta-Prompting", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: NotCovered, substrate: "TD-learning on ThinkingStyle Q-values (W32-39)" }, - Recipe { id: 24, code: "ZCF", name: "Zero-Shot Concept Fusion", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "VSA bind(A,B): new vector valid in both spaces, recoverable" }, - Recipe { id: 25, code: "HPM", name: "Hyperdimensional Pattern Matching", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "the substrate: fingerprint cosine/Hamming sweep (SIMD)" }, - Recipe { id: 26, code: "CUR", name: "Cascading Uncertainty Reduction", tier: Hard, mechanism: ParallelIndependence, bucket: Gate, spo2cubed: NotCovered, substrate: "FreeEnergy / CRP percentiles; coarse-to-fine prune" }, - Recipe { id: 27, code: "MPC", name: "Multi-Perspective Compression", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "bundle = majority-vote-per-bit consensus + delta encode" }, - Recipe { id: 28, code: "SSAM", name: "Self-Supervised Analogical Mapping", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Datapath, spo2cubed: Partial, substrate: "NARS analogy A→B,C≈A⊢C→B; bind+similarity (Gentner)" }, - Recipe { id: 29, code: "IDR", name: "Intent-Driven Reframing", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "GrammarTriangle CausalityFlow agent/action/patient/reason" }, - Recipe { id: 30, code: "SPP", name: "Shadow Parallel Processing", tier: Hard, mechanism: ParallelIndependence, bucket: Control, spo2cubed: Partial, substrate: "independent paths + agreement (ECC/RAID); the CF majority/minority fork" }, - Recipe { id: 31, code: "ICR", name: "Iterative Counterfactual Reasoning", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Control, spo2cubed: Covered, substrate: "world⊗factual⊗counterfactual (XOR self-inverse); SPO=0b111; CausalEdge64 −6 mantissa" }, - Recipe { id: 32, code: "SDD", name: "Semantic Distortion Detection", tier: CrossTier, mechanism: Infrastructure, bucket: Datapath, spo2cubed: NotCovered, substrate: "Berry-Esseen noise floor + reciprocal A→B,B→A validation" }, - Recipe { id: 33, code: "DTMF", name: "Dynamic Task Meta-Framing", tier: CrossTier, mechanism: Infrastructure, bucket: Control, spo2cubed: NotCovered, substrate: "template switch on CollapseGate BLOCK (shift all modulation)" }, - Recipe { id: 34, code: "HKF", name: "Hyperdimensional Knowledge Fusion", tier: ExtremelyHard, mechanism: StructuralDivergence, bucket: Datapath, spo2cubed: NotCovered, substrate: "cross-domain bind(A,rel,B); reversible/auditable fusion" }, + Recipe { + id: 1, + code: "RTE", + name: "Recursive Thought Expansion", + tier: Hard, + mechanism: ParallelIndependence, + bucket: Control, + spo2cubed: NotCovered, + substrate: "rung depth × Expand/Compress; Berry-Esseen stop", + }, + Recipe { + id: 2, + code: "HTD", + name: "Hierarchical Thought Decomposition", + tier: Hard, + mechanism: ParallelIndependence, + bucket: Control, + spo2cubed: NotCovered, + substrate: "CLAM bipolar split / Decompose op", + }, + Recipe { + id: 3, + code: "SMAD", + name: "Structured Multi-Agent Debate", + tier: ExtremelyHard, + mechanism: TruthAwareInference, + bucket: Control, + spo2cubed: NotCovered, + substrate: "a2a_blackboard + InnerCouncil (NARS-revised vote)", + }, + Recipe { + id: 4, + code: "RCR", + name: "Reverse Causality Reasoning", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Control, + spo2cubed: Covered, + substrate: "SPO 2³ backward S_O + Abduction + Granger", + }, + Recipe { + id: 5, + code: "TCP", + name: "Thought Chain Pruning", + tier: Hard, + mechanism: ParallelIndependence, + bucket: Gate, + spo2cubed: NotCovered, + substrate: "CollapseGate SD BLOCK prunes branch", + }, + Recipe { + id: 6, + code: "TR", + name: "Thought Randomization", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Gate, + spo2cubed: NotCovered, + substrate: "temperature (Staunen) perturb above noise floor", + }, + Recipe { + id: 7, + code: "ASC", + name: "Adversarial Self-Critique", + tier: ExtremelyHard, + mechanism: TruthAwareInference, + bucket: Control, + spo2cubed: Partial, + substrate: "InnerCouncil split / 5 challenge types (negation projection)", + }, + Recipe { + id: 8, + code: "CAS", + name: "Conditional Abstraction Scaling", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Gate, + spo2cubed: NotCovered, + substrate: "HDR cascade INT1/4/8/32 × Abstract↔Concretize", + }, + Recipe { + id: 9, + code: "IRS", + name: "Iterative Roleplay Synthesis", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Control, + spo2cubed: NotCovered, + substrate: "persona FieldModulation (structurally distinct kernels)", + }, + Recipe { + id: 10, + code: "MCP", + name: "Meta-Cognition Prompting", + tier: Hard, + mechanism: TruthAwareInference, + bucket: Control, + spo2cubed: NotCovered, + substrate: "MUL DK + Brier calibration; Meta lane", + }, + Recipe { + id: 11, + code: "CR", + name: "Contradiction Resolution", + tier: Hard, + mechanism: TruthAwareInference, + bucket: Control, + spo2cubed: Partial, + substrate: "NARS opposing-truth detect + coherence; Contradiction preserved", + }, + Recipe { + id: 12, + code: "TCA", + name: "Temporal Context Augmentation", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "Granger temporal lane / Markov ±5 / 24 temporal verbs", + }, + Recipe { + id: 13, + code: "CDT", + name: "Convergent & Divergent Thinking", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Gate, + spo2cubed: NotCovered, + substrate: "explore↔exploit temperature; style oscillation", + }, + Recipe { + id: 14, + code: "MCT", + name: "Multimodal Chain-of-Thought", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "GrammarTriangle: NSM+Causality+Qualia → one fingerprint", + }, + Recipe { + id: 15, + code: "LSI", + name: "Latent Space Introspection", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Control, + spo2cubed: NotCovered, + substrate: "CRP distribution / Mexican-hat over fingerprint clusters", + }, + Recipe { + id: 16, + code: "PSO", + name: "Prompt Scaffold Optimization", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Control, + spo2cubed: NotCovered, + substrate: "ThinkingTemplate slots + TD-learned discovery", + }, + Recipe { + id: 17, + code: "CDI", + name: "Cognitive Dissonance Induction", + tier: CrossTier, + mechanism: TruthAwareInference, + bucket: Control, + spo2cubed: Partial, + substrate: "Festinger dissonance = opposing NARS truth on similar fp; HOLD", + }, + Recipe { + id: 18, + code: "CWS", + name: "Context Window Simulation", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Control, + spo2cubed: NotCovered, + substrate: "persistent BindSpace / WitnessCorpus / episodic memory", + }, + Recipe { + id: 19, + code: "ARE", + name: "Algorithmic Reverse Engineering", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "ABBA unbind: A⊗B⊗B=A (exact algebraic inverse)", + }, + Recipe { + id: 20, + code: "TCF", + name: "Thought Cascade Filtering", + tier: Hard, + mechanism: ParallelIndependence, + bucket: Gate, + spo2cubed: NotCovered, + substrate: "N search strategies + agreement rate; SD select", + }, + Recipe { + id: 21, + code: "SSR", + name: "Self-Skepticism Reinforcement", + tier: Hard, + mechanism: TruthAwareInference, + bucket: Control, + spo2cubed: Partial, + substrate: "challenge schedule × MUL uncertainty; truth-drop = weak", + }, + Recipe { + id: 22, + code: "ETD", + name: "Emergent Task Decomposition", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Control, + spo2cubed: NotCovered, + substrate: "CLAM cluster geometry determines subtasks (no spec)", + }, + Recipe { + id: 23, + code: "AMP", + name: "Adaptive Meta-Prompting", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Control, + spo2cubed: NotCovered, + substrate: "TD-learning on ThinkingStyle Q-values (W32-39)", + }, + Recipe { + id: 24, + code: "ZCF", + name: "Zero-Shot Concept Fusion", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "VSA bind(A,B): new vector valid in both spaces, recoverable", + }, + Recipe { + id: 25, + code: "HPM", + name: "Hyperdimensional Pattern Matching", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "the substrate: fingerprint cosine/Hamming sweep (SIMD)", + }, + Recipe { + id: 26, + code: "CUR", + name: "Cascading Uncertainty Reduction", + tier: Hard, + mechanism: ParallelIndependence, + bucket: Gate, + spo2cubed: NotCovered, + substrate: "FreeEnergy / CRP percentiles; coarse-to-fine prune", + }, + Recipe { + id: 27, + code: "MPC", + name: "Multi-Perspective Compression", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "bundle = majority-vote-per-bit consensus + delta encode", + }, + Recipe { + id: 28, + code: "SSAM", + name: "Self-Supervised Analogical Mapping", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Datapath, + spo2cubed: Partial, + substrate: "NARS analogy A→B,C≈A⊢C→B; bind+similarity (Gentner)", + }, + Recipe { + id: 29, + code: "IDR", + name: "Intent-Driven Reframing", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Control, + spo2cubed: NotCovered, + substrate: "GrammarTriangle CausalityFlow agent/action/patient/reason", + }, + Recipe { + id: 30, + code: "SPP", + name: "Shadow Parallel Processing", + tier: Hard, + mechanism: ParallelIndependence, + bucket: Control, + spo2cubed: Partial, + substrate: "independent paths + agreement (ECC/RAID); the CF majority/minority fork", + }, + Recipe { + id: 31, + code: "ICR", + name: "Iterative Counterfactual Reasoning", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Control, + spo2cubed: Covered, + substrate: + "world⊗factual⊗counterfactual (XOR self-inverse); SPO=0b111; CausalEdge64 −6 mantissa", + }, + Recipe { + id: 32, + code: "SDD", + name: "Semantic Distortion Detection", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "Berry-Esseen noise floor + reciprocal A→B,B→A validation", + }, + Recipe { + id: 33, + code: "DTMF", + name: "Dynamic Task Meta-Framing", + tier: CrossTier, + mechanism: Infrastructure, + bucket: Control, + spo2cubed: NotCovered, + substrate: "template switch on CollapseGate BLOCK (shift all modulation)", + }, + Recipe { + id: 34, + code: "HKF", + name: "Hyperdimensional Knowledge Fusion", + tier: ExtremelyHard, + mechanism: StructuralDivergence, + bucket: Datapath, + spo2cubed: NotCovered, + substrate: "cross-domain bind(A,rel,B); reversible/auditable fusion", + }, ]; /// Look up a recipe by tactic id (1..=34). @@ -140,7 +447,9 @@ pub fn by_mechanism(m: Mechanism) -> impl Iterator { /// All recipes that ride the SPO 2³ causal lattice (Covered or Partial). pub fn causal() -> impl Iterator { - RECIPES.iter().filter(|r| matches!(r.spo2cubed, Coverage::Covered | Coverage::Partial)) + RECIPES + .iter() + .filter(|r| matches!(r.spo2cubed, Coverage::Covered | Coverage::Partial)) } #[cfg(test)] @@ -167,10 +476,17 @@ mod tests { #[test] fn only_causal_tactics_are_2cubed_covered() { // Exactly RCR (#4) and ICR (#31) fully cover the causal lattice. - let covered: Vec = RECIPES.iter().filter(|r| r.spo2cubed == Coverage::Covered).map(|r| r.id).collect(); + let covered: Vec = RECIPES + .iter() + .filter(|r| r.spo2cubed == Coverage::Covered) + .map(|r| r.id) + .collect(); assert_eq!(covered, vec![4, 31]); // 2³ is the causal spine only — the rest are Partial or orthogonal. - assert!(causal().count() < RECIPES.len() / 2, "most tactics are NOT causal"); + assert!( + causal().count() < RECIPES.len() / 2, + "most tactics are NOT causal" + ); } #[test] diff --git a/crates/lance-graph-contract/src/savants.rs b/crates/lance-graph-contract/src/savants.rs index 647fbebd..b88e7280 100644 --- a/crates/lance-graph-contract/src/savants.rs +++ b/crates/lance-graph-contract/src/savants.rs @@ -59,32 +59,282 @@ use StyleCluster::*; /// The 25 Odoo savants (SAVANTS.md roster). `family = None` = needs an alignment axiom. pub const SAVANTS: [Savant; 25] = [ // ── L8–L15 gap lanes (15) ── - Savant { id: 1, name: "FiscalPositionResolver", family: Some(0x80), kind: CustomerCategory, inference: Deduction, semiring: NarsTruth, style: Analytical, lane: "L9", decides: "which fiscal position (tax mapping) applies to a partner" }, - Savant { id: 2, name: "PartnerTrustAdvisor", family: Some(0x80), kind: CustomerCategory, inference: Revision, semiring: NarsTruth, style: Empathic, lane: "L9", decides: "partner trust / dunning-risk from payment history" }, - Savant { id: 3, name: "PricelistAssignmentAgent", family: Some(0x64), kind: Other(other_kind::PRICELIST_ASSIGNMENT), inference: Revision, semiring: NarsTruth, style: Analytical, lane: "L8", decides: "partner pricelist when no explicit property (country-group/config fallback)" }, - Savant { id: 4, name: "AnalyticDistributionSuggester", family: Some(0x62), kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L10", decides: "suggested cost-centre distribution for a move line" }, - Savant { id: 5, name: "AnalyticModelScorer", family: None, kind: CustomerCategory, inference: Deduction, semiring: HammingMin, style: Analytical, lane: "L10", decides: "which analytic.distribution.model matches (priority-scored)" }, - Savant { id: 6, name: "SequenceGapAnomalyDetector", family: Some(0x62), kind: PostingAnomaly, inference: Abduction, semiring: NarsTruth, style: Analytical, lane: "L11", decides: "journal sequence gaps ⇒ deleted posted entries (GoBD)" }, - Savant { id: 7, name: "ExchangeAccountSelector", family: Some(0x62), kind: Other(other_kind::CHART_ACCOUNT_MAPPING), inference: Deduction, semiring: Boolean, style: Analytical, lane: "L12", decides: "gain/loss account for FX diff (sign-driven; config-assist)" }, - Savant { id: 8, name: "ReportRateTypeSelector", family: Some(0x62), kind: Other(other_kind::CONSOLIDATION_RATE_POLICY), inference: Deduction, semiring: Boolean, style: Analytical, lane: "L12", decides: "current/historical/average rate per report line (IFRS vs HGB)" }, - Savant { id: 9, name: "CurrencySelectionAdvisor", family: Some(0x62), kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L12", decides: "which currencies to enable (geography signal)" }, - Savant { id: 10, name: "UserCompanyAccessAdvisor", family: Some(0x80), kind: CustomerCategory, inference: Induction, semiring: NarsTruth, style: Empathic, lane: "L12", decides: "branch-access subset by user role/context" }, - Savant { id: 11, name: "ProcurementRuleSelector", family: None, kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L13", decides: "route among equal-sequence rules (lead/availability/reliability)" }, - Savant { id: 12, name: "ReorderTimingAdvisor", family: None, kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L13", decides: "reorder timing under demand/supplier uncertainty" }, - Savant { id: 13, name: "ReplenishmentReportAdvisor", family: None, kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L13", decides: "real shortfall vs demand noise in the replenishment report" }, - Savant { id: 14, name: "RouteTiebreaker", family: None, kind: NextBestAction, inference: Abduction, semiring: NarsTruth, style: Analytical, lane: "L13", decides: "equal-sequence route tiebreak (supplier lead/cost/capacity)" }, - Savant { id: 15, name: "TaxExigibilitySuggestor", family: Some(0x62), kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L15", decides: "tax exigibility (on-invoice vs on-payment / cash-basis)" }, + Savant { + id: 1, + name: "FiscalPositionResolver", + family: Some(0x80), + kind: CustomerCategory, + inference: Deduction, + semiring: NarsTruth, + style: Analytical, + lane: "L9", + decides: "which fiscal position (tax mapping) applies to a partner", + }, + Savant { + id: 2, + name: "PartnerTrustAdvisor", + family: Some(0x80), + kind: CustomerCategory, + inference: Revision, + semiring: NarsTruth, + style: Empathic, + lane: "L9", + decides: "partner trust / dunning-risk from payment history", + }, + Savant { + id: 3, + name: "PricelistAssignmentAgent", + family: Some(0x64), + kind: Other(other_kind::PRICELIST_ASSIGNMENT), + inference: Revision, + semiring: NarsTruth, + style: Analytical, + lane: "L8", + decides: "partner pricelist when no explicit property (country-group/config fallback)", + }, + Savant { + id: 4, + name: "AnalyticDistributionSuggester", + family: Some(0x62), + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L10", + decides: "suggested cost-centre distribution for a move line", + }, + Savant { + id: 5, + name: "AnalyticModelScorer", + family: None, + kind: CustomerCategory, + inference: Deduction, + semiring: HammingMin, + style: Analytical, + lane: "L10", + decides: "which analytic.distribution.model matches (priority-scored)", + }, + Savant { + id: 6, + name: "SequenceGapAnomalyDetector", + family: Some(0x62), + kind: PostingAnomaly, + inference: Abduction, + semiring: NarsTruth, + style: Analytical, + lane: "L11", + decides: "journal sequence gaps ⇒ deleted posted entries (GoBD)", + }, + Savant { + id: 7, + name: "ExchangeAccountSelector", + family: Some(0x62), + kind: Other(other_kind::CHART_ACCOUNT_MAPPING), + inference: Deduction, + semiring: Boolean, + style: Analytical, + lane: "L12", + decides: "gain/loss account for FX diff (sign-driven; config-assist)", + }, + Savant { + id: 8, + name: "ReportRateTypeSelector", + family: Some(0x62), + kind: Other(other_kind::CONSOLIDATION_RATE_POLICY), + inference: Deduction, + semiring: Boolean, + style: Analytical, + lane: "L12", + decides: "current/historical/average rate per report line (IFRS vs HGB)", + }, + Savant { + id: 9, + name: "CurrencySelectionAdvisor", + family: Some(0x62), + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L12", + decides: "which currencies to enable (geography signal)", + }, + Savant { + id: 10, + name: "UserCompanyAccessAdvisor", + family: Some(0x80), + kind: CustomerCategory, + inference: Induction, + semiring: NarsTruth, + style: Empathic, + lane: "L12", + decides: "branch-access subset by user role/context", + }, + Savant { + id: 11, + name: "ProcurementRuleSelector", + family: None, + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L13", + decides: "route among equal-sequence rules (lead/availability/reliability)", + }, + Savant { + id: 12, + name: "ReorderTimingAdvisor", + family: None, + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L13", + decides: "reorder timing under demand/supplier uncertainty", + }, + Savant { + id: 13, + name: "ReplenishmentReportAdvisor", + family: None, + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L13", + decides: "real shortfall vs demand noise in the replenishment report", + }, + Savant { + id: 14, + name: "RouteTiebreaker", + family: None, + kind: NextBestAction, + inference: Abduction, + semiring: NarsTruth, + style: Analytical, + lane: "L13", + decides: "equal-sequence route tiebreak (supplier lead/cost/capacity)", + }, + Savant { + id: 15, + name: "TaxExigibilitySuggestor", + family: Some(0x62), + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L15", + decides: "tax exigibility (on-invoice vs on-payment / cash-basis)", + }, // ── L1–L7 original lanes (10; id 16 intentionally absent per SAVANTS.md) ── - Savant { id: 17, name: "AutopostRecommender", family: Some(0x81), kind: PostingAnomaly, inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L1", decides: "recommend auto-posting bills after 3+ unmodified from a partner" }, - Savant { id: 18, name: "LockDateAdvancer", family: Some(0x81), kind: PostingAnomaly, inference: Abduction, semiring: NarsTruth, style: Analytical, lane: "L1", decides: "which next open period to advance a move into when date is locked" }, - Savant { id: 19, name: "ReconcileMatchSelector", family: None, kind: Other(other_kind::RECONCILE_MATCH), inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L2", decides: "which open items to propose as reconciliation candidates" }, - Savant { id: 20, name: "BankStatementMatcher", family: None, kind: Other(other_kind::BANK_STATEMENT_MATCH), inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L5", decides: "which reconcile-model rule matches a bank line + write-offs" }, - Savant { id: 21, name: "PaymentToInvoiceMatcher", family: None, kind: Other(other_kind::RECONCILE_MATCH), inference: Induction, semiring: NarsTruth, style: Analytical, lane: "L5", decides: "whether a payment fully reconciles open invoices (Mahnwesen gate)" }, - Savant { id: 22, name: "UpsellActivityTrigger", family: Some(0x81), kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Exploratory, lane: "L6", decides: "qty_delivered>ordered ⇒ upsell TODO for salesperson" }, - Savant { id: 23, name: "PricelistRecommender", family: Some(0x81), kind: NextBestAction, inference: Synthesis, semiring: NarsTruth, style: Exploratory, lane: "L6", decides: "which pricelist rule when multiple candidates apply" }, - Savant { id: 24, name: "RemovalStrategySelector", family: None, kind: NextBestAction, inference: Induction, semiring: XorBundle, style: Exploratory, lane: "L7", decides: "which quants to bind to a reservation (FIFO/FEFO/LIFO/closest)" }, - Savant { id: 25, name: "MoveAssignmentPrioritizer", family: None, kind: NextBestAction, inference: Induction, semiring: NarsTruth, style: Exploratory, lane: "L7", decides: "which confirmed moves to satisfy first (priority/deadline/quants)" }, - Savant { id: 26, name: "BackorderJudge", family: None, kind: NextBestAction, inference: Abduction, semiring: NarsTruth, style: Exploratory, lane: "L7", decides: "partial fulfilment ⇒ backorder vs cancel remainder" }, + Savant { + id: 17, + name: "AutopostRecommender", + family: Some(0x81), + kind: PostingAnomaly, + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L1", + decides: "recommend auto-posting bills after 3+ unmodified from a partner", + }, + Savant { + id: 18, + name: "LockDateAdvancer", + family: Some(0x81), + kind: PostingAnomaly, + inference: Abduction, + semiring: NarsTruth, + style: Analytical, + lane: "L1", + decides: "which next open period to advance a move into when date is locked", + }, + Savant { + id: 19, + name: "ReconcileMatchSelector", + family: None, + kind: Other(other_kind::RECONCILE_MATCH), + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L2", + decides: "which open items to propose as reconciliation candidates", + }, + Savant { + id: 20, + name: "BankStatementMatcher", + family: None, + kind: Other(other_kind::BANK_STATEMENT_MATCH), + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L5", + decides: "which reconcile-model rule matches a bank line + write-offs", + }, + Savant { + id: 21, + name: "PaymentToInvoiceMatcher", + family: None, + kind: Other(other_kind::RECONCILE_MATCH), + inference: Induction, + semiring: NarsTruth, + style: Analytical, + lane: "L5", + decides: "whether a payment fully reconciles open invoices (Mahnwesen gate)", + }, + Savant { + id: 22, + name: "UpsellActivityTrigger", + family: Some(0x81), + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Exploratory, + lane: "L6", + decides: "qty_delivered>ordered ⇒ upsell TODO for salesperson", + }, + Savant { + id: 23, + name: "PricelistRecommender", + family: Some(0x81), + kind: NextBestAction, + inference: Synthesis, + semiring: NarsTruth, + style: Exploratory, + lane: "L6", + decides: "which pricelist rule when multiple candidates apply", + }, + Savant { + id: 24, + name: "RemovalStrategySelector", + family: None, + kind: NextBestAction, + inference: Induction, + semiring: XorBundle, + style: Exploratory, + lane: "L7", + decides: "which quants to bind to a reservation (FIFO/FEFO/LIFO/closest)", + }, + Savant { + id: 25, + name: "MoveAssignmentPrioritizer", + family: None, + kind: NextBestAction, + inference: Induction, + semiring: NarsTruth, + style: Exploratory, + lane: "L7", + decides: "which confirmed moves to satisfy first (priority/deadline/quants)", + }, + Savant { + id: 26, + name: "BackorderJudge", + family: None, + kind: NextBestAction, + inference: Abduction, + semiring: NarsTruth, + style: Exploratory, + lane: "L7", + decides: "partial fulfilment ⇒ backorder vs cancel remainder", + }, ]; /// Look up a savant by roster id. @@ -120,7 +370,11 @@ mod tests { assert_eq!(SAVANTS.len(), 25); for s in &SAVANTS { assert!(!s.name.is_empty() && !s.decides.is_empty()); - assert_eq!(savant(s.id).map(|x| x.name), Some(s.name), "id lookup round-trips"); + assert_eq!( + savant(s.id).map(|x| x.name), + Some(s.name), + "id lookup round-trips" + ); } assert!(savant(16).is_none(), "id 16 intentionally absent"); } diff --git a/crates/lance-graph-contract/src/scheduler.rs b/crates/lance-graph-contract/src/scheduler.rs index 46c7a912..1bb55b14 100644 --- a/crates/lance-graph-contract/src/scheduler.rs +++ b/crates/lance-graph-contract/src/scheduler.rs @@ -83,12 +83,12 @@ impl VersionScheduler for NextPhaseScheduler { // `next_phases()` is empty exactly for the absorbing columns (Commit/Prune): // `?` short-circuits to `None`, i.e. "the cycle ended — schedule nothing". let to = *from.next_phases().first()?; - let libet_offset_us = - if from == KanbanColumn::Planning && to == KanbanColumn::CognitiveWork { - -550_000 - } else { - 0 - }; + let libet_offset_us = if from == KanbanColumn::Planning && to == KanbanColumn::CognitiveWork + { + -550_000 + } else { + 0 + }; Some(KanbanMove { mailbox: view.mailbox_id(), from, @@ -146,13 +146,21 @@ mod tests { } fn view(phase: KanbanColumn) -> FakeView { - FakeView { id: 42, phase, cycle: 9 } + FakeView { + id: 42, + phase, + cycle: 9, + } } #[test] fn planning_schedules_cognitive_work_with_libet_anchor() { let m = NextPhaseScheduler - .on_version(&view(KanbanColumn::Planning), DatasetVersion(1), ExecTarget::Native) + .on_version( + &view(KanbanColumn::Planning), + DatasetVersion(1), + ExecTarget::Native, + ) .expect("Planning is not absorbing"); assert_eq!(m.from, KanbanColumn::Planning); assert_eq!(m.to, KanbanColumn::CognitiveWork); // forward arc, not the Prune veto @@ -164,13 +172,21 @@ mod tests { #[test] fn mid_cycle_advances_carry_no_libet_anchor() { let cw = NextPhaseScheduler - .on_version(&view(KanbanColumn::CognitiveWork), DatasetVersion(2), ExecTarget::Native) + .on_version( + &view(KanbanColumn::CognitiveWork), + DatasetVersion(2), + ExecTarget::Native, + ) .unwrap(); assert_eq!(cw.to, KanbanColumn::Evaluation); assert_eq!(cw.libet_offset_us, 0); let ev = NextPhaseScheduler - .on_version(&view(KanbanColumn::Evaluation), DatasetVersion(3), ExecTarget::Native) + .on_version( + &view(KanbanColumn::Evaluation), + DatasetVersion(3), + ExecTarget::Native, + ) .unwrap(); assert_eq!(ev.to, KanbanColumn::Commit); // forward arc = calcify assert_eq!(ev.libet_offset_us, 0); @@ -179,7 +195,11 @@ mod tests { #[test] fn plan_re_deliberates_back_to_planning() { let m = NextPhaseScheduler - .on_version(&view(KanbanColumn::Plan), DatasetVersion(4), ExecTarget::Native) + .on_version( + &view(KanbanColumn::Plan), + DatasetVersion(4), + ExecTarget::Native, + ) .unwrap(); assert_eq!(m.from, KanbanColumn::Plan); assert_eq!(m.to, KanbanColumn::Planning); // re-enter carrying the witness @@ -189,10 +209,18 @@ mod tests { fn absorbing_columns_schedule_nothing() { // Commit + Prune are absorbing: the cycle has ended, no move is due. assert!(NextPhaseScheduler - .on_version(&view(KanbanColumn::Commit), DatasetVersion(5), ExecTarget::Native) + .on_version( + &view(KanbanColumn::Commit), + DatasetVersion(5), + ExecTarget::Native + ) .is_none()); assert!(NextPhaseScheduler - .on_version(&view(KanbanColumn::Prune), DatasetVersion(6), ExecTarget::Native) + .on_version( + &view(KanbanColumn::Prune), + DatasetVersion(6), + ExecTarget::Native + ) .is_none()); } @@ -200,7 +228,12 @@ mod tests { fn exec_target_threads_through_to_the_move() { // The scheduler carries the backend selection onto the precipitated move // (the Native/Jit/SurrealQl/Elixir routing tag for the IN-direction). - for exec in [ExecTarget::Native, ExecTarget::Jit, ExecTarget::SurrealQl, ExecTarget::Elixir] { + for exec in [ + ExecTarget::Native, + ExecTarget::Jit, + ExecTarget::SurrealQl, + ExecTarget::Elixir, + ] { let m = NextPhaseScheduler .on_version(&view(KanbanColumn::Planning), DatasetVersion(7), exec) .unwrap(); diff --git a/crates/lance-graph-contract/src/soa_envelope.rs b/crates/lance-graph-contract/src/soa_envelope.rs index 58f32a65..6fe45bc2 100644 --- a/crates/lance-graph-contract/src/soa_envelope.rs +++ b/crates/lance-graph-contract/src/soa_envelope.rs @@ -121,7 +121,11 @@ pub enum EnvelopeError { /// A column's byte range ends past the declared row stride. Distinct from /// [`StrideMismatch`]: the widths can sum to the stride while a column is /// still positioned (via its `row_offset`) so its end exceeds the stride. - ColumnOutOfBounds { col: u16, col_end: usize, stride: usize }, + ColumnOutOfBounds { + col: u16, + col_end: usize, + stride: usize, + }, /// `as_le_bytes().len()` is not `row_stride * n_rows` (backing store size mismatch). PacketSizeMismatch { expected: usize, found: usize }, /// A requested row or column index is out of bounds. @@ -361,13 +365,33 @@ mod tests { // Two 4-byte columns at offsets 4 and 8 with stride 8. // Width sum = 8 = stride, but column B's end (12) > stride (8). let cols = vec![ - ColumnDescriptor { name_id: 0, kind: ColumnKind::F32, elems_per_row: 1, row_offset: 4 }, - ColumnDescriptor { name_id: 1, kind: ColumnKind::F32, elems_per_row: 1, row_offset: 8 }, + ColumnDescriptor { + name_id: 0, + kind: ColumnKind::F32, + elems_per_row: 1, + row_offset: 4, + }, + ColumnDescriptor { + name_id: 1, + kind: ColumnKind::F32, + elems_per_row: 1, + row_offset: 8, + }, ]; - let env = TestEnvelope { cols, stride: 8, rows: 1, bytes: vec![0u8; 8], cycle: 0 }; + let env = TestEnvelope { + cols, + stride: 8, + rows: 1, + bytes: vec![0u8; 8], + cycle: 0, + }; assert!(matches!( env.verify_layout(), - Err(EnvelopeError::ColumnOutOfBounds { col: 1, col_end: 12, stride: 8 }) + Err(EnvelopeError::ColumnOutOfBounds { + col: 1, + col_end: 12, + stride: 8 + }) )); } diff --git a/crates/lance-graph-contract/src/transaction/ctx.rs b/crates/lance-graph-contract/src/transaction/ctx.rs index dd734e6a..5539b171 100644 --- a/crates/lance-graph-contract/src/transaction/ctx.rs +++ b/crates/lance-graph-contract/src/transaction/ctx.rs @@ -111,6 +111,9 @@ pub trait DolceCtx: Context { // on (DolceCategory, OdooEntityRef). pub trait FibuCtx: Context { /// Align a (DOLCE category, Odoo entity) pair to a FIBU/FIBO frame. - fn align_fibu(&self, dolce: DolceCategory, odoo: crate::cognition::entity::OdooEntityRef) - -> FibuAlignmentRef; + fn align_fibu( + &self, + dolce: DolceCategory, + odoo: crate::cognition::entity::OdooEntityRef, + ) -> FibuAlignmentRef; } diff --git a/crates/lance-graph-contract/src/witness_table.rs b/crates/lance-graph-contract/src/witness_table.rs index d3594ae9..4474c6c8 100644 --- a/crates/lance-graph-contract/src/witness_table.rs +++ b/crates/lance-graph-contract/src/witness_table.rs @@ -165,7 +165,10 @@ mod tests { }; table.set(7, entry).expect("slot 7 is in range"); let got = table.get(7).expect("slot 7 must be present"); - assert_eq!(*got, entry, "get must return the exact entry written by set"); + assert_eq!( + *got, entry, + "get must return the exact entry written by set" + ); } /// Out-of-bounds set returns Err; out-of-bounds get returns None.