From 099ab37b18eab008bf6f0735af341858a5d8dca3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 20:32:15 +0000 Subject: [PATCH 01/11] =?UTF-8?q?board:=20ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLA?= =?UTF-8?q?TION=20=E2=80=94=20osint=5Fsystem=200x0700=20hits=20the=20reser?= =?UTF-8?q?ved=20root=20slot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surfaced when merged OGAR #145 + lance-graph #624 met: OGAR shipped osint_system at 0x0700 (CC==0x00, the reserved domain-root slot per canon), which the mirror's in-tree zero-slot test forbids — so the parallel-mirror is blocked on a remap decision (Option A: move to 0x0702 + q2 update; Option B recommended: 0x0700 is the OSINT domain-root/default class, drop from the concept CODEBOOK, mirror only osint_person 0x0701). lance-graph main CI is green (fuse is in the excluded lance-graph-ogar); consumers vendoring lance-graph-ogar are the exposure. Board: ISSUES ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION (blocks ISS-OGAR-OSINT-MIRROR-PENDING). Co-Authored-By: Claude --- .claude/board/ISSUES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 5faa7460..e055c906 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,5 +1,19 @@ # Issues Log — Open + Resolved (double-entry, append-only) +## 2026-07-01 — ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION — OGAR shipped `osint_system` at the reserved `0x0700` root slot; the lance-graph mirror canon forbids it (`CC==0x00` = domain root, reserved) — the parallel-mirror is BLOCKED on a remap decision + +**Status:** OPEN · **BLOCKS `ISS-OGAR-OSINT-MIRROR-PENDING`.** Owner: OGAR `ogar-vocab` (merged, needs follow-up) + `lance-graph-contract::ogar_codebook` mirror + q2 `osint_classview`. Surfaced 2026-07-01 when the merged OGAR #145 + lance-graph #624 met and I ran `cargo test -p lance-graph-contract`. + +**The violation.** The shared codebook canon (documented in `ogar_codebook.rs` module header: *"`CC == 0x00` = the domain root, reserved"*) requires every concept id `0xDDCC` to have `CC ≥ 0x01`; `0x__00` is the domain-root/default, NOT a concrete concept. OGAR main ships **`("osint_system", 0x0700)`** — `CC == 0x00`, the reserved root. `("osint_person", 0x0701)` is valid (`CC==01`, operator-frozen). The lance-graph mirror enforces the canon via the workspace-member test `codebook_has_no_duplicate_ids_or_zero_concept_slot` (`assert_ne!(id & 0x00FF, 0x00)`), so **mirroring `0x0700` fails lance-graph's own default CI** (748 pass, 1 fail). The `COUNT_FUSE` (in the *excluded* `lance-graph-ogar`) is a separate, downstream break; this one is in-tree. + +**Current blast radius.** lance-graph main's default CI is GREEN (mirror still 65, zero-slot test passes; the `COUNT_FUSE` lives in the excluded `lance-graph-ogar`). Consumers vendoring `lance-graph-ogar` against OGAR-main-67 vs mirror-65 will break on the count fuse. The parallel-mirror fix is **blocked** because the obvious "+2 rows" fix trips the zero-slot invariant. + +**Decision needed (operator).** Two coherent reads of `osint_system @ 0x0700`: +- **Option A — it's a concrete concept → remap.** Move `osint_system` to `0x0702` in OGAR (fresh PR; `0x0701` frozen for `osint_person`); mirror `{0x0701, 0x0702}` (count 67); update q2 `OSINT_SYSTEM_CLASS 0x0700 → 0x0702`. Canon satisfied, but a merged id moves + q2 change. +- **Option B (recommended) — `0x0700` IS the OSINT domain root/default class, not a counted concept.** This is exactly what the canon reserves `0x__00` for ("zero = fall through to the broader default"). OGAR drops `osint_system` from the *concept* `CODEBOOK`/`class_ids::ALL` (keep an `OSINT_SYSTEM = 0x0700` const documented as the domain-root class if useful); `ALL` → 66; mirror carries only `("osint_person", 0x0701)` → 66; the fuse balances at 66; q2 keeps `0x0700` as the renderable domain-default class (canon-legal: the root IS a real default class, just not a codebook *concept* row). No id moves; aligns with the user's "0x0701 is the frozen concept" framing. + +Both are OGAR-side follow-ups (OGAR #145 is merged) landed in parallel with the lance-graph mirror rows, per `E-OGAR-LANCEGRAPH-MOVE-IN-PARALLEL`. + ## 2026-07-01 — ISS-OGAR-OSINT-MIRROR-PENDING — OGAR #145's OSINT mint (+2 to `class_ids::ALL`) breaks the contract-mirror `COUNT_FUSE` on merge; the paired lance-graph mirror rows must land in the same arc **Status:** OPEN (tracked) · **Resolution path RULED by operator 2026-07-01: keep the fuse (it IS the dependency contract enforcing OGAR↔lance-graph parallel movement); do NOT pin to a rev — "option 1" is REJECTED. Land the 2 mirror rows + `domains_agree` arm in parallel with OGAR #145 (option 2 / coordinated merge; brief transient red is acceptable — "the fuse is okay for now"). See `E-OGAR-LANCEGRAPH-MOVE-IN-PARALLEL`.** · Owner: OGAR `ogar-vocab` (PR #145) + `lance-graph-contract::ogar_codebook` mirror + `lance-graph-ogar::parity::domains_agree`. Surfaced 2026-07-01 while self-reviewing PR #624 / #145. Same cross-repo-arc shape as `ISS-OGAR-AUTH-MIRROR-DRIFT` (which took medcare CI red) and `ISS-OGAR-GENETICS-MIRROR-PENDING`; cited by `E-CODEBOOK-MINT-IS-A-CROSS-REPO-ARC`. From 2c1109fcf5fe5ef411abb8a46de36865204d0355 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 20:47:05 +0000 Subject: [PATCH 02/11] board: render = class + bitmask + askama (Redmine ERB), NOT semiring Frozen concept (lo u16) = the AR class identity (a class is just a class). The non-frozen 0x1000 app prefix is the render side that adapts the askama SoA view: class -> ClassView -> bitmask (implicit focus of attention over SoA columns) -> askama template (Redmine-ERB over AR). No semiring on the view path. The osint_system 0x0700->0x0702 fix is a frozen-side lo-u16 slot correction, orthogonal to this render doctrine. Board: EPIPHANIES E-RENDER-IS-CLASS-BITMASK-ASKAMA-NOT-SEMIRING. Co-Authored-By: Claude --- .claude/board/EPIPHANIES.md | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 05644ea3..11227097 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,43 @@ +## 2026-07-01 — E-RENDER-IS-CLASS-BITMASK-ASKAMA-NOT-SEMIRING — the view/attention side is `class + bitmask + askama` (Redmine ERB over AR), NOT semiring algebra; it lives in the non-frozen `0x1000` app prefix + +**Status:** DOCTRINE (operator, 2026-07-01: "we don't want semiring, we use +classes and bitmask and askama (redmine ERB pattern) … the 1000 non-frozen later +gets more complicated because it adapts the askama SoA view (implicit focus of +attention)"). + +**The split.** +- **Frozen concept (lo u16, e.g. `0x0701`)** = the shared **Active Record class + identity**. A class is just a class (Redmine `Issue`/`Project`/`User` shape); + the domain prefix is namespacing, nothing to philosophize about. Frozen, + cross-app, RBAC+ontology. +- **Non-frozen app prefix (hi u16, e.g. `0x1000`)** = the **render / view** side, + which "adapts the askama SoA view." This is where the complexity accretes + *later*, per-app, and it is NOT the concept. + +**The render mechanism — NO semiring.** View/attention is: +`classid → ClassView (the AR class) → bitmask (the implicit focus of attention: +which SoA value-tenant columns render/attend) → askama template (Redmine-ERB over +the AR model)`. The **bitmask is the focus-of-attention selector**; askama is the +ERB view; the class is the model. No semiring algebra on this path — the AGI-glove +render surface is `class + bitmask + askama`, not `Σ`. + +**Consequences.** +- The 36-semiring machinery (`docs/SEMIRING_ALGEBRA_SURFACE.md`) is NOT the render + path. Do not reach for a semiring to select/compose *view fields* — that's the + bitmask + askama job. +- The bitmask = the `ClassView` field-mask over the SoA value tenants (the "focus + of awareness" from `E-SPO-2CUBE-…` / the AGI-glove `MetaColumn`), consumed by an + askama template per app prefix. +- The immediate `osint_system 0x0700 → 0x0702` fix is a **frozen-concept lo-u16 + slot** correction (a class needs a non-root id) — entirely orthogonal to this + render-side doctrine; the askama/bitmask complexity is the `0x1000` side, later. + +**Cross-ref:** `E-SPO-2CUBE-GIVES-QUESTIONS-AND-CANDIDATES` (bitmask = focus); +OGAR `docs/OSINT-SUBSTRATE-REUSE-MAP.md` (ClassView + askama-ERB transfer stack); +`ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION` (the frozen-side id fix). + +--- + ## 2026-07-01 — E-OGAR-LANCEGRAPH-MOVE-IN-PARALLEL — OGAR + lance-graph are one coupled pair moved together every session; the `COUNT_FUSE` is the intentional dependency contract that enforces it (NOT a break to engineer away) **Status:** DOCTRINE (operator, 2026-07-01: "every session needs to move OGAR and From 217a6984292c84a1603fbb1313085d26bb8acce3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 21:35:33 +0000 Subject: [PATCH 03/11] osint-v3: certify the Cognitive tenant carve (field-isolation matrix) + unblock resolve via local ndarray patch Phase-2 seam of the V3 substrate: the AriGraph-shaped SoA tenants already exist (ValueSchema::Cognitive = the 7 hot lanes the MailboxSoaView columns expose); what was missing was their certification. New probe osint_v3_cognitive_tenant_carve_field_isolation_matrix extends the I-LEGACY mandatory matrix from Kanban-only to the whole Cognitive carve on a registry-minted OSINT-V3 row (mint_for dispatch, never a hardcoded constructor): per-tenant lane flip changes zero bytes outside the lane, key+edges untouched, EntityType lane carries the canon 0x0700 concept (the 0x1000 gen-marker never leaks into the entity discriminator), and the typed accessors decode the same certified slab. Build unblock: [patch.crates-io] ndarray git-URL -> local sibling path. The git form re-fetched the fork + its burn submodule on every resolve; AdaWorldAPI/burn is outside the session repo scope (403; gitlink 9b2b671 unfetchable), deadlocking scoped/offline sessions. The patch was [[patch.unused]] in the lock either way (TD-NDARRAY-PATCH-0_16), so the resolved graph is unchanged - only the fetch deadlock is gone. Same fork, local source, per the P0 fork doctrine. facet_schema.rs: fmt normalization only. Gates: 762 lib tests green (guid-v2-tail,guid-v3-tail), 749 default, cargo fmt clean, clippy -D warnings clean. Board: EPIPHANIES E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT (same commit). Co-Authored-By: Claude --- .claude/board/EPIPHANIES.md | 38 ++++++ Cargo.lock | 5 - Cargo.toml | 8 +- .../src/canonical_node.rs | 113 ++++++++++++++++++ .../lance-graph-contract/src/facet_schema.rs | 5 +- 5 files changed, 162 insertions(+), 7 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 11227097..477095be 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,41 @@ +## 2026-07-01 — E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT — the "V3 substrate for AriGraph-shaped SoA tenants" already exists in `canonical_node.rs`; Phase-2 work is certification, not invention + +**Status:** FINDING (probe green: `osint_v3_cognitive_tenant_carve_field_isolation_matrix`). + +**The near-miss.** This session was about to invent a "new V3 substrate for +AriGraph-shaped SoA tenants" (operator caught it). Reading before writing showed +every piece already shipped: +- `TailVariant::V3` + `CLASSID_OSINT_V3 = 0x1000_0700` (gen-marker hi u16, canon + concept lo u16 — exactly the operator's `0x07:01 / 1000`), registry-resolved via + `classid_read_mode` → `ReadMode::OSINT_V3 = {V3, Cognitive, CoarseOnly}`. +- The AriGraph-hot tenants ARE `ValueSchema::Cognitive`'s 7 lanes (Meta/Qualia/ + Fingerprint/Energy/Plasticity/EntityType/Kanban) — the same columns + `MailboxSoaView` exposes (`meta_raw`/`energy`/`entity_type`≡`class_id`) and + `arigraph::markov_soa` (the wave) folds over. +- `mint_for(classid_read_mode(c).tail_variant, …)` is the sanctioned mint; no + `new_v3` exists by design. + +**What was actually missing (and now shipped):** the I-LEGACY mandatory +field-isolation matrix covered ONLY the Kanban tenant. New probe extends it to +the whole Cognitive carve on a registry-minted OSINT-V3 row: per-tenant lane +flip → zero bytes change outside the lane, key+edges untouched, EntityType lane +carries the canon `0x0700` concept (gen-marker never leaks into the entity +discriminator), typed accessors (`set_kanban`/`qualia`) decode the same certified +slab. Phase 2 ("shape the V3 tenants on top", CPIC doc) proceeds as *readings +over this certified carve* — `classid → ClassView` interpretations, never new +`ValueSchema` variants (#496/#500 guardrail). + +**Build unblock (same commit):** `[patch.crates-io] ndarray` moved from the git +URL to the local sibling path (`path = "../ndarray"`). The git form re-fetched +the fork + its `burn` submodule on every resolve; `AdaWorldAPI/burn` is outside +the session repo scope (403, unfetchable gitlink `9b2b671`), deadlocking every +offline/scoped session. The patch was `[[patch.unused]]` in the lock either way +(documented TD-NDARRAY-PATCH-0_16), so resolution is unchanged — only the +fetch-deadlock is gone. P0 fork doctrine holds: same fork, local source +("prefer the local/fork source over the registry, always"). + +--- + ## 2026-07-01 — E-RENDER-IS-CLASS-BITMASK-ASKAMA-NOT-SEMIRING — the view/attention side is `class + bitmask + askama` (Redmine ERB over AR), NOT semiring algebra; it lives in the non-frozen `0x1000` app prefix **Status:** DOCTRINE (operator, 2026-07-01: "we don't want semiring, we use diff --git a/Cargo.lock b/Cargo.lock index b237f5a1..55ffc294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10359,8 +10359,3 @@ dependencies = [ "cc", "pkg-config", ] - -[[patch.unused]] -name = "ndarray" -version = "0.17.2" -source = "git+https://github.com/AdaWorldAPI/ndarray.git?branch=master#f22a28b2488677126a3bad0440b1967ab91cb1a8" diff --git a/Cargo.toml b/Cargo.toml index 54c23ec1..a4e84d4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -217,4 +217,10 @@ resolver = "2" # The patch is a declared-intent override; effect on the transitive depends # on semver compat. See BLOCKED(D) comment above + TD-NDARRAY-PATCH-0_16. [patch.crates-io] -ndarray = { git = "https://github.com/AdaWorldAPI/ndarray.git", branch = "master" } +# Local sibling checkout of the SAME fork (P0: "prefer the local/fork source, +# always"; the direct deps already assume the sibling via path = "../../../ndarray"). +# The git-URL form re-fetched AdaWorldAPI/ndarray+its burn submodule on every +# resolve; burn is outside the session repo scope (403) and the gitlink rev is +# unfetchable, so the git form deadlocks offline sessions. Path form = zero +# network, identical source. +ndarray = { path = "../ndarray" } diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index 129da4e1..c64089ef 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -2297,6 +2297,119 @@ mod tests { ); } + #[cfg(feature = "guid-v3-tail")] + #[test] + fn osint_v3_cognitive_tenant_carve_field_isolation_matrix() { + // Phase-2 seam (the CPIC doc's "Phase 2 shapes the V3 tenants … on top"): + // a V3 node's VALUE side is the tenant carve its registry read-mode names. + // The V3 exemplar OSINT-V3 resolves to ValueSchema::Cognitive — the + // AriGraph-hot set the mailbox SoA view reads (Meta/Energy/EntityType are + // the `meta_raw`/`energy`/`entity_type` columns of + // `crate::soa_view::MailboxSoaView`; EntityType IS the `class_id` alias). + // The I-LEGACY mandatory field-isolation matrix existed only for the + // Kanban tenant (kanban_tenant_round_trip_and_field_isolation); this + // extends it to EVERY tenant of the Cognitive carve: writing one tenant's + // byte lane changes NO byte outside it, and never touches key or edges. + // Nothing invented — key minted by registry dispatch (`mint_for`), lanes + // read from the compile-asserted `ValueTenant` carve. + + // (1) Registry says: OSINT-V3 = {V3 tail, Cognitive value, CoarseOnly edges}. + let rm = classid_read_mode(NodeGuid::CLASSID_OSINT_V3); + assert_eq!(rm.value_schema, ValueSchema::Cognitive); + + // (2) Pin the carve: exactly the 7 hot tenants, none of the codec residues. + let hot = [ + ValueTenant::Meta, + ValueTenant::Qualia, + ValueTenant::Fingerprint, + ValueTenant::Energy, + ValueTenant::Plasticity, + ValueTenant::EntityType, + ValueTenant::Kanban, + ]; + for t in hot { + assert!(rm.value_schema.has(t), "Cognitive materialises {t:?}"); + } + for t in [ + ValueTenant::MaterializedEdges, + ValueTenant::HelixResidue, + ValueTenant::TurbovecResidue, + ] { + assert!(!rm.value_schema.has(t), "Cognitive must NOT carry {t:?}"); + } + assert_eq!(rm.value_schema.field_mask().count() as usize, hot.len()); + + // (3) Mint the key by registry dispatch (consumers never hardcode a + // constructor) and build the canonical row. + let key = NodeGuid::mint_for( + rm.tail_variant, + NodeGuid::CLASSID_OSINT_V3, + 0x0101, + 0x0202, + 0x0303, + 0x0404, + 0x0505, + 0x0606, + ); + let mut row = NodeRow { + key, + edges: EdgeBlock::default(), + value: [0u8; 480], + }; + + // (4) THE MATRIX: flip every byte of one tenant's lane; assert the lane + // changed and every byte OUTSIDE it did not — per tenant, in carve order. + for t in hot { + let before = row.value; + let (o, n) = (t.value_offset(), t.byte_len()); + for b in &mut row.value[o..o + n] { + *b ^= 0xFF; + } + for (i, (&now, &was)) in row.value.iter().zip(before.iter()).enumerate() { + if (o..o + n).contains(&i) { + assert_ne!(now, was, "{t:?} lane byte {i} must have flipped"); + } else { + assert_eq!(now, was, "byte {i} outside {t:?} lane must not change"); + } + } + } + // Value-slab writes never move the key or the edge block. + assert_eq!(row.key, key, "key untouched by value-tenant writes"); + assert_eq!( + row.edges, + EdgeBlock::default(), + "edge block untouched by value-tenant writes" + ); + + // (5) EntityType tenant ↔ SoA class column: the u16 the slab carries is the + // same discriminator `MailboxSoaView::class_id()` (alias of + // `entity_type()`) exposes per row. Stamp the canon low-u16 concept and + // read it back — on the V3 class it is still the Osint concept 0x0700 + // (the high-u16 gen-marker never leaks into the entity discriminator). + let o = ValueTenant::EntityType.value_offset(); + row.value[o..o + 2].copy_from_slice(&(NodeGuid::CLASSID_OSINT_V3 as u16).to_le_bytes()); + let et = u16::from_le_bytes([row.value[o], row.value[o + 1]]); + assert_eq!( + et, 0x0700, + "EntityType tenant carries the canon Osint concept" + ); + + // (6) Typed accessors stay live on the V3 row: the kanban round-trip and + // the qualia read decode the SAME slab this matrix certified. + row.set_kanban(KanbanTenant { + phase: KanbanColumn::CognitiveWork, + exec: ExecTarget::Native, + cycle: 0x0701_1000, + }); + assert_eq!(row.kanban().cycle, 0x0701_1000); + assert_eq!(row.qualia().0, { + let qo = ValueTenant::Qualia.value_offset(); + let mut b = [0u8; 8]; + b.copy_from_slice(&row.value[qo..qo + 8]); + u64::from_le_bytes(b) + }); + } + #[cfg(feature = "guid-v2-tail")] #[test] fn mint_for_dispatches_to_the_right_constructor_per_tail() { diff --git a/crates/lance-graph-contract/src/facet_schema.rs b/crates/lance-graph-contract/src/facet_schema.rs index c97b72b6..47490501 100644 --- a/crates/lance-graph-contract/src/facet_schema.rs +++ b/crates/lance-graph-contract/src/facet_schema.rs @@ -154,7 +154,10 @@ mod tests { #[test] fn schema_defaults_to_tier_cascade() { // High byte low-2-bits == 0 → TierCascade (every existing facet). - assert_eq!(FacetSchema::of_classid(0x0012_3456), FacetSchema::TierCascade); + assert_eq!( + FacetSchema::of_classid(0x0012_3456), + FacetSchema::TierCascade + ); assert_eq!(FacetSchema::default(), FacetSchema::TierCascade); } From 4f06d6021ff6cea56cd7bcdc2271e5b070395314 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 21:37:42 +0000 Subject: [PATCH 04/11] =?UTF-8?q?fma/cpic-v3:=20certify=20the=20Compressed?= =?UTF-8?q?=20tenant=20carve=20=E2=80=94=20Phase-1=20V3=20carve=20coverage?= =?UTF-8?q?=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cold half of the Phase-1 V3 set: FMA-V3 and CPIC-V3 both resolve to ValueSchema::Compressed (Fingerprint/HelixResidue/TurbovecResidue/ EntityType). Same certification as the OSINT-V3/Cognitive matrix: pin the 4-lane carve (and the 6 absent hot tenants), registry-dispatched mint_for, per-tenant lane-flip isolation via the shared assert_value_lane_isolation helper (Cognitive matrix refactored onto it), key+edges untouched, EntityType discriminator carries the canon lo-u16 concept (Anatomy 0x0A01 / Genetics 0x0E00 — the 0x1000 gen-marker never leaks). With this, every carve a Phase-1 V3 class materialises is matrix- covered; Phase-2 tenant readings proceed over certified lanes. Gates: 763 lib tests green (guid-v2-tail,guid-v3-tail), 749 default, cargo fmt clean, clippy -D warnings clean. Board: EPIPHANIES E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT Status line updated (both probes named). Co-Authored-By: Claude --- .claude/board/EPIPHANIES.md | 2 +- .../src/canonical_node.rs | 116 +++++++++++++++--- 2 files changed, 103 insertions(+), 15 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 477095be..a604ff67 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,6 +1,6 @@ ## 2026-07-01 — E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT — the "V3 substrate for AriGraph-shaped SoA tenants" already exists in `canonical_node.rs`; Phase-2 work is certification, not invention -**Status:** FINDING (probe green: `osint_v3_cognitive_tenant_carve_field_isolation_matrix`). +**Status:** FINDING (probes green: `osint_v3_cognitive_tenant_carve_field_isolation_matrix` + `fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix` — BOTH carves a Phase-1 V3 class materialises, Cognitive hot + Compressed cold, are now matrix-covered). **The near-miss.** This session was about to invent a "new V3 substrate for AriGraph-shaped SoA tenants" (operator caught it). Reading before writing showed diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index c64089ef..a7a8adf3 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -2359,20 +2359,7 @@ mod tests { // (4) THE MATRIX: flip every byte of one tenant's lane; assert the lane // changed and every byte OUTSIDE it did not — per tenant, in carve order. - for t in hot { - let before = row.value; - let (o, n) = (t.value_offset(), t.byte_len()); - for b in &mut row.value[o..o + n] { - *b ^= 0xFF; - } - for (i, (&now, &was)) in row.value.iter().zip(before.iter()).enumerate() { - if (o..o + n).contains(&i) { - assert_ne!(now, was, "{t:?} lane byte {i} must have flipped"); - } else { - assert_eq!(now, was, "byte {i} outside {t:?} lane must not change"); - } - } - } + assert_value_lane_isolation(&mut row, &hot); // Value-slab writes never move the key or the edge block. assert_eq!(row.key, key, "key untouched by value-tenant writes"); assert_eq!( @@ -2410,6 +2397,107 @@ mod tests { }); } + /// Shared body of the tenant-carve matrix: flip every byte of one tenant's + /// lane; assert the lane changed and every byte OUTSIDE it did not — per + /// tenant, in carve order (the I-LEGACY mandatory field-isolation test). + #[cfg(feature = "guid-v3-tail")] + fn assert_value_lane_isolation(row: &mut NodeRow, lanes: &[ValueTenant]) { + for &t in lanes { + let before = row.value; + let (o, n) = (t.value_offset(), t.byte_len()); + for b in &mut row.value[o..o + n] { + *b ^= 0xFF; + } + for (i, (&now, &was)) in row.value.iter().zip(before.iter()).enumerate() { + if (o..o + n).contains(&i) { + assert_ne!(now, was, "{t:?} lane byte {i} must have flipped"); + } else { + assert_eq!(now, was, "byte {i} outside {t:?} lane must not change"); + } + } + } + } + + #[cfg(feature = "guid-v3-tail")] + #[test] + fn fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix() { + // The cold half of the Phase-1 V3 set: FMA-V3 and CPIC-V3 both resolve to + // ValueSchema::Compressed — the codec-stack carve (no hot lifecycle + // columns). Same certification as the OSINT-V3/Cognitive matrix above: + // registry-dispatched mint, per-tenant lane isolation, key+edges untouched. + // With this, EVERY carve a Phase-1 V3 class materialises is matrix-covered. + let rm = classid_read_mode(NodeGuid::CLASSID_FMA_V3); + assert_eq!(rm.value_schema, ValueSchema::Compressed); + // CPIC-V3 shares the SAME cold carve — one matrix certifies both. + assert_eq!( + classid_read_mode(NodeGuid::CLASSID_CPIC_V3).value_schema, + ValueSchema::Compressed + ); + + // Pin the carve: exactly the 4 codec tenants, none of the hot lifecycle set. + let cold = [ + ValueTenant::Fingerprint, + ValueTenant::HelixResidue, + ValueTenant::TurbovecResidue, + ValueTenant::EntityType, + ]; + for t in cold { + assert!(rm.value_schema.has(t), "Compressed materialises {t:?}"); + } + for t in [ + ValueTenant::Meta, + ValueTenant::Qualia, + ValueTenant::MaterializedEdges, + ValueTenant::Energy, + ValueTenant::Plasticity, + ValueTenant::Kanban, + ] { + assert!(!rm.value_schema.has(t), "Compressed must NOT carry {t:?}"); + } + assert_eq!(rm.value_schema.field_mask().count() as usize, cold.len()); + + // Registry-dispatched mint + the matrix over the cold lanes. + let key = NodeGuid::mint_for( + rm.tail_variant, + NodeGuid::CLASSID_FMA_V3, + 0x1111, + 0x2222, + 0x3333, + 0x4444, + 0x5555, + 0x6666, + ); + let mut row = NodeRow { + key, + edges: EdgeBlock::default(), + value: [0u8; 480], + }; + assert_value_lane_isolation(&mut row, &cold); + assert_eq!(row.key, key, "key untouched by value-tenant writes"); + assert_eq!( + row.edges, + EdgeBlock::default(), + "edge block untouched by value-tenant writes" + ); + + // The EntityType discriminator carries the canon lo-u16 concept on the + // cold classes too — Anatomy 0x0A01 / Genetics root 0x0E00, never the + // 0x1000 gen-marker. + let o = ValueTenant::EntityType.value_offset(); + row.value[o..o + 2].copy_from_slice(&(NodeGuid::CLASSID_FMA_V3 as u16).to_le_bytes()); + assert_eq!( + u16::from_le_bytes([row.value[o], row.value[o + 1]]), + 0x0A01, + "EntityType tenant carries the canon Anatomy concept" + ); + row.value[o..o + 2].copy_from_slice(&(NodeGuid::CLASSID_CPIC_V3 as u16).to_le_bytes()); + assert_eq!( + u16::from_le_bytes([row.value[o], row.value[o + 1]]), + 0x0E00, + "EntityType tenant carries the canon Genetics root" + ); + } + #[cfg(feature = "guid-v2-tail")] #[test] fn mint_for_dispatches_to_the_right_constructor_per_tail() { From 18b7f92e16b209092b3dde2198631f0605427528 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 21:40:17 +0000 Subject: [PATCH 05/11] board: ISS-Q2-CPIC-MIRROR-DIVERGES-FROM-CPIC-V3-REGISTRY (record-only) + stale-brief correction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit q2's cpic::NodeGuid mirror verified by read: V1 byte-layout parity TRUE, but V3 (part_of:is_a) tiles ride a V1 u24 tail under unregistered domain 0x0C — three divergences from the registered CPIC-V3 read-mode (0x1000_0E00, V3 tail). Record-only; resolution is an operator decision (q2 is push-gated; cross-repo blockers are never silently fixed). Also corrects the stale soa-value-tenant-migration-v1.md §2.5 blocker: new_v2 exists (7 groups, guid-v2-tail, matrix-tested) and q2 has no new_v2 call site today. Co-Authored-By: Claude --- .claude/board/ISSUES.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index e055c906..3e8a8d3c 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,5 +1,44 @@ # Issues Log — Open + Resolved (double-entry, append-only) +## 2026-07-01 — ISS-Q2-CPIC-MIRROR-DIVERGES-FROM-CPIC-V3-REGISTRY — q2's local `cpic::NodeGuid` mirror is V1-layout-parity-true but diverges from the registered CPIC-V3 read-mode on BOTH domain and tail shape + +**Status:** OPEN (record-only; resolve WITH the operator — q2 is push-gated and +cross-repo blockers are never silently fixed). Owner: q2 `cpic/src/lib.rs` + +`lance-graph-contract::canonical_node` (CPIC-V3 registry). Surfaced 2026-07-01 +by a verify-the-mirror read after the V3 tenant-carve certification. + +**Ground truth (read, not grepped).** q2 `cpic/src/lib.rs`: +- `NodeGuid::mint(classid, part[3], isa[3], family, identity)` builds + HEEL/HIP/TWIG as `(part<<8)|isa` — the V3 `(part_of:is_a)` 8:8 tile. ✓ canon. +- `key16()` packs `classid·heel·hip·twig·family(u24)·identity(u24)` LE — + **byte-identical to the contract's V1 layout** (its parity doc-comment is + TRUE at the byte-order level). But that is the **V1 tail**, not the V3 + (`leaf·family·identity` 3×u16) tail the registry's + `ReadMode::CPIC_V3.tail_variant = V3` reads. +- classids are `0x000C_0001..0x000C_0006` (`CID_GENE..CID_REC`) — domain + **`0x0C`**, NOT the operator-allocated Genetics `0x0E` + (`CLASSID_CPIC_V3 = 0x1000_0E00`), and no `0x1000` V3 gen-marker. + +**So:** V3 *tiles* on a V1 *tail* under an unregistered *domain* — three +divergences from the wired CPIC-V3 read-mode. A bake produced with this mirror +will not resolve to `ReadMode::CPIC_V3` (falls to `ReadMode::DEFAULT`) and its +tail bytes read differently under the registry's V3 lens. + +**Stale-brief correction (same sweep):** `soa-value-tenant-migration-v1.md` +§2.5's blocker — "q2 `osint-bake/fma.rs` calls `NodeGuid::new_v2(...)`, a +7-group API that does **not** exist" — is stale on both halves: `new_v2` DOES +exist (7 groups, feature `guid-v2-tail`, shipped + matrix-tested), and no +`new_v2` call site exists in q2 today (grep: only `cpic::NodeGuid::mint`). + +**Resolution paths (operator decision):** (a) q2 cpic re-mints via the +contract's `mint_for(classid_read_mode(CLASSID_CPIC_V3).tail_variant, …)` +pull (consumer-preflight shape — pull, never mirror); (b) the registry gains +the `0x0C` pharmacogenomics classids q2 actually minted; or (c) the q2 POC is +declared registry-exempt (bake-only) and its parity comment is scoped to +"V1 byte layout" explicitly. No action taken pending direction. + +--- + ## 2026-07-01 — ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION — OGAR shipped `osint_system` at the reserved `0x0700` root slot; the lance-graph mirror canon forbids it (`CC==0x00` = domain root, reserved) — the parallel-mirror is BLOCKED on a remap decision **Status:** OPEN · **BLOCKS `ISS-OGAR-OSINT-MIRROR-PENDING`.** Owner: OGAR `ogar-vocab` (merged, needs follow-up) + `lance-graph-contract::ogar_codebook` mirror + q2 `osint_classview`. Surfaced 2026-07-01 when the merged OGAR #145 + lance-graph #624 met and I ran `cargo test -p lance-graph-contract`. From eb4be79920c8b3867cb24bbd6be23aabdb729eaf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 21:41:36 +0000 Subject: [PATCH 06/11] =?UTF-8?q?board:=20correct=20ISS-Q2-CPIC=20entry=20?= =?UTF-8?q?=E2=80=94=20osint-bake=20DOES=20call=20new=5Fv2=20(truncated-gr?= =?UTF-8?q?ep=20error=20owned)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit osint-bake/src/lib.rs:606,745 mint via NodeGuid::new_v2 against the REAL contract import (no mirror); only cpic carries a local mirror. Appended dated correction per the append-only discipline. Also records the known tail-shape pending: OSINT rows mint a V2 tail for legacy CLASSID_OSINT (registered V1) while the FMA bins already use the mint_for dispatch; CLASSID_OSINT_V3 exists for that migration. Co-Authored-By: Claude --- .claude/board/ISSUES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 3e8a8d3c..3f24e770 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -30,6 +30,21 @@ tail bytes read differently under the registry's V3 lens. exist (7 groups, feature `guid-v2-tail`, shipped + matrix-tested), and no `new_v2` call site exists in q2 today (grep: only `cpic::NodeGuid::mint`). +**CORRECTION (2026-07-01, same session — the previous paragraph's second half +is WRONG; truncated-grep error, head_limit cut before osint-bake):** q2 +`osint-bake` DOES call `new_v2` — `crates/osint-bake/src/lib.rs:606` mints the +classid-`0x0700` OSINT rows via `NodeGuid::new_v2(NodeGuid::CLASSID_OSINT, …)` +(also `:745`), and it imports the REAL contract +(`use lance_graph_contract::canonical_node::{NodeGuid, classid_read_mode}`) — +no mirror in osint-bake; only `cpic` carries the local mirror. What stands: +the brief's "API does not exist" half is stale (`new_v2` exists and q2 links +it fine). NEW observation for the same operator decision: osint-bake's OSINT +rows mint a **V2 tail** directly (`new_v2`) for legacy `CLASSID_OSINT`, whose +registered read-mode is `tail_variant = V1` — the known per-classid-legacy-tail +pending noted in `ReadMode::DEFAULT`'s docs, while its FMA bins already use the +sanctioned `mint_for(classid_read_mode(c).tail_variant, …)` dispatch. The V3 +class `CLASSID_OSINT_V3 = 0x1000_0700` exists precisely for that migration. + **Resolution paths (operator decision):** (a) q2 cpic re-mints via the contract's `mint_for(classid_read_mode(CLASSID_CPIC_V3).tail_variant, …)` pull (consumer-preflight shape — pull, never mirror); (b) the registry gains From aa50cf8910ddd327797d65b948389a98658299e4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 21:50:09 +0000 Subject: [PATCH 07/11] =?UTF-8?q?markov=5Fsoa:=20clear=20the=20unverified-?= =?UTF-8?q?offline=20STATUS=20=E2=80=94=20core=20compiles=20+=20arigraph?= =?UTF-8?q?=20124/124=20green=20in-sandbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The module's declared caveat ('lance-graph core does NOT build in the offline sandbox') is obsolete: with crates.io in the proxy allow-list, the root [patch.crates-io] ndarray on the local sibling path, and protoc installed, cargo check -p lance-graph finishes clean and cargo test -p lance-graph --lib graph::arigraph passes 124/124 — including markov_soa's 4 wave-projector tests. The wire is probed end-to-end: V3 classid -> registry read-mode -> matrix-certified tenant carves -> AriGraph SoA wave. Board: EPIPHANIES E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT Status addendum (same commit). Co-Authored-By: Claude --- .claude/board/EPIPHANIES.md | 2 +- .../lance-graph/src/graph/arigraph/markov_soa.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index a604ff67..9a7c8ea6 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,6 +1,6 @@ ## 2026-07-01 — E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT — the "V3 substrate for AriGraph-shaped SoA tenants" already exists in `canonical_node.rs`; Phase-2 work is certification, not invention -**Status:** FINDING (probes green: `osint_v3_cognitive_tenant_carve_field_isolation_matrix` + `fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix` — BOTH carves a Phase-1 V3 class materialises, Cognitive hot + Compressed cold, are now matrix-covered). +**Status:** FINDING (probes green: `osint_v3_cognitive_tenant_carve_field_isolation_matrix` + `fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix` — BOTH carves a Phase-1 V3 class materialises, Cognitive hot + Compressed cold, are now matrix-covered. 2026-07-01 addendum: lance-graph CORE now compiles in-sandbox — crates.io noProxy + local ndarray path patch + protoc — and `graph::arigraph` is 124/124 green incl. `markov_soa`'s 4, so the wave projector's "unverified-offline" caveat is cleared: the full wire classid → read-mode → certified tenants → AriGraph wave is probed end-to-end). **The near-miss.** This session was about to invent a "new V3 substrate for AriGraph-shaped SoA tenants" (operator caught it). Reading before writing showed diff --git a/crates/lance-graph/src/graph/arigraph/markov_soa.rs b/crates/lance-graph/src/graph/arigraph/markov_soa.rs index 332a6ce9..b72ddcaa 100644 --- a/crates/lance-graph/src/graph/arigraph/markov_soa.rs +++ b/crates/lance-graph/src/graph/arigraph/markov_soa.rs @@ -45,14 +45,16 @@ //! is only legitimate while leashed to the deterministic chain that confirms it //! — an unleashed bundle degrades into "sink-in-and-pray" (Markov #3).** //! -//! ## STATUS: provisional / unverified-offline +//! ## STATUS: verified (2026-07-01) — compiles + tests green in-sandbox //! -//! Authored against the grounded `contract::soa_view::MailboxSoaView` surface, -//! but `lance-graph` core does NOT build in the offline sandbox (its -//! `lance`/`datafusion`/`arrow` deps fetch from crates.io). Compile-verify on a -//! full checkout before relying on it. The truly-correct home is *inside the -//! EW64-in-SoA seam* (P1+P2 of the three-Markovs ordering); this module is the -//! agnostic wave-projector that seam will host. +//! Authored against the grounded `contract::soa_view::MailboxSoaView` surface. +//! The earlier "unverified-offline" caveat (core deps fetch from crates.io) is +//! cleared: with crates.io in the proxy allow-list, the root +//! `[patch.crates-io] ndarray` pointed at the local sibling path, and `protoc` +//! installed, `lance-graph` core compiles and this module's 4 tests pass +//! (part of the 124-green `graph::arigraph` suite). The truly-correct home is +//! still *inside the EW64-in-SoA seam* (P1+P2 of the three-Markovs ordering); +//! this module is the agnostic wave-projector that seam will host. use lance_graph_contract::soa_view::MailboxSoaView; From 284078a4d9c09cc1368db365fcda7fc762f0d5dd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 21:51:28 +0000 Subject: [PATCH 08/11] =?UTF-8?q?board:=20AGENT=5FLOG=20entry=20=E2=80=94?= =?UTF-8?q?=20V3=20carve=20certification=20arc=20+=20tree-wide=20green=20s?= =?UTF-8?q?weep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logs the session arc (patch unblock, both carve matrices, core in-sandbox first build, markov_soa STATUS clear) and the Sonnet report-only sweep: core 925/925, planner 204/204, supervisor green, zero stale offline-status comments. Co-Authored-By: Claude --- .claude/board/AGENT_LOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index fae92b00..0f0daa69 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,7 @@ +## 2026-07-01 — V3 tenant-carve certification + core in-sandbox verification (Sonnet sweep under the new model split) + +**Main thread (Fable 5) + one Sonnet 5 grindwork agent (operator directive this session: Sonnet for grindwork, Fable for decisions/nuance).** Arc: (1) `[patch.crates-io] ndarray` git-URL → local sibling path (burn submodule outside repo scope, 403; patch was `[[patch.unused]]` either way — fetch deadlock gone, resolution unchanged) — `217a698`. (2) NEW probes `osint_v3_cognitive_tenant_carve_field_isolation_matrix` (`217a698`) + `fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix` (`4f06d60`): the I-LEGACY mandatory matrix extended from Kanban-only to BOTH carves a Phase-1 V3 class materialises (Cognitive hot / Compressed cold), on registry-dispatched `mint_for` mints; shared test-local `assert_value_lane_isolation`. Contract: 763 w/ features, 749 default, fmt+clippy clean. (3) protoc installed → **lance-graph core builds in-sandbox for the first time**; `markov_soa` "unverified-offline" STATUS cleared (`aa50cf8`), arigraph 124/124. (4) **Sonnet agent sweep (report-only, shared target/, no worktree):** core lib **925/925** (1 ignored), planner **204/204**, supervisor all green, zero stale offline-status comments remain. (5) Board: EPIPHANIES `E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT`; ISSUES `ISS-Q2-CPIC-MIRROR-DIVERGES-FROM-CPIC-V3-REGISTRY` (`18b7f92`, record-only) + same-session dated correction (`eb4be79`: osint-bake DOES call `new_v2` against the real contract import — truncated-grep error owned; only q2 `cpic` carries a local mirror). OGAR untouched (destructive-action halt stands). Branch `claude/v3-substrate-migration-review-o0yoxv`. + ## 2026-06-25 (cont.⁴³) — SoA value-tenant migration Phase-1 HARVEST: the filled §4 inventory (executing session) **Main thread (Opus), operator-directed (executing session for `soa-value-tenant-migration-v1.md`).** Ran the brief's Phase-1 harvest under read-not-grep. Read FULLY: `canonical_node.rs` 1–1091 (the whole `ValueTenant`/`VALUE_TENANTS`/`ValueSchema`/`ReadMode` surface — the `const _` assert `Full.field_mask().count()==VALUE_TENANTS.len()` PROVES exactly **10 tenants**, none hiding in the test module), `class_view.rs` (full), `cascade_key.rs` (full). Two parallel mapping subagents: in-workspace+ndarray producer/consumer map (Opus general-purpose, confirmed-by-read) + cross-repo consumer locator (Explore). **Deliverable:** NEW `.claude/plans/soa-value-tenant-migration-v1-harvest.md` (filled §4 inventory, 10 rows). **Two findings:** (A) **two disjoint SoA worlds** `[G]` — the canonical `NodeRow.value` 480 B slab vs a parallel `MailboxSoA` of separate `[T;N]` columns; only `EntityType≡class_id` shared; **6/10 slab tenants have NO live producer** (only Energy/EntityType/Kanban/Fingerprint are live slab writers) → near-term migration = RECONCILING the two worlds, not homogenizing. (B) **homogeneity-non-closure HOLDS over the slab** `[H]` (the honest §8.5 outcome) — 9/10 tenants irreducibly heterogeneous (identity/scalars/bitfield/cursor) → KEEP (EXCEPT Qualia i4-16D + the future thinking-style i4-32D, which **DEFER** for a bigger substrate-validation test — i4 faithfulness, `I-NOISE-FLOOR-JIRAK`); §8 reduces to "classid is a schema pointer", SHIPPED (`ReadMode`/`ValueSchema`/`ClassView`, `ocr.rs:105` exemplar). **The closure is the operator's ONE CONTAINED facet** (2026-06-25): `facet_classid(4) | helix-place(6 B/48-bit = HelixResidue) | cam-pq(6 B/48-bit canonical CAM-PQ) = 16 B` — identity⊥search⊥schema, codec-selected by facet_classid, layout-preserving (no `ValueSchema` variant, no #500), I-VSA-IDENTITIES-clean (disjoint byte ranges, never bundled). Precise point: the facet wants the **6 B CAM-PQ**, NOT today's 16 B `TurbovecResidue` turbovec — a width decision for §6. **Corrections logged:** q2 `new_v2` blocker is CLOSED (API landed gated, `guid-v2-tail`); the cross-repo agent's "medcare-rs/ogar disk-walled" is a casing miss (`/home/user/{MedCare-rs,OGAR}` ARE present — top follow-up corrective sweep). Doc-only, zero code, no collision. EPIPHANIES E-TWO-SOA-WORLDS + E-HOMOGENEITY-CLOSES-AS-CONTAINED-FACET; INTEGRATION_PLANS prepend supersedes the BRIEF entry's "additive `ValueSchema::Homogeneous`" line. On branch `claude/serene-mayer-1a09he`. From 328f0f266e41269fbaf86dbe379817247069f1ba Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 03:47:17 +0000 Subject: [PATCH 09/11] convergence: RungElevator (D-VCW-1a) + P6 wave probe (D-VCW-2) + the seam-list plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D-VCW-1a — the rung ladder as dispatch policy over certified mask algebra: RungLevel::{from_u8, elevate, de_elevate, pearl_level, causal_mask_bits} (0-2 observe / 3-5 intervene / 6-9 counterfactual — the enum names its own Pearl boundary at Counterfactual=6; PO/SPO masks P3-certified, O-for-L1 a labeled convention pending probe) + RungElevator (sustained-BLOCK elevates, sustained-FLOW relaxes to the dispatched base, HOLD resets streaks; threshold 2 hand-tuned, disclosed). Converged with the EXISTING escalation::rung_delta felt-parse hint via apply_delta — one ladder, two signal sources (gate streaks = System-2, qualia delta = System-1), no parallel type invented. 7 new tests; contract 755 green; clippy clean. D-VCW-2 — P6: the AriGraph wave's injected distance is the SAME certified 256x256 palette read as the P1-P3 particle chain (self-match exactly 1.0; hand-computed table arithmetic == best_guess_match output). markov_soa 6/6 green. (Sonnet grindwork per the session model split.) D-VCW-6 — worker Rule 7: negative-existence claims require an exhaustive-search declaration (born from this session's truncated-grep false claim, corrected on the board). Plan: .claude/plans/v3-convergence-wiring-v1.md (the seam list: every deliverable a probe or a wiring of existing types; two-SoA-worlds doctrine; P7 render-probe spec for q2; one-row registry direction; D7 deferred). Board (same commit): INTEGRATION_PLANS prepend, STATUS_BOARD D-VCW rows, EPIPHANIES E-RUNG-LADDER-IS-DISPATCH-POLICY-OVER-CERTIFIED-MASKS, ISSUES ISS-Q2-CASCADE3-NIBBLE-ANCESTRY + Option-B ground-truth addendum on ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION (two id spaces aliasing in the lo u16 — record-only, operator's decision). Co-Authored-By: Claude --- .claude/board/EPIPHANIES.md | 34 ++ .claude/board/INTEGRATION_PLANS.md | 4 + .claude/board/ISSUES.md | 39 +++ .claude/board/STATUS_BOARD.md | 15 + .../autoattended-multiagent-pattern.md | 22 ++ .claude/plans/v3-convergence-wiring-v1.md | 126 ++++++++ .../src/cognitive_shader.rs | 299 +++++++++++++++++- .../src/graph/arigraph/markov_soa.rs | 104 ++++++ 8 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 .claude/plans/v3-convergence-wiring-v1.md diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 9a7c8ea6..56630f35 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,37 @@ +## 2026-07-01 — E-RUNG-LADDER-IS-DISPATCH-POLICY-OVER-CERTIFIED-MASKS — rung elevation needs zero new math; and there is ONE ladder with TWO signal sources (gate streaks + felt-parse delta) + +**Status:** FINDING (shipped: `RungLevel::{from_u8, elevate, de_elevate, +pearl_level, causal_mask_bits}` + `RungElevator` in +`contract::cognitive_shader`, 755 lib tests green). + +**Half 1 — no new math.** "Elevates on sustained BLOCK" (documented intent on +`ShaderDispatch::rung` since the field landed; driver ran a `ctx.rung = 1` +proxy) is purely a dispatch policy over algebra the P2/P3 probes already +certified: rung → Pearl level (the enum names its own boundary — +`Counterfactual = 6` is where Level 3 starts; 0–2 observe, 3–5 intervene, +6–9 counterfactual) → SPO projection mask (`PO = 0b011` and `SPO = 0b111` +probe-certified; `O = 0b001` for L1 is a labeled convention pending its own +probe). The elevator is a ~40-line homeostatic state machine: sustained BLOCK +elevates, sustained FLOW relaxes toward the dispatched base, HOLD resets +streaks without ladder creep. Threshold 2 hand-tuned (disclosed per +`I-NOISE-FLOOR-JIRAK`). + +**Half 2 — convergence catch (anti-invention applied to my own type):** the +tree already had `escalation::rung_delta(emergence, coherence) → ±1` (the +felt-parse System-1 rung hint, `CollapseHint::RungElevate`). NOT a duplicate — +a second signal source. Resolution: `RungElevator::apply_delta(i8)` drives the +SAME accumulator the gate streaks drive. One ladder, two inputs: gate streaks += System-2 stuck/converged evidence; qualia delta = System-1 felt hint; both +respect the same base floor and Transcendent ceiling. The near-miss (almost +shipping a parallel ladder) is the same reflex `E-V3-TENANTS-ALREADY-EXIST- +WIRE-DONT-INVENT` names — caught this time by checking the test namespace +collision before committing. + +Plan: `.claude/plans/v3-convergence-wiring-v1.md` (D-VCW-1a; D-VCW-1b threads +it through the driver). + +--- + ## 2026-07-01 — E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT — the "V3 substrate for AriGraph-shaped SoA tenants" already exists in `canonical_node.rs`; Phase-2 work is certification, not invention **Status:** FINDING (probes green: `osint_v3_cognitive_tenant_carve_field_isolation_matrix` + `fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix` — BOTH carves a Phase-1 V3 class materialises, Cognitive hot + Compressed cold, are now matrix-covered. 2026-07-01 addendum: lance-graph CORE now compiles in-sandbox — crates.io noProxy + local ndarray path patch + protoc — and `graph::arigraph` is 124/124 green incl. `markov_soa`'s 4, so the wave projector's "unverified-offline" caveat is cleared: the full wire classid → read-mode → certified tenants → AriGraph wave is probed end-to-end). diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index cc695fbb..ce304598 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,7 @@ +## 2026-07-01 — v3-convergence-wiring (wire, don't invent — every layer already contains its own solution) + +Plan: `.claude/plans/v3-convergence-wiring-v1.md`. **Operator all-in ("I'm all in for your ideas — document and PR so other sessions converge"); model split Sonnet-grindwork/Fable-decisions.** The organizing finding (`E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT`): the V3 substrate's gaps are unwired seams, not missing machinery — every deliverable is a probe or a wiring of EXISTING types, §0 anti-invention throughout. **Seam list:** D1a `RungLevel::{from_u8,elevate,de_elevate,pearl_level,causal_mask_bits}` + `RungElevator` (zero-dep "elevates on sustained BLOCK" as a pure policy over `GateDecision` + the P2/P3-certified mask algebra; converged with `escalation::rung_delta` via `apply_delta` — one ladder, two signal sources: gate streaks = System-2, felt-parse = System-1) — SHIPPED; D1b driver wiring (replace `ctx.rung = 1` proxy, dedup wire/grpc u8→rung matches) — Sonnet in flight; D2 P6 wave-convergence probe (markov_soa's injected distance = the SAME certified 256×256 palette read as P1–P3; the table is the join object) — SHIPPED (2 tests, 6/6 module green); D3 P7 render probe spec (ClassView bitmask → askama; rendered fields == masked tenants; q2-side, push-gated, spec ready §3); D4 one-row registry (codebook row seeds `{tail, value_schema, edge_codec, bitmask, template}`; read-mode parity fuse sibling of COUNT_FUSE; Phase B gated on operator + osint decision); D5 nibble-hierarchy falsifier (FNV `cascade3` bytes have no nibble ancestry → HHTL on bake mints is tier-granular; `ISS-Q2-CASCADE3-NIBBLE-ANCESTRY`); D6 negative-existence-claims worker rule (knowledge doc Rule 7) — SHIPPED; D7 rig/rs-graph-llm FailureTicket loop — deferred frontier (first place determinism ends). **Doctrine (no code):** the two SoA worlds stay two — Lance columnar I/O is the reconciler; consumers write against per-row accessors; column borrows are the owner's privilege. ACTIVE. + ## 2026-06-26 — ogar-sink-in + consumer-bridge removal (medcare-bridge → UnifiedBridge → delete) Plan: `.claude/plans/ogar-sink-in-and-consumer-bridge-removal-v1.md`. **Codifies the sink-in layering + sequences the final bridge deletion.** Grounded finding: the migration is **~90% shipped** — all 6 per-consumer bridges (incl. `medcare_bridge.rs`) are already `#[deprecated] type FooBridge = UnifiedBridge` aliases over one generic harness; `docs/CONSUMER-BRIDGE-DEPRECATION.md` (OGAR#95) is the live doctrine ("nothing removed; deletion lands after consumers migrate"). **Sink-in decision (§2):** OGAR owns the *meaning* (PortSpec / codebook / ClassView / ActionDef / ontology); `lance-graph-contract` owns the zero-dep *wire* mirror (the parity guard, never a dep); `lance-graph-ontology` owns the OGIT registry; `lance-graph-rbac` owns grants keyed on the **shared lo-u16 concept** (render hi-u16 never gates RBAC — the CLASSID-RBAC-KEYSTONE-SPEC); `lance-graph-ogar` keeps only the `UnifiedBridge

