From 7ca4ea70d63d7d9649f1a55cebf06302f786270b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 16:42:30 +0000 Subject: [PATCH] callcenter: fix the graph_table fixture's classid + tail mint (2 red tests) `cargo test -p lance-graph-callcenter --features query` was 209/2 red. Both failures were the same defect, and it is NOT a DataFusion-54 regression: the assertion that fires is `num_rows() == 0` vs 4, decided before DataFusion is involved. `OSINT_GOTHAM.classid` resolves to `CLASSID_OSINT_V3` (0x0701_1000) under the default `guid-v3-tail` feature, falling back to the V1 `CLASSID_OSINT` (0x0700_0000) only with the feature off. The fixture minted its two rows with the V1 constant written out literally, so `project_snapshot`'s `classid == domain.classid` filter -- the codex-P1 cross-class leak guard -- matched zero rows and every assertion below it ran against an empty snapshot. Fixing only the classid would have been half a fix: with a V3 classid, `family_of` reads the tail via `family_v2()`, so a `NodeGuid::new` (V1 tail) key would carry the family in the wrong bits even once the filter passed. The fixture now mints through `mint_for(classid_read_mode(c).tail_variant, ...)` with the classid taken from the domain -- which is what the CANON requires of every new mint anyway -- so it is correct under either feature setting rather than encoding one branch of it. 211/211 green under `--features query`. This is V1->V3 migration residue (ISS-V1-TAIL-RESIDUE), not new breakage. It survived because nothing runs this feature combination: the same coverage hole that let `vsa_udfs` and `transcode::ontology_table` report clippy EXIT=0 earlier in this arc while never being compiled. Recorded as E-A-FEATURE-CONDITIONAL-CLASSID-SILENTLY-EMPTIED-A-FIXTURE-1; the CI gap itself is still open. --- .claude/board/EPIPHANIES.md | 40 +++++++++++++++++++ .../lance-graph-callcenter/src/graph_table.rs | 33 +++++++++++++-- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 7554dd362..1ee3977f3 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,43 @@ +## E-A-FEATURE-CONDITIONAL-CLASSID-SILENTLY-EMPTIED-A-FIXTURE-1 (2026-08-05, measured) + +**A test that hardcodes a classid the domain now selects BY FEATURE does not +fail loudly — it runs every assertion against an empty projection.** Found by +finally running `lance-graph-callcenter` under `--features query`, which no +`-p` sweep and no CI tier had exercised. + +**The mechanism, in three links.** (1) `soa_graph::OSINT_GOTHAM.classid` is +`OSINT_GOTHAM_CLASSID`, which is `NodeGuid::CLASSID_OSINT_V3` (`0x0701_1000`) +under the default `guid-v3-tail` and only falls back to the V1 +`NodeGuid::CLASSID_OSINT` (`0x0700_0000`) with the feature off. (2) +`project_snapshot` filters `r.key.classid() == domain.classid` — the codex-P1 +guard that stops a mixed-class board leaking other domains' rows. (3) The +`graph_table` fixture minted its rows with the V1 constant spelled literally. +So in a DEFAULT build the filter matched **zero of two rows**, and the test +compared 0 against 4. + +**The second link, which the first one hides.** Fixing only the classid is not +enough: once the classid is V3, `family_of` dispatches through `family_v2()`, +so a `NodeGuid::new` key (V1 tail, `family:u24` at bytes 10..13) would carry the +family in the wrong bits even after the filter passed. The fixture had to move +to `mint_for(classid_read_mode(c).tail_variant, …)` — which is what the CANON +already requires of every new mint, and exactly the `ISS-V1-TAIL-RESIDUE` class. +A fixture that reads BOTH the classid and the tail variant off the domain is +correct under either feature setting; one that encodes either is a time bomb. + +**Why it survived.** Not a DataFusion-54 regression — `num_rows() == 0` is +decided before DataFusion is involved. It is V1→V3 migration residue that no +gate ran: the failing pair only surfaces with a feature combination outside the +default sweep. This is the SECOND instance this session of the same shape +(the first: `vsa_udfs` / `transcode::ontology_table` returning clippy EXIT=0 +while never compiling, both behind `query` / `query-lite`). The generalization +worth keeping: **a feature-gated module is not covered by a green run that +never compiled it, and a feature-CONDITIONAL constant is not pinned by a test +that spells one of its branches.** + +**Fixed:** `crates/lance-graph-callcenter/src/graph_table.rs` — 211/211 green +under `--features query`. **Not fixed:** nothing yet runs that feature in CI, so +the coverage hole that hid this is still open. + ## E-LANCE-IS-UPSTREAM-AUTHORITATIVE-1 (2026-08-05, operator-ruled — corrects a P0 in `CLAUDE.md`) **The lance family is consumed from crates.io upstream and NEVER from a fork — diff --git a/crates/lance-graph-callcenter/src/graph_table.rs b/crates/lance-graph-callcenter/src/graph_table.rs index 81285fc5e..e17823c9f 100644 --- a/crates/lance-graph-callcenter/src/graph_table.rs +++ b/crates/lance-graph-callcenter/src/graph_table.rs @@ -115,10 +115,37 @@ pub fn register_graph( #[cfg(test)] mod tests { use super::*; - use lance_graph_contract::canonical_node::{EdgeBlock, NodeRow}; + use lance_graph_contract::canonical_node::{classid_read_mode, EdgeBlock, NodeRow}; use lance_graph_contract::soa_graph::{project_snapshot, OSINT_GOTHAM}; use lance_graph_contract::NodeGuid; + /// Mint an OSINT member key the way the CANON requires: through + /// `mint_for(classid_read_mode(c).tail_variant, …)`, with the classid taken + /// from the DOMAIN rather than hardcoded. + /// + /// Both halves matter and both were wrong here before. `OSINT_GOTHAM.classid` + /// is `CLASSID_OSINT_V3` under the default `guid-v3-tail` and only falls back + /// to the V1 `CLASSID_OSINT` with the feature off; pinning the V1 constant + /// made `project_snapshot`'s `classid == domain.classid` filter match ZERO + /// rows in a default build, so every assertion below ran against an empty + /// snapshot. And once the classid is V3, `family_of` reads the tail through + /// `family_v2()` — so a `NodeGuid::new` (V1 tail) key would carry the family + /// in the wrong bits even after the filter passed. Reading both the classid + /// and the tail variant off the domain keeps the fixture correct under either + /// feature setting instead of encoding one of them. + fn osint_key(heel: u16, family: u32, identity: u32) -> NodeGuid { + NodeGuid::mint_for( + classid_read_mode(OSINT_GOTHAM.classid).tail_variant, + OSINT_GOTHAM.classid, + heel, + 0, + 0, + 0, + family, + identity, + ) + } + /// Two OSINT members in families 0xA, 0xB; the family-0xA member carries an /// out-of-family adapter byte 0x0B → family 0xB. project_snapshot → /// GraphSnapshot → arrow tables. End-to-end head → DataFusion. @@ -127,12 +154,12 @@ mod tests { a_edges.out_family[0] = 0x0B; let rows = [ NodeRow { - key: NodeGuid::new(NodeGuid::CLASSID_OSINT, 1, 0, 0, 0xA, 1), + key: osint_key(1, 0xA, 1), edges: a_edges, value: [0u8; 480], }, NodeRow { - key: NodeGuid::new(NodeGuid::CLASSID_OSINT, 2, 0, 0, 0xB, 1), + key: osint_key(2, 0xB, 1), edges: EdgeBlock::default(), value: [0u8; 480], },