From a71423486e44d887f70e712f4d0ee24c0973097b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 01:39:52 +0000 Subject: [PATCH 1/5] =?UTF-8?q?facet:=20probe=20the=20per-axis=20LCP=20?= =?UTF-8?q?=E2=80=94=20four=20arms,=20one=20harness,=20oracle=20first?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entry (16) E-FORMAT-SLOT-FOLD-IS-THE-SAME-OP-AS-THE-VL-DESCENT-1 records "loop 12.5 ns -> masked readout 5.8 ns" on 64K random pairs, and that number retired the [u8; 6] chain fold for the masked u128 shared_axis. No harness for it is committed anywhere in this tree; the sibling ndarray number in the same entry names its probe by path, and the entry's status line claims MEASURED only for the ndarray side. So the comparison was not reproducible. This makes it so. Four arms, same inputs, both axes: A the retired by-value chain fold, B a by-ref as_bytes() PEEK packed to u64, C the shipped hi_distance/lo_distance, D B with the loads shared across axes. Every arm is cross-checked against the shipped API on every workload before any timing; the shared-prefix depth knob is asserted to bind; arm A's prefix-length dependence is asserted (it is the only arm whose work varies with depth); black_box guards elision; arms are inline(never) so cargo asm / objdump can find them. Measured here (release, 64K pairs, min of 7, ns/op both axes): workload A chain_loop B peek_u64 C masked_u128 D peek_both random 1.72 4.75 3.64 5.18 depth 0 1.66 4.60 4.68 4.70 depth 5 3.50 4.88 3.56 4.50 identical 3.30 4.46 3.73 4.49 The ordering is inverted against the record, and the disassembly says why: arm A compiles to constant-offset byte loads issued lazily with early exit (movzbl 0x5(%rdi) / cmp 0x5(%rsi),%al / jne) -- LLVM never materialises the [u8; 6] and never gathers. Arm C performs those same byte loads and then reassembles them (shl/or), materialises the mask (movabs $0xff00ff00ff00ff00), splits trailing_zeros across two tzcnt with a cmove, and applies the offset correction. It is strictly more work from identical starting loads. So the "the gather dominated" premise describes code that is not generated. One machine, one toolchain; what the recorded 12.5 ns measured is still unknown, because its harness does not exist. Measurement only -- no verdict, no code change. shared_axis is untouched and its differential test stands. Board correction on entry (16), and whether to revert shared_axis, are open decisions; this commit must NOT be opened as a PR without the EPIPHANIES strike-and-append in the same commit (board-hygiene rule). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d --- .../examples/facet_axis_lcp_probe.rs | 375 ++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs diff --git a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs new file mode 100644 index 000000000..c1f71a524 --- /dev/null +++ b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs @@ -0,0 +1,375 @@ +//! Four-arm probe for the per-axis facet LCP — does the PEEK shape or the +//! masked single-register shape actually win, and at which workload? +//! +//! ## Why this exists +//! +//! `EPIPHANIES.md` (16) `E-FORMAT-SLOT-FOLD-IS-THE-SAME-OP-AS-THE-VL-DESCENT-1` +//! records *"loop 12.5 ns → masked readout 5.8 ns for both axes"* on 64K random +//! pairs, and that number retired the `[u8; 6]` chain fold in favour of the +//! masked `u128` readout (`shared_axis`, `facet.rs`). **No harness for that +//! number is committed anywhere in this tree** — the sibling ndarray number in +//! the same entry names `examples/ternlogq_tail_descent_probe.rs`, this one +//! names nothing, and the entry's own status line says MEASURED for the ndarray +//! side and only SHIPPED for the contract side. So the comparison is not +//! reproducible and not falsifiable as recorded. This probe makes it both. +//! +//! ## The two open questions +//! +//! 1. **Was the baseline the fold, or the fold compiled badly?** A `[u8; 6]` +//! pick off a `repr(C, align(16))` struct is six constant-offset byte reads — +//! a PEEK. That is nearly free *when the bytes have addresses* (in memory, or +//! in an xmm register a shuffle can index). Hoisted into a GPR pair it has no +//! byte addressing and the pick degrades to scalar shift-and-mask. Arms A and +//! B differ in exactly that: A takes the facet **by value** (`hi_chain()` +//! consumes `self`), B reads **through `as_bytes()`** (the documented +//! reinterpret no-op) at constant offsets. +//! 2. **Was the workload representative?** Uniformly random pairs diverge at +//! tier 0 almost always, which is the *shortest* possible prefix. An LCP is +//! asked about near neighbours, where the prefix is long. Every arm is +//! therefore measured at each shared-prefix depth 0..=6, not just random. +//! +//! ## Arms (all four compute the same two numbers: shared hi- and lo-prefix) +//! +//! | arm | shape | readout | +//! |---|---|---| +//! | A `chain_loop` | by-value `hi_chain()`/`lo_chain()` + byte loop — the shape #1244 retired | early-exit compare | +//! | B `peek_u64` | `as_bytes()` + six constant-offset reads packed to `u64` | `xor`, `tzcnt`, `>> 3` — contiguous, **no mask, no −32** | +//! | C `masked_u128` | the shipped `hi_distance`/`lo_distance` | `xor`, `& AXIS`, `tzcnt`, `− 32`, `/ 16` | +//! | D `peek_both` | one `as_bytes()` pair, both axes from the same reads | as B, shared loads | +//! +//! D exists because A/B/C are each called twice when both axes are wanted, and +//! the recorded 5.8 ns for both axes (2.9 ns each) shows zero sharing. +//! +//! ## Falsifiability +//! +//! * **Oracle first.** Every arm is cross-checked against every other arm AND +//! against the shipped `hi_distance`/`lo_distance` on every generated pair, +//! before any timing. A mismatch aborts with the pair printed — no numbers are +//! reported from a wrong arm. +//! * **Anti-vacuity on the workload.** The per-depth rows prove the depth knob +//! binds: arm A's cost must rise with depth (its loop runs longer) or the +//! generator is not actually producing longer prefixes. That is asserted. +//! * **Dead-code guard.** Inputs and the accumulated checksum go through +//! `black_box`; the checksum is printed so the loop cannot be elided. +//! * **Arms are `#[inline(never)]`** so `cargo asm` can find them. Call overhead +//! is therefore included, identically, in all four — it does not move the +//! comparison, and it is what makes the asm question answerable: +//! `cargo asm --example facet_axis_lcp_probe ` on A vs B is expected to +//! show scalar shift/mask for A and byte loads (or a shuffle) for B. +//! +//! This probe measures; it rules nothing. Whatever it prints is a measurement on +//! one machine and one toolchain, and belongs in the board entry as such. +//! +//! ## Run +//! +//! ```text +//! cargo run --release --example facet_axis_lcp_probe -p lance-graph-contract +//! ``` + +use lance_graph_contract::facet::FacetCascade; +use std::hint::black_box; +use std::time::Instant; + +// ───────────────────────────────────────────────────────────────────────────── +// Deterministic input generation (SplitMix64, the workspace's probe seed). +// ───────────────────────────────────────────────────────────────────────────── + +struct SplitMix64(u64); + +impl SplitMix64 { + fn next_u64(&mut self) -> u64 { + self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = self.0; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + + fn fill16(&mut self) -> [u8; 16] { + let (a, b) = (self.next_u64().to_le_bytes(), self.next_u64().to_le_bytes()); + let mut out = [0u8; 16]; + out[..8].copy_from_slice(&a); + out[8..].copy_from_slice(&b); + out + } +} + +/// What kind of pair to generate. +#[derive(Clone, Copy)] +enum Workload { + /// Two independent random facets — both axes diverge at tier 0 with + /// probability (1 - 2^-8) ≈ 0.996. This is what the recorded 12.5/5.8 used. + Random, + /// `b` equals `a` except one byte flipped at tier `d` on BOTH axes, so the + /// shared prefix is exactly `d` on each. `d == 6` ⇒ identical pair. + Depth(u8), +} + +impl Workload { + fn label(self) -> String { + match self { + Workload::Random => "random".to_string(), + Workload::Depth(6) => "identical".to_string(), + Workload::Depth(d) => format!("depth {d}"), + } + } +} + +fn make_pairs(w: Workload, n: usize, rng: &mut SplitMix64) -> Vec<(FacetCascade, FacetCascade)> { + (0..n) + .map(|_| { + let ba = rng.fill16(); + let bb = match w { + Workload::Random => rng.fill16(), + Workload::Depth(d) => { + let mut bb = ba; + if d < 6 { + let t = d as usize; + bb[4 + 2 * t] ^= 0x80; // lo byte of tier d + bb[5 + 2 * t] ^= 0x80; // hi byte of tier d + } + bb + } + }; + (FacetCascade::from_bytes(&ba), FacetCascade::from_bytes(&bb)) + }) + .collect() +} + +// ───────────────────────────────────────────────────────────────────────────── +// The four arms. Each returns (shared_hi, shared_lo), both 0..=6. +// ───────────────────────────────────────────────────────────────────────────── + +/// **Arm A** — the `[u8; 6]` chain fold that #1244 retired, verbatim: `hi_chain` +/// takes `self` by value, so the facet is copied out of memory before the pick. +#[inline(never)] +fn arm_a_chain_loop(a: &FacetCascade, b: &FacetCascade) -> (u8, u8) { + fn looped(x: [u8; 6], y: [u8; 6]) -> u8 { + let mut n = 0u8; + while (n as usize) < 6 && x[n as usize] == y[n as usize] { + n += 1; + } + n + } + ( + looped(a.hi_chain(), b.hi_chain()), + looped(a.lo_chain(), b.lo_chain()), + ) +} + +/// Pack one axis's six bytes, coarse→fine, into the low 48 bits of a `u64`. +/// `axis_off` is 1 for `hi` (bytes 5,7,…,15), 0 for `lo` (bytes 4,6,…,14). +#[inline(always)] +fn peek_axis(bytes: &[u8; 16], axis_off: usize) -> u64 { + let mut v = 0u64; + let mut t = 0; + while t < 6 { + v |= (bytes[4 + 2 * t + axis_off] as u64) << (8 * t); + t += 1; + } + v +} + +/// Shared prefix of two packed axes: contiguous bytes, so `tz >> 3` with no +/// mask and no offset correction — the classid was never picked up. +#[inline(always)] +fn shared_packed(x: u64) -> u8 { + if x == 0 { + 6 + } else { + (x.trailing_zeros() >> 3) as u8 + } +} + +/// **Arm B** — the PEEK shape: read through `as_bytes()` (the documented +/// reinterpret no-op) at constant offsets, pack contiguous, one `tzcnt` each. +#[inline(never)] +fn arm_b_peek_u64(a: &FacetCascade, b: &FacetCascade) -> (u8, u8) { + let (pa, pb) = (a.as_bytes(), b.as_bytes()); + ( + shared_packed(peek_axis(pa, 1) ^ peek_axis(pb, 1)), + shared_packed(peek_axis(pa, 0) ^ peek_axis(pb, 0)), + ) +} + +/// **Arm C** — the shipped masked `u128` readout, through the public API. +#[inline(never)] +fn arm_c_masked_u128(a: &FacetCascade, b: &FacetCascade) -> (u8, u8) { + (6 - a.hi_distance(*b), 6 - a.lo_distance(*b)) +} + +/// **Arm D** — arm B with the two `as_bytes()` reads shared across both axes, +/// which is what a caller wanting locality on both hierarchies actually needs. +#[inline(never)] +fn arm_d_peek_both(a: &FacetCascade, b: &FacetCascade) -> (u8, u8) { + let (pa, pb) = (a.as_bytes(), b.as_bytes()); + let (mut h, mut l) = (0u64, 0u64); + let mut t = 0; + while t < 6 { + let (i_lo, i_hi) = (4 + 2 * t, 5 + 2 * t); + h |= ((pa[i_hi] ^ pb[i_hi]) as u64) << (8 * t); + l |= ((pa[i_lo] ^ pb[i_lo]) as u64) << (8 * t); + t += 1; + } + (shared_packed(h), shared_packed(l)) +} + +type Arm = (&'static str, fn(&FacetCascade, &FacetCascade) -> (u8, u8)); + +const ARMS: [Arm; 4] = [ + ("A chain_loop", arm_a_chain_loop), + ("B peek_u64", arm_b_peek_u64), + ("C masked_u128", arm_c_masked_u128), + ("D peek_both", arm_d_peek_both), +]; + +// ───────────────────────────────────────────────────────────────────────────── +// Oracle — every arm against every other arm, and against the shipped API. +// ───────────────────────────────────────────────────────────────────────────── + +fn check_arms_agree(pairs: &[(FacetCascade, FacetCascade)], what: &str) { + for (a, b) in pairs { + let shipped = (6 - a.hi_distance(*b), 6 - a.lo_distance(*b)); + for (name, f) in ARMS { + let got = f(a, b); + assert_eq!( + got, + shipped, + "ORACLE MISMATCH in {what}: arm {name} gave {got:?}, shipped \ + hi_distance/lo_distance gave {shipped:?}\n a = {:02x?}\n b = {:02x?}", + a.as_bytes(), + b.as_bytes(), + ); + } + } +} + +/// The depth knob must bind: a pair generated at depth `d` must actually have +/// shared prefix `d` on both axes. Without this the per-depth rows are theatre. +fn check_depth_knob_binds(rng: &mut SplitMix64) { + for d in 0..=6u8 { + let pairs = make_pairs(Workload::Depth(d), 64, rng); + for (a, b) in &pairs { + let got = arm_b_peek_u64(a, b); + assert_eq!( + got, + (d, d), + "depth knob does not bind: asked for depth {d}, measured {got:?}" + ); + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Timing +// ───────────────────────────────────────────────────────────────────────────── + +/// Min-of-`runs` ns/op. Min is the right statistic here: the true cost is a +/// floor and every perturbation (interrupt, migration, frequency dip) only adds. +fn time_arm( + f: fn(&FacetCascade, &FacetCascade) -> (u8, u8), + pairs: &[(FacetCascade, FacetCascade)], + runs: usize, +) -> f64 { + // Warm up: caches, branch predictors, frequency. + let mut sink = 0u64; + for _ in 0..2 { + for (a, b) in pairs { + let (h, l) = f(black_box(a), black_box(b)); + sink = sink.wrapping_add(h as u64).wrapping_add(l as u64); + } + } + black_box(sink); + + let mut best = f64::MAX; + for _ in 0..runs { + let mut acc = 0u64; + let t0 = Instant::now(); + for (a, b) in pairs { + let (h, l) = f(black_box(a), black_box(b)); + acc = acc.wrapping_add(h as u64).wrapping_add(l as u64); + } + let ns = t0.elapsed().as_nanos() as f64 / pairs.len() as f64; + black_box(acc); + if ns < best { + best = ns; + } + } + best +} + +fn main() { + const N: usize = 65_536; // the entry's "64K random pairs" + const RUNS: usize = 7; + + let mut rng = SplitMix64(0x9E37_79B9_7F4A_7C15); + + println!("facet per-axis LCP — four arms, {N} pairs, min of {RUNS} runs, ns/op (both axes)"); + println!("target: {}", std::env::var("RUSTFLAGS").unwrap_or_default()); + println!(); + + // Gates before numbers. + check_depth_knob_binds(&mut rng); + println!("depth knob binds (0..=6 verified on 64 pairs each)"); + + let workloads = [ + Workload::Random, + Workload::Depth(0), + Workload::Depth(1), + Workload::Depth(2), + Workload::Depth(3), + Workload::Depth(4), + Workload::Depth(5), + Workload::Depth(6), + ]; + + let mut rows: Vec<(String, Vec)> = Vec::new(); + let mut arm_a_by_depth: Vec = Vec::new(); + + for w in workloads { + let pairs = make_pairs(w, N, &mut rng); + check_arms_agree(&pairs[..256.min(pairs.len())], &w.label()); + let times: Vec = ARMS.iter().map(|(_, f)| time_arm(*f, &pairs, RUNS)).collect(); + if let Workload::Depth(_) = w { + arm_a_by_depth.push(times[0]); + } + rows.push((w.label(), times)); + } + println!("oracle: all 4 arms agree with the shipped API on every workload"); + println!(); + + // Table. + print!("{:<12}", "workload"); + for (name, _) in ARMS { + print!("{name:>16}"); + } + println!(); + println!("{}", "-".repeat(12 + 16 * ARMS.len())); + for (label, times) in &rows { + print!("{label:<12}"); + for t in times { + print!("{t:>16.2}"); + } + println!(); + } + println!(); + + // Anti-vacuity: arm A is the only arm whose work depends on prefix length, + // so its cost must rise from depth 0 to depth 5. If it does not, the + // early-exit loop is not being compiled as one and the A/B contrast is not + // measuring what this probe claims. + let (a0, a5) = (arm_a_by_depth[0], arm_a_by_depth[5]); + println!( + "arm A depth-0 {a0:.2} ns → depth-5 {a5:.2} ns ({:+.1}%) — the early-exit loop {}", + (a5 - a0) / a0 * 100.0, + if a5 > a0 * 1.05 { + "does depend on prefix length, as it must" + } else { + "does NOT depend on prefix length — READ THE ASM before trusting any row above" + } + ); + println!(); + println!("next: cargo asm --example facet_axis_lcp_probe arm_a_chain_loop"); + println!(" cargo asm --example facet_axis_lcp_probe arm_b_peek_u64"); + println!(" A scalar shift/mask vs B byte-loads is the whole question."); +} From b08db9b702854c26dd8218723ce86d1d66567c12 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 02:03:41 +0000 Subject: [PATCH 2/5] facet: revert the axis LCP to the PEEK chain; name the three carriers #1244 moved the per-axis LCP from an early-exit byte chain to a masked single-register readout, citing EPIPHANIES (16)'s "loop 12.5 ns -> masked 5.8 ns". On this carrier that ordering is inverted. Measured by the in-tree four-arm probe, 64K pairs, min of 7, both axes, ns/op: workload A chain PEEK B pack-u64 C masked u128 D shared loads random 1.72 4.75 3.64 5.18 depth 0 1.66 4.60 4.68 4.70 depth 5 3.50 4.88 3.56 4.50 identical 3.30 4.46 3.73 4.49 A wins at every workload; 2.1x on the entry's own workload. A's depth-0 -> depth-5 slope (+111%) is the anti-vacuity evidence that the early exit is the mechanism. Both pack-to-register arms lose: assembling six addressed bytes costs more than the compare it replaces. That prediction was wrong and the board records it as wrong. The premise "the gather dominated" is refuted by disassembly. LLVM never materializes the [u8; 6]: movzbl 0x5(%rdi),%eax ; tier 0 hi of a cmp 0x5(%rsi),%al ; straight against b's memory jne Arm C issues the same loads, then reassembles them, materializes a mask constant, runs two tzcnt + a cmove, applies the -32 offset correction, and has no early exit. Strictly more work on the same loads. Why it inverted: three carriers share one vocabulary. 1 bit-planes (mailbox_soa identity_plane_at -> &[u64]) -> mask correct 2 nibble path (NiblePath::common_prefix_depth, u64) -> mask NOT DONE 3 facet cascade(FacetCascade, 6x2x8 byte-addressed) -> PEEK this revert Masking wins when the slice is granular; PEEK wins when the slice is addressed. A measurement is a statement about (operation, carrier, workload) -- drop the carrier and it is a slogan. (16)'s 12.5 ns is plausible as a carrier-2-shaped loop, which is what common_prefix_depth still is; what is unsupportable is transferring it to carrier 3. No harness shipped with that number, so what it timed is unknown and the board says so. Changes: - facet.rs: shared_axis -> shared_axis_chain (const, by-ref, early exit). hi_distance/lo_distance take &self/&Self so the compare reaches memory directly; both stay const. - the differential test is REVERSED, not deleted: the masked form becomes masked_axis_oracle under #[cfg(test)], so the test compares shipped against oracle instead of against a restatement of itself. The prior disable-run (swap the hi/lo masks -> fail at "hi flip at tier 0") still holds. - probe arm C made self-contained so it keeps measuring the masked shape. - knowledge/three-prefix-fold-carriers.md: the doctrine, with the law, the per-carrier table, the measurements, what is NOT settled, and a falsifier. - plans/three-carrier-blast-radius-v1.md: four passes, five gates. Read-only; authorizes no code. Explicitly forbids widening FacetTier's u8:u8 to cheapen a mask -- the fold adapts to the layout, never the reverse. - ISSUES: ISS-NIBLEPATH-FOLD-IS-CARRIER-2-UNMASKED -- the real opportunity, filed as CONJECTURE and gated on its own probe, precisely because landing it on (16)'s number would be the same defect again. - EPIPHANIES (17) + a partial strike appended to (16). (16)'s -f naming, shared_prefix_tiles, the -32 note, the 23-site count and the whole ndarray ternlogq descent half stand unchanged -- that half is a different carrier, measured on its own. hi_distance/lo_distance have zero callers in this tree, so the revert is near-zero-risk and neither form's performance was observed by anything shipping. 1425 contract tests green; clippy clean. cargo check on lance-graph fails in lance-encoding's build script (protoc absent in this container) -- pre-existing and untouched by a zero-dep crate. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d --- .claude/board/EPIPHANIES.md | 108 ++++++++++++ .claude/board/INTEGRATION_PLANS.md | 17 ++ .claude/board/ISSUES.md | 34 ++++ .claude/board/STATUS_BOARD.md | 14 ++ .../knowledge/three-prefix-fold-carriers.md | 155 ++++++++++++++++++ .../plans/three-carrier-blast-radius-v1.md | 128 +++++++++++++++ .../examples/facet_axis_lcp_probe.rs | 27 ++- crates/lance-graph-contract/src/facet.rs | 125 ++++++++------ 8 files changed, 553 insertions(+), 55 deletions(-) create mode 100644 .claude/knowledge/three-prefix-fold-carriers.md create mode 100644 .claude/plans/three-carrier-blast-radius-v1.md diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 621669000..62d9fe646 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,97 @@ +## 2026-09-17 (17) — E-THREE-CARRIERS-THREE-FOLDS-1 — one workspace holds THREE prefix-fold carriers; entry (16) measured a real win on one of them and shipped it into another, where it is 2.1× SLOWER + +**Status:** MEASURED (`crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs`, +four arms, 64K pairs, min of 7, oracle-first, in-tree and reproducible) + +SHIPPED (the revert, same PR). The carrier-2 opportunity is CONJECTURE — named, +not measured. +**Confidence:** HIGH on the numbers and the disassembly. The three-carrier +taxonomy is a reading of the tree, offered as the strongest available +explanation of (16)'s inversion, not as a proof of what (16) originally timed. + +### The correction + +Entry (16) records *"loop 12.5 ns → masked readout 5.8 ns"* for the facet +per-axis LCP. **On the facet carrier that ordering is inverted.** Measured +2026-09-17 on its own 64K-random workload, both axes, ns/op: + +| workload | A chain fold (retired by #1244) | C masked `u128` (shipped by #1244) | +|---|---|---| +| random | **1.72** | 3.64 | +| depth 0 | **1.66** | 4.68 | +| depth 5 | **3.50** | 3.56 | +| identical | **3.30** | 3.73 | + +Arm A wins at every workload; on the entry's own workload by **2.1×**. Arm A +depth-0 1.66 → depth-5 3.50 (+111%) — the early exit is real and is where the +win comes from. Two further arms (a pack-to-`u64` PEEK, and a shared-load +variant) were slower than both; the prediction that packing would win was +wrong and is recorded as wrong. + +**⊘ Struck from (16): "the gather dominated".** The disassembly refutes the +premise. LLVM never materializes the `[u8; 6]` and never gathers: + +``` +movzbl 0x5(%rdi),%eax ; PEEK hi[0] of a +cmp 0x5(%rsi),%al ; compare straight against b's memory +jne ; done — one tier compared +movzbl 0x7(%rdi),%r8d ; only now PEEK hi[1] +``` + +Arm C performs the *same* byte loads, then pays to reassemble them +(`shl`/`or`), materialize `movabs $0xff00ff00ff00ff00`, run two `tzcnt` + a +`cmove`, and apply the `−32` correction — and cannot exit early. It is strictly +more work on the same loads. + +**What survives (16) unchanged:** the `-f` naming; `shared_prefix_tiles` (a +genuine whole-register `xor`+`tzcnt`, different op, untouched); the entire +ndarray `ternlogq` descent half (5–8× over padding — measured on its own +carrier and not in question); the `−32` correction note; the 23-tail-site count. + +### Why it inverted — three carriers, three folds + +The same words ("prefix fold", "LCP", "shared depth") name three different +operations on three different carriers. The op that is optimal on one is +pessimal on the next, and nothing in the vocabulary flags the crossing. + +| # | carrier | shape | right fold | state | +|---|---|---|---|---| +| 1 | **bit-planes** — `mailbox_soa.rs` `identity_plane_at → &[u64]`, `N × WORDS_PER_FP` | sub-byte, no byte addresses | **mask / popcount** (`DistanceMeans::Hamming`) | correct, untouched | +| 2 | **nibble path** — `NiblePath` (`hhtl.rs:251`), packed `u64`, 16 nibbles | sub-byte, packed into one register | **mask** — `xor` + `leading_zeros() >> 2` | **walks nibble-by-nibble today; the one real opportunity** | +| 3 | **facet cascade** — `FacetCascade`, 6×2×8 bytes at fixed offsets | byte-addressed, offsets known at compile time | **PEEK** — `movzbl` + `cmp` + early exit | reverted to PEEK by this entry | + +The rule, in the operator's formulation (2026-09-17): **masking wins when the +slice is granular; PEEK wins when the slice is addressed.** A fold result is +scoped to its carrier and does not travel. (16)'s 12.5 ns is entirely plausible +as a measurement of a *carrier-2-shaped* loop — a per-step walk against a +packed integer, which is exactly what `common_prefix_depth` still is. What is +not supportable is transferring that conclusion to carrier 3, where the loop +compiles to compares against memory. + +### The opportunity this names (carrier 2 — CONJECTURE, unmeasured) + +`NiblePath::common_prefix_depth` is the fold `mailbox_scan.rs:263` actually +calls for CAKES nearest-ranking. It walks up to 16 nibbles, each step a shift, +an `Option` construct and a two-field compare, to compute what is +`((a.path ^ b.path).leading_zeros() >> 2)` clamped to `min(depth)`. This is the +carrier where (16)'s instinct was right and was never applied. **Gate:** the +same four-arm probe harness, against this carrier, before any rewrite — the +whole point of this entry is that a fold is not portable on argument alone. + +### The generalized rule + +**Before moving a fold, name its carrier.** A measurement is a statement about +(operation, carrier, workload); dropping the carrier makes it a slogan. Any PR +that changes a prefix/LCP/distance fold must state which of the three carriers +it touches and carry a probe on *that* carrier. Sibling of the falsifiability +rule in `CLAUDE.md`: an assertion implied by the code it tests is not a test, +and a measurement transferred off its carrier is not a measurement. + +Doctrine: `.claude/knowledge/three-prefix-fold-carriers.md`. +Blast radius: `.claude/plans/three-carrier-blast-radius-v1.md`. +Cross-ref: (16) above (struck in part, cited in full); `E-PANCAKES-IS-RADIX-IS-HHTL` +(carrier 2's doctrine); `E-VACUOUS-ASSERTION-IS-THE-HOUSE-STYLE-1` (the +differential test was inverted in the revert so it stays falsifiable). + ## 2026-09-16 (16) — E-FORMAT-SLOT-FOLD-IS-THE-SAME-OP-AS-THE-VL-DESCENT-1 — `"{0}{1}" -f hi,lo`: the register is a template with fixed arity, and both the facet LCP and the ternlogq tail are "pick the template whose arity matches the arguments, never pad them" **Status:** MEASURED on the ndarray side (the descent probe, ndarray @@ -79,6 +173,20 @@ carried):** `pack` follow-up would retire all 23, not 12; whether the Morton-shift tail fits the same helper is part of that follow-up, not settled here. +**⊘ PARTIAL STRIKE 2026-09-17 (appended; see entry (17) +`E-THREE-CARRIERS-THREE-FOLDS-1` above).** The facet half of this entry is +inverted on its own carrier: measured four ways in-tree, the retired chain fold +is **1.72 ns** and the shipped masked readout **3.64 ns** on this entry's own +64K-random workload — the opposite ordering, 2.1×. The premise *"the gather +dominated"* is refuted by disassembly: LLVM never materializes the `[u8; 6]`. +`shared_axis` is reverted to the chain fold; the masked form is retained as its +test oracle. Everything else here stands unchanged — the `-f` naming, +`shared_prefix_tiles`, the `−32` note, the 23-site count, and the whole ndarray +`ternlogq` descent half (a different carrier, measured on its own and not in +question). What the 12.5 ns actually timed is unknown — no harness was +committed with it; entry (17) gives the strongest available account (a +carrier-2-shaped loop) and labels it as such. + ## 2026-09-16 (15) — E-THE-SPINE-IS-WHATEVER-THE-READER-ALREADY-HAS-AN-ADDRESS-FOR-1 — the operator's quack redirect, and the four errors of one session that all substituted an address for the thing **Status:** OPERATOR-RULED (the redirect, verbatim below) + MEASURED (the census diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index c9558b00b..4cf9916f5 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,20 @@ +## 2026-09-17 (1) — three-carrier-blast-radius-v1 — how far do the three prefix-fold carriers reach, and where do they touch → `.claude/plans/three-carrier-blast-radius-v1.md` + +**Status:** PROPOSAL. Read-only census + seam map; gates only, no code +authorized. Arose from `E-THREE-CARRIERS-THREE-FOLDS-1`: an optimization +measured on the bit-plane/nibble side was shipped into the byte-addressed facet +side, where it is 2.1× slower. The defect was a **scope error, not a bad +optimization** — and no gate the workspace runs can see one, because the three +carriers share a vocabulary ("prefix fold", "LCP", "shared depth"). + +Four passes (type census · fold census · **seams** · outward radius), five +gates (G-EXH / G-FOLD / G-SEAM / G-PROBE / G-ZERO). Pass 3 is the highest-value +one: `mailbox_scan.rs` already consumes two carriers, which is the exact +confusion surface. Explicitly NOT authorized: any carrier-2 rewrite before its +own probe, any carrier-1 change, and any widening of `FacetTier`'s `u8:u8` to +make a mask cheaper (`E-V3-FACET-4-PLUS-12` — the fold adapts to the layout, +never the reverse). + ## 2026-09-16 (1) — lance-graph-as-the-modelgraph-v1 — the ModelGraph stops being a transient Rust object and becomes addressed rows in the V3 SoA → `.claude/plans/lance-graph-as-the-modelgraph-v1.md` **Status:** PROPOSAL (operator-set endgame: *"endgame should be to wire diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 5f26a3ce0..219a02558 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,3 +1,37 @@ +## ISS-NIBLEPATH-FOLD-IS-CARRIER-2-UNMASKED (2026-09-17) — OPEN, the one prefix fold that genuinely wants a mask, and does not have one + +`NiblePath::common_prefix_depth` (`crates/lance-graph-contract/src/hhtl.rs:251`) is +carrier 2 per `.claude/knowledge/three-prefix-fold-carriers.md`: 16 nibbles packed into +one `u64`, sub-byte, no byte addresses. It walks depth-by-depth, each step a shift, an +`Option` construct and a two-field compare: + +```rust +while d < max { + match (self.prefix(next), other.prefix(next)) { + (Some(a), Some(b)) if a.path == b.path && a.depth == b.depth => d = next, + _ => break, + } +} +``` + +Up to 16 iterations for what is `((self.path ^ other.path).leading_zeros() >> 2)` clamped +to `min(self.depth, other.depth)` — root-first, so `leading_zeros`, not `trailing_zeros`. + +**Live, not hypothetical:** `crates/lance-graph/src/graph/mailbox_scan.rs:263` calls it for +CAKES nearest-ranking (`E-PANCAKES-IS-RADIX-IS-HHTL`). Two further local copies of the +same fold shape exist over `HhtlKey` in `crates/perturbation-sim/examples/` +(`outage_over_hhtl_hops.rs:76`, `basin_placement_learning.rs:76`) and should be +classified with it. + +**This is the opportunity the 2026-09-16 sweep was reaching for and applied to the wrong +carrier** (`E-THREE-CARRIERS-THREE-FOLDS-1`). Which is exactly why it must NOT be rewritten +on that entry's authority: **CONJECTURE until probed on this carrier.** Gate: the four-arm +harness from `examples/facet_axis_lcp_probe.rs`, re-armed for `NiblePath`, with workloads +covering `EMPTY`, unequal depths, ancestor pairs, and full-16 agreement — the depth clamp +and the edge cases are where a fast prefix fold gets quietly wrong. + +Sequencing and the gates: `.claude/plans/three-carrier-blast-radius-v1.md` steps 4–5. + ## ISS-LANCEDB-038-NEEDS-REMOTE-TO-COMPILE (2026-09-15) — OPEN, upstream bug, our `lancedb-sdk` feature does not build `lancedb 0.38.0` does not compile with its own default feature set. Measured, reading the diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index b7fab254b..29f8ac598 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -1,3 +1,17 @@ +## three-carrier prefix folds (D-ids minted 2026-09-17, plan `.claude/plans/three-carrier-blast-radius-v1.md`) + +Arose from the −32 offset correction sweep (#1244) and the operator's challenge to it. +Doctrine: `.claude/knowledge/three-prefix-fold-carriers.md`. Board: +`E-THREE-CARRIERS-THREE-FOLDS-1`. + +| D-id | scope | status | gate / falsifier | +|---|---|---|---| +| D-3CF-1 | probe the facet per-axis LCP four ways and settle which fold the byte-addressed carrier wants | **Shipped.** `examples/facet_axis_lcp_probe.rs`, 64K pairs, min-of-7, oracle-first. Chain PEEK **1.72 ns** vs masked `u128` **3.64 ns** on random; PEEK wins at every workload; the two pack-to-register arms are slowest | all four arms must agree with the shipped API before timing; the depth knob must bind (A: 1.66 → 3.50 ns over depth 0..5, +111%) — a flat slope would mean the early exit is not the mechanism | +| D-3CF-2 | revert `shared_axis` to the chain fold; keep the differential test falsifiable | **Shipped.** Masked form retained as `masked_axis_oracle` under `#[cfg(test)]` — direction reversed so the test compares shipped-vs-oracle, not shipped-vs-itself | post-revert re-run reproduces the ordering (A 1.76 / C 4.48); 1354 contract tests green; the prior disable-run (swap hi/lo masks ⇒ fail at `hi flip at tier 0`) still holds on the oracle | +| D-3CF-3 | name the three carriers and the law that separates them | **Shipped.** `.claude/knowledge/three-prefix-fold-carriers.md` — bit-planes / nibble path / facet cascade; *masking wins when the slice is granular, PEEK wins when the slice is addressed* | falsified by a fourth carrier, or by one carrier consuming another's fold. Two greps must stay empty: `FacetCascade` in `mailbox_soa.rs`; `trailing_zeros\|leading_zeros` in `hhtl.rs` | +| D-3CF-4 | carrier-2 (`NiblePath::common_prefix_depth`) masked rewrite | **Queued — CONJECTURE, deliberately unbuilt.** `ISS-NIBLEPATH-FOLD-IS-CARRIER-2-UNMASKED`. This is the carrier the 2026-09-16 instinct was right about | must be probed on ITS OWN carrier first (G-PROBE). Landing it on entry (16)'s number would be the original defect recurring | +| D-3CF-5 | blast-radius census of all three carriers + their seams | **Queued.** Plan above, passes 1–4, read-only | G-EXH (walk, not a chosen file list) · G-FOLD (every fold site carries a carrier) · G-SEAM (owner-in / owner-out / cost) · G-ZERO (a "no callers" claim verified by opening, never by an empty grep) | + ## elk/ro factfinder — the meet, the horizon and their generality (D-ids minted 2026-09-15, probe `.claude/probes/elk-generality-v1/`) Arose from the EWA 12-hop fanout question ("would MQ offer a cheap gating"). No plan diff --git a/.claude/knowledge/three-prefix-fold-carriers.md b/.claude/knowledge/three-prefix-fold-carriers.md new file mode 100644 index 000000000..6abdd605b --- /dev/null +++ b/.claude/knowledge/three-prefix-fold-carriers.md @@ -0,0 +1,155 @@ +# Three prefix-fold carriers — name the carrier before you move the fold + +> **READ BY:** any session touching a prefix / LCP / shared-depth / distance +> fold, any `trailing_zeros` / `leading_zeros` / `popcount` readout, any +> "optimize this loop into a mask" proposal, `facet.rs`, `hhtl.rs`, +> `mailbox_soa.rs`, `mailbox_scan.rs`, or `ndarray::simd`'s masking ops. +> **MANDATORY** before changing any of the three folds catalogued below. +> +> Born 2026-09-17 from `E-THREE-CARRIERS-THREE-FOLDS-1`: a fold optimization +> measured on one carrier was shipped into another, where it is 2.1× slower. +> Nothing in the shared vocabulary ("prefix fold", "LCP", "shared depth") +> flagged the crossing. + +## §1 The law + +**A measurement is a statement about `(operation, carrier, workload)`.** Drop +the carrier and it becomes a slogan that will be applied where it is false. + +**Masking wins when the slice is GRANULAR. PEEK wins when the slice is +ADDRESSED.** (Operator, 2026-09-17.) Granular = the datum has no byte address +of its own (a bit in a plane, a nibble in a packed word) and must be extracted +arithmetically, so you may as well extract all of them at once. Addressed = the +datum sits at a constant byte offset, so the CPU can load and compare it +directly and stop at the first mismatch. + +**Corollary — PEEK is not a fallback, it is a different instruction sequence.** +A masked readout over an addressed carrier performs *the same loads* as the +PEEK and then pays extra to reassemble them, and it forfeits the early exit. +It is strictly more work, never less. + +## §2 The three carriers + +| # | name | type / site | granularity | stride | fold today | right fold | +|---|---|---|---|---|---|---| +| 1 | **bit-planes** | `MailboxSoA` / `MailboxSoaView::identity_plane_at → &[u64]` (`cognitive-shader-driver/src/mailbox_soa.rs`), `N × WORDS_PER_FP`; consumed by `graph::mailbox_scan::DistanceMeans::Hamming` | 1 bit | 64-bit word | mask + popcount | **mask** ✓ correct | +| 2 | **nibble path** | `NiblePath { path: u64, depth: u8 }` (`lance-graph-contract/src/hhtl.rs:251`), 16 nibbles, `MAX_DEPTH = 16`; consumed by `graph/mailbox_scan.rs:263` for CAKES nearest | 4 bits | 4-bit nibble | **per-nibble walk** | **mask** ✗ opportunity | +| 3 | **facet cascade** | `FacetCascade` (`lance-graph-contract/src/facet.rs`), `repr(C, align(16))`, `classid: u32` + `tiers: [FacetTier { lo: u8, hi: u8 }; 6]` | 8 bits | 16-bit tier, byte-addressed | **PEEK chain** | **PEEK** ✓ correct (since the 2026-09-17 revert) | + +### Carrier 1 — bit-planes + +Sub-byte with no addresses at all: a fingerprint bit has no offset you can name +at compile time. Every operation is necessarily whole-word arithmetic — +`popcount`, `ternlog`, `xor`-then-count. There is no early exit to forfeit +because there is nothing to exit from: Hamming distance needs every word. +**Masking is not merely right here, it is the only shape.** This carrier is +also where `ndarray::simd`'s masking ops and the `ternlogq` tail descent live, +and those results are sound on their own terms. + +### Carrier 2 — the nibble path + +Sub-byte but packed into ONE register. A nibble has no byte address, so +extracting nibble `t` costs a shift and a mask; extracting *all* of them costs +one `xor` and one `leading_zeros`. The shipped fold does the expensive thing 16 +times: + +```rust +// hhtl.rs:251 — as of 2026-09-17 +while d < max { + match (self.prefix(next), other.prefix(next)) { // shift + reconstruct + Option + (Some(a), Some(b)) if a.path == b.path && a.depth == b.depth => d = next, + _ => break, + } +} +``` + +The masked form is `((self.path ^ other.path).leading_zeros() >> 2)` clamped to +`min(self.depth, other.depth)` — root-first, so `leading_zeros`, not +`trailing_zeros`. **This is the one place in the tree where the 2026-09-16 +instinct was correct and was never applied.** It is CONJECTURE until probed: +see §4. + +### Carrier 3 — the facet cascade + +Byte-addressed with compile-time-constant offsets. Tier `t`'s `lo` byte is at +`4 + 2t`, its `hi` at `5 + 2t`. In C64 terms: a **PEEK**. The address is known, +so the load is one instruction and the comparison can happen against the other +facet's memory without either side being assembled into anything: + +``` +movzbl 0x5(%rdi),%eax ; tier 0 hi of a +cmp 0x5(%rsi),%al ; straight against b's memory +jne ; first mismatch ends it +``` + +`[u8; 6]` in the source (`hi_chain` / `lo_chain`) **is a lens, not a +materialization** — LLVM never builds the array. That is what the operator +meant by *"`[u8; 6]` was meant to preserve zero copy"*: it names six addressed +bytes; it does not gather them. + +The masked alternative (`xor` the `u128`, `&` an axis mask, `tzcnt`, subtract +32, divide by 16) issues the same `movzbl`s, then `shl`/`or`s them back +together, materializes a 64-bit mask constant, runs `tzcnt` + `cmove`, and +applies the offset correction — with no early exit. + +**The `−32`** is where the carrier mismatch shows up in the source itself: the +classid's 32 bits sit *below* the tiers in the LE `u128`, so the mask removes +the classid's *bits* but not its *offset*. A correction term whose only job is +to undo a coordinate system the operation didn't want is a carrier smell. + +## §3 Measured (2026-09-17, `examples/facet_axis_lcp_probe.rs`) + +Four arms, 64K pairs, min of 7 runs, both axes, ns/op. All four arms verified +against the shipped API on every workload before timing (oracle-first); the +depth knob is verified to bind 0..=6. + +| workload | A chain PEEK | B pack-to-`u64` PEEK | C masked `u128` | D PEEK, shared loads | +|---|---|---|---|---| +| random | **1.72** | 4.75 | 3.64 | 5.18 | +| depth 0 | **1.66** | 4.60 | 4.68 | 4.70 | +| depth 1 | **1.91** | 5.07 | 3.55 | 4.85 | +| depth 2 | **2.27** | 5.34 | 3.45 | 4.70 | +| depth 3 | **2.50** | 5.14 | 3.56 | 4.61 | +| depth 4 | **2.98** | 4.77 | 3.56 | 5.07 | +| depth 5 | **3.50** | 4.88 | 3.56 | 4.50 | +| identical | **3.30** | 4.46 | 3.73 | 4.49 | + +Re-run post-revert reproduced the ordering and magnitudes (A 1.76 / C 4.48 on +random; run-to-run spread ≈ ±0.9 ns on the masked arms, ≈ ±0.05 on A). + +Reading, including the parts that went against the author's prediction: + +- **A wins everywhere**, and by most at shallow depth — the early exit is the + mechanism. A's own depth-0 → depth-5 slope (+111%) is the anti-vacuity + evidence that the knob binds. +- **B is the slowest arm.** Packing six addressed bytes into a `u64` to then + `tzcnt` them costs twelve shift-or pairs — more than the compare it replaces. + The prediction that B would win was wrong. *Recorded as wrong: a PEEK carrier + does not want its bytes assembled, even into a register it could then mask.* +- **C only catches A at depth 5 / identical**, i.e. exactly where A has no early + exit left to use. That is the honest boundary of the masked form's value on + this carrier, and it is a tie, not a win. + +## §4 What is NOT settled + +- **What (16)'s 12.5 ns measured.** No harness was committed with it. The + carrier-2 account is the strongest available explanation, not a finding. +- **Carrier 2's rewrite.** Unmeasured. `leading_zeros` on a `u64` is one + instruction, but the depth-clamp and the `EMPTY`/ancestor edge cases are + where a fast fold gets subtly wrong. Probe before rewriting. +- **Production callers of carrier 3's axis distances.** `hi_distance` / + `lo_distance` have **zero** callers in this tree (out-of-tree consumers are + not verifiable from here). The revert is therefore near-zero-risk *and* the + performance of either form is currently unobserved by anything shipping. + +## §5 Falsifier for this page + +- The four-arm probe is in-tree, runs in ~2 s, and is the falsifier for §3. If + a future toolchain inverts the ordering, this page is wrong and says so by + failing to reproduce. +- §2's carrier assignment is falsified by finding a *fourth* carrier, or by + finding one of the three consuming another's fold. Both are greps that must + come back empty: `FacetCascade` in `mailbox_soa.rs` (it does not appear); + `trailing_zeros`/`leading_zeros` in `hhtl.rs` (it does not appear). +- The law in §1 is falsified by a measured case where a masked readout beats a + PEEK on a byte-addressed carrier with a live early exit. None is known. diff --git a/.claude/plans/three-carrier-blast-radius-v1.md b/.claude/plans/three-carrier-blast-radius-v1.md new file mode 100644 index 000000000..0593c5d06 --- /dev/null +++ b/.claude/plans/three-carrier-blast-radius-v1.md @@ -0,0 +1,128 @@ +# three-carrier-blast-radius-v1 — how far do the three prefix-fold carriers reach? + +**Status:** PROPOSAL (2026-09-17). Gates only; no code change is authorized by +this document. +**Doctrine:** `.claude/knowledge/three-prefix-fold-carriers.md` +**Board:** `E-THREE-CARRIERS-THREE-FOLDS-1` (EPIPHANIES entry 17) + +## Why a blast-radius pass at all + +The defect that produced this plan was not a bad optimization — arm C is +competently written and correctly tested. It was a **scope error**: a result +true of one carrier was applied to another because the two share a vocabulary. +That failure mode is invisible to every gate the workspace already runs. Clippy +does not know what a carrier is; the differential test passed (both forms +compute the same answer); the board entry was well-formed. **The only thing +that catches it is knowing how far each carrier reaches, and where they touch.** + +So the deliverable is not "a faster fold". It is a **carrier census**: for each +of the three, the exhaustive set of types, folds, call sites, tests, docs and +cross-repo consumers — and, critically, the set of places where one carrier's +output feeds another's input, because those seams are where a future transfer +will happen. + +## Method, in four passes + +Each pass is `grep FINDS, reading DECIDES` (CLAUDE.md P0): grep produces +candidates, every candidate is **opened** before it enters a census row, and a +negative result is never recorded without opening the place the thing would +live. Delegate the sweeps to Sonnet (grindwork: "find every site that X"), +keep the classification on the main thread (accumulation: "is this site +carrier 2 or carrier 3"). + +### Pass 1 — type census (per carrier, exhaustive) + +For each carrier, the type(s) that ARE it, and every constructor / accessor / +`from_*_bytes` / `to_*_bytes` on them. Output: a table of +`type → file:line → carrier → is it a fold, a lens, or a materialization`. + +Seed greps (candidates only): +- C1: `identity_plane_at`, `WORDS_PER_FP`, `IdentityPlane`, `DistanceMeans` +- C2: `NiblePath`, `common_prefix_depth`, `HhtlKey`, `prefix(`, `MAX_DEPTH` +- C3: `FacetCascade`, `FacetTier`, `hi_chain`, `lo_chain`, `shared_prefix_tiles`, + `row_match_mask`, `CascadeShape` + +Known-present trap: `perturbation-sim/examples/{outage_over_hhtl_hops, +basin_placement_learning}.rs` each define a LOCAL `common_prefix_depth` over +`HhtlKey`. Two more carrier-2-shaped folds, neither routed through `NiblePath`. +They must be classified, not skipped as examples. + +### Pass 2 — fold census (the thing that actually moved) + +Every site that computes a prefix, an LCP, a shared depth, a Hamming distance, +or a "how far do these agree" answer — regardless of what it is named. Grep +`trailing_zeros|leading_zeros|count_ones|popcnt|common_prefix|shared_|_distance| +is_ancestor|prefix_depth`, then open each and assign a carrier. + +**Gate G-FOLD:** every row in this census names its carrier. A row that cannot +be assigned is a finding — it means a fourth carrier exists, or that a site +mixes two. + +### Pass 3 — the seams (highest value; do not skip) + +Where does one carrier's output become another's input? Three candidate seams +are already visible and each must be opened and characterized: + +- **S1 — GUID → `NiblePath`.** `NiblePath::from_guid_prefix` / `_v3` fold a + 128-bit GUID (byte-addressed, carrier-3-shaped) into a packed `u64` + (carrier 2). This is a *deliberate* carrier change and is the seam most + likely to invite "the fold is the same on both sides". +- **S2 — `NiblePath` → `mailbox_scan`.** `mailbox_scan.rs:263` calls carrier + 2's fold while `mailbox_soa.rs` supplies carrier 1's planes to the same + scan. **One file consuming two carriers is exactly the confusion surface** + that produced this plan; `identity_plane_at`'s own doc comment points at + `DistanceMeans::Hamming`, and that pointer was the thread that unravelled it. +- **S3 — facet ↔ SoA row.** The 12-byte facet register and the 480-byte value + slab live in one 512-byte row. Any code that reads both in one sweep is a + seam. + +**Gate G-SEAM:** each seam gets one sentence stating which carrier owns the +input, which owns the output, and what the conversion costs. A seam with no +stated cost is unassessed, not free. + +### Pass 4 — outward radius (docs, tests, cross-repo) + +- Board + knowledge + plan mentions of each fold (the words travel further than + the code; entry (16) is the proof). +- `ndarray::simd` masking-ops docs that cite the facet result. +- Cross-repo consumers of `lance-graph-contract`: in-tree (planner, callcenter, + smb-bridge), `ladybug-rs`, `lance-graph-java` (`lgj-abi` imports + `lance_graph_contract::facet` — the G11 fence names it). **`hi_distance` / + `lo_distance` have zero in-tree callers; the Java side is where an + out-of-tree caller would be**, and it is checkable — the G11 fence test + enumerates exactly what crosses. + +## Falsification gates (the plan is only real if it can fail) + +| gate | passes when | fails when | +|---|---|---| +| **G-EXH** | the type census is exhaustive by construction — a directory walk, not a file list someone chose | any row was found by intuition rather than by the walk | +| **G-FOLD** | every fold site carries a carrier assignment | a site resists assignment (⇒ fourth carrier, or a mixed site) | +| **G-SEAM** | every seam states owner-in / owner-out / conversion cost | a seam is listed as "just a cast" | +| **G-PROBE** | each carrier that gets a *change* has a probe on **that** carrier, red-then-green | a change lands citing another carrier's number — the original defect, recurring | +| **G-ZERO** | a claimed "no callers" was verified by opening the place a caller would live, not by an empty grep | a negative grep is the only evidence | + +## Sequencing, and what is explicitly NOT authorized + +1. Passes 1–2 (census). Read-only. +2. Pass 3 (seams). Read-only. **Highest value — if only one pass runs, run this.** +3. Pass 4 (outward radius). Read-only. +4. *Then* the carrier-2 probe (four arms, same harness, `NiblePath` workloads + incl. `EMPTY`, unequal depths, ancestor pairs, full-16 agreement). +5. *Then*, and only on a green probe, the carrier-2 rewrite. + +**Not authorized by this plan:** any rewrite of carrier 2 before step 4; any +change to carrier 1 (it is correct and this plan produces no evidence about +it); any widening of `FacetTier`'s `u8:u8` into a `u16` to "make the mask +cheaper" — that is a canon violation (`E-V3-FACET-4-PLUS-12`), and it is +precisely the shape a carrier-3 masking argument tends toward. **The fold +adapts to the layout; the layout never adapts to the fold.** + +## Cost + +Passes 1–4 are one session with 3–4 Sonnet sweeps and main-thread +classification. The carrier-2 probe reuses `facet_axis_lcp_probe.rs`'s harness +(SplitMix64 seed `0x9E37_79B9_7F4A_7C15`, min-of-7, oracle-first, anti-vacuity +gates) — the arms change, the scaffolding does not. That reuse is itself a +finding worth keeping: **the harness is carrier-agnostic even though the +folds are not.** diff --git a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs index c1f71a524..30736aabe 100644 --- a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs +++ b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs @@ -195,7 +195,25 @@ fn arm_b_peek_u64(a: &FacetCascade, b: &FacetCascade) -> (u8, u8) { /// **Arm C** — the shipped masked `u128` readout, through the public API. #[inline(never)] fn arm_c_masked_u128(a: &FacetCascade, b: &FacetCascade) -> (u8, u8) { - (6 - a.hi_distance(*b), 6 - a.lo_distance(*b)) + const fn axis_mask(axis_off: u32) -> u128 { + let mut m = 0u128; + let mut t = 0; + while t < 6 { + m |= 0xFF << (8 * (4 + 2 * t + axis_off)); + t += 1; + } + m + } + const fn shared(x: u128, mask: u128) -> u8 { + let x = x & mask; + if x == 0 { + 6 + } else { + ((x.trailing_zeros() - 32) / 16) as u8 + } + } + let x = a.as_u128() ^ b.as_u128(); + (shared(x, axis_mask(1)), shared(x, axis_mask(0))) } /// **Arm D** — arm B with the two `as_bytes()` reads shared across both axes, @@ -229,7 +247,7 @@ const ARMS: [Arm; 4] = [ fn check_arms_agree(pairs: &[(FacetCascade, FacetCascade)], what: &str) { for (a, b) in pairs { - let shipped = (6 - a.hi_distance(*b), 6 - a.lo_distance(*b)); + let shipped = (6 - a.hi_distance(b), 6 - a.lo_distance(b)); for (name, f) in ARMS { let got = f(a, b); assert_eq!( @@ -329,7 +347,10 @@ fn main() { for w in workloads { let pairs = make_pairs(w, N, &mut rng); check_arms_agree(&pairs[..256.min(pairs.len())], &w.label()); - let times: Vec = ARMS.iter().map(|(_, f)| time_arm(*f, &pairs, RUNS)).collect(); + let times: Vec = ARMS + .iter() + .map(|(_, f)| time_arm(*f, &pairs, RUNS)) + .collect(); if let Workload::Depth(_) = w { arm_a_by_depth.push(times[0]); } diff --git a/crates/lance-graph-contract/src/facet.rs b/crates/lance-graph-contract/src/facet.rs index 5d06949c4..9799205f8 100644 --- a/crates/lance-graph-contract/src/facet.rs +++ b/crates/lance-graph-contract/src/facet.rs @@ -222,54 +222,54 @@ impl FacetCascade { [t[0].lo, t[1].lo, t[2].lo, t[3].lo, t[4].lo, t[5].lo] } - /// Byte mask selecting the `hi` byte of every tier in the LE `u128` facet - /// (bytes 5, 7, … 15; the classid occupies 0..4, tier `t` sits at `4 + 2t`). - const HI_BYTES: u128 = Self::tier_byte_mask(1); - /// Byte mask selecting the `lo` byte of every tier (bytes 4, 6, … 14). - const LO_BYTES: u128 = Self::tier_byte_mask(0); - - const fn tier_byte_mask(axis_off: u32) -> u128 { - let mut m = 0u128; - let mut t = 0; - while t < 6 { - m |= 0xFF << (8 * (4 + 2 * t + axis_off)); - t += 1; - } - m - } - - /// Shared coarse→fine prefix length (0..=6) along one axis, read straight - /// off the single-register facet — no chain gather, no re-fold. + /// Shared coarse→fine prefix length (0..=6) along one axis — the early-exit + /// byte chain fold, comparing tier `t`'s axis byte straight out of each + /// facet's backing bytes. /// - /// The facet's `u128` already holds both axes formatted by position - /// (`"{0}{1}" -f hi,lo` per tier, tier 0 lowest), so the `-f` was done once, - /// at mint. An axis prefix is the whole-facet xor masked to that axis's - /// bytes, then `trailing_zeros / 16` past the 4 classid bytes — the same - /// readout [`shared_prefix_tiles`](Self::shared_prefix_tiles) uses for the - /// whole facet. `xor == 0` under the mask ⇔ all six bytes agree. - const fn shared_axis(x: u128, axis: u128) -> u8 { - let x = x & axis; - if x == 0 { - 6 - } else { - ((x.trailing_zeros() - 32) / 16) as u8 + /// **This is a PEEK, not a mask** (`E-THREE-CARRIERS-THREE-FOLDS-1`). The + /// cascade is byte-addressed: tier `t`'s axis byte sits at the compile-time + /// constant offset `4 + 2t` (lo) or `5 + 2t` (hi), so each step lowers to a + /// `movzbl` plus a `cmp` against the other facet's memory and a `jne` that + /// exits at the first divergence. Nothing is gathered — LLVM never + /// materializes the `[u8; 6]`. The single-register masked readout (`xor`, + /// mask, `tzcnt`, offset-correct) performs the SAME byte loads, then pays + /// to reassemble them and cannot exit early; measured 2026-09-17 by + /// `examples/facet_axis_lcp_probe.rs` at **3.64 ns vs 1.72 ns** on 64K + /// random pairs, and slower at every divergence depth 0..5. + /// + /// The masked form is retained as this fold's test oracle + /// (`masked_axis_oracle`), the same license a raw intrinsic gets under + /// `#[cfg(test)]`. + const fn shared_axis_chain(a: &Self, b: &Self, hi: bool) -> u8 { + let mut n = 0usize; + while n < 6 { + let (x, y) = if hi { + (a.tiers[n].hi, b.tiers[n].hi) + } else { + (a.tiers[n].lo, b.tiers[n].lo) + }; + if x != y { + break; + } + n += 1; } + n as u8 } /// `hi`-chain distance: `6 − shared hi-prefix` — locality along the `hi` hierarchy, /// orthogonal to [`lo_distance`](Self::lo_distance). #[inline] #[must_use] - pub const fn hi_distance(self, other: Self) -> u8 { - 6 - Self::shared_axis(self.as_u128() ^ other.as_u128(), Self::HI_BYTES) + pub const fn hi_distance(&self, other: &Self) -> u8 { + 6 - Self::shared_axis_chain(self, other, true) } /// `lo`-chain distance: `6 − shared lo-prefix` — locality along the orthogonal `lo` /// hierarchy, on the SAME facet. #[inline] #[must_use] - pub const fn lo_distance(self, other: Self) -> u8 { - 6 - Self::shared_axis(self.as_u128() ^ other.as_u128(), Self::LO_BYTES) + pub const fn lo_distance(&self, other: &Self) -> u8 { + 6 - Self::shared_axis_chain(self, other, false) } /// Number of fully-matching low **tiles** (0..=8, classid tiles 0–1 first, then the @@ -683,8 +683,8 @@ mod tests { let mut b = sample(); b[4] = 0x99; // tier0 lo let g = FacetCascade::from_bytes(&b); - assert_eq!(f.hi_distance(g), 0, "hi chain unchanged"); - assert!(f.lo_distance(g) > 0, "lo chain diverges at tier0"); + assert_eq!(f.hi_distance(&g), 0, "hi chain unchanged"); + assert!(f.lo_distance(&g) > 0, "lo chain diverges at tier0"); assert_eq!( f.shared_prefix_tiles(g), 2, @@ -699,25 +699,42 @@ mod tests { assert_eq!(h.row_match_mask(f), 0b1110); } - /// The masked single-register axis readout against the byte loop it replaced, - /// at every divergence position and on the identical case. A mask off by one - /// byte (hi/lo swapped, or the classid bytes included) or a missing - /// identical-clamp fails one of these rows. Disable-verified 2026-09-16: - /// swapping `HI_BYTES`/`LO_BYTES` fails `hi flip at tier 0`. + /// The shipped byte-chain fold against the **masked single-register oracle** + /// it is measured faster than, at every divergence position and on the + /// identical case. + /// + /// Direction reversed 2026-09-17 (`E-THREE-CARRIERS-THREE-FOLDS-1`): the + /// masked readout is now the `#[cfg(test)]` oracle and the chain is what + /// ships, so this stays a real differential rather than becoming a + /// restatement of the implementation. The `−32` in the oracle is the + /// classid's 32 bits sitting below the tiers in the LE `u128`: the mask + /// removes the classid's *bits*, never its *offset*. Disable-verified + /// 2026-09-16 in its prior direction: swapping the hi/lo masks fails + /// `hi flip at tier 0`; it still does. #[test] fn folded_axis_prefix_matches_the_loop_at_every_position() { - const fn looped(a: [u8; 6], b: [u8; 6]) -> u8 { - let mut n = 0u8; - while (n as usize) < 6 && a[n as usize] == b[n as usize] { - n += 1; + /// The retired single-register readout, kept as the oracle: xor the whole + /// facet, mask to one axis's six bytes, `tzcnt`, subtract the classid's + /// 32-bit offset, divide by the 16-bit tier stride. + const fn masked_axis_oracle(a: u128, b: u128, axis_off: u32) -> u8 { + let mut mask = 0u128; + let mut t = 0; + while t < 6 { + mask |= 0xFF << (8 * (4 + 2 * t + axis_off)); + t += 1; + } + let x = (a ^ b) & mask; + if x == 0 { + 6 + } else { + ((x.trailing_zeros() - 32) / 16) as u8 } - n } let f = FacetCascade::from_bytes(&sample()); let base = sample(); - // identical: both axes fully shared (the xor == 0 clamp). - assert_eq!(f.hi_distance(f), 0); - assert_eq!(f.lo_distance(f), 0); + // identical: both axes fully shared (the oracle's xor == 0 clamp). + assert_eq!(f.hi_distance(&f), 0); + assert_eq!(f.lo_distance(&f), 0); // flip exactly tier `t`'s hi byte, then its lo byte: prefix must be `t` on // that axis and 6 on the other, and equal the loop's answer. for t in 0..6usize { @@ -725,9 +742,13 @@ mod tests { let mut b = base; b[4 + 2 * t + axis_off] ^= 0x80; let g = FacetCascade::from_bytes(&b); - let (sh, sl) = (6 - f.hi_distance(g) as usize, 6 - f.lo_distance(g) as usize); - assert_eq!(sh, looped(f.hi_chain(), g.hi_chain()) as usize, "hi t={t}"); - assert_eq!(sl, looped(f.lo_chain(), g.lo_chain()) as usize, "lo t={t}"); + let (sh, sl) = ( + 6 - f.hi_distance(&g) as usize, + 6 - f.lo_distance(&g) as usize, + ); + let (xf, xg) = (f.as_u128(), g.as_u128()); + assert_eq!(sh, masked_axis_oracle(xf, xg, 1) as usize, "hi t={t}"); + assert_eq!(sl, masked_axis_oracle(xf, xg, 0) as usize, "lo t={t}"); if is_hi { assert_eq!((sh, sl), (t, 6), "hi flip at tier {t}"); } else { From 5984e852d78162727058201b5f17d8ab70d9a572 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:32:15 +0000 Subject: [PATCH 3/5] board: D-ids the gate can see; stable anchors instead of line numbers Two CI gates on #1245, both this PR's: - added-plans-have-dids: the plan carried no D-id the tool recognises. The ids I minted as D-3CF-* do not match the workspace pattern (`D-[A-Z]{2,}...` -- two letters first, so a leading digit is invisible to plan_dids.py and to the supersession index's coverage column). Renamed D-3CF-1..5 -> D-TCF-1..5 in STATUS_BOARD and cited them from the plan's passes. - citation-decay: two ISSUES.md citations pointed at line numbers (hhtl.rs:251, mailbox_scan.rs:263) whose backtick anchors were not literally present in the +-3 window. Per the gate's own rule, the fix is not a corrected number: both now cite the file plus a stable symbol anchor and no line. Both tools green locally; supersession index regenerated last, unchanged. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d --- .claude/board/ISSUES.md | 4 ++-- .claude/board/STATUS_BOARD.md | 10 +++++----- .claude/plans/three-carrier-blast-radius-v1.md | 10 +++++++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 219a02558..c4699f7d3 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,6 +1,6 @@ ## ISS-NIBLEPATH-FOLD-IS-CARRIER-2-UNMASKED (2026-09-17) — OPEN, the one prefix fold that genuinely wants a mask, and does not have one -`NiblePath::common_prefix_depth` (`crates/lance-graph-contract/src/hhtl.rs:251`) is +`NiblePath::common_prefix_depth` (`crates/lance-graph-contract/src/hhtl.rs`, `fn common_prefix_depth`) is carrier 2 per `.claude/knowledge/three-prefix-fold-carriers.md`: 16 nibbles packed into one `u64`, sub-byte, no byte addresses. It walks depth-by-depth, each step a shift, an `Option` construct and a two-field compare: @@ -17,7 +17,7 @@ while d < max { Up to 16 iterations for what is `((self.path ^ other.path).leading_zeros() >> 2)` clamped to `min(self.depth, other.depth)` — root-first, so `leading_zeros`, not `trailing_zeros`. -**Live, not hypothetical:** `crates/lance-graph/src/graph/mailbox_scan.rs:263` calls it for +**Live, not hypothetical:** `crates/lance-graph/src/graph/mailbox_scan.rs` (`pa.common_prefix_depth(pb)`) calls it for CAKES nearest-ranking (`E-PANCAKES-IS-RADIX-IS-HHTL`). Two further local copies of the same fold shape exist over `HhtlKey` in `crates/perturbation-sim/examples/` (`outage_over_hhtl_hops.rs:76`, `basin_placement_learning.rs:76`) and should be diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 29f8ac598..729cb257f 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -6,11 +6,11 @@ Doctrine: `.claude/knowledge/three-prefix-fold-carriers.md`. Board: | D-id | scope | status | gate / falsifier | |---|---|---|---| -| D-3CF-1 | probe the facet per-axis LCP four ways and settle which fold the byte-addressed carrier wants | **Shipped.** `examples/facet_axis_lcp_probe.rs`, 64K pairs, min-of-7, oracle-first. Chain PEEK **1.72 ns** vs masked `u128` **3.64 ns** on random; PEEK wins at every workload; the two pack-to-register arms are slowest | all four arms must agree with the shipped API before timing; the depth knob must bind (A: 1.66 → 3.50 ns over depth 0..5, +111%) — a flat slope would mean the early exit is not the mechanism | -| D-3CF-2 | revert `shared_axis` to the chain fold; keep the differential test falsifiable | **Shipped.** Masked form retained as `masked_axis_oracle` under `#[cfg(test)]` — direction reversed so the test compares shipped-vs-oracle, not shipped-vs-itself | post-revert re-run reproduces the ordering (A 1.76 / C 4.48); 1354 contract tests green; the prior disable-run (swap hi/lo masks ⇒ fail at `hi flip at tier 0`) still holds on the oracle | -| D-3CF-3 | name the three carriers and the law that separates them | **Shipped.** `.claude/knowledge/three-prefix-fold-carriers.md` — bit-planes / nibble path / facet cascade; *masking wins when the slice is granular, PEEK wins when the slice is addressed* | falsified by a fourth carrier, or by one carrier consuming another's fold. Two greps must stay empty: `FacetCascade` in `mailbox_soa.rs`; `trailing_zeros\|leading_zeros` in `hhtl.rs` | -| D-3CF-4 | carrier-2 (`NiblePath::common_prefix_depth`) masked rewrite | **Queued — CONJECTURE, deliberately unbuilt.** `ISS-NIBLEPATH-FOLD-IS-CARRIER-2-UNMASKED`. This is the carrier the 2026-09-16 instinct was right about | must be probed on ITS OWN carrier first (G-PROBE). Landing it on entry (16)'s number would be the original defect recurring | -| D-3CF-5 | blast-radius census of all three carriers + their seams | **Queued.** Plan above, passes 1–4, read-only | G-EXH (walk, not a chosen file list) · G-FOLD (every fold site carries a carrier) · G-SEAM (owner-in / owner-out / cost) · G-ZERO (a "no callers" claim verified by opening, never by an empty grep) | +| D-TCF-1 | probe the facet per-axis LCP four ways and settle which fold the byte-addressed carrier wants | **Shipped.** `examples/facet_axis_lcp_probe.rs`, 64K pairs, min-of-7, oracle-first. Chain PEEK **1.72 ns** vs masked `u128` **3.64 ns** on random; PEEK wins at every workload; the two pack-to-register arms are slowest | all four arms must agree with the shipped API before timing; the depth knob must bind (A: 1.66 → 3.50 ns over depth 0..5, +111%) — a flat slope would mean the early exit is not the mechanism | +| D-TCF-2 | revert `shared_axis` to the chain fold; keep the differential test falsifiable | **Shipped.** Masked form retained as `masked_axis_oracle` under `#[cfg(test)]` — direction reversed so the test compares shipped-vs-oracle, not shipped-vs-itself | post-revert re-run reproduces the ordering (A 1.76 / C 4.48); 1354 contract tests green; the prior disable-run (swap hi/lo masks ⇒ fail at `hi flip at tier 0`) still holds on the oracle | +| D-TCF-3 | name the three carriers and the law that separates them | **Shipped.** `.claude/knowledge/three-prefix-fold-carriers.md` — bit-planes / nibble path / facet cascade; *masking wins when the slice is granular, PEEK wins when the slice is addressed* | falsified by a fourth carrier, or by one carrier consuming another's fold. Two greps must stay empty: `FacetCascade` in `mailbox_soa.rs`; `trailing_zeros\|leading_zeros` in `hhtl.rs` | +| D-TCF-4 | carrier-2 (`NiblePath::common_prefix_depth`) masked rewrite | **Queued — CONJECTURE, deliberately unbuilt.** `ISS-NIBLEPATH-FOLD-IS-CARRIER-2-UNMASKED`. This is the carrier the 2026-09-16 instinct was right about | must be probed on ITS OWN carrier first (G-PROBE). Landing it on entry (16)'s number would be the original defect recurring | +| D-TCF-5 | blast-radius census of all three carriers + their seams | **Queued.** Plan above, passes 1–4, read-only | G-EXH (walk, not a chosen file list) · G-FOLD (every fold site carries a carrier) · G-SEAM (owner-in / owner-out / cost) · G-ZERO (a "no callers" claim verified by opening, never by an empty grep) | ## elk/ro factfinder — the meet, the horizon and their generality (D-ids minted 2026-09-15, probe `.claude/probes/elk-generality-v1/`) diff --git a/.claude/plans/three-carrier-blast-radius-v1.md b/.claude/plans/three-carrier-blast-radius-v1.md index 0593c5d06..605b2081b 100644 --- a/.claude/plans/three-carrier-blast-radius-v1.md +++ b/.claude/plans/three-carrier-blast-radius-v1.md @@ -4,6 +4,10 @@ this document. **Doctrine:** `.claude/knowledge/three-prefix-fold-carriers.md` **Board:** `E-THREE-CARRIERS-THREE-FOLDS-1` (EPIPHANIES entry 17) +**D-ids (STATUS_BOARD § three-carrier prefix folds):** the four census passes +below are **D-TCF-5**; the carrier-2 probe + rewrite they gate is **D-TCF-4**. +The shipped rows this plan builds on are D-TCF-1 (probe), D-TCF-2 (revert) and +D-TCF-3 (doctrine). ## Why a blast-radius pass at all @@ -30,7 +34,7 @@ live. Delegate the sweeps to Sonnet (grindwork: "find every site that X"), keep the classification on the main thread (accumulation: "is this site carrier 2 or carrier 3"). -### Pass 1 — type census (per carrier, exhaustive) +### Pass 1 — type census (per carrier, exhaustive) — D-TCF-5 For each carrier, the type(s) that ARE it, and every constructor / accessor / `from_*_bytes` / `to_*_bytes` on them. Output: a table of @@ -58,7 +62,7 @@ is_ancestor|prefix_depth`, then open each and assign a carrier. be assigned is a finding — it means a fourth carrier exists, or that a site mixes two. -### Pass 3 — the seams (highest value; do not skip) +### Pass 3 — the seams (highest value; do not skip) — D-TCF-5 Where does one carrier's output become another's input? Three candidate seams are already visible and each must be opened and characterized: @@ -107,7 +111,7 @@ stated cost is unassessed, not free. 1. Passes 1–2 (census). Read-only. 2. Pass 3 (seams). Read-only. **Highest value — if only one pass runs, run this.** 3. Pass 4 (outward radius). Read-only. -4. *Then* the carrier-2 probe (four arms, same harness, `NiblePath` workloads +4. *Then* the carrier-2 probe — **D-TCF-4** (four arms, same harness, `NiblePath` workloads incl. `EMPTY`, unequal depths, ancestor pairs, full-16 agreement). 5. *Then*, and only on a green probe, the carrier-2 rewrite. From c3de1d2efbb7e6a08e404583565add383c6b7c68 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 21:03:17 +0000 Subject: [PATCH 4/5] probe: oracle over every pair; anti-vacuity is a gate, not a remark Two Codex P2 findings on #1245, both correct: - check_arms_agree ran over pairs[..256] while the probe's doc promised every generated pair is cross-checked before timing. Now the whole slice: 65,536 pairs x 8 workloads x 4 arms, all against the shipped API. Costs ~0.2 s; the promise is now true. - The depth-0 -> depth-5 slope check printed a warning and exited 0 when it failed -- exactly the condition the probe says invalidates every row above it. It is now an assert!: a flat slope fails the process so an inattentive run cannot bank a meaningless table. Re-run: oracle green on every workload; slope +83.7% (1.81 -> 3.32 ns); ordering unchanged (A 1.83 / C 3.95 on random). fmt + clippy clean. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d --- .../examples/facet_axis_lcp_probe.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs index 30736aabe..8f2e462da 100644 --- a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs +++ b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs @@ -346,7 +346,7 @@ fn main() { for w in workloads { let pairs = make_pairs(w, N, &mut rng); - check_arms_agree(&pairs[..256.min(pairs.len())], &w.label()); + check_arms_agree(&pairs, &w.label()); let times: Vec = ARMS .iter() .map(|(_, f)| time_arm(*f, &pairs, RUNS)) @@ -380,16 +380,26 @@ fn main() { // early-exit loop is not being compiled as one and the A/B contrast is not // measuring what this probe claims. let (a0, a5) = (arm_a_by_depth[0], arm_a_by_depth[5]); + let binds = a5 > a0 * 1.05; println!( "arm A depth-0 {a0:.2} ns → depth-5 {a5:.2} ns ({:+.1}%) — the early-exit loop {}", (a5 - a0) / a0 * 100.0, - if a5 > a0 * 1.05 { + if binds { "does depend on prefix length, as it must" } else { "does NOT depend on prefix length — READ THE ASM before trusting any row above" } ); println!(); + // This is a gate, not a remark: a flat slope means the A/B contrast is not + // measuring what this probe claims, so the table above must not be + // recorded. Fail the process rather than let an inattentive run bank it. + assert!( + binds, + "ANTI-VACUITY FAILED: arm A depth-5 ({a5:.2} ns) is not >5% above depth-0 \ + ({a0:.2} ns); the early-exit chain is not being compiled as one on this \ + target and every row above is invalid" + ); println!("next: cargo asm --example facet_axis_lcp_probe arm_a_chain_loop"); println!(" cargo asm --example facet_axis_lcp_probe arm_b_peek_u64"); println!(" A scalar shift/mask vs B byte-loads is the whole question."); From 6e79c78424a4f43a0a8434619df440cd05adcbcc Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 21:05:26 +0000 Subject: [PATCH 5/5] facet: restore the pre-#1242 shared6 verbatim, not a respelling The revert in b08db9b7 shipped a semantically equivalent chain fold (by-ref, tiers[n] indexed, bool axis selector) rather than the code #1242 replaced. The probe's arm A is the ORIGINAL shared6 verbatim, so the 1.72 ns measurement belonged to the original, not to what was shipped. Restored byte-for-byte from 95e28637^: const fn shared6(a: [u8; 6], b: [u8; 6]) -> u8 pub const fn hi_distance(self, other: Self) -> u8 // by-value again pub const fn lo_distance(self, other: Self) -> u8 Verified identical by diff against that commit. This also undoes the unintended public-signature change (&self/&Self -> self/Self), so the API is exactly what it was before #1242. The inverted test oracle and the doc comment stay; probe's shipped-reference call sites follow. 1425 tests green; fmt + clippy clean; probe: oracle green on every workload, A 1.83 / C 3.95 on random, slope +80.9%. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HScwwezRdMxFfTs3WLG19d --- .../examples/facet_axis_lcp_probe.rs | 2 +- crates/lance-graph-contract/src/facet.rs | 40 +++++++------------ 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs index 8f2e462da..8d62d388e 100644 --- a/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs +++ b/crates/lance-graph-contract/examples/facet_axis_lcp_probe.rs @@ -247,7 +247,7 @@ const ARMS: [Arm; 4] = [ fn check_arms_agree(pairs: &[(FacetCascade, FacetCascade)], what: &str) { for (a, b) in pairs { - let shipped = (6 - a.hi_distance(b), 6 - a.lo_distance(b)); + let shipped = (6 - a.hi_distance(*b), 6 - a.lo_distance(*b)); for (name, f) in ARMS { let got = f(a, b); assert_eq!( diff --git a/crates/lance-graph-contract/src/facet.rs b/crates/lance-graph-contract/src/facet.rs index 9799205f8..e4a62c631 100644 --- a/crates/lance-graph-contract/src/facet.rs +++ b/crates/lance-graph-contract/src/facet.rs @@ -239,37 +239,30 @@ impl FacetCascade { /// /// The masked form is retained as this fold's test oracle /// (`masked_axis_oracle`), the same license a raw intrinsic gets under - /// `#[cfg(test)]`. - const fn shared_axis_chain(a: &Self, b: &Self, hi: bool) -> u8 { - let mut n = 0usize; - while n < 6 { - let (x, y) = if hi { - (a.tiers[n].hi, b.tiers[n].hi) - } else { - (a.tiers[n].lo, b.tiers[n].lo) - }; - if x != y { - break; - } + /// `#[cfg(test)]`. This is the pre-#1242 `shared6` verbatim — restored, not + /// rewritten, so the probe's arm A and the shipped path are one function. + const fn shared6(a: [u8; 6], b: [u8; 6]) -> u8 { + let mut n = 0u8; + while (n as usize) < 6 && a[n as usize] == b[n as usize] { n += 1; } - n as u8 + n } /// `hi`-chain distance: `6 − shared hi-prefix` — locality along the `hi` hierarchy, /// orthogonal to [`lo_distance`](Self::lo_distance). #[inline] #[must_use] - pub const fn hi_distance(&self, other: &Self) -> u8 { - 6 - Self::shared_axis_chain(self, other, true) + pub const fn hi_distance(self, other: Self) -> u8 { + 6 - Self::shared6(self.hi_chain(), other.hi_chain()) } /// `lo`-chain distance: `6 − shared lo-prefix` — locality along the orthogonal `lo` /// hierarchy, on the SAME facet. #[inline] #[must_use] - pub const fn lo_distance(&self, other: &Self) -> u8 { - 6 - Self::shared_axis_chain(self, other, false) + pub const fn lo_distance(self, other: Self) -> u8 { + 6 - Self::shared6(self.lo_chain(), other.lo_chain()) } /// Number of fully-matching low **tiles** (0..=8, classid tiles 0–1 first, then the @@ -683,8 +676,8 @@ mod tests { let mut b = sample(); b[4] = 0x99; // tier0 lo let g = FacetCascade::from_bytes(&b); - assert_eq!(f.hi_distance(&g), 0, "hi chain unchanged"); - assert!(f.lo_distance(&g) > 0, "lo chain diverges at tier0"); + assert_eq!(f.hi_distance(g), 0, "hi chain unchanged"); + assert!(f.lo_distance(g) > 0, "lo chain diverges at tier0"); assert_eq!( f.shared_prefix_tiles(g), 2, @@ -733,8 +726,8 @@ mod tests { let f = FacetCascade::from_bytes(&sample()); let base = sample(); // identical: both axes fully shared (the oracle's xor == 0 clamp). - assert_eq!(f.hi_distance(&f), 0); - assert_eq!(f.lo_distance(&f), 0); + assert_eq!(f.hi_distance(f), 0); + assert_eq!(f.lo_distance(f), 0); // flip exactly tier `t`'s hi byte, then its lo byte: prefix must be `t` on // that axis and 6 on the other, and equal the loop's answer. for t in 0..6usize { @@ -742,10 +735,7 @@ mod tests { let mut b = base; b[4 + 2 * t + axis_off] ^= 0x80; let g = FacetCascade::from_bytes(&b); - let (sh, sl) = ( - 6 - f.hi_distance(&g) as usize, - 6 - f.lo_distance(&g) as usize, - ); + let (sh, sl) = (6 - f.hi_distance(g) as usize, 6 - f.lo_distance(g) as usize); let (xf, xg) = (f.as_u128(), g.as_u128()); assert_eq!(sh, masked_axis_oracle(xf, xg, 1) as usize, "hi t={t}"); assert_eq!(sl, masked_axis_oracle(xf, xg, 0) as usize, "lo t={t}");