` mechanism + `parity`. **medcare recast (§3):** the medcare domain dissolves into **reusable patterns** (the shared `HealthcarePort::class_id` / convergent codebook surface in OGAR) **+ domain-specific Ontology Schema enrichment** (Healthcare `0x09XX` / FMA-V3 `0x1000_0A01` / CPIC-V3 `0x1000_0E00` in OGIT) — never a bridge in the spine. **Deliverables:** D-SINK-1 ratify layering → D-SINK-2 migrate consumers off `MedcareBridge` (cross-repo, Iron Rule 5) → D-SINK-3 delete the 6 aliases (scope-lock tests ported first) → D-SINK-4 reconcile the two `WoaBridge` identities → D-SINK-5 sink the medcare ontology (the cross-repo codebook-mint arc, `ISS-OGAR-GENETICS-MIRROR-PENDING`). **Gated:** no deletion before consumers migrate (OGAR#95); intersects the operator-gated classid realign (D-OVC) + Canon:Custom. Extends CONSUMER-BRIDGE-DEPRECATION + ogar-vocab-contract-codebook-migration-v1; does not supersede them. Doc-only. PROPOSED. diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 3f24e770..94ed4953 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,5 +1,29 @@ # Issues Log — Open + Resolved (double-entry, append-only) +## 2026-07-01 — ISS-Q2-CASCADE3-NIBBLE-ANCESTRY — q2 `cascade3` FNV bytes are byte-hierarchical but NOT nibble-hierarchical; HHTL routing over bake mints is sound only at whole-tier granularity + +**Status:** OPEN (falsifier specified, not yet run — q2 is push-gated). Owner: +q2 `cpic/src/lib.rs::cascade3` (+ any bake reusing it) vs the OGAR canon's +256=4⁴ hierarchical-codebook condition. Plan: `v3-convergence-wiring-v1.md` §5. + +**The claim tension.** OGAR canon: each tier's 256-entry codebook is a 4-level +4-ary centroid HIERARCHY so a byte's nibbles are the centroid's ancestry — +`is_ancestor_of` = containment, prefix routing rigorous at nibble depth. q2's +`cascade3` derives tier byte `i` as the FNV-1a low byte of the cumulative DN +prefix at depth `i`: siblings share leading BYTES (per-tier prefix routing +holds), but a hash byte's nibbles carry NO ancestry — below whole-byte +granularity the tree structure is noise. + +**Falsifier (runnable in q2 when opened):** two DNs sharing a 3-deep prefix +must show `common_prefix_depth` at nibble granularity ≈ random beyond the +shared-byte boundary (vs the 4⁴ condition's prediction of structured nibble +sharing). Confirmed ⇒ either (a) HHTL routing over bake mints clamps to tier +granularity (document the boundary), or (b) the cascade generator moves to a +hierarchical codebook (bigger change, operator call). No routing code should +assume sub-byte ancestry on these mints until this runs. + +--- + ## 2026-07-01 — ISS-Q2-CPIC-MIRROR-DIVERGES-FROM-CPIC-V3-REGISTRY — q2's local `cpic::NodeGuid` mirror is V1-layout-parity-true but diverges from the registered CPIC-V3 read-mode on BOTH domain and tail shape **Status:** OPEN (record-only; resolve WITH the operator — q2 is push-gated and @@ -68,6 +92,21 @@ declared registry-exempt (bake-only) and its parity comment is scoped to Both are OGAR-side follow-ups (OGAR #145 is merged) landed in parallel with the lance-graph mirror rows, per `E-OGAR-LANCEGRAPH-MOVE-IN-PARALLEL`. +**ADDENDUM (2026-07-01, later session — ground-truth strengthening of Option B; +still the operator's decision, no action taken):** the codebase ALREADY lives +Option B's distinction. There are two id spaces aliasing in the lo u16: the +**classid space** (what nodes mint under) and the **concept-vocabulary space** +(what the codebook counts). Evidence: `canonical_node.rs` ships +`CLASSID_OSINT = 0x0000_0700` as a LIVE registered class (`ReadMode::OSINT` in +`BUILTIN_READ_MODES`) and q2 `osint-bake/src/lib.rs:606` mints real `0x0700` +rows — while the mirror's zero-slot invariant only ever governed *vocabulary +rows*. So `0xDD00` = "the ONE class per domain" (valid classid, exactly the +operator's "OSINT is ONE class") and simultaneously "not a nameable concept" +(no codebook row) — no contradiction once the spaces are named. Under this +reading OGAR's `osint_system` mint was the same move lance-graph already made, +just landed in the wrong space (a vocabulary row instead of a classid const). +Option B resolves it without deleting the idea or moving any id. + ## 2026-07-01 — ISS-OGAR-OSINT-MIRROR-PENDING — OGAR #145's OSINT mint (+2 to `class_ids::ALL`) breaks the contract-mirror `COUNT_FUSE` on merge; the paired lance-graph mirror rows must land in the same arc **Status:** OPEN (tracked) · **Resolution path RULED by operator 2026-07-01: keep the fuse (it IS the dependency contract enforcing OGAR↔lance-graph parallel movement); do NOT pin to a rev — "option 1" is REJECTED. Land the 2 mirror rows + `domains_agree` arm in parallel with OGAR #145 (option 2 / coordinated merge; brief transient red is acceptable — "the fuse is okay for now"). See `E-OGAR-LANCEGRAPH-MOVE-IN-PARALLEL`.** · Owner: OGAR `ogar-vocab` (PR #145) + `lance-graph-contract::ogar_codebook` mirror + `lance-graph-ogar::parity::domains_agree`. Surfaced 2026-07-01 while self-reviewing PR #624 / #145. Same cross-repo-arc shape as `ISS-OGAR-AUTH-MIRROR-DRIFT` (which took medcare CI red) and `ISS-OGAR-GENETICS-MIRROR-PENDING`; cited by `E-CODEBOOK-MINT-IS-A-CROSS-REPO-ARC`. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index b3f3c7fa..dbed687c 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,18 @@ +## v3-convergence-wiring-v1 — wire, don't invent (the seam list) + +Plan: `.claude/plans/v3-convergence-wiring-v1.md`. Sonnet-grindwork/Fable-decisions split. + +| D-id | Title | Crate(s) | Status | Evidence | +|---|---|---|---|---| +| D-VCW-1a | RungLevel arithmetic + RungElevator (sustained-BLOCK policy over certified mask algebra; converged with escalation::rung_delta) | lance-graph-contract | **Shipped** | 755 lib tests green incl. 6 new; clippy clean | +| D-VCW-1b | Driver wiring: elevator through cycle loop, ctx.rung proxy retired, wire/grpc from_u8 dedup | cognitive-shader-driver | In progress | Sonnet agent in flight | +| D-VCW-2 | P6 wave-convergence probe (wave dist == certified palette read) | lance-graph core (arigraph) | **Shipped** | markov_soa 6/6 green (2 new P6 tests) | +| D-VCW-3 | P7 render probe (bitmask → askama; fields == masked tenants) | q2 (push-gated) | Queued | spec ready (plan §3) | +| D-VCW-4 | One-row registry + read-mode parity fuse | contract (+OGAR Phase B) | Queued | plan §4; Phase B operator-gated | +| D-VCW-5 | cascade3 nibble-ancestry falsifier | q2 (push-gated) | Recorded | ISS-Q2-CASCADE3-NIBBLE-ANCESTRY | +| D-VCW-6 | Rule 7: negative-existence claims need exhaustive-search declaration | knowledge doc | **Shipped** | autoattended-multiagent-pattern.md §5 Rule 7 | +| D-VCW-7 | rig/rs-graph-llm FailureTicket loop | rs-graph-llm (sibling) | Deferred | plan §6; probe-first when opened | + ## cognitive-compilation-v1 — Elixir-template stack (LLM teaches, Lance-Graph runs) Plan: `.claude/plans/cognitive-compilation-v1.md`. The new idea is the diff --git a/.claude/knowledge/autoattended-multiagent-pattern.md b/.claude/knowledge/autoattended-multiagent-pattern.md index 9c1e810c..f60623b8 100644 --- a/.claude/knowledge/autoattended-multiagent-pattern.md +++ b/.claude/knowledge/autoattended-multiagent-pattern.md @@ -191,6 +191,28 @@ Files >150 lines: write in chunks via `tee -a`, commit per chunk, push per chunk For ports / behavior-preserving rewrites: every commit message quotes the source file + line range for the function being ported. Reviewers can `grep -rn "Source: " src/` to find every port. Without this, behavior-drift creeps in invisibly. +### Rule 7 — negative-existence claims require an exhaustive-search declaration + +Any claim of the form "X does not exist / is never called / has no call site" +MUST be accompanied by the search that grounds it: the exact pattern, the scope, +and an explicit statement that the search was **exhaustive** (no `head_limit` / +`| head` truncation hit, or the truncation boundary was checked). A truncated +grep that matches 20 hits in directory A and silently cuts before directory B +produces a confident false negative — the worst kind, because it reads as +diligence. + +Belegte (2026-07-01, lance-graph): a board entry claimed "no `new_v2` call +site exists in q2" from a `head_limit 20` grep whose matches were exhausted by +`cpic/` paths; `osint-bake/src/lib.rs:606,745` DID call `new_v2`. Cost: a +published false claim + a dated correction commit +(`ISS-Q2-CPIC-MIRROR-DIVERGES-FROM-CPIC-V3-REGISTRY`, correction `eb4be79`). + +Operational form for workers: before asserting absence, re-run the search +unbounded (or with a count mode) and state `exhaustive: yes, N total matches, +all accounted for`. A vague "I didn't find it" is honest and acceptable; a +specific "it doesn't exist" without the declaration is a Lie-Detector trigger +(LD-4 negative-knowledge class). + --- ## 6. Memory-files pattern (the autopilot foundation) diff --git a/.claude/plans/v3-convergence-wiring-v1.md b/.claude/plans/v3-convergence-wiring-v1.md new file mode 100644 index 00000000..9e67a5db --- /dev/null +++ b/.claude/plans/v3-convergence-wiring-v1.md @@ -0,0 +1,126 @@ +# V3 Convergence Wiring — Plan v1 (wire, don't invent) + +> **Status:** ACTIVE (2026-07-01). Operator: "I'm all in for your ideas — document +> and PR so the other sessions benefit and converge." Model split in effect: +> Sonnet 5 grindwork / Fable 5 decisions+nuance. +> +> **The organizing finding** (`EPIPHANIES.md` +> `E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT`): every layer of the V3 +> substrate already contains its own solution — the gaps are unwired seams, +> not missing machinery. This plan is the seam list, ordered by +> value-per-line, each item either a probe or a wiring of EXISTING types. +> §0 anti-invention holds throughout: no new `ValueSchema` variants, no new +> carriers, no parallel registries. + +## D-ids + +| D | Layer | Deliverable | Status | +|---|---|---|---| +| D1a | reasoning ladder | `RungLevel::{from_u8, elevate, de_elevate, pearl_level, causal_mask_bits}` + `RungElevator` (zero-dep, contract) — "elevates on sustained BLOCK" as a pure policy over `GateDecision` + the P2/P3-certified mask algebra | **Shipped this session** | +| D1b | reasoning ladder | Driver wiring: thread a `RungElevator` through the cycle loop (replace `driver.rs` `ctx.rung = 1` proxy); dedup `wire.rs`/`grpc.rs` u8→rung matches through `RungLevel::from_u8` | In progress (Sonnet) | +| D2 | AriGraph wave | P6 probe: `markov_soa::best_guess_match` driven by a real 256×256 palette-table distance — the wave uses the SAME certified metric as the particle chain (P1–P3) | In progress (Sonnet) | +| D3 | render / q2 | P7 probe (q2-side, spec in §3): ClassView bitmask → askama render; rendered fields == masked tenants | Queued (q2 push-gated; spec ready) | +| D4 | registry | One-row registry: codebook row seeds `{tail, value_schema, edge_codec, bitmask, template}`; read-mode parity fuse next to COUNT_FUSE | Planned (§4) | +| D5 | tiles / q2 | Nibble-hierarchy falsifier: FNV `cascade3` bytes have no nibble ancestry → HHTL routing on bake mints is tier-granular only | Recorded (`ISS-Q2-CASCADE3-...`, §5) | +| D6 | process | Negative-existence claims require an exhaustive-search declaration (worker rule) | **Shipped this session** (knowledge doc) | +| D7 | orchestration | rig / rs-graph-llm FailureTicket loop — the only fully unwired angle | Deferred frontier (§7) | + +Carve certification (both `ValueSchema` presets matrix-covered) and the +`markov_soa` verification shipped immediately before this plan — +see EPIPHANIES `E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT`. + +## §1 The two-SoA-worlds doctrine (decision, no code this round) + +Canonical `NodeRow` (AoS, 512 B) and `MailboxSoA` (true columnar) stay TWO +worlds; **Lance's columnar I/O is the reconciler** (the tombstone write IS the +AoS→SoA shred). Runtime bridging happens through `MailboxSoaView`'s per-row +deferred accessors (`energy_at`, `edge_block_at`, `hhtl_path_at`) — both worlds +satisfy those honestly; the `&[T]` column borrows are the columnar OWNER's +privilege. **Rule for future consumers:** write against per-row access unless +you provably own columns. Do NOT build an in-RAM AoS→SoA mirror — that would be +the serialization-in-the-hot-path anti-pattern (ADR-022 in spirit). + +## §2 Rung ladder = dispatch policy over certified mask algebra (D1) + +No new math. `RungLevel` 0–9 already names the Pearl boundary +(`Counterfactual = 6`). The mapping shipped in `cognitive_shader.rs`: + +- rung 0–2 → Pearl L1 (Association) → mask `O = 0b001` (**convention**, pending probe) +- rung 3–5 → Pearl L2 (Intervention) → mask `PO = 0b011` (**P3-certified**) +- rung 6–9 → Pearl L3 (Counterfactual) → mask `SPO = 0b111` (**P3-certified**) + +`RungElevator` (threshold 2, hand-tuned per `I-NOISE-FLOOR-JIRAK` disclosure): +sustained BLOCK elevates, sustained FLOW relaxes toward the dispatched base, +HOLD resets streaks without creep. The driver threads it per cycle; the +provenance rung is the elevator's live level, not the `= 1` proxy. + +## §3 P7 render probe spec (D3 — q2-side, ready to execute when q2 opens) + +In q2 (askama lives there; lance-graph has none by design — render is the +consumer's side of the membrane): + +1. Take one OSINT-V3 `NodeRow` (mint via + `mint_for(classid_read_mode(CLASSID_OSINT_V3).tail_variant, …)`). +2. Resolve `ClassView`/`ReadMode` → `ValueSchema::Cognitive.field_mask()`. +3. Render through a minimal askama template that iterates ONLY mask-present + tenants (the bitmask IS the focus of attention). +4. Assert: the rendered field set == exactly the mask's tenant set — no + phantom fields, no dropped fields; a `Compressed`-class row renders a + DIFFERENT field set through the same template machinery. + +Pass ⇒ `E-RENDER-IS-CLASS-BITMASK-ASKAMA-NOT-SEMIRING` gets its anchor. +Fail ⇒ the doctrine needs revision BEFORE more render code accretes. + +## §4 One-row registry (D4 — the structural one) + +Today three surfaces must agree and nothing forces it: `ogar_codebook` +(concept ids), `BUILTIN_READ_MODES` (8 hardcoded entries), `ClassView`. +Target shape (NO new types — a wiring): + +1. **Phase A (lance-graph only):** a `classid → ReadMode` *derivation default* + from the codebook row's domain (e.g. every `0x07XX` defaults to the OSINT + read-mode family) with the explicit `BUILTIN_READ_MODES` entries as + overrides. Fuse test: every classid in `BUILTIN_READ_MODES` has a codebook + domain route (`classid_concept_domain != Unassigned` after masking the + gen-marker) — the read-mode parity fuse, sibling of COUNT_FUSE. +2. **Phase B (cross-repo, with OGAR):** when OGAR emits vocab into the compiled + binary, it emits read-mode axes with it; `BUILTIN_READ_MODES` becomes the + bootstrap fallback. Gated on the operator (OGAR is halt-adjacent this + session) and on the osint-codebook decision. + +Litmus for every step: *is this a new layer?* → reject; *is this the codebook +row resolving to more of what already exists?* → proceed. + +## §5 Nibble-hierarchy falsifier (D5 — recorded, q2-side) + +q2 `cpic::cascade3` derives tier bytes from FNV-1a over cumulative prefixes: +sibling-shared BYTES per tier (sound), but hash bytes carry no NIBBLE ancestry +— the OGAR canon's 4⁴-hierarchical-codebook condition (`is_ancestor_of` = +centroid containment) does NOT hold below whole-byte granularity on these +mints. Falsifier spec: take two DNs sharing a 3-deep prefix, show +`common_prefix_depth` at nibble granularity is ~random beyond the shared-byte +boundary. Consequence if confirmed: HHTL routing over bake mints must clamp to +tier granularity, OR the cascade generator moves to a hierarchical codebook. +Recorded in `ISS-Q2-CASCADE3-NIBBLE-ANCESTRY` (board). + +## §6 What stays deferred and why + +- **D7 rig/rs-graph-llm loop:** first place determinism ends; probe-first + treatment when the operator opens it. Everything below is now integer-green + (contract 763+, core 925, planner 204, arigraph 124). +- **osint `0x0700` reconciliation:** operator decision; the two-id-spaces + reading (classid space vs concept-vocabulary space, aliasing in the lo u16) + is on `ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION` as Option C. +- **classid human-readable reorder (`0x07:01::1000`):** DEFERRED-by-design, + hands off until post-V3, implemented as the one flippable split-order. + +## §7 Session-A2A notes for the next session + +- Build works in-sandbox now: crates.io is proxy-allow-listed, root + `[patch.crates-io] ndarray` is the local sibling path, `protoc` via apt. + `cargo test -p lance-graph` is ~2 min cold. +- Model split: Sonnet 5 = grindwork (bounded input, known shape); Fable 5 = + accumulation/decisions/nuanced plans. Same test as the old Opus policy. +- Every negative-existence claim ("X does not exist / is never called") + requires an exhaustive-search declaration (D6; born from a truncated-grep + false claim this session, corrected on the board). diff --git a/crates/lance-graph-contract/src/cognitive_shader.rs b/crates/lance-graph-contract/src/cognitive_shader.rs index c0a0d2aa..de128925 100644 --- a/crates/lance-graph-contract/src/cognitive_shader.rs +++ b/crates/lance-graph-contract/src/cognitive_shader.rs @@ -152,7 +152,7 @@ pub enum StyleSelector { // Rung level — semantic depth elevation (0..9) // ═══════════════════════════════════════════════════════════════════════════ -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum RungLevel { #[default] @@ -168,6 +168,185 @@ pub enum RungLevel { Transcendent = 9, } +impl RungLevel { + /// Decode a wire ordinal, saturating: `0..=9` map to their rung, anything + /// above clamps to [`Transcendent`](RungLevel::Transcendent). This is the + /// ONE u8→rung mapping — the driver's wire/grpc decoders route through it + /// instead of hand-rolling the same 10-arm match twice. + #[inline] + pub const fn from_u8(v: u8) -> Self { + match v { + 0 => RungLevel::Surface, + 1 => RungLevel::Shallow, + 2 => RungLevel::Contextual, + 3 => RungLevel::Analogical, + 4 => RungLevel::Abstract, + 5 => RungLevel::Structural, + 6 => RungLevel::Counterfactual, + 7 => RungLevel::Meta, + 8 => RungLevel::Recursive, + _ => RungLevel::Transcendent, + } + } + + /// One rung up, saturating at [`Transcendent`](RungLevel::Transcendent). + #[inline] + pub const fn elevate(self) -> Self { + Self::from_u8((self as u8).saturating_add(1)) + } + + /// One rung down, saturating at [`Surface`](RungLevel::Surface). + #[inline] + pub const fn de_elevate(self) -> Self { + Self::from_u8((self as u8).saturating_sub(1)) + } + + /// The Pearl-ladder level this rung consults: `1` = Association + /// (observation), `2` = Intervention, `3` = Counterfactual. The rung + /// ladder exists to make higher reasoning depend on observations, with + /// hypothesis-testing-with-counterfactual on top — and the enum names the + /// boundary itself: [`Counterfactual`](RungLevel::Counterfactual)` = 6` is + /// where Level 3 starts. Rungs 0–2 observe, 3–5 intervene, 6–9 run + /// counterfactuals (Meta/Recursive/Transcendent are counterfactuals *about* + /// counterfactuals — still Level 3 machinery, deeper self-reference). + #[inline] + pub const fn pearl_level(self) -> u8 { + match self as u8 { + 0..=2 => 1, + 3..=5 => 2, + _ => 3, + } + } + + /// The 3-bit SPO causal-projection mask (S=0b100, P=0b010, O=0b001 — the + /// bit convention the P3 probe certified is shared between + /// `causal_edge::CausalMask` and the planner's `SpoDistances::causal_distance`) + /// this rung's Pearl level consults: + /// + /// - Level 2 → `PO = 0b011` — **probe-certified** (P3: Intervention projects + /// out the Subject confounder; strictly less distance than SPO when the + /// Subject term is non-zero). + /// - Level 3 → `SPO = 0b111` — **probe-certified** (P3: the full Level-3 + /// Counterfactual distance). + /// - Level 1 → `O = 0b001` — the observational plane (Association reads the + /// outcome/object plane alone). CONVENTION, hand-chosen pending its own + /// probe — recorded per the label-everything rule; the L2/L3 rows above + /// are the grounded anchor. + #[inline] + pub const fn causal_mask_bits(self) -> u8 { + match self.pearl_level() { + 1 => 0b001, + 2 => 0b011, + _ => 0b111, + } + } +} + +/// The rung **elevation policy** — "elevates on sustained BLOCK" (the intent +/// [`ShaderDispatch::rung`] has documented since the field landed), as a pure, +/// zero-dep state machine over the existing [`GateDecision`] ordinals. No new +/// math: rung → Pearl level → SPO projection mask is the P2/P3-certified mask +/// algebra; this struct only decides *which rung is current*. +/// +/// Policy (homeostatic — the shader must be able to come back down): +/// - **BLOCK** streak of `threshold` consecutive cycles → [`RungLevel::elevate`] +/// one rung (streak resets). The system is stuck; look deeper. +/// - **FLOW** streak of `threshold` consecutive cycles → [`RungLevel::de_elevate`] +/// one rung, **never below `base`** (the dispatched rung). The system is +/// converging; relax toward the requested depth instead of staying meta forever. +/// - **HOLD** resets both streaks and keeps the level: superposition is neither +/// stuck nor converged, so it must not creep the ladder in either direction. +/// +/// `DEFAULT_THRESHOLD = 2` is hand-tuned ("sustained" = the second consecutive +/// gate agreeing), not Jirak-derived — recorded per `I-NOISE-FLOOR-JIRAK`'s +/// hand-tuned-values-must-say-so rule. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct RungElevator { + /// The dispatched base rung — the floor `de_elevate` relaxes back to. + pub base: RungLevel, + /// The current rung (starts at `base`). + pub level: RungLevel, + /// Consecutive BLOCK count toward the next elevation. + pub block_streak: u8, + /// Consecutive FLOW count toward the next relaxation. + pub flow_streak: u8, + /// Streak length that counts as "sustained". + pub threshold: u8, +} + +impl RungElevator { + /// Hand-tuned "sustained" streak length (see type docs). + pub const DEFAULT_THRESHOLD: u8 = 2; + + /// Elevator anchored at the dispatch's rung with the default threshold. + #[inline] + pub const fn new(base: RungLevel) -> Self { + Self { + base, + level: base, + block_streak: 0, + flow_streak: 0, + threshold: Self::DEFAULT_THRESHOLD, + } + } + + /// Feed one cycle's gate decision; returns the (possibly changed) current + /// rung. Pure state transition — no storage, no side effects. + #[inline] + pub fn on_gate(&mut self, gate: crate::collapse_gate::GateDecision) -> RungLevel { + if gate.is_block() { + self.flow_streak = 0; + self.block_streak = self.block_streak.saturating_add(1); + if self.block_streak >= self.threshold { + self.level = self.level.elevate(); + self.block_streak = 0; + } + } else if gate.is_flow() { + self.block_streak = 0; + self.flow_streak = self.flow_streak.saturating_add(1); + if self.flow_streak >= self.threshold { + if (self.level as u8) > (self.base as u8) { + self.level = self.level.de_elevate(); + } + self.flow_streak = 0; + } + } else { + // HOLD: neither stuck nor converged — no ladder creep either way. + self.block_streak = 0; + self.flow_streak = 0; + } + self.level + } + + /// The SPO projection mask the CURRENT rung consults + /// ([`RungLevel::causal_mask_bits`]) — the value a dispatch loop feeds to + /// `causal_distance(&a, &b, mask)`. + #[inline] + pub const fn causal_mask_bits(&self) -> u8 { + self.level.causal_mask_bits() + } + + /// Apply a signed rung-shift hint to the SAME accumulator the gate streaks + /// drive — one rung state, two signal sources. The canonical producer is + /// [`crate::escalation::rung_delta`] (the felt-parse System-1 hint: + /// `emergence`/`coherence` → ±1, voted as + /// [`CollapseHint::RungElevate`](crate::escalation::CollapseHint::RungElevate)); + /// the gate streaks in [`on_gate`](RungElevator::on_gate) are the System-2 + /// stuck/converged evidence. Both move the one ladder; neither owns it. + /// Clamps at the dispatched `base` floor and the + /// [`Transcendent`](RungLevel::Transcendent) ceiling. Streaks are left + /// untouched — a felt hint is not gate evidence. + #[inline] + pub fn apply_delta(&mut self, delta: i8) -> RungLevel { + if delta > 0 { + self.level = self.level.elevate(); + } else if delta < 0 && (self.level as u8) > (self.base as u8) { + self.level = self.level.de_elevate(); + } + self.level + } +} + // ═══════════════════════════════════════════════════════════════════════════ // Φ ShaderDispatch — request into the cycle // ═══════════════════════════════════════════════════════════════════════════ @@ -552,4 +731,122 @@ mod tests { assert!(b.gate.is_hold()); assert_eq!(b.emitted_edge_count, 0); } + + // ── RungLevel arithmetic + RungElevator policy ───────────────────────── + + #[test] + fn rung_from_u8_saturates_and_round_trips() { + for v in 0..=9u8 { + assert_eq!(RungLevel::from_u8(v) as u8, v, "ordinal {v} round-trips"); + } + assert_eq!(RungLevel::from_u8(10), RungLevel::Transcendent); + assert_eq!(RungLevel::from_u8(u8::MAX), RungLevel::Transcendent); + // elevate/de_elevate saturate at the ladder ends. + assert_eq!(RungLevel::Transcendent.elevate(), RungLevel::Transcendent); + assert_eq!(RungLevel::Surface.de_elevate(), RungLevel::Surface); + assert_eq!(RungLevel::Surface.elevate(), RungLevel::Shallow); + assert_eq!( + RungLevel::Counterfactual.de_elevate(), + RungLevel::Structural + ); + } + + #[test] + fn rung_pearl_levels_and_masks_follow_the_certified_convention() { + // Rungs 0-2 observe (L1), 3-5 intervene (L2), 6-9 counterfactual (L3) — + // the enum itself names the L3 boundary (Counterfactual = 6). + assert_eq!(RungLevel::Surface.pearl_level(), 1); + assert_eq!(RungLevel::Contextual.pearl_level(), 1); + assert_eq!(RungLevel::Analogical.pearl_level(), 2); + assert_eq!(RungLevel::Structural.pearl_level(), 2); + assert_eq!(RungLevel::Counterfactual.pearl_level(), 3); + assert_eq!(RungLevel::Transcendent.pearl_level(), 3); + // Masks: L2 = PO (P3-certified Intervention projection), L3 = SPO + // (P3-certified full distance), L1 = O (observational convention). + assert_eq!(RungLevel::Surface.causal_mask_bits(), 0b001); + assert_eq!(RungLevel::Abstract.causal_mask_bits(), 0b011); + assert_eq!(RungLevel::Counterfactual.causal_mask_bits(), 0b111); + // Monotone: deeper rung never consults FEWER planes. + let mut prev = 0u32; + for v in 0..=9u8 { + let planes = RungLevel::from_u8(v).causal_mask_bits().count_ones(); + assert!(planes >= prev, "plane count must not shrink as rung rises"); + prev = planes; + } + } + + #[test] + fn elevator_elevates_on_sustained_block_and_relaxes_on_sustained_flow() { + use crate::collapse_gate::GateDecision; + let mut e = RungElevator::new(RungLevel::Shallow); + assert_eq!(e.level, RungLevel::Shallow); + + // One BLOCK is not "sustained" — level holds. + assert_eq!(e.on_gate(GateDecision::BLOCK), RungLevel::Shallow); + // Second consecutive BLOCK = sustained → elevate one rung. + assert_eq!(e.on_gate(GateDecision::BLOCK), RungLevel::Contextual); + // Two more → Analogical (streak reset after each elevation). + e.on_gate(GateDecision::BLOCK); + assert_eq!(e.on_gate(GateDecision::BLOCK), RungLevel::Analogical); + // The elevated rung crosses into Pearl L2 → the consulted mask widens. + assert_eq!(e.causal_mask_bits(), 0b011); + + // Sustained FLOW relaxes one rung per streak… + e.on_gate(GateDecision::FLOW_XOR); + assert_eq!(e.on_gate(GateDecision::FLOW_BUNDLE), RungLevel::Contextual); + e.on_gate(GateDecision::FLOW_XOR); + assert_eq!(e.on_gate(GateDecision::FLOW_XOR), RungLevel::Shallow); + // …but never below the dispatched base. + e.on_gate(GateDecision::FLOW_XOR); + assert_eq!(e.on_gate(GateDecision::FLOW_XOR), RungLevel::Shallow); + assert_eq!(e.base, RungLevel::Shallow); + } + + #[test] + fn elevator_hold_resets_streaks_without_ladder_creep() { + use crate::collapse_gate::GateDecision; + let mut e = RungElevator::new(RungLevel::Surface); + // BLOCK, then HOLD breaks the streak: the next BLOCK starts over, + // so no elevation happens until two CONSECUTIVE blocks. + e.on_gate(GateDecision::BLOCK); + assert_eq!(e.on_gate(GateDecision::HOLD), RungLevel::Surface); + assert_eq!(e.on_gate(GateDecision::BLOCK), RungLevel::Surface); + assert_eq!(e.on_gate(GateDecision::BLOCK), RungLevel::Shallow); + // HOLD also breaks a FLOW streak (no relaxation creep). + e.on_gate(GateDecision::FLOW_XOR); + e.on_gate(GateDecision::HOLD); + assert_eq!(e.on_gate(GateDecision::FLOW_XOR), RungLevel::Shallow); + assert_eq!(e.block_streak, 0); + } + + #[test] + fn elevator_saturates_at_transcendent_under_endless_block() { + use crate::collapse_gate::GateDecision; + let mut e = RungElevator::new(RungLevel::Surface); + for _ in 0..64 { + e.on_gate(GateDecision::BLOCK); + } + assert_eq!(e.level, RungLevel::Transcendent); + assert_eq!(e.causal_mask_bits(), 0b111); + } + + #[test] + fn elevator_accepts_felt_parse_rung_delta_on_the_same_ladder() { + // One rung state, two signal sources: the felt-parse System-1 hint + // (escalation::rung_delta) drives the SAME accumulator the gate + // streaks drive — convergence, not a parallel ladder. + use crate::escalation::rung_delta; + let mut e = RungElevator::new(RungLevel::Shallow); + // emergent + incoherent → +1 (the detector.rs-grounded rule). + assert_eq!(rung_delta(0.6, 0.3), 1); + assert_eq!(e.apply_delta(rung_delta(0.6, 0.3)), RungLevel::Contextual); + // coherent + settled → -1, relaxing back… + assert_eq!(e.apply_delta(rung_delta(0.05, 0.9)), RungLevel::Shallow); + // …but never below the dispatched base (same floor as sustained FLOW). + assert_eq!(e.apply_delta(-1), RungLevel::Shallow); + // Neutral hint (0) holds. + assert_eq!(e.apply_delta(rung_delta(0.5, 0.5)), RungLevel::Shallow); + // A hint does NOT touch gate streaks (a feeling is not gate evidence). + assert_eq!((e.block_streak, e.flow_streak), (0, 0)); + } } diff --git a/crates/lance-graph/src/graph/arigraph/markov_soa.rs b/crates/lance-graph/src/graph/arigraph/markov_soa.rs index b72ddcaa..3f714d1a 100644 --- a/crates/lance-graph/src/graph/arigraph/markov_soa.rs +++ b/crates/lance-graph/src/graph/arigraph/markov_soa.rs @@ -324,4 +324,108 @@ mod tests { assert_eq!(empty.best_guess_match(&ne, dist), 0.0); assert_eq!(ne.best_guess_match(&empty, dist), 0.0); } + + /// P6 — the wave's injected distance is the SAME 256×256 palette read the + /// particle chain (P1–P3) certified; the join object is the table. + /// + /// `crates/lance-graph-osint/tests/p1_distance_identity.rs` proved + /// `SpoDistances` (`lance-graph-planner::cache::nars_engine`) ≡ + /// `MatrixDistance` (`lance-graph-arm-discovery`) on one 256×256 palette + /// derived from `deepnsm::codebook::Codebook`. This module's own doc + /// comment (top of file, "AriGraph is agnostic") claims the wave's + /// injected `dist` closure IS "AriGraph's own `cam_pq::DistanceTables`" — + /// the same *kind* of 256×256 table the particle chain certified, just + /// read as `u8` here (the wave's contract) instead of `u16`/`u32`. + /// `markov_soa` cannot depend on `lance-graph-planner` or `deepnsm` + /// (dependency flows AriGraph(core) → sensor, never reverse — see the + /// module doc), so the TABLE ITSELF is the join object: build one + /// deterministically here, exactly as P1–P3 built one and handed it + /// across their crate boundaries. + mod p6_palette_join { + use super::*; + + /// Deterministic PRNG — SplitMix64, mirroring + /// `lance-graph-osint/tests/common/mod.rs::splitmix64`. No `rand`, no + /// seed entropy; byte-identical on every run and every target. + fn splitmix64(state: &mut u64) -> u64 { + *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = *state; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + + /// A deterministic, symmetric, zero-diagonal 256×256 `u8` palette — + /// the cam_pq-shaped table read. Mirrors + /// `p2_p3_edge_pearl.rs::synth_palette`, narrowed to `u8` because the + /// wave's `dist` closure returns `u8` (not `SpoDistances`'s `u16`). + fn synth_palette_u8(seed: u64) -> Vec { + let mut t = vec![0u8; 256 * 256]; + let mut s = seed; + for a in 0..256usize { + for b in (a + 1)..256usize { + let v = 1 + (splitmix64(&mut s) % 255) as u8; // 1..=255 + t[a * 256 + b] = v; + t[b * 256 + a] = v; + } + } + t + } + + /// The join object realized as a closure: a plain table lookup, the + /// cam_pq-shaped distance read `markov_soa`'s doc comment describes. + fn table_dist(table: &[u8]) -> impl Fn(u16, u16) -> u8 + '_ { + move |a, b| table[(a as usize & 0xFF) * 256 + (b as usize & 0xFF)] + } + + #[test] + fn p6_self_match_under_real_palette_table_is_exact_one() { + let table = synth_palette_u8(0x0700_0006); + let s = soa(20); + let proj = SoaWavePrimer::new(3).project(&s, 10, row_triple); + let same = SoaWavePrimer::new(3).project(&s, 10, row_triple); + let sim = proj.best_guess_match(&same, table_dist(&table)); + assert!( + (sim - 1.0).abs() < 1e-6, + "self-match under a real palette-backed table must be exact: got {sim}" + ); + } + + #[test] + fn p6_single_triple_similarity_matches_hand_computed_table_read() { + let table = synth_palette_u8(0x0700_0007); + let dist = table_dist(&table); + + let a = SpoRanks { + s: 3, + p: 200, + o: 77, + }; + let b = SpoRanks { s: 9, p: 5, o: 250 }; + let here = WaveProjection { + triples: vec![a], + provenance: BundleProvenance::default(), + }; + let there = WaveProjection { + triples: vec![b], + provenance: BundleProvenance::default(), + }; + + // Hand-computable: with exactly one triple on each side, the + // "nearest" search degenerates to the single distance — the SAME + // per-role table reads `best_guess_match` performs internally, + // recomputed here independently from the same real table. + let d_s = dist(a.s, b.s); + let d_p = dist(a.p, b.p); + let d_o = dist(a.o, b.o); + let d = ((d_s as u16 + d_p as u16 + d_o as u16) / 3) as u8; + let expected = 1.0 - (d as f32 / u8::MAX as f32); + + let got = here.best_guess_match(&there, dist); + assert!( + (got - expected).abs() < 1e-6, + "expected {expected}, got {got} (table entries: s={d_s} p={d_p} o={d_o})" + ); + } + } } From c9588b2f1b699ee011ee5908391aa4b412b1d888 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 03:59:05 +0000 Subject: [PATCH 10/11] =?UTF-8?q?driver:=20thread=20RungElevator=20through?= =?UTF-8?q?=20the=20dispatch=20cycle=20(D-VCW-1b)=20=E2=80=94=20ctx.rung?= =?UTF-8?q?=20proxy=20retired?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sustained-BLOCK elevation policy goes live in the driver: - ShaderDriver gains a persistent RwLock (same updated-every-cycle pattern as awareness) — a per-call-local elevator could never accumulate a streak across dispatches, since each run() is one cycle by this module's own definition. - A dispatch requesting a different base rung resets the elevator to that base: streaks never leak across unrelated dispatch contexts. - The cycle's already-decided gate feeds elevator.on_gate() AFTER the gate decision — provenance never alters the gate (invariant kept). - materialize_provenance takes the live rung parameter; the documented 'ctx.rung <- 1 (proxy)' is retired. Rung now reaches ThoughtCtx tier selection (materialize.rs rung >= 7 / >= 4 gating). - wire.rs + grpc.rs: identical hand-rolled 10-arm u8->RungLevel matches deduped through the contract's RungLevel::from_u8. Tests: driver 100/100 green, incl. 2 new — sustained-BLOCK elevation across real dispatch() calls (deterministic BLOCK via empty row window) + rung load-bearing in tactic selection (honesty note in-test: tier is a +1 tie-weight; inequality asserted for the empirically-differentiating input, rung 1 -> tactic 17 vs rung 9 -> tactic 3, not claimed universal). Contract regression 755 green; fmt clean; driver-own lints clean. Implementation: Sonnet grindwork agent (killed mid-test by a worker restart), reviewed + finished on the main thread. Board (same commit): STATUS_BOARD D-VCW-1b Shipped, plan D1b/D2 status, AGENT_LOG entry for both agents, TECH_DEBT TD-DEPRECATED-ACCESSORS-BLOCK-DEP-CLIPPY (pre-existing ontology/planner deprecations fail any dependent's clippy -D warnings run). Co-Authored-By: Claude --- .claude/board/AGENT_LOG.md | 4 + .claude/board/STATUS_BOARD.md | 2 +- .claude/board/TECH_DEBT.md | 15 +- .claude/plans/v3-convergence-wiring-v1.md | 4 +- crates/cognitive-shader-driver/src/driver.rs | 148 ++++++++++++++++++- crates/cognitive-shader-driver/src/grpc.rs | 13 +- crates/cognitive-shader-driver/src/wire.rs | 13 +- 7 files changed, 165 insertions(+), 34 deletions(-) diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 0f0daa69..ee87e5ef 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,7 @@ +## 2026-07-01 (cont.) — v3-convergence-wiring D1/D2 execution (2 Sonnet grindwork agents + Fable finish) + +**Main thread (Fable 5) + two Sonnet 5 agents (edit-only, shared checkout, no worktrees).** (1) **P6 agent (D-VCW-2, completed):** extended `markov_soa` tests with `p6_palette_join` — self-match exactly 1.0 under a real zero-diagonal 256×256 palette table + hand-computed table arithmetic == `best_guess_match` output; 6/6 module green; correctly refused a planner dep (the TABLE is the join object, dependency flows AriGraph→sensor never reverse). Flagged the pre-existing planner deprecation clippy debt (→ TD-DEPRECATED-ACCESSORS-BLOCK-DEP-CLIPPY). (2) **D1b agent (D-VCW-1b, killed mid-test by worker restart; Fable finished):** driver-persistent `RwLock` on `ShaderDriver` (per-call-local would never accumulate a streak — the agent's own correct design call), base-change reset so streaks never leak across dispatch contexts, gate fed POST-decision (provenance never alters the gate), `materialize_provenance(…, rung)` replaces the `ctx.rung = 1` proxy, `wire.rs`/`grpc.rs` 10-arm matches deduped through `RungLevel::from_u8`. Fable finished the second test honestly: rung is a +1 tie-weight in tactic scoring, so inequality is asserted for the EMPIRICALLY-differentiating input (rung 1→tactic 17, rung 9→tactic 3 at authoring), not claimed universal. Gates: driver 100/100, contract 755 regression green, fmt clean, driver-own lints clean (dep-closure clippy blocked by the pre-existing deprecation debt, recorded). Commit: this one. + ## 2026-07-01 — V3 tenant-carve certification + core in-sandbox verification (Sonnet sweep under the new model split) **Main thread (Fable 5) + one Sonnet 5 grindwork agent (operator directive this session: Sonnet for grindwork, Fable for decisions/nuance).** Arc: (1) `[patch.crates-io] ndarray` git-URL → local sibling path (burn submodule outside repo scope, 403; patch was `[[patch.unused]]` either way — fetch deadlock gone, resolution unchanged) — `217a698`. (2) NEW probes `osint_v3_cognitive_tenant_carve_field_isolation_matrix` (`217a698`) + `fma_cpic_v3_compressed_tenant_carve_field_isolation_matrix` (`4f06d60`): the I-LEGACY mandatory matrix extended from Kanban-only to BOTH carves a Phase-1 V3 class materialises (Cognitive hot / Compressed cold), on registry-dispatched `mint_for` mints; shared test-local `assert_value_lane_isolation`. Contract: 763 w/ features, 749 default, fmt+clippy clean. (3) protoc installed → **lance-graph core builds in-sandbox for the first time**; `markov_soa` "unverified-offline" STATUS cleared (`aa50cf8`), arigraph 124/124. (4) **Sonnet agent sweep (report-only, shared target/, no worktree):** core lib **925/925** (1 ignored), planner **204/204**, supervisor all green, zero stale offline-status comments remain. (5) Board: EPIPHANIES `E-V3-TENANTS-ALREADY-EXIST-WIRE-DONT-INVENT`; ISSUES `ISS-Q2-CPIC-MIRROR-DIVERGES-FROM-CPIC-V3-REGISTRY` (`18b7f92`, record-only) + same-session dated correction (`eb4be79`: osint-bake DOES call `new_v2` against the real contract import — truncated-grep error owned; only q2 `cpic` carries a local mirror). OGAR untouched (destructive-action halt stands). Branch `claude/v3-substrate-migration-review-o0yoxv`. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index dbed687c..0e54e50b 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -5,7 +5,7 @@ Plan: `.claude/plans/v3-convergence-wiring-v1.md`. Sonnet-grindwork/Fable-decisi | D-id | Title | Crate(s) | Status | Evidence | |---|---|---|---|---| | D-VCW-1a | RungLevel arithmetic + RungElevator (sustained-BLOCK policy over certified mask algebra; converged with escalation::rung_delta) | lance-graph-contract | **Shipped** | 755 lib tests green incl. 6 new; clippy clean | -| D-VCW-1b | Driver wiring: elevator through cycle loop, ctx.rung proxy retired, wire/grpc from_u8 dedup | cognitive-shader-driver | In progress | Sonnet agent in flight | +| D-VCW-1b | Driver wiring: elevator through cycle loop, ctx.rung proxy retired, wire/grpc from_u8 dedup | cognitive-shader-driver | **Shipped** | driver 100/100 green (2 new tests: sustained-BLOCK elevation across dispatches + rung load-bearing in tactic selection); driver-persistent RwLock elevator, base-change reset | | D-VCW-2 | P6 wave-convergence probe (wave dist == certified palette read) | lance-graph core (arigraph) | **Shipped** | markov_soa 6/6 green (2 new P6 tests) | | D-VCW-3 | P7 render probe (bitmask → askama; fields == masked tenants) | q2 (push-gated) | Queued | spec ready (plan §3) | | D-VCW-4 | One-row registry + read-mode parity fuse | contract (+OGAR Phase B) | Queued | plan §4; Phase B operator-gated | diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index 2f1a38e7..a5b9af39 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -15,7 +15,20 @@ ## Open Debt -### TD-RUNGLEVEL-DUP — `RungLevel` duplicated verbatim in `thinking-engine` vs the canonical `lance-graph-contract` (2026-07-01) +### TD-DEPRECATED-ACCESSORS-BLOCK-DEP-CLIPPY — pre-existing deprecation debt in `lance-graph-ontology` (oxrdf::Subject, 12 sites) + `lance-graph-planner` (CausalEdge64 v1 accessors, 2 sites) makes `cargo clippy -D warnings` fail for any DEPENDENT crate (2026-07-01) + +**Open.** Surfaced twice this session by the v3-convergence gates: clippy +`-D warnings` on `cognitive-shader-driver` dies in `lance-graph-ontology` +(`oxrdf::Subject` → `NamedOrBlankNode`, `hydrators/owl.rs:226,242` + +`ttl_parse.rs:532,534` + 8 more), and on `lance-graph --lib` dies in +`lance-graph-planner` (`CausalEdge64::{inference_type, temporal}` deprecated +under v2 layout, `cache/nars_engine.rs:492-493` — the documented I-LEGACY +migration pointers). Both are *within-workspace* deprecations, so `-D warnings` +propagates them as errors to every dependent's clippy run. Payment: migrate the +call sites (ontology → `NamedOrBlankNode`; planner → `inference_mantissa()` + +structural temporal per the deprecation messages). Until paid, per-crate clippy +gates must scope to crates whose dep-closure is deprecation-free, or scan the +target crate's own lints without `-D warnings` on the closure. **Open.** `thinking-engine::cognitive_stack::RungLevel` is a byte-for-byte duplicate of `lance_graph_contract::cognitive_shader::RungLevel` (0..9: diff --git a/.claude/plans/v3-convergence-wiring-v1.md b/.claude/plans/v3-convergence-wiring-v1.md index 9e67a5db..22e66a7b 100644 --- a/.claude/plans/v3-convergence-wiring-v1.md +++ b/.claude/plans/v3-convergence-wiring-v1.md @@ -17,8 +17,8 @@ | D | Layer | Deliverable | Status | |---|---|---|---| | D1a | reasoning ladder | `RungLevel::{from_u8, elevate, de_elevate, pearl_level, causal_mask_bits}` + `RungElevator` (zero-dep, contract) — "elevates on sustained BLOCK" as a pure policy over `GateDecision` + the P2/P3-certified mask algebra | **Shipped this session** | -| D1b | reasoning ladder | Driver wiring: thread a `RungElevator` through the cycle loop (replace `driver.rs` `ctx.rung = 1` proxy); dedup `wire.rs`/`grpc.rs` u8→rung matches through `RungLevel::from_u8` | In progress (Sonnet) | -| D2 | AriGraph wave | P6 probe: `markov_soa::best_guess_match` driven by a real 256×256 palette-table distance — the wave uses the SAME certified metric as the particle chain (P1–P3) | In progress (Sonnet) | +| D1b | reasoning ladder | Driver wiring: thread a `RungElevator` through the cycle loop (replace `driver.rs` `ctx.rung = 1` proxy); dedup `wire.rs`/`grpc.rs` u8→rung matches through `RungLevel::from_u8` | **Shipped this session** | +| D2 | AriGraph wave | P6 probe: `markov_soa::best_guess_match` driven by a real 256×256 palette-table distance — the wave uses the SAME certified metric as the particle chain (P1–P3) | **Shipped this session** | | D3 | render / q2 | P7 probe (q2-side, spec in §3): ClassView bitmask → askama render; rendered fields == masked tenants | Queued (q2 push-gated; spec ready) | | D4 | registry | One-row registry: codebook row seeds `{tail, value_schema, edge_codec, bitmask, template}`; read-mode parity fuse next to COUNT_FUSE | Planned (§4) | | D5 | tiles / q2 | Nibble-hierarchy falsifier: FNV `cascade3` bytes have no nibble ancestry → HHTL routing on bake mints is tier-granular only | Recorded (`ISS-Q2-CASCADE3-...`, §5) | diff --git a/crates/cognitive-shader-driver/src/driver.rs b/crates/cognitive-shader-driver/src/driver.rs index f5657091..30d401dd 100644 --- a/crates/cognitive-shader-driver/src/driver.rs +++ b/crates/cognitive-shader-driver/src/driver.rs @@ -32,8 +32,8 @@ use causal_edge::plasticity::PlasticityState; use causal_edge::tables::{unpack_c, unpack_f, NarsTables}; use lance_graph_contract::cognitive_shader::{ AlphaComposite, CognitiveShaderDriver, EmitMode, MaterializeProvenance, MetaSummary, NullSink, - ShaderBus, ShaderCrystal, ShaderDispatch, ShaderHit, ShaderResonance, ShaderSink, - ALPHA_COMPOSITE_DIMS, + RungElevator, RungLevel, ShaderBus, ShaderCrystal, ShaderDispatch, ShaderHit, ShaderResonance, + ShaderSink, ALPHA_COMPOSITE_DIMS, }; use lance_graph_contract::collapse_gate::{GateDecision, MergeMode, ALPHA_SATURATION_THRESHOLD}; use lance_graph_contract::grammar::free_energy::{FreeEnergy, EPIPHANY_MARGIN}; @@ -97,6 +97,17 @@ pub struct ShaderDriver { /// is unchanged — this field is purely additive and does not alter /// any existing dispatch semantics. Removed at cutover (plan S3). pub(crate) mailboxes: std::collections::HashMap>, + /// Persistent rung-elevation state (`RungElevator`) across dispatch + /// cycles. Each `run()` call is "one cycle" per this module's own + /// docstring ("emits one CycleFingerprint per cycle — the unit of + /// thought"); the elevator lives here — same `RwLock`-guarded, + /// updated-every-cycle pattern as `awareness` above — so that + /// "sustained BLOCK elevates" (the policy `RungElevator::on_gate` + /// documents) can actually observe more than one gate. A + /// per-call-local elevator would reset every dispatch and could + /// never accumulate a streak. Reset to the dispatch's requested + /// base whenever `ShaderDispatch::rung` changes between calls. + pub(crate) rung_elevator: RwLock, } impl ShaderDriver { @@ -118,6 +129,7 @@ impl ShaderDriver { awareness: RwLock::new(awareness), nars_tables: None, mailboxes: std::collections::HashMap::new(), + rung_elevator: RwLock::new(RungElevator::new(RungLevel::default())), } } @@ -568,6 +580,23 @@ impl ShaderDriver { _ => None, }; + // Feed this cycle's already-decided `gate` into the persistent + // per-driver `RungElevator` (sustained-BLOCK elevation over the + // dispatched base — see the field doc on `ShaderDriver::rung_elevator`). + // A dispatch requesting a different base rung than the elevator + // currently tracks resets it to that new base first, so streaks + // never leak across unrelated dispatch contexts. + let elevated_rung: u8 = { + let mut elevator = self + .rung_elevator + .write() + .expect("rung_elevator RwLock poisoned"); + if elevator.base != req.rung { + *elevator = RungElevator::new(req.rung); + } + elevator.on_gate(gate) as u8 + }; + // Materialized-awareness provenance — runs the F→34→F loop + HHTL fork as // a SIDE analysis over this cycle's observables. Provenance-only: it has // NOT influenced `gate` above and does not influence persistence; it only @@ -579,6 +608,7 @@ impl ShaderDriver { top_resonance, awareness_skill, &candidate_resonances, + elevated_rung, ); // [8] NARS revision — phi-1 humility ceiling. @@ -798,6 +828,7 @@ impl CognitiveShaderBuilder { awareness: RwLock::new(awareness), nars_tables: self.nars_tables, mailboxes: self.mailboxes, + rung_elevator: RwLock::new(RungElevator::new(RungLevel::default())), } } } @@ -849,13 +880,15 @@ fn entropy_std(hits: &[ShaderHit]) -> (f32, f32) { /// - `dissonance` ← `|top_resonance - (1 - F.total)|` (the Dunning-Kruger gap: /// what the cycle *feels* vs what it has *demonstrated*) /// - `temperature` ← `std_dev` clamp (spread ⇒ explore; proxy) -/// - `rung` ← `1` (no HHTL cascade depth surfaced in this cycle; proxy) +/// - `rung` ← live `RungElevator` level (sustained-BLOCK elevation +/// over the dispatched base — see `ShaderDriver::rung_elevator`) fn materialize_provenance( free_energy: &FreeEnergy, std_dev: f32, top_resonance: f32, awareness_skill: f64, candidate_resonances: &[f32], + rung: u8, ) -> MaterializeProvenance { let demonstrated = (1.0 - free_energy.total).clamp(0.0, 1.0); let mut ctx = ThoughtCtx::new(candidate_resonances.to_vec()); @@ -866,7 +899,7 @@ fn materialize_provenance( .abs() .clamp(0.0, 1.0); ctx.temperature = std_dev.clamp(0.0, 1.0); - ctx.rung = 1; + ctx.rung = rung; let trace = materialize(&mut ctx, 64); @@ -1095,20 +1128,123 @@ mod tests { // Confident cycle: high resonance, low dispersion, ample skill → Boredom → // Commit (fork 0). The leaf residue is small, the domain over-explains. let fe_lo = FreeEnergy::compose(0.95, 0.05); - let confident = materialize_provenance(&fe_lo, 0.05, 0.95, 0.9, &[0.95, 0.9, 0.88]); + let confident = materialize_provenance(&fe_lo, 0.05, 0.95, 0.9, &[0.95, 0.9, 0.88], 1); assert_eq!(confident.fork, 0, "confident cycle commits (no fork)"); // Scattered cycle: low resonance, high dispersion, short skill → Anxiety at // leaf → ForkDomain (fork 3): the orthogonal leaf residue is strong enough // that free energy forks into a new domain. let fe_hi = FreeEnergy::compose(0.3, 0.4); - let scattered = materialize_provenance(&fe_hi, 0.4, 0.3, 0.1, &[0.3, 0.28, 0.31]); + let scattered = materialize_provenance(&fe_hi, 0.4, 0.3, 0.1, &[0.3, 0.28, 0.31], 1); assert_eq!( scattered.fork, 3, "scattered low-skill cycle forks to a new domain" ); } + /// Full driver-cycle test for the `RungElevator` wiring: a dispatch with + /// `rung: RungLevel::Surface` whose cycles (each `dispatch()` call is + /// "one cycle") produce sustained BLOCK gates elevates the driver's + /// persistent `rung_elevator` above `Surface`. Uses an empty row window + /// (`ColumnWindow::new(0, 0)`) so `hits` is deterministically empty on + /// every call → `top_resonance = 0.0`, `std_dev = 0.0` → + /// `FreeEnergy::compose(0.0, 0.0).total == 1.0 > FAILURE_CEILING (0.8)` + /// → `GateDecision::BLOCK`, every single cycle, no flakiness. + #[test] + fn rung_elevator_persists_across_cycles_and_elevates_on_sustained_block() { + let bs = Arc::new(demo_bindspace()); + let sr = Arc::new(demo_semiring()); + let driver = CognitiveShaderBuilder::new() + .bindspace(bs) + .semiring(sr) + .planes(demo_planes()) + .build(); + + let req = ShaderDispatch { + rows: ColumnWindow::new(0, 0), + meta_prefilter: MetaFilter::ALL, + layer_mask: 0xFF, + radius: u16::MAX, + style: StyleSelector::Ordinal(auto_style::ANALYTICAL), + rung: RungLevel::Surface, + ..Default::default() + }; + + // Base rung starts at Surface (RungElevator::new(RungLevel::default())). + assert_eq!( + driver.rung_elevator.read().unwrap().level, + RungLevel::Surface + ); + + // Cycle 1: BLOCK, but `RungElevator::DEFAULT_THRESHOLD == 2` — a + // single BLOCK is not yet "sustained", so no elevation. + let crystal1 = driver.dispatch(&req); + assert_eq!(crystal1.bus.gate, GateDecision::BLOCK); + assert_eq!( + driver.rung_elevator.read().unwrap().level, + RungLevel::Surface, + "one BLOCK cycle must not elevate (threshold is 2 consecutive)" + ); + + // Cycle 2: second consecutive BLOCK — sustained — elevates one rung. + let crystal2 = driver.dispatch(&req); + assert_eq!(crystal2.bus.gate, GateDecision::BLOCK); + assert_eq!( + driver.rung_elevator.read().unwrap().level, + RungLevel::Shallow, + "two consecutive BLOCK cycles must elevate Surface -> Shallow" + ); + + // A dispatch requesting a *different* base rung resets the + // elevator to that new base (streaks never leak across unrelated + // dispatch contexts) — the `materialize_provenance` seam this + // elevator feeds (`ctx.rung ← elevator.on_gate(gate)`) is exercised + // directly in the narrower unit test below. + let req_meta = ShaderDispatch { + rung: RungLevel::Meta, + ..req + }; + let _ = driver.dispatch(&req_meta); + assert_eq!( + driver.rung_elevator.read().unwrap().base, + RungLevel::Meta, + "a dispatch with a different requested base resets the elevator" + ); + } + + /// Narrower unit test of the wired seam itself: `materialize_provenance` + /// now takes the elevator's current level as an explicit `rung: u8` + /// parameter and threads it into `ThoughtCtx::rung`, which + /// `materialize()` uses for tier selection (`materialize.rs` lines + /// ~99-101: `ctx.rung >= 7` / `>= 4` gate which tactic tier is offered). + /// This asserts the parameter is load-bearing, not decorative: raising + /// `rung` from `1` to `9` (`RungLevel::Transcendent`) changes the offered + /// tactic tier for an otherwise-identical scattered cycle. + #[test] + fn materialize_provenance_threads_elevated_rung_into_thought_ctx() { + let fe_hi = FreeEnergy::compose(0.3, 0.4); + let low_rung = materialize_provenance(&fe_hi, 0.4, 0.3, 0.1, &[0.3, 0.28, 0.31], 1); + let high_rung = materialize_provenance(&fe_hi, 0.4, 0.3, 0.1, &[0.3, 0.28, 0.31], 9); + // Both are well-formed provenance (sanity floor from the existing + // dispatch_populates_materialize_provenance assertions). + assert!(low_rung.first_tactic <= 34); + assert!(high_rung.first_tactic <= 34); + assert!(low_rung.final_free_energy.is_finite()); + assert!(high_rung.final_free_energy.is_finite()); + // Load-bearing check. Honesty note: tier is a +1 tie-weight in the + // 34-tactic scoring (mechanism +5 dominates), so different rungs are + // NOT guaranteed to select different tactics for every input — but for + // THIS scattered mid-surprise cycle they empirically do (rung 1 → + // tactic 17, rung 9 → tactic 3 at authoring time). Asserting + // inequality (not exact ids) pins the property "the rung parameter + // reaches tier selection and can flip the choice" without freezing the + // recipe table. + assert_ne!( + low_rung.first_tactic, high_rung.first_tactic, + "rung must be load-bearing in tactic selection for this input" + ); + } + #[test] fn dispatch_with_prefilter_excludes_rows() { let bs = Arc::new(demo_bindspace()); diff --git a/crates/cognitive-shader-driver/src/grpc.rs b/crates/cognitive-shader-driver/src/grpc.rs index 1adf7cd1..6c1d4e6a 100644 --- a/crates/cognitive-shader-driver/src/grpc.rs +++ b/crates/cognitive-shader-driver/src/grpc.rs @@ -346,18 +346,7 @@ fn proto_to_dispatch(req: &pb::DispatchRequest) -> ShaderDispatch { }) .unwrap_or(StyleSelector::Auto); - let rung = match req.rung { - 0 => RungLevel::Surface, - 1 => RungLevel::Shallow, - 2 => RungLevel::Contextual, - 3 => RungLevel::Analogical, - 4 => RungLevel::Abstract, - 5 => RungLevel::Structural, - 6 => RungLevel::Counterfactual, - 7 => RungLevel::Meta, - 8 => RungLevel::Recursive, - _ => RungLevel::Transcendent, - }; + let rung = RungLevel::from_u8(req.rung as u8); let emit = match pb::EmitMode::try_from(req.emit) { Ok(pb::EmitMode::Bundle) => EmitMode::Bundle, diff --git a/crates/cognitive-shader-driver/src/wire.rs b/crates/cognitive-shader-driver/src/wire.rs index e1f36631..b69629a3 100644 --- a/crates/cognitive-shader-driver/src/wire.rs +++ b/crates/cognitive-shader-driver/src/wire.rs @@ -918,18 +918,7 @@ impl WireDispatch { WireStyleSelector::Ordinal(n) => StyleSelector::Ordinal(*n), WireStyleSelector::Named(s) => StyleSelector::Ordinal(named_to_ordinal(s)), }; - let rung = match self.rung { - 0 => RungLevel::Surface, - 1 => RungLevel::Shallow, - 2 => RungLevel::Contextual, - 3 => RungLevel::Analogical, - 4 => RungLevel::Abstract, - 5 => RungLevel::Structural, - 6 => RungLevel::Counterfactual, - 7 => RungLevel::Meta, - 8 => RungLevel::Recursive, - _ => RungLevel::Transcendent, - }; + let rung = RungLevel::from_u8(self.rung); let emit = match self.emit.as_str() { "bundle" => EmitMode::Bundle, "persist" => EmitMode::Persist, From 04712d4b04cc5b9ee5f7bfb63c26d830bc21d644 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 04:10:20 +0000 Subject: [PATCH 11/11] grpc: saturate wire rung before narrowing (codex P2 #626) + repair pre-existing lab-feature breakage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit codex P2: DispatchRequest.rung is a proto uint32; 'as u8' truncation made 256 wrap to Surface (the shallowest depth) where the pre-dedup match saturated any out-of-range value to Transcendent. Fixed via rung_from_wire_u32 (u8::try_from().unwrap_or(u8::MAX) -> from_u8) + regression test covering the exact wrap cases (256, 257, u32::MAX). wire.rs is unaffected (its rung field is already u8). Verifying the fix required repairing pre-existing --features grpc breakage (broken on the merge-base too, verified by stash-check): - wire.rs + grpc.rs ShaderDispatch initializers missing the Pillar-7 merge_override / alpha_saturation_override fields -> None defaults (the wire DTO / proto do not carry them). - cypher_bridge.rs tests calling ContextChain::new(8) against the contract's zero-arg new(). Lab-feature suite now green: 186 lib tests incl. the new saturation test; default suite still 100/100. CodeRabbit (2 findings): plan 'Option C' label corrected to 'the dated ADDENDUM strengthening Option B' (the issue defines only A/B). The ISSUES.md rewrite request is declined on-thread: board files are append-only ledgers — corrections append as dated entries, never rewrite prior text. Co-Authored-By: Claude --- .claude/plans/v3-convergence-wiring-v1.md | 4 ++- .../src/cypher_bridge.rs | 4 +-- crates/cognitive-shader-driver/src/grpc.rs | 33 ++++++++++++++++++- crates/cognitive-shader-driver/src/wire.rs | 4 +++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.claude/plans/v3-convergence-wiring-v1.md b/.claude/plans/v3-convergence-wiring-v1.md index 22e66a7b..4fbd7e3b 100644 --- a/.claude/plans/v3-convergence-wiring-v1.md +++ b/.claude/plans/v3-convergence-wiring-v1.md @@ -110,7 +110,9 @@ Recorded in `ISS-Q2-CASCADE3-NIBBLE-ANCESTRY` (board). (contract 763+, core 925, planner 204, arigraph 124). - **osint `0x0700` reconciliation:** operator decision; the two-id-spaces reading (classid space vs concept-vocabulary space, aliasing in the lo u16) - is on `ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION` as Option C. + is on `ISS-OSINT-SYSTEM-ROOT-SLOT-VIOLATION` as the dated ADDENDUM + strengthening its Option B (the issue defines Options A/B; no separate + option was minted). - **classid human-readable reorder (`0x07:01::1000`):** DEFERRED-by-design, hands off until post-V3, implemented as the one flippable split-order. diff --git a/crates/cognitive-shader-driver/src/cypher_bridge.rs b/crates/cognitive-shader-driver/src/cypher_bridge.rs index 7cd30ae3..9be53152 100644 --- a/crates/cognitive-shader-driver/src/cypher_bridge.rs +++ b/crates/cognitive-shader-driver/src/cypher_bridge.rs @@ -251,7 +251,7 @@ mod tests { /// TD-INT-6 — empty candidate list escalates. #[test] fn disambiguate_empty_candidates_escalates() { - let chain = ContextChain::new(8); + let chain = ContextChain::new(); let result = disambiguate_parse_candidates(&chain, 0, Vec::new()); assert!(result.is_err(), "empty candidates must escalate"); } @@ -259,7 +259,7 @@ mod tests { /// TD-INT-6 — single candidate escalates (margin = 0). #[test] fn disambiguate_single_candidate_escalates() { - let chain = ContextChain::new(8); + let chain = ContextChain::new(); let cand = CrystalFingerprint::Binary16K(Box::new([0u64; 256])); let result = disambiguate_parse_candidates(&chain, 0, vec![cand]); assert!(result.is_err(), "single candidate must escalate"); diff --git a/crates/cognitive-shader-driver/src/grpc.rs b/crates/cognitive-shader-driver/src/grpc.rs index 6c1d4e6a..0a2138c0 100644 --- a/crates/cognitive-shader-driver/src/grpc.rs +++ b/crates/cognitive-shader-driver/src/grpc.rs @@ -346,7 +346,7 @@ fn proto_to_dispatch(req: &pb::DispatchRequest) -> ShaderDispatch { }) .unwrap_or(StyleSelector::Auto); - let rung = RungLevel::from_u8(req.rung as u8); + let rung = rung_from_wire_u32(req.rung); let emit = match pb::EmitMode::try_from(req.emit) { Ok(pb::EmitMode::Bundle) => EmitMode::Bundle, @@ -376,6 +376,10 @@ fn proto_to_dispatch(req: &pb::DispatchRequest) -> ShaderDispatch { max_cycles: req.max_cycles as u16, entropy_floor: req.entropy_floor, emit, + // Pillar-7 knobs: the proto does not carry them; keep the documented + // defaults (no sink-stage merge override). + merge_override: None, + alpha_saturation_override: None, } } @@ -398,3 +402,30 @@ fn unified_styles_proto() -> Vec { }) .collect() } + +/// Decode the proto `uint32` rung, saturating BEFORE narrowing: an +/// out-of-range client value clamps to `Transcendent` (the pre-dedup match's +/// `_ =>` arm), never wraps (`256 as u8 == 0` would silently run the request +/// at `Surface`, the shallowest depth — codex P2 on #626). +fn rung_from_wire_u32(v: u32) -> RungLevel { + RungLevel::from_u8(u8::try_from(v).unwrap_or(u8::MAX)) +} + +#[cfg(test)] +mod rung_wire_tests { + use super::*; + + #[test] + fn wire_rung_saturates_never_wraps() { + // In-range ordinals round-trip. + assert_eq!(rung_from_wire_u32(0), RungLevel::Surface); + assert_eq!(rung_from_wire_u32(6), RungLevel::Counterfactual); + assert_eq!(rung_from_wire_u32(9), RungLevel::Transcendent); + // Out-of-range clamps to the SAFEST maximum depth — the codex-P2 + // wrap cases: 256 must NOT become Surface, 257 must NOT become Shallow. + assert_eq!(rung_from_wire_u32(10), RungLevel::Transcendent); + assert_eq!(rung_from_wire_u32(256), RungLevel::Transcendent); + assert_eq!(rung_from_wire_u32(257), RungLevel::Transcendent); + assert_eq!(rung_from_wire_u32(u32::MAX), RungLevel::Transcendent); + } +} diff --git a/crates/cognitive-shader-driver/src/wire.rs b/crates/cognitive-shader-driver/src/wire.rs index b69629a3..14edf106 100644 --- a/crates/cognitive-shader-driver/src/wire.rs +++ b/crates/cognitive-shader-driver/src/wire.rs @@ -946,6 +946,10 @@ impl WireDispatch { max_cycles: self.max_cycles, entropy_floor: self.entropy_floor, emit, + // Pillar-7 knobs: the wire DTO does not carry them; keep the + // documented defaults (no sink-stage merge override). + merge_override: None, + alpha_saturation_override: None, } } }