diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 7554dd36..1ee3977f 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 81285fc5..e17823c9 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], },