Skip to content

Commit 4e3496a

Browse files
committed
feat(contract): ValueSchema value-slab presets (Full/Cognitive/Compressed/Bootstrap)
The value-side analog of EdgeCodecFlavor: per-class presets for which tenants the 480-byte NodeRow::value slab materialises. Closes the SoA-extension dilution gap — helix-48 was the one element still only a TODO comment in the value slab; it is now a first-class tenant alongside turbovec-Pq32x4 + signed-CoarseResidue. - ValueTenant: 9 stable append-only positions (discriminant == FieldMask bit == VALUE_TENANTS index) — Meta/Qualia/MaterializedEdges/Fingerprint/HelixResidue/ TurbovecResidue/Energy/Plasticity/EntityType. - VALUE_TENANTS: stable row-relative carve [32,186) (reserve-don't-reclaim, contiguous, compile-time asserted <= 480). - ValueSchema presets via FieldMask: Bootstrap (EMPTY default) / Cognitive (58 B) / Compressed (98 B) / Full (154 B = all 9 tenants). - ClassView::value_schema() defaulted to Bootstrap (non-breaking; mirrors edge_codec_flavor). - Layout-preserving: carves WITHIN the reserved slab, NODE_ROW_STRIDE=512 untouched, no ENVELOPE_LAYOUT_VERSION bump. Reuses class_view::FieldMask (presence) + soa_envelope::ColumnDescriptor (carve) — no new presence type. +6 tests + 3 compile-time canon asserts; fmt + clippy -D warnings + test -p lance-graph-contract green (611 lib tests). Board: LATEST_STATE Contract Inventory + AGENT_LOG updated same commit. https://claude.ai/code/session_01D2WSmezQBNC3bUdHuGfGmo
1 parent dd92d7a commit 4e3496a

5 files changed

Lines changed: 338 additions & 1 deletion

File tree

.claude/board/AGENT_LOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 2026-06-14 — `ValueSchema` value-slab presets (Full + Cognitive/Compressed/Bootstrap) — closes the helix-48 dilution gap
2+
3+
**Main thread.** Operator: *"create different Schema presets, the 'full' is one of the options."* Context: after confirming the SoA-extension ((12+4) edges + turbovec residue + signed) was already locked as `EdgeCodecFlavor` (commit `920671d`) on #489's `EdgeBlock`, **helix-48** was the one element still only a TODO-comment in the `value(480)` slab — the dilution risk the operator flagged.
4+
5+
**Shipped (contract, additive, build-verified):** `canonical_node::{ValueSchema, ValueTenant, VALUE_TENANTS}` — the value-side analog of `EdgeCodecFlavor`. 9 stable append-only `ValueTenant`s (discriminant == `FieldMask` bit == `VALUE_TENANTS` index) carving the value slab contiguously `[32,186)` (154 of 480 B; reserve-don't-reclaim). Four presets via `FieldMask`: `Bootstrap` (EMPTY default), `Cognitive` (58 B), `Compressed` (98 B), `Full` (154 B = all tenants). `ClassView::value_schema()` defaulted to `Bootstrap` (non-breaking, mirrors `edge_codec_flavor`). Layout-preserving (no stride change, no `ENVELOPE_LAYOUT_VERSION` bump). **helix-48 + turbovec-`Pq32x4` + signed-`CoarseResidue` are now all first-class tenants — the dilution gap is closed.**
6+
7+
**Reused, not duplicated** (operator's "refactor into what exists"): `class_view::FieldMask` (presence) + `soa_envelope::ColumnDescriptor` (carve) — no new presence type. +6 tests + 3 compile-time canon asserts; `cargo fmt` / `clippy -D warnings` / `test -p lance-graph-contract` all green (611 lib tests). Pushed to #495 (safe fallback intact). `EdgeCodecFlavor` (operator's earlier idea, `920671d`) untouched.
8+
19
## 2026-06-14 — Doc-sweep: stale `lance 6.0.0 / lancedb 0.29.0` → canonical `7.0.0 / 0.30.0` across CLAUDE.md + plans + boards
210

311
**Main thread.** Operator: *"it's 7.0.0 + 0.30, please update all plans boards accordingly"* + *"update the .claude/board ledgers epiphany technical debt implemented plans phases etc"*. Swept every stale `lance =6.0.0 / lancedb =0.29.0` reference to the canonical stack the workspace already runs.

.claude/board/LATEST_STATE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,5 +520,6 @@ PR sequence: #360 → #361 → post-#360 substrate-sweep (this PR).
520520
### Current Contract Inventory — new entry
521521

522522
- **`canonical_node::EdgeCodecFlavor`** (NEW; re-exported from `lib.rs`). Per-class selector for how a node's 16-byte `EdgeBlock` (+ optional value-slab residue) is *interpreted* — `CoarseOnly` (1 B palette index, the canon zero-fallback default), `CoarseResidue` (1 + ⌈D/2⌉ B, value-slab signed-4-bit residue), `Pq32x4` (16 B = 32×4-bit product code, the turbovec PQ model). `bytes_per_vector(dim)` + `is_layout_preserving()` (always `true`). **Iron invariant:** the flavor is interpretation, NOT layout — every variant leaves `NODE_ROW_STRIDE = 512` untouched, so adoption needs no `ENVELOPE_LAYOUT_VERSION` bump (canon "registry-resolved via `classid → ClassView`"). Default `CoarseOnly` matches the all-zero bootstrap. Selection surface: new defaulted `ClassView::edge_codec_flavor(&self, ClassId) -> EdgeCodecFlavor` (non-breaking — `RegistryClassView` inherits the default; per-class override is the follow-up wiring in `lance-graph-ontology`). +3 tests; contract lib 609 green; clippy `-D warnings` clean.
523+
- **`canonical_node::ValueSchema` + `ValueTenant` + `VALUE_TENANTS`** (NEW; re-exported from `lib.rs`). The **value-side analog of `EdgeCodecFlavor`**: per-class presets for which tenants the 480-byte `NodeRow::value` slab materialises. `ValueTenant` = 9 stable append-only positions (discriminant == `FieldMask` bit == `VALUE_TENANTS` index): Meta(`MetaWord`) · Qualia(`QualiaI4_16D`) · MaterializedEdges(4×`CausalEdge64`) · Fingerprint(32 B) · **HelixResidue(48 B)** · **TurbovecResidue(`Pq32x4` 16 B)** · Energy(f32) · Plasticity(u32) · EntityType(u16). `VALUE_TENANTS` = the stable row-relative byte carve `[32,186)` (reserve-don't-reclaim, contiguous, compile-time asserted ≤ 480). Presets: `Bootstrap` (EMPTY, zero-fallback **default**) · `Cognitive` (58 B: hot SoA columns, no codec residues) · `Compressed` (98 B: codec stack + fingerprint, no hot lifecycle) · `Full` (154 B: all 9 tenants). Built on existing `class_view::FieldMask` (presence) + `soa_envelope::ColumnDescriptor` — **no new presence type** (per "refactor into what exists"). `is_layout_preserving()` always `true` (carves WITHIN the reserved slab; `NODE_ROW_STRIDE=512` untouched → no `ENVELOPE_LAYOUT_VERSION` bump). Selection surface: defaulted `ClassView::value_schema(&self, ClassId) -> ValueSchema` (default `Bootstrap`, non-breaking — mirrors `edge_codec_flavor`). **Closes the SoA-extension dilution gap**: the formerly-comment-only helix-48 is now a first-class tenant alongside turbovec-`Pq32x4` + signed-`CoarseResidue`. +6 tests + 3 compile-time canon asserts; contract lib 611 green; clippy `-D warnings` + fmt clean.
523524
- **Encode/measure kernels live in `ndarray` (the hardware layer), not the contract:** `ndarray::hpc::edge_codec` (Codebook k-means, `CoarseResidueCodec`, `ProductQuantizer`, `reconstruct_coarse`) + `ndarray::hpc::reliability` (Pearson r, Spearman ρ, Cronbach α, ICC(2,1), `FidelityReport`). Harness `examples/edge_codec_compare` measures all flavors × {blob, continuous} regimes. **Measured:** CoarseResidue dominates agreement (ICC 0.97–0.99, ρ 0.98, α 0.99); Pq32x4 keeps rank (ρ 0.60–0.67) but not absolute distance (ICC 0.11–0.29); CoarseOnly collapses on continuous (ICC 0.003); AMX `matmul_i8_to_i32` assign 100% vs scalar, 24–28 GMAC/s. ndarray commit `d3b608f`.
524525
- **Deferred (flagged):** turbovec PQ4 *throughput* path (the FastScan nibble-LUT for the Pq32x4 flavor) blocked on the **#493 P2** build break — `lance-graph-turbovec` requests the `ndarray-simd` turbovec feature that was REMOVED (turbovec commit `7fa217c`); the polyfill fns are gone. turbovec's API is end-to-end (owns encode+scan), so it is a *PQ4 flavor*, not a residue-nibble-scan primitive. Fidelity (what ICC/Pearson/α measure) is independent of the fast kernel, so this is throughput-only follow-up.

crates/lance-graph-contract/src/canonical_node.rs

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ const _: () = assert!(core::mem::size_of::<NodeRow>() == 512);
259259

260260
// ── SoaEnvelope binding for [NodeRow] ────────────────────────────────────────
261261

262+
use crate::class_view::FieldMask;
262263
use crate::soa_envelope::{ColumnDescriptor, ColumnKind, SoaEnvelope};
263264

264265
/// Stable column-id ordinals for [`NodeRow`]'s three top-level slots.
@@ -304,6 +305,230 @@ pub const NODE_ROW_COLUMNS: &[ColumnDescriptor] = &[
304305
/// Row stride for [`NodeRow`] in bytes — equal to `size_of::<NodeRow>()`.
305306
pub const NODE_ROW_STRIDE: usize = 512;
306307

308+
// ── Value-slab schema presets: which tenants a class materialises ─────────────
309+
310+
/// Full-row byte offset of the value slab (key 16 + edges 16).
311+
pub const VALUE_SLAB_ROW_OFFSET: usize = 32;
312+
/// Bytes available in the [`NodeRow::value`] slab.
313+
pub const VALUE_SLAB_LEN: usize = 480;
314+
315+
/// A named tenant of the 480-byte [`NodeRow::value`] slab.
316+
///
317+
/// Stable, append-only positions — the canon "reserve, don't reclaim" rule and
318+
/// the [`FieldMask`] N3 contract: a tenant's presence bit and its byte offset in
319+
/// [`VALUE_TENANTS`] never move once instances persist, and retired tenants are
320+
/// never reused. **The discriminant IS the [`FieldMask`] bit position** and the
321+
/// index into [`VALUE_TENANTS`] (asserted at compile time below).
322+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
323+
#[repr(u8)]
324+
pub enum ValueTenant {
325+
/// `MetaWord` — thinking / awareness / NARS / free-energy bits.
326+
Meta = 0,
327+
/// `QualiaI4_16D` — 16 signed-4-bit chroma channels.
328+
Qualia = 1,
329+
/// The 4 out-of-family edges materialised as full `CausalEdge64`.
330+
MaterializedEdges = 2,
331+
/// `Fingerprint<256>` — 32-byte identity print.
332+
Fingerprint = 3,
333+
/// helix golden-spiral Place/Residue (48 B).
334+
HelixResidue = 4,
335+
/// turbovec PQ residue ([`EdgeCodecFlavor::Pq32x4`], 16 B).
336+
TurbovecResidue = 5,
337+
/// Spatio-temporal accumulator (`f32`).
338+
Energy = 6,
339+
/// Hebbian plasticity counter + last-active stamp.
340+
Plasticity = 7,
341+
/// OGIT entity-type / class discriminator (`u16`).
342+
EntityType = 8,
343+
}
344+
345+
/// Stable byte carve of the value slab. Offsets are **row-relative** (within one
346+
/// row packet, in the value region `[32, 512)`) — consistent with
347+
/// [`NODE_ROW_COLUMNS`], one level finer. Contiguous, in [`ValueTenant`]
348+
/// discriminant order, no gaps; the Full set fits the slab (all asserted at
349+
/// compile time below). This is the per-class carve the canon defers to
350+
/// `ClassView`; it is NOT surfaced as its own top-level envelope column.
351+
pub const VALUE_TENANTS: &[ColumnDescriptor] = &[
352+
ColumnDescriptor {
353+
name_id: ValueTenant::Meta as u16,
354+
kind: ColumnKind::U64,
355+
elems_per_row: 1,
356+
row_offset: 32,
357+
},
358+
ColumnDescriptor {
359+
name_id: ValueTenant::Qualia as u16,
360+
kind: ColumnKind::U64,
361+
elems_per_row: 1,
362+
row_offset: 40,
363+
},
364+
ColumnDescriptor {
365+
name_id: ValueTenant::MaterializedEdges as u16,
366+
kind: ColumnKind::U64,
367+
elems_per_row: 4,
368+
row_offset: 48,
369+
},
370+
ColumnDescriptor {
371+
name_id: ValueTenant::Fingerprint as u16,
372+
kind: ColumnKind::U8,
373+
elems_per_row: 32,
374+
row_offset: 80,
375+
},
376+
ColumnDescriptor {
377+
name_id: ValueTenant::HelixResidue as u16,
378+
kind: ColumnKind::U8,
379+
elems_per_row: 48,
380+
row_offset: 112,
381+
},
382+
ColumnDescriptor {
383+
name_id: ValueTenant::TurbovecResidue as u16,
384+
kind: ColumnKind::U8,
385+
elems_per_row: 16,
386+
row_offset: 160,
387+
},
388+
ColumnDescriptor {
389+
name_id: ValueTenant::Energy as u16,
390+
kind: ColumnKind::F32,
391+
elems_per_row: 1,
392+
row_offset: 176,
393+
},
394+
ColumnDescriptor {
395+
name_id: ValueTenant::Plasticity as u16,
396+
kind: ColumnKind::U32,
397+
elems_per_row: 1,
398+
row_offset: 180,
399+
},
400+
ColumnDescriptor {
401+
name_id: ValueTenant::EntityType as u16,
402+
kind: ColumnKind::U16,
403+
elems_per_row: 1,
404+
row_offset: 184,
405+
},
406+
];
407+
408+
// Compile-time canon: VALUE_TENANTS is discriminant-ordered, contiguous within the
409+
// value slab, and the Full carve fits the 480-byte slab.
410+
const _: () = {
411+
let mut i = 0usize;
412+
let mut prev_end = VALUE_SLAB_ROW_OFFSET;
413+
while i < VALUE_TENANTS.len() {
414+
let c = &VALUE_TENANTS[i];
415+
assert!(
416+
c.name_id as usize == i,
417+
"ValueTenant discriminant must equal its VALUE_TENANTS index"
418+
);
419+
assert!(
420+
c.row_offset as usize == prev_end,
421+
"VALUE_TENANTS must be contiguous within the value slab (no gaps/overlap)"
422+
);
423+
prev_end = c.row_offset as usize + c.col_bytes_per_row();
424+
i += 1;
425+
}
426+
assert!(
427+
prev_end <= NODE_ROW_STRIDE,
428+
"value tenants must fit within the 512-byte row"
429+
);
430+
assert!(
431+
prev_end - VALUE_SLAB_ROW_OFFSET <= VALUE_SLAB_LEN,
432+
"value tenants must fit the 480-byte slab"
433+
);
434+
};
435+
436+
/// Which value-slab schema a class materialises — the value-side analog of
437+
/// [`EdgeCodecFlavor`]. A preset is a presence [`FieldMask`] over [`ValueTenant`]
438+
/// positions; a class selects it via
439+
/// [`ClassView::value_schema`](crate::class_view::ClassView::value_schema).
440+
///
441+
/// **Layout-preserving:** every preset carves WITHIN the reserved 480-byte value
442+
/// slab, so the choice never changes [`NODE_ROW_STRIDE`] (no
443+
/// `ENVELOPE_LAYOUT_VERSION` bump — canon "registry-resolved via
444+
/// `classid → ClassView`", never a stride change). [`Bootstrap`] is the
445+
/// zero-fallback default: value all zero, only key + edges meaningful.
446+
///
447+
/// [`Bootstrap`]: ValueSchema::Bootstrap
448+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
449+
#[repr(u8)]
450+
pub enum ValueSchema {
451+
/// Empty value slab — the canon zero-fallback (key + edges only).
452+
#[default]
453+
Bootstrap = 0,
454+
/// Hot self-thinking set: Meta + Qualia + Fingerprint + Energy + Plasticity +
455+
/// EntityType. No materialised edges, no codec residues.
456+
Cognitive = 1,
457+
/// Cold / compressed codec stack: Fingerprint + Helix-48 + turbovec residue +
458+
/// EntityType. No hot lifecycle columns.
459+
Compressed = 2,
460+
/// Every [`ValueTenant`] materialised — the densest node.
461+
Full = 3,
462+
}
463+
464+
impl ValueSchema {
465+
/// The presence [`FieldMask`] over [`ValueTenant`] positions for this preset.
466+
pub const fn field_mask(self) -> FieldMask {
467+
match self {
468+
ValueSchema::Bootstrap => FieldMask::EMPTY,
469+
ValueSchema::Cognitive => FieldMask::from_positions(&[
470+
ValueTenant::Meta as u8,
471+
ValueTenant::Qualia as u8,
472+
ValueTenant::Fingerprint as u8,
473+
ValueTenant::Energy as u8,
474+
ValueTenant::Plasticity as u8,
475+
ValueTenant::EntityType as u8,
476+
]),
477+
ValueSchema::Compressed => FieldMask::from_positions(&[
478+
ValueTenant::Fingerprint as u8,
479+
ValueTenant::HelixResidue as u8,
480+
ValueTenant::TurbovecResidue as u8,
481+
ValueTenant::EntityType as u8,
482+
]),
483+
ValueSchema::Full => FieldMask::from_positions(&[
484+
ValueTenant::Meta as u8,
485+
ValueTenant::Qualia as u8,
486+
ValueTenant::MaterializedEdges as u8,
487+
ValueTenant::Fingerprint as u8,
488+
ValueTenant::HelixResidue as u8,
489+
ValueTenant::TurbovecResidue as u8,
490+
ValueTenant::Energy as u8,
491+
ValueTenant::Plasticity as u8,
492+
ValueTenant::EntityType as u8,
493+
]),
494+
}
495+
}
496+
497+
/// Does this preset materialise `tenant`?
498+
#[inline]
499+
pub const fn has(self, tenant: ValueTenant) -> bool {
500+
self.field_mask().has(tenant as u8)
501+
}
502+
503+
/// Total bytes this preset occupies in the value slab (Σ present tenants).
504+
pub const fn tenant_bytes(self) -> usize {
505+
let mask = self.field_mask();
506+
let mut total = 0usize;
507+
let mut i = 0usize;
508+
while i < VALUE_TENANTS.len() {
509+
let c = &VALUE_TENANTS[i];
510+
if mask.has(c.name_id as u8) {
511+
total += c.col_bytes_per_row();
512+
}
513+
i += 1;
514+
}
515+
total
516+
}
517+
518+
/// Every preset carves within the reserved 480-byte slab — none changes
519+
/// [`NODE_ROW_STRIDE`], so none forces an `ENVELOPE_LAYOUT_VERSION` bump.
520+
#[inline]
521+
pub const fn is_layout_preserving(self) -> bool {
522+
true
523+
}
524+
}
525+
526+
// Compile-time canon: the densest preset fits the slab; Full covers every tenant;
527+
// Bootstrap is empty.
528+
const _: () = assert!(ValueSchema::Full.tenant_bytes() <= VALUE_SLAB_LEN);
529+
const _: () = assert!(ValueSchema::Full.field_mask().count() as usize == VALUE_TENANTS.len());
530+
const _: () = assert!(ValueSchema::Bootstrap.field_mask().is_empty());
531+
307532
/// Zero-copy [`SoaEnvelope`] wrapper over a contiguous slice of [`NodeRow`].
308533
///
309534
/// `NodeRow` is `#[repr(C, align(64))]` with the locked 16/16/480 byte
@@ -660,4 +885,90 @@ mod tests {
660885
// verify_layout exercises that gate.
661886
assert!(pkt.verify_layout().is_ok());
662887
}
888+
889+
// ── Value-slab schema presets ────────────────────────────────────────────
890+
891+
#[test]
892+
fn value_tenants_contiguous_within_slab() {
893+
let mut prev_end = VALUE_SLAB_ROW_OFFSET;
894+
for (i, c) in VALUE_TENANTS.iter().enumerate() {
895+
assert_eq!(c.name_id as usize, i, "discriminant == index");
896+
assert_eq!(c.row_offset as usize, prev_end, "no gap before {c:?}");
897+
prev_end = c.row_offset as usize + c.col_bytes_per_row();
898+
}
899+
assert!(prev_end <= NODE_ROW_STRIDE);
900+
assert_eq!(
901+
prev_end - VALUE_SLAB_ROW_OFFSET,
902+
154,
903+
"current Full carve uses 154 of 480 B"
904+
);
905+
assert!(prev_end - VALUE_SLAB_ROW_OFFSET <= VALUE_SLAB_LEN);
906+
}
907+
908+
#[test]
909+
fn value_schema_default_is_bootstrap_empty() {
910+
assert_eq!(ValueSchema::default(), ValueSchema::Bootstrap);
911+
assert!(ValueSchema::Bootstrap.field_mask().is_empty());
912+
assert_eq!(ValueSchema::Bootstrap.tenant_bytes(), 0);
913+
}
914+
915+
#[test]
916+
fn value_schema_full_covers_every_tenant() {
917+
let full = ValueSchema::Full;
918+
assert_eq!(full.field_mask().count() as usize, VALUE_TENANTS.len());
919+
for t in [
920+
ValueTenant::Meta,
921+
ValueTenant::Qualia,
922+
ValueTenant::MaterializedEdges,
923+
ValueTenant::Fingerprint,
924+
ValueTenant::HelixResidue,
925+
ValueTenant::TurbovecResidue,
926+
ValueTenant::Energy,
927+
ValueTenant::Plasticity,
928+
ValueTenant::EntityType,
929+
] {
930+
assert!(full.has(t), "Full must materialise {t:?}");
931+
}
932+
}
933+
934+
#[test]
935+
fn value_schema_byte_budgets_are_locked() {
936+
assert_eq!(ValueSchema::Bootstrap.tenant_bytes(), 0);
937+
assert_eq!(ValueSchema::Cognitive.tenant_bytes(), 58);
938+
assert_eq!(ValueSchema::Compressed.tenant_bytes(), 98);
939+
assert_eq!(ValueSchema::Full.tenant_bytes(), 154);
940+
for s in [
941+
ValueSchema::Bootstrap,
942+
ValueSchema::Cognitive,
943+
ValueSchema::Compressed,
944+
ValueSchema::Full,
945+
] {
946+
assert!(s.tenant_bytes() <= VALUE_SLAB_LEN);
947+
assert!(s.is_layout_preserving());
948+
}
949+
}
950+
951+
#[test]
952+
fn value_schema_presets_carry_expected_tenants() {
953+
// Cognitive: hot columns, no codec residues, no materialised edges.
954+
let c = ValueSchema::Cognitive;
955+
assert!(c.has(ValueTenant::Meta) && c.has(ValueTenant::Qualia));
956+
assert!(c.has(ValueTenant::Energy) && c.has(ValueTenant::EntityType));
957+
assert!(!c.has(ValueTenant::HelixResidue));
958+
assert!(!c.has(ValueTenant::TurbovecResidue));
959+
assert!(!c.has(ValueTenant::MaterializedEdges));
960+
// Compressed: codec residues, no hot lifecycle.
961+
let z = ValueSchema::Compressed;
962+
assert!(z.has(ValueTenant::HelixResidue) && z.has(ValueTenant::TurbovecResidue));
963+
assert!(z.has(ValueTenant::Fingerprint));
964+
assert!(!z.has(ValueTenant::Energy) && !z.has(ValueTenant::Meta));
965+
}
966+
967+
#[test]
968+
fn value_schema_preserves_node_stride() {
969+
// A preset is an interpretation of the reserved value slab, never a
970+
// stride change — same canon invariant as EdgeCodecFlavor.
971+
assert_eq!(NODE_ROW_STRIDE, core::mem::size_of::<NodeRow>());
972+
assert_eq!(VALUE_SLAB_ROW_OFFSET + VALUE_SLAB_LEN, NODE_ROW_STRIDE);
973+
}
663974
}

crates/lance-graph-contract/src/class_view.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,21 @@ pub trait ClassView {
218218
fn edge_codec_flavor(&self, _class: ClassId) -> crate::canonical_node::EdgeCodecFlavor {
219219
crate::canonical_node::EdgeCodecFlavor::CoarseOnly
220220
}
221+
222+
/// Which value-slab schema preset this class materialises in
223+
/// [`NodeRow::value`](crate::canonical_node::NodeRow::value).
224+
///
225+
/// Default is
226+
/// [`ValueSchema::Bootstrap`](crate::canonical_node::ValueSchema::Bootstrap)
227+
/// — the canon zero-fallback (value all zero; only key + edges meaningful). An
228+
/// implementor overrides this to declare a denser preset (`Cognitive` /
229+
/// `Compressed` / `Full`). Selection only: every preset carves within the
230+
/// reserved 480-byte value slab, so the choice never changes `NODE_ROW_STRIDE`
231+
/// (canon "registry-resolved via `classid → ClassView`", never a stride change).
232+
#[inline]
233+
fn value_schema(&self, _class: ClassId) -> crate::canonical_node::ValueSchema {
234+
crate::canonical_node::ValueSchema::Bootstrap
235+
}
221236
}
222237

223238
/// One populated field to render — the late-resolved `label` + its `predicate` key.

0 commit comments

Comments
 (0)