Skip to content

feat(contract): SoaEnvelope binding for canonical NodeRow — NodeRowPacket wrapper#492

Merged
AdaWorldAPI merged 2 commits into
mainfrom
claude/soa-envelope-for-node-row
Jun 13, 2026
Merged

feat(contract): SoaEnvelope binding for canonical NodeRow — NodeRowPacket wrapper#492
AdaWorldAPI merged 2 commits into
mainfrom
claude/soa-envelope-for-node-row

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Jun 13, 2026

Copy link
Copy Markdown
Owner

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 / -0 across two files (canonical_node.rs + AGENT_LOG.md). No code changes outside of pure additions to canonical_node.

What's new (all in crates/lance-graph-contract/src/canonical_node.rs)

pub enum NodeRowColumn { Key = 0, Edges = 1, Value = 2 }

pub const NODE_ROW_COLUMNS: &[ColumnDescriptor] = &[];
pub const NODE_ROW_STRIDE: usize = 512;

pub struct NodeRowPacket<'a> {
    rows: &'a [NodeRow],
    cycle: u32,
}

impl<'a> SoaEnvelope for NodeRowPacket<'a> {}

Column descriptor table

Column kind elems_per_row row_offset What it carries
NodeRowColumn::Key U8 16 0 NodeGuid bytes (canon-LE: classid · HEEL · HIP · TWIG · family · identity)
NodeRowColumn::Edges U8 16 16 EdgeBlock bytes (12 in-family + 4 out-of-family)
NodeRowColumn::Value U8 480 32 Class-resolved value slab (registry ClassView carves)

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). Class-resolved sub-columns inside Value are not surfaced as envelope columns; the registry resolves them per classid → ClassView per the canon's "reserve, don't reclaim" discipline.

Zero-copy as_le_bytes()

&[NodeRow] is already a row-strided LE byte packet because:

  1. NodeRow is #[repr(C, align(64))] with the locked 16/16/480 layout (guaranteed by const _: () = assert!(size_of == 512)).
  2. NodeGuid::new writes every group with to_le_bytes (canon LE).
  3. EdgeBlock is plain [u8; _] (endianness-irrelevant).

So core::slice::from_raw_parts(rows.as_ptr().cast::<u8>(), rows.len() * 512) is sound and zero-copy. The unsafe is contained inside as_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 rows slice and the as_le_bytes() byte view — confirming no copy and no intermediate buffer.

Tests added (+9)

Test What it locks
node_row_column_table_sums_to_row_stride Σ(col_bytes_per_row) = 512 = size_of::()
node_row_column_table_is_in_offset_order_without_gaps Contiguous: key 0..16, edges 16..32, value 32..512
empty_packet_verifies 0 rows → 0 bytes → verify_layout Ok
single_row_packet_verifies_and_byte_view_is_zero_copy Pointer equality: as_le_bytes().as_ptr() == rows.as_ptr()
multi_row_packet_byte_length_is_stride_times_rows as_le_bytes().len() == 3 * 512 for 3 rows
row_le_view_returns_one_full_row row_le(i) returns 512 bytes; classid round-trips through LE
column_le_view_returns_the_named_slot Value column hits offsets [0] = 0xAB / [479] = 0xCD; key column hits LE bytes of classid
key_bytes_in_canon_le_order End-to-end canon-LE: classid at [0..4], HEEL at [4..6], HIP at [6..8], TWIG at [8..10], family at [10..13], identity at [13..16]
envelope_layout_version_matches_envelope_default <NodeRowPacket as SoaEnvelope>::LAYOUT_VERSION == ENVELOPE_LAYOUT_VERSION

Gates

cargo test -p lance-graph-contract --lib
test result: ok. 603 passed; 0 failed; 0 ignored

(+9 from this PR; prior 594 unchanged.)

cargo clippy -p lance-graph-contract --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.62s

Clean.

Why this is the keystone

Per the SoA migration diff resolution doc §3.4, the SoaEnvelope trait had been shipped since #477 with zero real implementors — only TestEnvelope in 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 a NodeRowPacket directly without any envelope-side mediation.

The next moves the resolution doc names (in §7) become unblocked or near-unblocked:

  • §7.3 D-MBX-A2 (Hamming planes + temporal/expert on MailboxSoA<N>) — still gated on type work, but the row layout it would target is now well-defined.
  • The eventual MailboxSoA migration from column-major [T; N] arrays to a row-strided [NodeRow; N] backing store — directly buildable on this wrapper once MailboxSoA decides to make the shape switch.

What this PR does NOT do

  • No public-API drift in existing code. NodeRowPacket, NodeRowColumn, NODE_ROW_COLUMNS, NODE_ROW_STRIDE are pure additions to canonical_node; the rest of the module + lib.rs re-exports are unchanged.
  • No ENVELOPE_LAYOUT_VERSION bump. The wrapper uses the default LAYOUT_VERSION = ENVELOPE_LAYOUT_VERSION = 1 (covered by the envelope_layout_version_matches_envelope_default test). Future class-resolved value-slot column sub-tables would warrant a version bump; this PR doesn't carve those.
  • No MailboxSoA change. The [T; N] column-major layout it currently uses (see cognitive-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).
  • No lance-graph/CLAUDE.md edit. The Staunen / Wisdom misnaming is still TD-CLAUDE-MD-STAUNEN-MISNAME per the resolution doc.

Anchors

Test plan

  • cargo test -p lance-graph-contract --lib — 603/603 green
  • cargo clippy -p lance-graph-contract --all-targets -- -D warnings — clean
  • as_le_bytes() zero-copy verified by pointer-equality test
  • Canon-LE field byte ranges verified end-to-end (classid · HEEL · HIP · TWIG · family · identity)
  • LAYOUT_VERSION parity with ENVELOPE_LAYOUT_VERSION verified
  • verify_layout() covers all the new descriptor-table invariants
  • CI workspace check + CodeRabbit review

https://claude.ai/code/session_01VysoWJ6vsyg3wEGc5v7T5v


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added a faster, zero-copy way to work with canonical graph rows, improving data access efficiency.
    • Introduced consistent column-level handling for row data, including stable sizing and ordering.
  • Bug Fixes

    • Improved layout validation to help ensure row data is read correctly and consistently.
  • Tests

    • Added broader coverage for row layout, byte slicing, and data ordering checks.

claude added 2 commits June 13, 2026 22:27
…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
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7649059e-4cbe-4543-9a0a-36c227375f02

📥 Commits

Reviewing files that changed from the base of the PR and between 2337e75 and 4ec2197.

📒 Files selected for processing (2)
  • .claude/board/AGENT_LOG.md
  • crates/lance-graph-contract/src/canonical_node.rs

📝 Walkthrough

Walkthrough

Adds a NodeRow SoA envelope binding with exported column metadata, a 512-byte row stride constant, and a zero-copy packet wrapper over &[NodeRow]. The change also adds tests for layout, slicing, byte order, pointer reuse, and layout-version parity, plus a log entry.

Changes

NodeRow envelope binding

Layer / File(s) Summary
Envelope contract and byte view
crates/lance-graph-contract/src/canonical_node.rs
Adds NodeRowColumn, NODE_ROW_COLUMNS, NODE_ROW_STRIDE, and NodeRowPacket<'a> with a SoaEnvelope implementation that exposes a zero-copy little-endian byte slice over contiguous NodeRow data.
Layout validation and recorded change
crates/lance-graph-contract/src/canonical_node.rs, .claude/board/AGENT_LOG.md
Adds tests for column offsets, row stride, row_le/column_le slicing, zero-copy byte access, little-endian key bytes, and layout-version parity, then records the new binding and test coverage in the agent log.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AdaWorldAPI/lance-graph#489: It also updates crates/lance-graph-contract/src/canonical_node.rs around the canonical NodeRow binary layout that this packet binding now wraps.

Poem

🐇 I wrapped each row in bytes so neat,
with key and value kept in beat.
No copying paws, just views that glide,
through 512-byte rows side by side.
The tests all nibble, sniff, approve—
and little-endian tails now groove.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a SoaEnvelope binding via the NodeRowPacket wrapper for canonical NodeRow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AdaWorldAPI AdaWorldAPI merged commit 2f9c3ca into main Jun 13, 2026
6 checks passed
AdaWorldAPI pushed a commit that referenced this pull request Jun 15, 2026
…-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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants