feat(contract): SoaEnvelope binding for canonical NodeRow — NodeRowPacket wrapper#492
Conversation
…cket wrapper Closes punchlist item §7.2 of the 2026-06-13 SoA migration diff resolution doc (PR #491): the canonical row layout (#489) is now bound to the envelope ABI (#477) via a zero-copy borrowed-slice wrapper. New API (additive only, all in canonical_node.rs): pub struct NodeRowPacket<'a> { rows: &'a [NodeRow], cycle: u32 } impl SoaEnvelope for NodeRowPacket<'_> pub enum NodeRowColumn { Key=0, Edges=1, Value=2 } pub const NODE_ROW_COLUMNS: &[ColumnDescriptor] = ... pub const NODE_ROW_STRIDE: usize = 512; Three-column descriptor table: - Key (16 × U8) at offset 0 ← NodeGuid bytes, canon-LE - Edges (16 × U8) at offset 16 ← 12 in-family + 4 out-of-family - Value (480 × U8) at offset 32 ← class-resolved carve-out Sum = 512 = NODE_ROW_STRIDE = size_of::<NodeRow>(). The envelope contract is at the row-stride level — internal structure within each slot stays canon-described (NodeGuid for the key, EdgeBlock for the edges, registry ClassView for the value carve-out). as_le_bytes() is zero-copy: a &[NodeRow] is already a row-strided LE byte packet because NodeRow is #[repr(C, align(64))] with the locked 16/16/480 layout, and NodeGuid::new uses to_le_bytes for every group. The unsafe core::slice::from_raw_parts is documented with the alignment + size + LE guarantees that justify it. Tests (+9, all in canonical_node::tests): - node_row_column_table_sums_to_row_stride - node_row_column_table_is_in_offset_order_without_gaps - empty_packet_verifies - single_row_packet_verifies_and_byte_view_is_zero_copy (pointer equality) - multi_row_packet_byte_length_is_stride_times_rows - row_le_view_returns_one_full_row (classid round-trip through LE) - column_le_view_returns_the_named_slot (value + key with LE offsets) - key_bytes_in_canon_le_order (classid · HEEL · HIP · TWIG · family · identity at expected byte ranges end-to-end) - envelope_layout_version_matches_envelope_default cargo test -p lance-graph-contract --lib: 603/603 green (+9 from this PR) cargo clippy -p lance-graph-contract --all-targets -- -D warnings: clean This is the keystone the BindSpace dissolution sequence (D-MBX-A2 → S1 → S2 → S3 → S4 per the 2026-06-13 diff resolution doc) has been blocked behind: Lance's columnar I/O can now read the canonical row packet directly. The next step is MailboxSoA migrating from its current column-major [T; N] layout to a row-strided [NodeRow; N] backing store that impls SoaEnvelope through this wrapper. Anchors: - OGAR/CLAUDE.md P0 — operator-pinned GUID canon. - lance-graph PR #477 — SoaEnvelope trait + three-tier model. - lance-graph PR #489 / #490 — canonical_node code form. - lance-graph PR #491 — diff resolution + §7.2 punchlist entry this closes. https://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v
Per the Mandatory Board-Hygiene Rule. AGENT_LOG entry on the same change set as the code commit (separated for review clarity since the code commit was pushed before the Edit completed). https://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a ChangesNodeRow envelope binding
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
…-before-dilution) THE file:line-grounded reference doc for the integrated-cognitive-planner arc — the target the 5-savant expansion + 3-brutal hardening agents must cite so they do not hallucinate architecture. 4-layer P0 (FORGET LADYBUG: thinking-engine>P64>cognitive-shader-driver), §1 grounded current state, §2 the 6 additive seams with FOLDs, §3 (hhtl-guid):path:documentid / ScopedReference addressing + Pinpoint/TiKV lessons, §4 the 8-step cognitive cycle -> Rubicon phases, §5 measure-first probes, §6 open questions, §7 reference index. Verdict: the integrated planner ~90% EXISTS (#437-#492 + unmerged jolly-cori-clnf9); remaining = 6 seams + addressing + a CognitiveCycle sequencer, NOT a new build. Board hygiene (same commit): prepend INTEGRATION_PLANS.md + AGENT_LOG.md. https://claude.ai/code/session_01D2WSmezQBNC3bUdHuGfGmo
Summary
Closes punchlist item §7.2 of the 2026-06-13 SoA migration diff resolution doc (lance-graph #491). The canonical row layout from #489/#490 (
NodeGuid·EdgeBlock·NodeRow) is now bound to the envelope ABI from #477 (SoaEnvelope) via a zero-copy borrowed-slice wrapper.Two commits,
+290 / -0across two files (canonical_node.rs+AGENT_LOG.md). No code changes outside of pure additions tocanonical_node.What's new (all in
crates/lance-graph-contract/src/canonical_node.rs)Column descriptor table
kindelems_per_rowrow_offsetNodeRowColumn::KeyU8NodeGuidbytes (canon-LE: classid · HEEL · HIP · TWIG · family · identity)NodeRowColumn::EdgesU8EdgeBlockbytes (12 in-family + 4 out-of-family)NodeRowColumn::ValueU8ClassViewcarves)Sum = 512 =
NODE_ROW_STRIDE=size_of::<NodeRow>(). The envelope contract is at the row-stride level — internal structure within each slot stays canon-described (NodeGuidfor the key,EdgeBlockfor the edges, registryClassViewfor the value carve-out). Class-resolved sub-columns insideValueare not surfaced as envelope columns; the registry resolves them perclassid → ClassViewper the canon's "reserve, don't reclaim" discipline.Zero-copy
as_le_bytes()&[NodeRow]is already a row-strided LE byte packet because:NodeRowis#[repr(C, align(64))]with the locked 16/16/480 layout (guaranteed byconst _: () = assert!(size_of == 512)).NodeGuid::newwrites every group withto_le_bytes(canon LE).EdgeBlockis plain[u8; _](endianness-irrelevant).So
core::slice::from_raw_parts(rows.as_ptr().cast::<u8>(), rows.len() * 512)is sound and zero-copy. Theunsafeis contained insideas_le_bytes()with a documented SAFETY note covering alignment, size, and the canon-LE field semantics that make the byte view canon-compliant.The single-row test explicitly verifies pointer equality between the
rowsslice and theas_le_bytes()byte view — confirming no copy and no intermediate buffer.Tests added (+9)
node_row_column_table_sums_to_row_stridenode_row_column_table_is_in_offset_order_without_gapsempty_packet_verifiesverify_layoutOksingle_row_packet_verifies_and_byte_view_is_zero_copyas_le_bytes().as_ptr() == rows.as_ptr()multi_row_packet_byte_length_is_stride_times_rowsas_le_bytes().len() == 3 * 512for 3 rowsrow_le_view_returns_one_full_rowrow_le(i)returns 512 bytes; classid round-trips through LEcolumn_le_view_returns_the_named_slotkey_bytes_in_canon_le_orderenvelope_layout_version_matches_envelope_default<NodeRowPacket as SoaEnvelope>::LAYOUT_VERSION == ENVELOPE_LAYOUT_VERSIONGates
(+9 from this PR; prior 594 unchanged.)
Clean.
Why this is the keystone
Per the SoA migration diff resolution doc §3.4, the
SoaEnvelopetrait had been shipped since #477 with zero real implementors — onlyTestEnvelopein tests. The BindSpace dissolution sequence (D-MBX-A2 → S1 → S2 → S3 → S4) was blocked behind binding the canonical row layout into the envelope ABI. This PR removes that block: Lance's columnar I/O can now read aNodeRowPacketdirectly without any envelope-side mediation.The next moves the resolution doc names (in §7) become unblocked or near-unblocked:
MailboxSoA<N>) — still gated on type work, but the row layout it would target is now well-defined.MailboxSoAmigration from column-major[T; N]arrays to a row-strided[NodeRow; N]backing store — directly buildable on this wrapper onceMailboxSoAdecides to make the shape switch.What this PR does NOT do
NodeRowPacket,NodeRowColumn,NODE_ROW_COLUMNS,NODE_ROW_STRIDEare pure additions tocanonical_node; the rest of the module +lib.rsre-exports are unchanged.ENVELOPE_LAYOUT_VERSIONbump. The wrapper uses the defaultLAYOUT_VERSION = ENVELOPE_LAYOUT_VERSION = 1(covered by theenvelope_layout_version_matches_envelope_defaulttest). Future class-resolved value-slot column sub-tables would warrant a version bump; this PR doesn't carve those.MailboxSoAchange. The[T; N]column-major layout it currently uses (seecognitive-shader-driver/src/mailbox_soa.rs) is untouched. Migrating it to a row-strided[NodeRow; N]is the natural follow-up but a much bigger PR (touches the engine_bridge re-encode boundary, the BindSpace allocations in the two binaries, the §6.2 framing's "markers derived from these columns" implementation).lance-graph/CLAUDE.mdedit. The Staunen / Wisdom misnaming is stillTD-CLAUDE-MD-STAUNEN-MISNAMEper the resolution doc.Anchors
SUBSTRATE_STATE_FRAMINGS.md(the durable architectural framing).SoaEnvelopetrait + three-tier model.canonical_nodecode form + canon wiring.Test plan
cargo test -p lance-graph-contract --lib— 603/603 greencargo clippy -p lance-graph-contract --all-targets -- -D warnings— cleanas_le_bytes()zero-copy verified by pointer-equality testLAYOUT_VERSIONparity withENVELOPE_LAYOUT_VERSIONverifiedverify_layout()covers all the new descriptor-table invariantshttps://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v
Generated by Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests