Skip to content

Commit eef7e5f

Browse files
committed
feat(contract): keystone — GUID decode + classid→read-mode LazyLock (single source)
NodeGuid gains HHT accessors (heel/hip/twig), a one-shot decode() -> GuidParts (classid + HEEL/HIP/TWIG + family + identity in canon print order), and a carrier read_mode() method. ReadMode bundles the two already-existing read-mode axes (ValueSchema + EdgeCodecFlavor) — not a new node property, not a SoA column, just the resolution lens (§0 anti-invention). classid_read_mode(u32) is the single source both the consumer and OGAR inherit: a LazyLock<HashMap> builtin registry, zero-fallback to ReadMode::DEFAULT for unconfigured classids. ReadMode::DEFAULT = {Full, CoarseOnly} mirrors the ClassView::value_schema POC default; the two sites revert together (TD-VALUESCHEMA-FULL-POC-DEFAULT paired, guard test read_mode_default_is_full_poc). Display deduped onto the new HHT accessors. +6 tests; 619 contract lib green; clippy -D warnings + fmt clean. Delivers the contract-side half of the #496 keystone; the ontology-side NiblePath::from_guid_prefix meets it at the classid (follow-up). https://claude.ai/code/session_01D2WSmezQBNC3bUdHuGfGmo
1 parent 2106be2 commit eef7e5f

4 files changed

Lines changed: 242 additions & 8 deletions

File tree

.claude/board/LATEST_STATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
1111
---
1212

