From 44c67d128d3399479974e8fa87dc32bc41bfa651 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 22:38:24 +0000 Subject: [PATCH 1/2] contract: un-gate mint_for + default-on guid-v3-tail; route ocr/aiwar mints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executes the structural half of ISS-V1-TAIL-RESIDUE (operator ruling 2026-07-04, "default-on guid-v3-tail now so production mints route through mint_for in every build"). - canonical_node.rs: `mint_for` moved to an unconditional `impl NodeGuid` — V1 arm always available; V2/V3 arms feature-gated (`guid-v2-tail`) with a dead V1 fallback so `--no-default-features` still compiles. - Cargo.toml: `default = ["guid-v3-tail"]` — every normal build has the V3 mint path. - ocr.rs + aiwar.rs: route through `mint_for(classid_read_mode(c).tail_variant, …)` instead of hardcoded `NodeGuid::new`. ocr is classid-param-driven (V3- ready). aiwar stays on the V1 `CLASSID_OSINT` (see below), behavior-preserving. - .claude/commands/v3-audit.md: check #6 forbids `NodeGuid::new(` in non-test production code (mechanical guard). Discovered blocker (documented, deferred): flipping aiwar to `CLASSID_OSINT_V3` broke `projects_to_family_node_graph` — the V3 tail puts `family` at bytes 12..14 (u16) but `soa_graph::project_snapshot` reads it via the V1 `family()` u24 accessor. So aiwar's V3-classid flip is now gated on making that consumer tail-aware (I-LEGACY-API-FEATURE-GATED), not on the feature gate the issue originally named. ISSUES.md carries the progress + remaining work. Verification: 854 contract lib tests green (default); `--no-default-features` compiles; `lance-graph` + `lance-graph-planner` check clean (default-on propagates safely); fmt + clippy clean. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MLBnPuScZy6w9di2QEjsXM --- .claude/board/ISSUES.md | 12 ++ .claude/commands/v3-audit.md | 6 + crates/lance-graph-contract/Cargo.toml | 5 + crates/lance-graph-contract/src/aiwar.rs | 20 ++- .../src/canonical_node.rs | 121 +++++++++--------- crates/lance-graph-contract/src/ocr.rs | 17 ++- 6 files changed, 118 insertions(+), 63 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index e842b5f2..8dbc4a05 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,5 +1,17 @@ # Issues Log — Open + Resolved (double-entry, append-only) +## 2026-07-07 — ISS-V1-TAIL-RESIDUE PROGRESS — structural fix landed; aiwar V3-classid flip now blocked on a consumer, not the feature gate + +**Status:** PARTIALLY RESOLVED. The mandatory *structural* half of ISS-V1-TAIL-RESIDUE (below) is done in one PR: +- **`mint_for` un-gated** (`canonical_node.rs`): moved to an unconditional `impl NodeGuid` — V1 arm always available, V2/V3 arms feature-gated with a dead V1 fallback so `--no-default-features` still compiles. +- **`guid-v3-tail` default-on** (`lance-graph-contract/Cargo.toml`), per the operator ruling. 854 lib tests green on default; `--no-default-features` still compiles; downstream `lance-graph` + `lance-graph-planner` check clean. +- **Both mint sites route through `mint_for`** now — `ocr.rs` (`mint_for(classid_read_mode(classid).tail_variant, …)`, classid-param-driven) and `aiwar.rs` (`osint_classid`, still `CLASSID_OSINT`). No hardcoded `NodeGuid::new` remains in either production path. +- **`/v3-audit` check #6** added: forbids `NodeGuid::new(` in non-test production code (the mechanical guard). + +**Newly discovered blocker (was NOT in the original issue).** Flipping `aiwar` to `CLASSID_OSINT_V3` (a real V3 mint) **broke `aiwar::tests::projects_to_family_node_graph`**: the V3 tail lays `family` at bytes 12..14 (u16), but `soa_graph::project_snapshot` reads `family` via the **V1 `family()` u24 accessor** (bytes 10..13) → family grouping reads garbage (leaf-dominated). So aiwar stays on the V1 `CLASSID_OSINT` (behavior-preserving via `mint_for`); its V3-classid flip is now gated on making `project_snapshot` (and any `family()`-reading consumer) **tail-aware** — an I-LEGACY-API-FEATURE-GATED consumer migration, not the feature gate the original issue named. Once `CLASSID_OSINT`'s registry `tail_variant` flips, the aiwar mint auto-follows. + +**Remaining (still OPEN):** (1) make `project_snapshot`/`OSINT_GOTHAM` read family tail-aware; (2) then flip `CLASSID_OSINT` (or point aiwar/ocr at a V3-marked classid). ocr is already V3-ready (classid-param-driven through `mint_for`). + ## 2026-07-04 — ISS-V1-TAIL-RESIDUE — two pre-existing `NodeGuid::new` (V1 `u24+u24`) mint sites must migrate to V3 (`mint_for` / V3-marked classid) **Status:** OPEN — **MIGRATION MANDATORY** (operator ruling 2026-07-04, `E-V1-TAIL-FORBIDDEN-V3-IS-CONTENT-BLIND-1`). Deferred in *timing*, not in *obligation*; NOT to be churned into unrelated PRs. Owner: whoever next moves each output path onto a V3-marked classid. diff --git a/.claude/commands/v3-audit.md b/.claude/commands/v3-audit.md index ff5041a2..74db62a2 100644 --- a/.claude/commands/v3-audit.md +++ b/.claude/commands/v3-audit.md @@ -36,6 +36,12 @@ every hit with file:line and a one-line disposition — verify const size asserts unchanged or `ENVELOPE_LAYOUT_VERSION` bumped, and that no `_LEGACY` read-mode key was removed (retirement is corpus-proof-gated). +6. **V1-mint forbid** (ISS-V1-TAIL-RESIDUE): pattern `NodeGuid::new\(` + in non-test production code is a violation — new mints route through + `NodeGuid::mint_for(classid_read_mode(c).tail_variant, …)` so the + class registry drives the tail. Sanctioned: `#[cfg(test)]` modules, + `mint_for`'s own V1 arm (`canonical_node.rs`), and legacy-compat + *reads* (`family()`/`identity()` accessors are reads, not mints). End with a verdict per the v3-mailbox-warden vocabulary (OWNED / BOOTSTRAP-OK / ORPHAN-WRITE / RESURRECTION) plus LAYOUT-CLEAN / diff --git a/crates/lance-graph-contract/Cargo.toml b/crates/lance-graph-contract/Cargo.toml index 4172f79a..d306b9b6 100644 --- a/crates/lance-graph-contract/Cargo.toml +++ b/crates/lance-graph-contract/Cargo.toml @@ -36,6 +36,11 @@ name = "i4_batch" harness = false [features] +# default-on `guid-v3-tail` (operator ruling 2026-07-04, ISS-V1-TAIL-RESIDUE): +# every normal build mints real V3 keys via `mint_for`. `--no-default-features` +# still compiles (mint_for's V1 arm is un-gated; V2/V3 arms fall back to V1). +default = ["guid-v3-tail"] + # A-unlock-stepdomain — `step_trajectory_hash` forward stub for the E4 # cross-PR bridge between PR #278 audit log + PR #279 grammar trajectory. # No-op alias today; concrete impl lands once the bridge PR ships. diff --git a/crates/lance-graph-contract/src/aiwar.rs b/crates/lance-graph-contract/src/aiwar.rs index ec2327c7..114e37b2 100644 --- a/crates/lance-graph-contract/src/aiwar.rs +++ b/crates/lance-graph-contract/src/aiwar.rs @@ -13,7 +13,7 @@ //! `GraphSnapshot` to the Quadro-2 visual. Run it on the real graph with //! `cargo run -p lance-graph-contract --example aiwar_family_poc`. -use crate::canonical_node::{EdgeBlock, NodeGuid, NodeRow}; +use crate::canonical_node::{classid_read_mode, EdgeBlock, NodeGuid, NodeRow}; use crate::literal_graph::LiteralGraph; use std::collections::{BTreeMap, BTreeSet}; @@ -72,6 +72,14 @@ impl AiwarClassView { /// projected graph links category hubs (Nation → System, etc.). Head-only — the /// 480-byte value slab stays zero. pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { + // Route through `mint_for` (ISS-V1-TAIL-RESIDUE) — never a hardcoded `new`. + // Kept on the V1 `CLASSID_OSINT` for now: flipping to `CLASSID_OSINT_V3` + // gives the V3 `leaf·family·identity` tail, but `project_snapshot` still + // reads `family` via the V1 `family()` u24 accessor, so the V3 flip is + // gated on making that consumer tail-aware (a follow-up). Once + // `CLASSID_OSINT`'s registry `tail_variant` flips, this mint auto-follows. + let osint_classid = NodeGuid::CLASSID_OSINT; + let view = AiwarClassView::from_graph(graph); let ids = graph.all_node_ids(); let fam_of = @@ -101,8 +109,14 @@ pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { edges.out_family[k] = b; } NodeRow { - key: NodeGuid::new( - NodeGuid::CLASSID_OSINT, + // Route through `mint_for` so the classid's registered + // `tail_variant` drives the key layout (ISS-V1-TAIL-RESIDUE). + // V1 today ⇒ family/identity keep the u24 mask; a V3 registry + // flip would narrow them to u16 automatically. + key: NodeGuid::mint_for( + classid_read_mode(osint_classid).tail_variant, + osint_classid, + 0, 0, 0, 0, diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index ff399ba1..40fca1a9 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -310,6 +310,68 @@ impl NodeGuid { } } +impl NodeGuid { + /// Mint a node by its **tail variant** — the carrier form of the Phase-1 + /// symmetric spine: a consumer mints with + /// `mint_for(classid_read_mode(c).tail_variant, …)`, NEVER by hardcoding + /// `new` vs `new_v2`. Migrating a class's identity to V3 is then a one-line + /// flip of its `tail_variant` in the registry, with zero consumer rewrite. + /// + /// **Un-gated dispatch (`ISS-V1-TAIL-RESIDUE`):** `mint_for` itself is always + /// available so production mints (`ocr.rs`, `aiwar.rs`) route through it in + /// every build. The [`V1`](TailVariant::V1) arm is unconditional + /// ([`new`](NodeGuid::new)); the [`V2`](TailVariant::V2)/[`V3`](TailVariant::V3) + /// arms lower to `new_v2` only under `guid-v2-tail`. With the feature off no + /// classid registers a V2/V3 `tail_variant` ([`classid_read_mode`] returns + /// V1), so the fallback arm is dead — it exists purely so the crate compiles + /// `--no-default-features`. + /// + /// **No silent truncation:** the V2/V3 arm asserts `family`/`identity` fit + /// `u16`, mirroring [`new`](NodeGuid::new)'s own 24-bit guard. + #[allow(clippy::too_many_arguments)] + pub const fn mint_for( + tail_variant: TailVariant, + classid: u32, + heel: u16, + hip: u16, + twig: u16, + leaf: u16, + family: u32, + identity: u32, + ) -> Self { + match tail_variant { + TailVariant::V1 => Self::new(classid, heel, hip, twig, family, identity), + #[cfg(feature = "guid-v2-tail")] + TailVariant::V2 | TailVariant::V3 => { + assert!( + family <= 0xFFFF, + "v2/v3 family must fit in 16 bits (no silent truncation)" + ); + assert!( + identity <= 0xFFFF, + "v2/v3 identity must fit in 16 bits (no silent truncation)" + ); + Self::new_v2( + classid, + heel, + hip, + twig, + leaf, + family as u16, + identity as u16, + ) + } + #[cfg(not(feature = "guid-v2-tail"))] + TailVariant::V2 | TailVariant::V3 => { + // feature off ⇒ V2/V3 unreachable (no classid registers them); + // fall back to the V1 layout so the crate compiles. + let _ = leaf; + Self::new(classid, heel, hip, twig, family, identity) + } + } + } +} + // ── GUID v2 tail (leaf·family·identity, 3×u16) — D-GV2-1, feature-gated ──────── // // The v2 basin tail repartitions bytes 10..16: leaf(u16) 10..12 (the 4th HHTL @@ -353,63 +415,8 @@ impl NodeGuid { ]) } - /// Mint a node by its **tail variant** — the carrier form of the Phase-1 - /// symmetric spine (`soa-value-tenant-migration-v2.md` §2.1): a consumer - /// mints with `mint_for(classid_read_mode(c).tail_variant, …)`, NEVER by - /// hardcoding `new` vs `new_v2`. The key-side analog of the value-side - /// `to_node_row(classid_read_mode(c).value_schema, …)` — same - /// [`classid_read_mode`] lookup, sibling field. Migrating a class's identity - /// to V3 is then a one-line flip of its `tail_variant` in the registry, with - /// zero consumer rewrite (the "extend the one `ReadMode`, never a public - /// `new_v3`" litmus). - /// - /// Dispatch (all three [`TailVariant`] arms exist unconditionally as enum - /// values; only the constructors they call are gated): - /// - [`V1`](TailVariant::V1) → [`new`](NodeGuid::new): the canonical - /// `family(u24)·identity(u24)` tail. `leaf` is not part of the V1 tail and - /// is intentionally ignored (the V1 cascade is HEEL·HIP·TWIG only). - /// - [`V2`](TailVariant::V2) / [`V3`](TailVariant::V3) → [`new_v2`](NodeGuid::new_v2): - /// the shared `leaf·family·identity` 3×u16 tail bytes. V3 differs from V2 - /// only in how those bytes are *read* (the `(part_of:is_a)` cascade tile), - /// not how they are *stored* — so it mints through the same constructor. - /// - /// **No silent truncation** (the footgun v2 exists to remove): the V2/V3 arm - /// asserts `family`/`identity` fit `u16`, mirroring [`new`](NodeGuid::new)'s - /// own 24-bit guard. An out-of-range value is a loud panic, never a wrong key. - #[allow(clippy::too_many_arguments)] - pub const fn mint_for( - tail_variant: TailVariant, - classid: u32, - heel: u16, - hip: u16, - twig: u16, - leaf: u16, - family: u32, - identity: u32, - ) -> Self { - match tail_variant { - TailVariant::V1 => Self::new(classid, heel, hip, twig, family, identity), - TailVariant::V2 | TailVariant::V3 => { - assert!( - family <= 0xFFFF, - "v2/v3 family must fit in 16 bits (no silent truncation)" - ); - assert!( - identity <= 0xFFFF, - "v2/v3 identity must fit in 16 bits (no silent truncation)" - ); - Self::new_v2( - classid, - heel, - hip, - twig, - leaf, - family as u16, - identity as u16, - ) - } - } - } + // `mint_for` moved to the unconditional `impl NodeGuid` above (un-gated per + // ISS-V1-TAIL-RESIDUE): V1 arm always available, V2/V3 arms feature-gated. /// v2 `leaf` — bytes 10..12, the 4th HHTL routing tier (cascade terminal). #[inline] diff --git a/crates/lance-graph-contract/src/ocr.rs b/crates/lance-graph-contract/src/ocr.rs index 034c9c2e..bafe43f8 100644 --- a/crates/lance-graph-contract/src/ocr.rs +++ b/crates/lance-graph-contract/src/ocr.rs @@ -116,9 +116,20 @@ impl<'a> LayoutBlock<'a> { NodeRow { // HHT unbound (0) and default basin for the POC — only `identity` - // discriminates (the canon bootstrap address); `classid` still selects - // the read-mode. Minting HEEL/HIP/TWIG + family is the OGAR follow-up. - key: NodeGuid::new(classid, 0, 0, 0, NodeGuid::FAMILY_DEFAULT, identity), + // discriminates (the canon bootstrap address). Route through + // `mint_for` so the classid's registered `tail_variant` (V1 or V3) + // drives the key layout — never a hardcoded `new` (ISS-V1-TAIL-RESIDUE). + // Minting HEEL/HIP/TWIG + family is the OGAR follow-up. + key: NodeGuid::mint_for( + classid_read_mode(classid).tail_variant, + classid, + 0, + 0, + 0, + 0, + NodeGuid::FAMILY_DEFAULT, + identity, + ), edges: EdgeBlock::default(), value, } From 4bc25ba2c2f8d60b4552184caf9a6bb5a925367b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Jul 2026 04:00:33 +0000 Subject: [PATCH 2/2] =?UTF-8?q?contract:=20complete=20aiwar=20V3=20flip=20?= =?UTF-8?q?=E2=80=94=20OSINT=5FGOTHAM=20to=20V3=20class=20(ISS-V1-TAIL-RES?= =?UTF-8?q?IDUE=20resolved)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the migration: aiwar now mints the real V3 `CLASSID_OSINT_V3` in every normal build (V1 fallback only under `--no-default-features`). The mid-work diagnosis in the prior commit was WRONG and is corrected here. `soa_graph`'s read path is already tail-aware (`family_of`/`identity_of` route through `classid_read_mode(...).tail_variant` → `family_v2`/`identity_v2` for V3; proven by `v3_rows_decode_family_and_identity_via_tail_variant`). The actual blocker was a one-line domain-spec pin: `OSINT_GOTHAM.classid = CLASSID_OSINT` (V1), and `project_snapshot` filters rows by EXACT classid, so V3-minted aiwar rows were dropped (empty snapshot). Consumers were V3-ready; the straggler was the domain constant. - soa_graph.rs: `OSINT_GOTHAM.classid` -> `CLASSID_OSINT_V3` (via a cfg-selected `OSINT_GOTHAM_CLASSID`, V1 fallback for `--no-default-features`). The `node()` test helper mints via `mint_for` so its rows carry the domain's tail layout. - aiwar.rs: mint `CLASSID_OSINT_V3` (cfg fallback), u16 masks; test asserts the V3 classid. - ISSUES.md: ISS-V1-TAIL-RESIDUE marked RESOLVED with the corrected diagnosis. Verification: default build 854 lib tests green (V3), `--no-default-features` 840 green (V1 fallback), fmt + clippy clean. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MLBnPuScZy6w9di2QEjsXM --- .claude/board/ISSUES.md | 17 ++++++----- crates/lance-graph-contract/src/aiwar.rs | 30 +++++++++++++------- crates/lance-graph-contract/src/soa_graph.rs | 29 +++++++++++++++++-- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 8dbc4a05..3f1693ac 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,16 +1,19 @@ # Issues Log — Open + Resolved (double-entry, append-only) -## 2026-07-07 — ISS-V1-TAIL-RESIDUE PROGRESS — structural fix landed; aiwar V3-classid flip now blocked on a consumer, not the feature gate +## 2026-07-07 — ISS-V1-TAIL-RESIDUE — RESOLVED (un-gate + default-on + both mint sites V3-routed; aiwar mints real V3) -**Status:** PARTIALLY RESOLVED. The mandatory *structural* half of ISS-V1-TAIL-RESIDUE (below) is done in one PR: +**Status:** RESOLVED. Landed in one PR (#663): - **`mint_for` un-gated** (`canonical_node.rs`): moved to an unconditional `impl NodeGuid` — V1 arm always available, V2/V3 arms feature-gated with a dead V1 fallback so `--no-default-features` still compiles. -- **`guid-v3-tail` default-on** (`lance-graph-contract/Cargo.toml`), per the operator ruling. 854 lib tests green on default; `--no-default-features` still compiles; downstream `lance-graph` + `lance-graph-planner` check clean. -- **Both mint sites route through `mint_for`** now — `ocr.rs` (`mint_for(classid_read_mode(classid).tail_variant, …)`, classid-param-driven) and `aiwar.rs` (`osint_classid`, still `CLASSID_OSINT`). No hardcoded `NodeGuid::new` remains in either production path. -- **`/v3-audit` check #6** added: forbids `NodeGuid::new(` in non-test production code (the mechanical guard). +- **`guid-v3-tail` default-on** (`lance-graph-contract/Cargo.toml`), per the operator ruling. +- **Both mint sites route through `mint_for`** — `ocr.rs` (classid-param-driven) and `aiwar.rs` (`CLASSID_OSINT_V3` in a normal build; V1 fallback under `--no-default-features`). No hardcoded `NodeGuid::new` remains in either production path. +- **`OSINT_GOTHAM` flipped to the V3 classid** (`soa_graph.rs`), so the projector's exact-classid filter matches the V3-minted aiwar rows; the `node()` test helper now mints via `mint_for` too. +- **`/v3-audit` check #6** forbids `NodeGuid::new(` in non-test production code. -**Newly discovered blocker (was NOT in the original issue).** Flipping `aiwar` to `CLASSID_OSINT_V3` (a real V3 mint) **broke `aiwar::tests::projects_to_family_node_graph`**: the V3 tail lays `family` at bytes 12..14 (u16), but `soa_graph::project_snapshot` reads `family` via the **V1 `family()` u24 accessor** (bytes 10..13) → family grouping reads garbage (leaf-dominated). So aiwar stays on the V1 `CLASSID_OSINT` (behavior-preserving via `mint_for`); its V3-classid flip is now gated on making `project_snapshot` (and any `family()`-reading consumer) **tail-aware** — an I-LEGACY-API-FEATURE-GATED consumer migration, not the feature gate the original issue named. Once `CLASSID_OSINT`'s registry `tail_variant` flips, the aiwar mint auto-follows. +**Correction of the mid-work diagnosis (recorded so it doesn't mislead).** An interim note in this entry claimed the aiwar V3 flip was blocked because `soa_graph::project_snapshot` reads `family` via the V1 `family()` u24 accessor. **That was WRONG** — `soa_graph`'s read path is already tail-aware (`family_of`/`identity_of` route through `classid_read_mode(guid.classid()).tail_variant` → `family_v2`/`identity_v2` for V3; proven by `v3_rows_decode_family_and_identity_via_tail_variant`). The *actual* blocker was a one-line domain-spec pin: `OSINT_GOTHAM.classid = CLASSID_OSINT` (V1), and `project_snapshot` filters rows by **exact** classid, so V3-minted rows were dropped (empty snapshot). Flipping the domain spec to the V3 classid fixed it. Consumers were V3-ready; the straggler was the domain constant, not the read path. -**Remaining (still OPEN):** (1) make `project_snapshot`/`OSINT_GOTHAM` read family tail-aware; (2) then flip `CLASSID_OSINT` (or point aiwar/ocr at a V3-marked classid). ocr is already V3-ready (classid-param-driven through `mint_for`). +**Verification:** default build **854** lib tests green (V3), `--no-default-features` **840** green (V1 fallback), downstream `lance-graph` + `lance-graph-planner` check clean, fmt + clippy clean. + +**Follow-up (separate, non-blocking):** `CLASSID_OSINT` (V1, `0x0700_0000`) remains a registered legacy alias — its retirement is corpus-proof-gated (W6), not part of this issue. ## 2026-07-04 — ISS-V1-TAIL-RESIDUE — two pre-existing `NodeGuid::new` (V1 `u24+u24`) mint sites must migrate to V3 (`mint_for` / V3-marked classid) diff --git a/crates/lance-graph-contract/src/aiwar.rs b/crates/lance-graph-contract/src/aiwar.rs index 114e37b2..e91563d4 100644 --- a/crates/lance-graph-contract/src/aiwar.rs +++ b/crates/lance-graph-contract/src/aiwar.rs @@ -72,12 +72,14 @@ impl AiwarClassView { /// projected graph links category hubs (Nation → System, etc.). Head-only — the /// 480-byte value slab stays zero. pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { - // Route through `mint_for` (ISS-V1-TAIL-RESIDUE) — never a hardcoded `new`. - // Kept on the V1 `CLASSID_OSINT` for now: flipping to `CLASSID_OSINT_V3` - // gives the V3 `leaf·family·identity` tail, but `project_snapshot` still - // reads `family` via the V1 `family()` u24 accessor, so the V3 flip is - // gated on making that consumer tail-aware (a follow-up). Once - // `CLASSID_OSINT`'s registry `tail_variant` flips, this mint auto-follows. + // ONE OSINT class, minted V3 (ISS-V1-TAIL-RESIDUE): route through `mint_for` + // so the classid's registered `tail_variant` drives the tail. `OSINT_GOTHAM` + // filters on the SAME selector, and `family_of`/`identity_of` decode the V3 + // basin tail-aware — so the round-trip is consistent. `--no-default-features` + // falls back to the V1 `CLASSID_OSINT` (the V3 class is gated out there). + #[cfg(feature = "guid-v3-tail")] + let osint_classid = NodeGuid::CLASSID_OSINT_V3; + #[cfg(not(feature = "guid-v3-tail"))] let osint_classid = NodeGuid::CLASSID_OSINT; let view = AiwarClassView::from_graph(graph); @@ -111,8 +113,8 @@ pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { NodeRow { // Route through `mint_for` so the classid's registered // `tail_variant` drives the key layout (ISS-V1-TAIL-RESIDUE). - // V1 today ⇒ family/identity keep the u24 mask; a V3 registry - // flip would narrow them to u16 automatically. + // Masked to u16 — fits both the V3 basin tail and the V1 fallback + // (POC cardinalities are tiny: category count + node count). key: NodeGuid::mint_for( classid_read_mode(osint_classid).tail_variant, osint_classid, @@ -120,8 +122,8 @@ pub fn aiwar_node_rows(graph: &LiteralGraph) -> Vec { 0, 0, 0, - fam & 0x00FF_FFFF, - (i as u32) & 0x00FF_FFFF, + fam & 0xFFFF, + (i as u32) & 0xFFFF, ), edges, value: [0u8; 480], @@ -199,9 +201,15 @@ mod tests { #[test] fn rows_are_osint_class_and_head_only() { + // ONE OSINT class: V3 in a normal build, V1 under `--no-default-features` + // (mirrors `aiwar_node_rows` + `OSINT_GOTHAM`). + #[cfg(feature = "guid-v3-tail")] + let expected_classid = NodeGuid::CLASSID_OSINT_V3; + #[cfg(not(feature = "guid-v3-tail"))] + let expected_classid = NodeGuid::CLASSID_OSINT; let g = ingest_aiwar_json(SAMPLE).unwrap(); for row in aiwar_node_rows(&g) { - assert_eq!(row.key.classid(), NodeGuid::CLASSID_OSINT); + assert_eq!(row.key.classid(), expected_classid); assert_eq!(row.value, [0u8; 480], "head-only: value slab stays zero"); } } diff --git a/crates/lance-graph-contract/src/soa_graph.rs b/crates/lance-graph-contract/src/soa_graph.rs index bbbc2c75..52c0b466 100644 --- a/crates/lance-graph-contract/src/soa_graph.rs +++ b/crates/lance-graph-contract/src/soa_graph.rs @@ -83,11 +83,20 @@ pub struct DomainSpec { pub member_edge: &'static str, } -/// The **OSINT / Palantir-Gotham** domain (classid [`NodeGuid::CLASSID_OSINT`]): +/// The OSINT domain classid: the V3 OSINT class (`CLASSID_OSINT_V3`) in a +/// normal build; the legacy V1 `CLASSID_OSINT` only under +/// `--no-default-features` (where the V3 class is gated out). aiwar mints under +/// the same selector, so its rows always match this domain's projector filter. +#[cfg(feature = "guid-v3-tail")] +const OSINT_GOTHAM_CLASSID: u32 = NodeGuid::CLASSID_OSINT_V3; +#[cfg(not(feature = "guid-v3-tail"))] +const OSINT_GOTHAM_CLASSID: u32 = NodeGuid::CLASSID_OSINT; + +/// The **OSINT / Palantir-Gotham** domain (V3 class [`NodeGuid::CLASSID_OSINT_V3`]): /// a neo4j-emulation entity graph. Anchor families are caller-supplied (the key /// entities of an investigation); the default declares none. pub const OSINT_GOTHAM: DomainSpec = DomainSpec { - classid: NodeGuid::CLASSID_OSINT, + classid: OSINT_GOTHAM_CLASSID, name: "OSINT/Gotham", anchor_families: &[], in_family_edge: "linked", @@ -401,6 +410,7 @@ mod tests { in_fam: &[u8], out_fam: &[u8], ) -> NodeRow { + use crate::canonical_node::classid_read_mode; let mut edges = EdgeBlock::default(); for (i, &b) in in_fam.iter().enumerate().take(12) { edges.in_family[i] = b; @@ -409,7 +419,20 @@ mod tests { edges.out_family[i] = b; } NodeRow { - key: NodeGuid::new(domain.classid, hht.0, hht.1, hht.2, family, identity), + // Route through `mint_for` so the domain classid's `tail_variant` + // drives the tail layout (V3 domains lay family/identity as u16 at + // 12..16; V1 as u24 at 10..16) — matching how `family_of`/`identity_of` + // decode them (ISS-V1-TAIL-RESIDUE). + key: NodeGuid::mint_for( + classid_read_mode(domain.classid).tail_variant, + domain.classid, + hht.0, + hht.1, + hht.2, + 0, + family, + identity, + ), edges, value: [0u8; 480], }