fix(contract): T5 -- schema-gate the two fixed-offset NaN sweepers - #873
Conversation
project_energy_nonfinite and energy_all_finite in nan_projection.rs read ValueTenant::Energy at its fixed reserved offset unconditionally, with no schema.has() gate -- the same anti-pattern I-LEGACY-API-FEATURE-GATED names, here on the read side of a sweep rather than a versioned accessor. value_offset() is a fixed reserved byte position, identical across every ValueSchema (RESERVE, DON'T RECLAIM), so the ungated read was never memory- unsafe. The risk is semantic: a row whose resolved schema doesn't materialise Energy (ValueSchema::Compressed, e.g. NodeGuid::CLASSID_FMA) has no writer obligated to keep that byte range meaningful, so a schema-blind sweep could misread foreign/uninitialized bytes as a real accumulator. Not biting today only because ReadMode::DEFAULT is a documented TEMPORARY 2026-06-15 POC pin to ValueSchema::Full (every tenant present) -- every row in the tree currently resolves to a schema that has Energy. The gate is a no-op in practice right now and becomes load-bearing the moment that pin reverts to Bootstrap (as its own doc comment says it will) or any classid is minted to a narrower schema. Landed before the flip, matching ocr.rs's own test comment: "when one is minted, the same schema.has() gate leaves its slab empty." Fix: row_has_energy() reads row.key.read_mode().value_schema.has(Energy) -- one branch per row on schema presence, never on the float value, so the branchless finiteness test (exponent-mask compare) survives unchanged for every row it actually applies to. Rows failing the gate are excluded from both project_energy_nonfinite and energy_all_finite; NanReport gains a skipped: usize field so the gate's effect is observable rather than a silent no-op. Tests: fixtures moved off NodeGuid::local(0) (classid 0, temporarily Full) onto NodeGuid::CLASSID_OSINT (permanently Cognitive) -- a fixture pinned to the temporary default would have silently gone vacuous on the eventual flip. New adversarial test uses real registered classids (CLASSID_OSINT vs CLASSID_FMA) with NaN/Inf poison planted at the Energy offset on the Compressed-schema rows, asserts the poison is real via a direct ungated read first (so the test can't pass vacuously), then asserts the gated sweep ignores it entirely (skipped, not flagged). The two pre-existing tests gained a skipped == 0 assertion as the inertness twin. cargo test -p lance-graph-contract --lib: 1135/1135 green (4/4 in nan_projection, including the new adversarial case). cargo clippy -p lance-graph-contract --all-targets -- -D warnings: clean. symbiont (the one external caller, domino.rs, bin-only crate with a heavy SurrealDB/OGAR/AMX dependency tree, excluded from the default workspace): verified compile-safe by direct read -- it only calls .is_clean()/.count()/ .nonfinite on the NanReport, never an exhaustive destructure, so the new field cannot break it, and its own rows all currently resolve to Full (has Energy) so the gate changes nothing about its runtime behaviour today. Filed ISS-DOMINO-WRITES-ENERGY-OUTSIDE-ITS-OWN-SCHEMA: domino.rs's own writers (set_energy/write_lanes) have the identical ungated-fixed-offset pattern on the WRITE side, currently masked by the same temporary DEFAULT POC pin. Not fixed here -- it's an architecture call (mint a proper classid vs. an explicit unchecked-sweep variant for private byte-region borrows) that T5's scope didn't license unilaterally. Left as a follow-on so the POC-default flip doesn't silently turn domino.rs's own NaN assertion vacuous. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3RyLEbuNSHxxB3NTTrGki
|
Warning Review limit reached
Next review available in: 40 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review Requesting now, while draft — this PR changes the public shape of
Generated by Claude Code |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Candidate files =='
fd -HI -t f 'nan_projection\.rs|canonical_node\.rs|domino\.rs|ISSUES\.md' . | sort
printf '%s\n' '== Source outlines =='
for f in $(fd -HI -t f 'nan_projection\.rs|canonical_node\.rs|domino\.rs' .); do
echo "--- $f"
ast-grep outline "$f" --items all --view expanded || true
done
printf '%s\n' '== NanReport definition/construction/accesses =='
rg -n -P -C 5 '\bNanReport\b|\.total\b|total\s*:' -g '*.rs' .
printf '%s\n' '== Read-mode and schema resolution paths =='
rg -n -P -C 5 '\bread_mode\s*\(|\bclassid_read_mode\b|\bValueSchema\b|\bschema\s*\(' -g '*.rs' .
printf '%s\n' '== Energy read/write and offset uses =='
rg -n -P -C 5 '\b(project_energy_nonfinite|energy_all_finite|row_has_energy|set_energy|write_lanes|value_offset\s*\(|ValueTenant::Energy)' -g '*.rs' .
printf '%s\n' '== Filed issue =='
issue_file="$(fd -HI -t f 'ISSUES\.md' . | head -n 1 || true)"
if [ -n "$issue_file" ]; then
rg -n -P -C 8 'ISS-DOMINO-WRITES-ENERGY-OUTSIDE-ITS-OWN-SCHEMA' "$issue_file"
fiLength of output: 201096 🏁 Script executed: #!/bin/bash
set -euo pipefail
np="crates/lance-graph-contract/src/nan_projection.rs"
cn="crates/lance-graph-contract/src/canonical_node.rs"
dm="crates/symbiont/src/domino.rs"
is=".claude/board/ISSUES.md"
printf '%s\n' '== nan_projection implementation and tests =='
sed -n '35,250p' "$np"
printf '%s\n' '== exact NanReport references and total field accesses (Rust only) =='
rg -n -P -g '*.rs' -C 3 '\bNanReport\b|\b(?:[A-Za-z_][A-Za-z0-9_]*\.)total\b|\btotal\s*:' \
crates
printf '%s\n' '== registry initialization and read-mode resolution =='
sed -n '1410,1485p' "$cn"
printf '%s\n' '== ValueSchema::has implementation =='
sed -n '1110,1195p' "$cn"
printf '%s\n' '== Domino construction, fixed-offset helpers, and report use =='
sed -n '35,115p' "$dm"
sed -n '135,180p' "$dm"
printf '%s\n' '== complete filed Domino issue through next heading =='
awk '
/^## ISS-DOMINO-WRITES-ENERGY-OUTSIDE-ITS-OWN-SCHEMA/ {on=1}
on {print}
on && NR > 1 && /^## / && $0 !~ /ISS-DOMINO-WRITES-ENERGY-OUTSIDE-ITS-OWN-SCHEMA/ {exit}
' "$is"
printf '%s\n' '== all NodeGuid::local constructors in symbiont and all project_energy_nonfinite callers =='
rg -n -P -g '*.rs' -C 3 'NodeGuid::local\s*\(|\bproject_energy_nonfinite\s*\(' crates/symbiont crates/lance-graph-contractLength of output: 146488
No blocking correctness issue found in this PR’s T5 gate itself. ✅ Action performedReview finished.
|
…ridge.rs Two findings from codex's review, both confirmed by direct source inspection before acting, neither a correctness bug in the gate itself. 1. The module doc's "the branchless finiteness test survives unchanged" was true but readable as a broader "this is still free" claim than intended. row_has_energy() calls NodeGuid::read_mode() -> classid_read_mode(), which is a HashMap lookup behind a LazyLock, not a bitmask -- confirmed by reading canonical_node.rs:1473. That is real added per-row cost, on top of the old four-byte load, that can plausibly dominate it for an in-cache homogeneous batch. Doc now says so explicitly and names the exact call chain, rather than leaving "branchless" to imply "costless." Not benchmarked -- flagged as the first place to look if this projection ever lands on a genuinely hot path. 2. codex found a sibling instance of the exact ISS-DOMINO pattern in crates/symbiont/src/bridge.rs: board() mints via NodeGuid::local(idx), set_energy/energy read/write Energy at its fixed offset with no schema.has() gate. Confirmed by reading the file. The distinction codex drew and I verified: bridge.rs never calls project_energy_nonfinite or energy_all_finite at all -- its own tests check finiteness via plain f32::is_finite() on the value straight out of energy(), bypassing nan_projection.rs entirely. So T5's gate doesn't touch bridge.rs's behaviour in either direction; its exposure is the write-side schema- contract violation on the same ReadMode::DEFAULT POC pin, felt differently (no vacuous-skip risk, since it never calls the gated functions -- just a value that stops meaning what its schema claims once the pin flips). Added as a labelled addendum to the existing ISS-DOMINO entry rather than a second issue, since the fix is the same architecture call (mint vs. explicit-unchecked) applied to a second call site. cargo test -p lance-graph-contract nan_projection --lib: 4/4 green, unchanged (doc-only change plus a governance-file addendum). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3RyLEbuNSHxxB3NTTrGki
|
Thanks — all three verified and addressed in
Generated by Claude Code |
Closes tracked item T5 ("schema.has() gate on the two fixed-offset sweepers") — located, not assumed: no literal string match anywhere in the tree, found by enumerating every file with multiple
value_offset()call sites and reading the candidates.What it is
nan_projection.rs'sproject_energy_nonfinite/energy_all_finitereadValueTenant::Energyat its fixed offset unconditionally, unlike the already-correct sibling pattern inocr.rs(if schema.has(ValueTenant::EntityType) { ... }).value_offset()is a fixed reserved position, identical across everyValueSchema(RESERVE, DON'T RECLAIM) — so the ungated read was never memory-unsafe. The risk is semantic: a row whose resolved schema doesn't materialiseEnergy(ValueSchema::Compressed, e.g.NodeGuid::CLASSID_FMA) has no writer obligated to keep that byte range meaningful, so a schema-blind sweep could misread foreign/uninitialized bytes as a real energy accumulator.Why this wasn't already biting anyone
ReadMode::DEFAULT(what classid 0 resolves to) is a documented TEMPORARY 2026-06-15 POC pin toValueSchema::Full(every tenant present) — its own doc comment says it flips back toBootstrap(no tenants) "when the POC ends." Every row in the tree currently resolves to a schema that hasEnergy, so the gate is a no-op today. It becomes load-bearing the moment that pin reverts, or any classid is minted to a narrower schema — landed now rather than as a fire drill later, matchingocr.rs's own test comment: "No classid resolves to Bootstrap today — when one is minted, the sameschema.has()gate leaves its slab empty."The fix
row_has_energy(row)— one branch per row on schema presence, never on the float value; the branchless finiteness test (exponent-mask compare) is untouched for every row it applies to.NanReportgainsskipped: usizeso the gate's effect is observable, not a silent no-op (the workspace's can-it-fire rule).Tests
Fixtures moved off
NodeGuid::local(0)(classid 0, temporarilyFull) ontoNodeGuid::CLASSID_OSINT(permanentlyCognitive) — a fixture pinned to the temporary default would silently go vacuous the moment it flips.CLASSID_OSINTvsCLASSID_FMA, not synthetic overrides) with NaN/Inf poison planted at theEnergyoffset on the Compressed-schema rows. Asserts the poison is real via a direct ungated read first (so the test can't pass vacuously), then asserts the gated sweep ignores it entirely.skipped == 0asserted on the two pre-existing homogeneous-batch tests — the gate changes nothing for well-formed batches.cargo test -p lance-graph-contract --lib: 1135/1135 green.cargo clippy --all-targets -- -D warnings: clean.The one external caller
symbiont/src/domino.rscallsproject_energy_nonfinite. Verified compile-safe by direct read — it only uses.is_clean()/.count()/.nonfinite(never an exhaustive destructure), and its own rows currently resolve toFull(hasEnergy), so the gate changes nothing about its runtime behaviour today.symbiontis a bin-only crate with a heavy SurrealDB/OGAR/AMX dependency tree excluded from the default workspace; acargo checkagainst it was launched to confirm end-to-end but hadn't finished within this session's window — the field/method-access proof above stands independent of that.Filed, not fixed here
ISS-DOMINO-WRITES-ENERGY-OUTSIDE-ITS-OWN-SCHEMA—domino.rs's own writers (set_energy,write_lanes) have the identical ungated-fixed-offset pattern on the write side, masked by the same temporaryDEFAULTpin. The moment that pin reverts, domino.rs's ownassert!(report.is_clean(), ...)would go vacuously true (every row skipped) rather than fail loudly. Fixing it means an architecture call — mint domino.rs a proper classid into the shared registry, or give it an explicit unchecked-sweep variant for private byte-region borrows outside the schema system — that T5's scope didn't license unilaterally. Left as a flagged follow-on.Generated by Claude Code