13+
> **2026-06-15 — branch work (post-#496)** — **keystone (contract half): GUID decode + classid→read-mode `LazyLock`.** `lance_graph_contract::canonical_node::{GuidParts, ReadMode, classid_read_mode}` + `NodeGuid::{heel(), hip(), twig(), decode() -> GuidParts, read_mode() -> ReadMode}` (re-exported from `lib.rs`). **The "read the GUID as a GUID" surface** the operator spec'd: `decode()` returns all six canon groups (classid + HHT·HEEL/HIP/TWIG + family·"Leaf" + identity) in one read; `ReadMode` bundles the two *already-existing* read-mode axes (`ValueSchema` + `EdgeCodecFlavor`) — **NOT a new node property, NOT a SoA column** (§0 anti-invention; it's the resolution lens, nothing stored on the row); `classid_read_mode(u32)` is the **single source both the consumer and OGAR inherit** — a `LazyLock<HashMap<u32,ReadMode>>` builtin registry (same immutable-after-init pattern `lance-graph-ontology` uses for its seed namespace registry), zero-fallback to `ReadMode::DEFAULT` for any unconfigured classid. `ReadMode::DEFAULT = {Full, CoarseOnly}` mirrors the `ClassView::value_schema` POC default (paired revert; `read_mode_default_is_full_poc` guards it). `Display` deduped onto the new HHT accessors. +6 tests (decode round-trip, HHT↔Display, read-mode single-source, carrier delegation, full-slab connect); **619 contract lib green; clippy `-D warnings` + fmt clean.** Delivers the contract-side half of the #496 keystone; the ontology-side `NiblePath::from_guid_prefix` (20→≤16-nibble subset) meets it at the classid (follow-up). Branch, not yet a PR.
14+
>
1315
> **2026-06-15 — branch work (post-#496)** — **helix `Signed360` codec + `HelixResidue` right-sized 48 B → 6 B.** Operator caught a slab over-allocation: `HelixResidue` reserved **48 *bytes*** but the intent was a 24-bit equal-area hemisphere **doubled = 48 *bit* = 6 B** (a bits→bytes slip; 42 dead bytes), and the tenant used **none** of the `helix` crate (zero-dep contract — only a doc string). Fixed: **(1) `helix::Signed360`** — the signed full-sphere codec: `HemispherePoint::signed_lift(n,N,sign)` (`y = sign·√(1−u)` → full sphere, `r²+y²=1`), `Sign{Pos,Neg}`, and `Signed360 {rim: ResidueEdge, polar: signed-lift centred@128 (sign recoverable), azimuth: u16 over 360°}` + `ResidueEncoder::encode_signed`. +9 tests; **helix 72 lib + 7 doctests green; lib clippy `-D warnings` + fmt clean.** **(2) contract** `HelixResidue.elems_per_row` 48→6, downstream tenants shifted (Turbovec 118 / Energy 134 / Plasticity 138 / EntityType 142), budgets re-locked (**Full 154→112, Compressed 98→56**); **613 contract green.** **NO `HelixFlavour` enum** — one canonical encoding, one tenant size (a fixed-offset SoA can't vary width per-class; Hemisphere = degenerate `sign=+`); the contract stays zero-dep, the producer writes `Signed360::to_bytes` into the 6 B. Cheap NOW (POC FULL default, no persisted real instances); after instances persist it's a version bump. Branch, not yet a PR. New: `TD-HELIX-PROBE-CLIPPY` (pre-existing `probe_mantissa_fill` clippy/fmt drift, NOT introduced here — helix is excluded so CI-invisible, same class as the standing `causal-edge` 47/1 red).
1416
>
1517
> **2026-06-15 — MERGED #496** (integrated-cognitive-planner reference map + ValueSchema + FULL POC default): `lance_graph_contract::canonical_node::{ValueSchema, ValueTenant, VALUE_TENANTS}` — the value-side `EdgeCodecFlavor` analog (9 append-only tenants carving `[32,186)`; presets Bootstrap/Cognitive/Compressed/Full). `ClassView::value_schema()` default flipped **Bootstrap→Full (TEMPORARY POC** — every unconfigured class materialises the full slab so consumers transcode against it; `TD-VALUESCHEMA-FULL-POC-DEFAULT` revert-when-POC-concludes; type-level `ValueSchema::default()` stays Bootstrap, only class→schema *resolution* flips). New reference plan `.claude/plans/integrated-cognitive-planner-v1.md` — **§0 ANTI-INVENTION GUARDRAIL (READ FIRST)**, §1–§7 grounded file:line map, §8 7-item additive ledger, §9 3-hardener verdicts; the SPEC for the integrated-planner refactor (~90% exists; remaining = the keystone + 6 seams, NOT a new build). CI 5/5 green; contract 613 lib tests; merge `2e58e034`. **The keystone = `NiblePath::from_guid_prefix` (the 20→≤16-nibble subset) + classid→ClassView read-mode on `lance-graph-ontology::registry` (already an immutable conflict-refusing `entity_type↔NiblePath` bijection)** — the single next unblock that converges the refactor, the tesseract-rs OCR transcode (`contract::ocr` → NodeRow), AND the OGAR-identity migration (`soa-migration-diff-resolution-2026-06-13.md`). HEEL=cache `dolce_id` / HIP·TWIG=deterministic subClassOf descent / registry=recorder-not-minter (verified `registry.rs`+`wikidata_hhtl.rs`). New: `TD-COARSERESIDUE-NO-VALUE-TENANT`, `TD-LAZY-IMPORT-VERSION-PIN`; IDEAS CLAM-residue-ladder TODO.

.claude/board/TECH_DEBT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151

5252
**The debt:** `ClassView::value_schema` (`class_view.rs:233`) returns `ValueSchema::Full` instead of the canon zero-fallback `ValueSchema::Bootstrap`. This INVERTS the zero-fallback ladder for the value-slab *resolution* (specialisation is now opt-IN: mint a class to go smaller/denser). It is layout-preserving (no `NODE_ROW_STRIDE` / `ENVELOPE_LAYOUT_VERSION` change — Full carves within the reserved 480 B) and a one-line revert. The TYPE-level `ValueSchema::default()` stays `Bootstrap`, so the substrate zero-fallback semantics are intact — only the class→schema resolution default flipped. **No invention** (honours the operator's anti-skew guardrail): `Full` activates the already-existing, already-tested 9 `ValueTenant`s; it adds no new property.
5353

54-
**Pay it by:** reverting `class_view.rs:233` to `ValueSchema::Bootstrap` once the consumer POCs settle on their real per-class presets, AND flipping the guard test `value_schema_default_is_full_temporary_poc` (`class_view.rs`) back to assert Bootstrap. The edge-codec axis (`edge_codec_flavor``CoarseOnly`) is a separate knob, untouched; flip it to a residue/PQ flavor only if a consumer POC needs full edge fidelity too. Tests: 613 lib green.
54+
**Pay it by:** reverting `class_view.rs:233` to `ValueSchema::Bootstrap` once the consumer POCs settle on their real per-class presets, AND flipping the guard test `value_schema_default_is_full_temporary_poc` (`class_view.rs`) back to assert Bootstrap. The edge-codec axis (`edge_codec_flavor``CoarseOnly`) is a separate knob, untouched; flip it to a residue/PQ flavor only if a consumer POC needs full edge fidelity too. **PAIRED SITE (2026-06-15):** `ReadMode::DEFAULT` (`canonical_node.rs`) also POC-defaults `value_schema = Full` so the `classid → read-mode` resolver (`classid_read_mode`) agrees with `ClassView` — revert BOTH `value_schema` fields to `Bootstrap` together; the test `read_mode_default_is_full_poc` (`canonical_node.rs`) guards the pairing and flips with them. Tests: 619 lib green.
5555

5656
### TD-NDARRAY-SIMD-POPCNT-NATIVE — `extract_rules` SIGILLs under `-C target-cpu=native` on larger RowMasks (2026-06-14)
5757

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

Lines changed: 237 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,53 @@ impl NodeGuid {
9999
u32::from_le_bytes([self.0[13], self.0[14], self.0[15], 0])
100100
}
101101

102+
/// HEEL — HHT cascade tier 1 (bytes 4..6, LE `u16`).
103+
#[inline]
104+
pub const fn heel(&self) -> u16 {
105+
u16::from_le_bytes([self.0[4], self.0[5]])
106+
}
107+
108+
/// HIP — HHT cascade tier 2 (bytes 6..8, LE `u16`).
109+
#[inline]
110+
pub const fn hip(&self) -> u16 {
111+
u16::from_le_bytes([self.0[6], self.0[7]])
112+
}
113+
114+
/// TWIG — HHT cascade tier 3 (bytes 8..10, LE `u16`).
115+
#[inline]
116+
pub const fn twig(&self) -> u16 {
117+
u16::from_le_bytes([self.0[8], self.0[9]])
118+
}
119+
120+
/// Decode the whole key in one read — every canon group as its native
121+
/// LE-decoded integer. This is the "read the GUID as a GUID" surface: a
122+
/// consumer or OGAR gets `classid + HHT (HEEL/HIP/TWIG) + family + identity`
123+
/// from one call instead of re-deriving each group from raw bytes. The six
124+
/// fields ARE the canon print order — nothing invented, nothing dropped (cf.
125+
/// [`Display`](NodeGuid#impl-Display-for-NodeGuid), which renders the same six).
126+
#[inline]
127+
pub const fn decode(&self) -> GuidParts {
128+
GuidParts {
129+
classid: self.classid(),
130+
heel: self.heel(),
131+
hip: self.hip(),
132+
twig: self.twig(),
133+
family: self.family(),
134+
identity: self.identity(),
135+
}
136+
}
137+
138+
/// The [`ReadMode`] this node's `classid` resolves to — which value tenants
139+
/// to materialise + how to read the edge block. The carrier-method form (the
140+
/// object speaks for itself): a consumer reads `guid.read_mode()`, OGAR reads
141+
/// [`classid_read_mode`]`(guid.classid())`; both inherit the SAME answer from
142+
/// the one [`LazyLock`] registry, so the LE interpretation of the node's bytes
143+
/// is single-sourced. Not `const` — it consults the runtime registry.
144+
#[inline]
145+
pub fn read_mode(&self) -> ReadMode {
146+
classid_read_mode(self.classid())
147+
}
148+
102149
/// Basin-local key: trailing 6 bytes (family ++ identity), zero-padded to u64.
103150
/// After an HHTL radix walk has bound classid+HEEL+HIP+TWIG, this is the only
104151
/// part that still discriminates — a single masked load, no gather.
@@ -147,6 +194,30 @@ impl NodeGuid {
147194
}
148195
}
149196

197+
/// The whole canonical key decoded in one shot — `classid · HEEL · HIP · TWIG ·
198+
/// family · identity`, each as its native LE-decoded integer.
199+
///
200+
/// This is the "read the GUID as a GUID and return classid + HHT + Leaf +
201+
/// identity" contract: one decode, six fields, in canon print order. It invents
202+
/// nothing — it is exactly [`NodeGuid::decode`] of the existing 16-byte key, the
203+
/// same six groups [`NodeGuid`]'s `Display` renders. `family` is the basin
204+
/// "Leaf" and `family ++ identity` is the trailing-6-byte [`NodeGuid::local_key`].
205+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206+
pub struct GuidParts {
207+
/// 0..4 — prefix-routable class id (default `0x0000_0000`).
208+
pub classid: u32,
209+
/// 4..6 — HEEL (HHT cascade tier 1).
210+
pub heel: u16,
211+
/// 6..8 — HIP (HHT cascade tier 2).
212+
pub hip: u16,
213+
/// 8..10 — TWIG (HHT cascade tier 3).
214+
pub twig: u16,
215+
/// 10..13 — family (u24, the basin "Leaf").
216+
pub family: u32,
217+
/// 13..16 — identity (u24).
218+
pub identity: u32,
219+
}
220+
150221
/// Canonical self-describing print: `classid-HEEL-HIP-TWIG-family·identity`.
151222
///
152223
/// The dash-groups ARE the semantic delimiters — every printed GUID is
@@ -155,16 +226,13 @@ impl NodeGuid {
155226
/// order (the field accessors fold LE bytes into u32/u16/u24 first).
156227
impl core::fmt::Display for NodeGuid {
157228
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158-
let h = u16::from_le_bytes([self.0[4], self.0[5]]);
159-
let p = u16::from_le_bytes([self.0[6], self.0[7]]);
160-
let t = u16::from_le_bytes([self.0[8], self.0[9]]);
161229
write!(
162230
f,
163231
"{:08x}-{:04x}-{:04x}-{:04x}-{:06x}{:06x}",
164232
self.classid(),
165-
h,
166-
p,
167-
t,
233+
self.heel(),
234+
self.hip(),
235+
self.twig(),
168236
self.family(),
169237
self.identity(),
170238
)
@@ -261,6 +329,8 @@ const _: () = assert!(core::mem::size_of::<NodeRow>() == 512);
261329

262330
use crate::class_view::FieldMask;
263331
use crate::soa_envelope::{ColumnDescriptor, ColumnKind, SoaEnvelope};
332+
use std::collections::HashMap;
333+
use std::sync::LazyLock;
264334

265335
/// Stable column-id ordinals for [`NodeRow`]'s three top-level slots.
266336
/// `name_id` in the [`ColumnDescriptor`] table; the registry-resolved value
@@ -533,6 +603,80 @@ const _: () = assert!(ValueSchema::Full.tenant_bytes() <= VALUE_SLAB_LEN);
533603
const _: () = assert!(ValueSchema::Full.field_mask().count() as usize == VALUE_TENANTS.len());
534604
const _: () = assert!(ValueSchema::Bootstrap.field_mask().is_empty());
535605

606+
// ── classid → read-mode: the LE contract both the consumer and OGAR inherit ────
607+
608+
/// The **read mode** a `classid` resolves to: the pair of *already-existing*
609+
/// read-mode axes — [`ValueSchema`] (which value tenants to materialise) and
610+
/// [`EdgeCodecFlavor`] (how to read the 16-byte edge block).
611+
///
612+
/// It is NOT a new node property and NOT a SoA column — nothing is stored on the
613+
/// row. This is the *resolution result* (the lens): the value-side analog of
614+
/// "which XSD parses this document". §0 anti-invention — it bundles the two
615+
/// read-mode enums that already exist, adding zero new fields to the node.
616+
///
617+
/// Both consumers and OGAR resolve `classid → ReadMode` through the one
618+
/// [`LazyLock`] registry ([`classid_read_mode`]), so the LE interpretation of a
619+
/// node's bytes is single-sourced: a consumer transcoding a [`NodeRow`] and OGAR
620+
/// minting/projecting the same class read the identical schema.
621+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
622+
pub struct ReadMode {
623+
/// Which value-slab tenants this class materialises.
624+
pub value_schema: ValueSchema,
625+
/// How this class reads its 16-byte edge block.
626+
pub edge_codec: EdgeCodecFlavor,
627+
}
628+
629+
impl ReadMode {
630+
/// The zero-fallback / POC default an *unconfigured* classid resolves to.
631+
///
632+
/// **TEMPORARY (2026-06-15 POC):** `value_schema = Full` mirrors the
633+
/// [`ClassView::value_schema`](crate::class_view::ClassView::value_schema)
634+
/// POC default so an unconfigured class materialises the whole slab for
635+
/// transcode; `edge_codec = CoarseOnly` is the canon zero-fallback edge
636+
/// reading. When the POC ends, flip `value_schema` back to
637+
/// [`ValueSchema::Bootstrap`] HERE and in `ClassView` together (one revert,
638+
/// two sites — the test `read_mode_default_is_full_poc` guards the pairing).
639+
pub const DEFAULT: ReadMode = ReadMode {
640+
value_schema: ValueSchema::Full,
641+
edge_codec: EdgeCodecFlavor::CoarseOnly,
642+
};
643+
644+
/// Both axes are layout-preserving (a preset/flavor re-interprets reserved
645+
/// bytes, never a stride change), so adopting any read-mode needs no
646+
/// `ENVELOPE_LAYOUT_VERSION` bump.
647+
#[inline]
648+
pub const fn is_layout_preserving(self) -> bool {
649+
self.value_schema.is_layout_preserving() && self.edge_codec.is_layout_preserving()
650+
}
651+
}
652+
653+
/// Builtin `classid → ReadMode` registry, built once on first use.
654+
///
655+
/// Immutable after init — the canon "already-immutable ontology registry" shape,
656+
/// the same [`LazyLock`] pattern `lance-graph-ontology` uses for its seed
657+
/// namespace registry. Holds only the canon builtins; a minted class's read-mode
658+
/// is layered in by OGAR one level up. Any classid NOT in the map falls through
659+
/// to [`ReadMode::DEFAULT`] — the same zero-fallback ladder as the key itself
660+
/// (`classid 0 ⇒ default class`).
661+
static BUILTIN_READ_MODES: LazyLock<HashMap<u32, ReadMode>> = LazyLock::new(|| {
662+
let mut m = HashMap::new();
663+
// The canon default class materialises the POC-Full slab (see ReadMode::DEFAULT).
664+
m.insert(NodeGuid::CLASSID_DEFAULT, ReadMode::DEFAULT);
665+
m
666+
});
667+
668+
/// Resolve a `classid` to its [`ReadMode`] — the single source both consumers
669+
/// and OGAR inherit. Reads the [`BUILTIN_READ_MODES`] registry, falling through
670+
/// to [`ReadMode::DEFAULT`] for any unconfigured classid (the key's own
671+
/// zero-fallback ladder). [`NodeGuid::read_mode`] is the carrier-method form.
672+
#[inline]
673+
pub fn classid_read_mode(classid: u32) -> ReadMode {
674+
BUILTIN_READ_MODES
675+
.get(&classid)
676+
.copied()
677+
.unwrap_or(ReadMode::DEFAULT)
678+
}
679+
536680
/// Zero-copy [`SoaEnvelope`] wrapper over a contiguous slice of [`NodeRow`].
537681
///
538682
/// `NodeRow` is `#[repr(C, align(64))]` with the locked 16/16/480 byte
@@ -975,4 +1119,91 @@ mod tests {
9751119
assert_eq!(NODE_ROW_STRIDE, core::mem::size_of::<NodeRow>());
9761120
assert_eq!(VALUE_SLAB_ROW_OFFSET + VALUE_SLAB_LEN, NODE_ROW_STRIDE);
9771121
}
1122+
1123+
// ── GUID decode + classid → read-mode (the keystone) ─────────────────────
1124+
1125+
#[test]
1126+
fn decode_returns_all_six_canon_groups() {
1127+
// One read yields classid + HHT (HEEL/HIP/TWIG) + family + identity, in
1128+
// canon print order — the "read the GUID as a GUID" contract.
1129+
let g = NodeGuid::new(0xDEAD_BEEF, 0x1111, 0x2222, 0x3333, 0x00_00AB, 0x00_00CD);
1130+
let p = g.decode();
1131+
assert_eq!(p.classid, 0xDEAD_BEEF);
1132+
assert_eq!(p.heel, 0x1111);
1133+
assert_eq!(p.hip, 0x2222);
1134+
assert_eq!(p.twig, 0x3333);
1135+
assert_eq!(p.family, 0x00_00AB);
1136+
assert_eq!(p.identity, 0x00_00CD);
1137+
// decode() is exactly the field accessors, no field invented/dropped.
1138+
assert_eq!(p.classid, g.classid());
1139+
assert_eq!(p.family, g.family());
1140+
assert_eq!(p.identity, g.identity());
1141+
}
1142+
1143+
#[test]
1144+
fn hht_accessors_match_display_groups() {
1145+
// The new HEEL/HIP/TWIG accessors fold the same LE bytes Display renders.
1146+
let g = NodeGuid::new(0xDEAD_BEEF, 0xA1B2, 0xC3D4, 0xE5F6, 0x12_3456, 0x78_9ABC);
1147+
assert_eq!(g.heel(), 0xA1B2);
1148+
assert_eq!(g.hip(), 0xC3D4);
1149+
assert_eq!(g.twig(), 0xE5F6);
1150+
// Display's middle three groups are exactly heel-hip-twig in hex.
1151+
let s = g.to_string();
1152+
let groups: Vec<&str> = s.split('-').collect();
1153+
assert_eq!(groups[1], format!("{:04x}", g.heel()));
1154+
assert_eq!(groups[2], format!("{:04x}", g.hip()));
1155+
assert_eq!(groups[3], format!("{:04x}", g.twig()));
1156+
}
1157+
1158+
#[test]
1159+
fn read_mode_default_is_full_poc() {
1160+
// The default classid resolves to the POC read-mode: Full value slab +
1161+
// CoarseOnly edges. This GUARDS the ClassView pairing — ReadMode::DEFAULT
1162+
// .value_schema MUST equal the ClassView POC default (Full). When the POC
1163+
// ends, both flip to Bootstrap together and this test flips with them.
1164+
let rm = classid_read_mode(NodeGuid::CLASSID_DEFAULT);
1165+
assert_eq!(rm, ReadMode::DEFAULT);
1166+
assert_eq!(rm.value_schema, ValueSchema::Full);
1167+
assert_eq!(rm.edge_codec, EdgeCodecFlavor::CoarseOnly);
1168+
assert!(rm.is_layout_preserving());
1169+
}
1170+
1171+
#[test]
1172+
fn read_mode_zero_fallback_for_unconfigured_classid() {
1173+
// Any classid NOT in the builtin registry falls through to DEFAULT — the
1174+
// key's own zero-fallback ladder (classid 0 ⇒ default class), extended to
1175+
// read-mode resolution.
1176+
assert_eq!(classid_read_mode(0xDEAD_BEEF), ReadMode::DEFAULT);
1177+
assert_eq!(classid_read_mode(0x0000_0001), ReadMode::DEFAULT);
1178+
assert_eq!(classid_read_mode(u32::MAX), ReadMode::DEFAULT);
1179+
}
1180+
1181+
#[test]
1182+
fn guid_read_mode_method_delegates_to_registry() {
1183+
// The carrier method (guid.read_mode()) and the free resolver
1184+
// (classid_read_mode(classid)) are the SAME answer — consumer and OGAR
1185+
// inherit one source.
1186+
let g = NodeGuid::new(0xCAFE_BABE, 1, 2, 3, 0x00_0001, 0x00_0002);
1187+
assert_eq!(g.read_mode(), classid_read_mode(g.classid()));
1188+
// A default-class node reads the Full POC slab.
1189+
assert_eq!(NodeGuid::local(0x00_00CD).read_mode(), ReadMode::DEFAULT);
1190+
}
1191+
1192+
#[test]
1193+
fn default_class_node_materialises_full_slab() {
1194+
// End-to-end connect: a bootstrap NodeRow → its classid resolves to Full →
1195+
// the Full preset covers every tenant and uses the locked 112-byte carve.
1196+
let row = sample_row(NodeGuid::CLASSID_DEFAULT, 0x00_00CD);
1197+
let rm = row.key.read_mode();
1198+
assert_eq!(rm.value_schema, ValueSchema::Full);
1199+
assert_eq!(
1200+
rm.value_schema.field_mask().count() as usize,
1201+
VALUE_TENANTS.len(),
1202+
"Full read-mode materialises every value tenant"
1203+
);
1204+
assert_eq!(rm.value_schema.tenant_bytes(), 112);
1205+
// The slab has room (112 ≤ 480) and the choice never grows the stride.
1206+
assert!(rm.value_schema.tenant_bytes() <= VALUE_SLAB_LEN);
1207+
assert!(rm.is_layout_preserving());
1208+
}
9781209
}

0 commit comments

Comments
 (0)