Skip to content

Commit 931e63f

Browse files
committed
feat(contract): D-CLS-FM — class_view (FieldMask + ClassView meta-DTO; class flies ABOVE the agnostic SoA)
The XML-parse framing: OGIT today = hashtable single lookups (uri->row); the class = a META lookup (class_id->shape) composing them. SoA row=XML doc, ObjectView=XSD, ClassView= parser+schema, FieldMask=present optional elements, askama=XSLT. Classes fly as a meta-DTO ABOVE the SoA so the SoA stays agnostic: zero labels in the bytes, labels/template/DOLCE resolved LATE from the OGIT cache at projection (classes.md:39 resolve-not-store; core inv #1). C2 free: bit=presence (SoA), bit->label=resolution (above). Zero-dep: ClassId(u16, reuses soa_view::class_id) + FieldMask(u64) + ClassView trait (fields/template/dolce_category_id/ project) extending ontology::ObjectView/FieldRef/DisplayTemplate (not duplicating). Dep-inversion like MailboxSoaView. 3 teeth-tests + 496 contract lib green; clippy+fmt clean. Board: STATUS_BOARD D-CLS-FM + EPIPHANIES. Deferred: ontology impl, group-by audit (64 curated), askama render crate. https://claude.ai/code/session_01R9AWgFa65uPnLyS2my2d2R
1 parent b31464d commit 931e63f

4 files changed

Lines changed: 284 additions & 0 deletions

File tree

.claude/board/EPIPHANIES.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 2026-05-31 — SHIPPED-in-PR: D-CLS-FM — class_view (FieldMask + ClassView meta-DTO; the class flies ABOVE the agnostic SoA)
2+
3+
**Status:** SHIPPED-in-PR (#440 D-CLS contract foundation). The XML-parse framing made real, OD-gates ratified.
4+
5+
`contract::class_view` (zero-dep): `ClassId(u16)` (reuses soa_view::class_id width, OD-CLASSID-WIDTH) + `FieldMask(u64)` presence bitmask (of/with/has/count, C2 presence-NEVER-semantics, N3 stable append-only positions) + **`ClassView` resolver TRAIT** (`fields`/`template`/`dolce_category_id`/`field_label`/`project`) + `ClassProjection` iterator. EXTENDS the existing `ontology::ObjectView`/`FieldRef`/`DisplayTemplate` (the per-class ordered field set = the bit basis), does NOT duplicate.
6+
7+
**The architecture (user's framing):** OGIT today = hashtable single lookups (uri→row); the class = a META lookup (class_id→shape: ordered fields+labels+template+bit-basis), composing many leaf lookups. XML map: SoA row=XML doc (agnostic bytes), ObjectView=XSD, ClassView=parser+schema, FieldMask=which optional elements present, askama=XSLT. **Classes fly as a meta-DTO ABOVE the SoA so the SoA stays agnostic — zero labels in the bytes; labels/template/DOLCE resolved LATE from the OGIT cache at projection** (classes.md:39 resolve-not-store; core inv #1 nothing-semantic-in-register). C2 falls out free: bit=presence (on SoA, structural); bit→field→label=resolution (above SoA, semantic).
8+
9+
**Layering (dep-inversion like MailboxSoaView):** contract=agnostic surface (FieldMask + ClassView trait, zero-dep); ontology=implements ClassView (the parser, resolves labels from OGIT hashmap — DOLCE-from-cache per OD ratification); render=consumes project()+template, skips off-bits. 3 teeth-tests: presence-bits, meta-DTO-projects-above-agnostic-(class,mask), late-label-resolution. 496 contract lib green; clippy+fmt clean.
10+
11+
**Next (deferred, the D-CLS waves, P0-corrected):** ontology-side `impl ClassView` over OntologyRegistry (the resolver/parser); D-CLS-2 structural-signature audit (scope: 64 curated consts, NOT the false-66/real-381 — brutal P0-1); D-CLS-3 deterministic group-by-on-structural-hash (NOT aerial-cluster vaporware); FieldPositionTable freeze-append-only-on-first-emit (AP2); the render crate (askama). class_id stays a discriminator OUTSIDE the CAM content layer.
12+
13+
**Cross-ref:** #440 plan; OD-gate ratifications (2026-05-31); REVIEW VERDICT P0/AP2; `ontology::ObjectView` (extended); `soa_view::class_id` (#437, reused); classes.md:39/48/49 (resolve / delta-bitmask / off-bits-skip); MailboxSoaView (the dep-inversion precedent #437).
114
## 2026-05-31 — OD-GATES RATIFIED (spec owner) for odoo-classes-bitmask-render-v1 (#440): DOLCE-from-cache (dissolves the 6-vs-4), ClassId u16 reuse-existing, kind+class_id both DTO-views, askama
215

316
**Status:** RATIFICATION (spec owner = user, 2026-05-31). Unblocks the plan's Wave-0 pre-conditions, WITH the review-verdict P0 corrections folded in. Two answers reframe the plan, not just answer it.

.claude/board/STATUS_BOARD.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ Plan path: `.claude/plans/unified-soa-convergence-v1.md`. Handover `.claude/hand
568568
| D-MBX-A6-P2 | Rubicon lifecycle enforcement + exec-target tag: `KanbanColumn::{next_phases, can_transition_to, is_absorbing}` (the lifecycle DAG) + `MailboxSoaOwner::try_advance_phase` (checked, `RubiconTransitionError`) + `ExecTarget{Native,Jit,SurrealQl,Elixir}` on `KanbanMove` | lance-graph-contract | 120 | LOW | **In PR** | builds on P1; 489 lib tests (+4); downstream cargo-check clean; gates the ractor owner-impl + planner emit (P3) |
569569
| D-MBX-A6-P3a | StyleStrategy: thinking-style -> cluster -> mechanism -> recipe_kernels Tactic selection (planning substrate; carries tau JIT addr) | lance-graph-planner | 130 | LOW | **In PR** | #439; first cut of A6-P3 consumer wiring; planner now consumes contract recipes/styles; deferred: i4-32D decode, Outcome->Candidate, tau->JIT, membrane commit |
570570
| D-MBX-A6-P3-M1 | `Tactic::requires() -> ThoughtMask` + `ThoughtField`/`ThoughtMask` (checklist-as-data keystone): 34 tactics declare their ThoughtCtx field-reads; `covered_by` = reliability-coverage gate | lance-graph-contract | 120 | LOW | **In PR** | #439; the panel-recalibrated keystone (extraction not construction); makes P1/P7/P11 derived; teeth-test asserts masks varied not stub |
571+
| D-CLS-FM | `class_view`: FieldMask(u64 presence) + ClassView meta-DTO resolver trait + ClassProjection (the class flies ABOVE the SoA; labels resolved late from OGIT cache, zero in the bytes) — extends ObjectView, reuses class_id | lance-graph-contract | 270 | LOW | **In PR** | #440 D-CLS contract foundation; OD-gates ratified; presence!=semantics (C2); N3 stable positions; 3 teeth-tests |
571572

572573
---
573574

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
//! # `class_view` — the class as a META lookup that flies ABOVE the SoA.
2+
//!
3+
//! ## The XML-parse framing
4+
//!
5+
//! Today OGIT (`lance-graph-ontology::OntologyRegistry`) is a **hashtable doing
6+
//! single lookups**: `uri → row`, `entity_type_id → row` — one key, one value,
7+
//! O(1), leaf. That is the *single* lookup. What a class needs is a **meta
8+
//! lookup**: `class_id → the whole shape` (ordered field set + labels + template
9+
//! + the presence-bit basis). The class composes many leaf lookups into one
10+
//! shape — the way an XSD schema composes element declarations.
11+
//!
12+
//! ```text
13+
//! SoA row = the XML document (agnostic bytes, no meaning)
14+
//! class / ObjectView = the XSD schema (the shape: which fields, in order)
15+
//! ClassView (this) = the parser+schema (projects row → typed view, late-bound)
16+
//! FieldMask = which optional elements are present (structural)
17+
//! askama template = the XSLT (renders the projected view)
18+
//! ```
19+
//!
20+
//! ## Classes fly as a meta-DTO ABOVE the SoA — the SoA stays agnostic
21+
//!
22+
//! The load-bearing rule (`cognitive-risc-classes.md`:39 "the meta-DTO resolves;
23+
//! it does not store"; `cognitive-risc-core.md` invariant #1 "nothing semantic in
24+
//! the register file"): the SoA row carries **only** `class_id` + a presence
25+
//! [`FieldMask`] + agnostic columns. **Zero labels in the bytes.** The
26+
//! labels / template / DOLCE-category are resolved *at projection time* by the
27+
//! flying meta-DTO from the OGIT cache — never hand-rolled onto the row.
28+
//!
29+
//! That makes the presence/semantics split (C2) fall out for free:
30+
//! - **bit = presence** — structural, lives on the SoA ("field N is populated").
31+
//! - **bit → field → label → template** — semantic resolution, lives in the
32+
//! meta-DTO *above* the SoA. A bit NEVER means "field N behaves differently."
33+
//!
34+
//! ## Layering (dependency inversion, same shape as `MailboxSoaView`)
35+
//!
36+
//! - **contract (here, zero-dep):** the agnostic surface — [`FieldMask`] presence
37+
//! bits + the [`ClassView`] resolver *trait*. Extends the existing
38+
//! [`crate::ontology::ObjectView`] (the per-class ordered field set = the bit
39+
//! basis), does not duplicate it.
40+
//! - **ontology (one layer up):** *implements* [`ClassView`] — the "parser" that
41+
//! walks the class shape and resolves labels late from the OGIT hashmap.
42+
//! - **render (a consumer):** reads the projected view + mask, picks the askama
43+
//! template, skips off-bits.
44+
45+
use crate::ontology::{DisplayTemplate, FieldRef};
46+
47+
/// Per-row class discriminator — the Cognitive-RISC `class_id` / `shape_id`.
48+
///
49+
/// A `u16` (≤ 65,535 shape-families; OD-CLASSID-WIDTH ratified). It is a
50+
/// *discriminator*, never a content hash — it stays OUTSIDE the CAM identity
51+
/// layer (`I-VSA-IDENTITIES`: never hashed-as-content, never superposed). Reuses
52+
/// the width of the existing [`crate::soa_view::MailboxSoaView::class_id`] accessor.
53+
pub type ClassId = u16;
54+
55+
/// A class's **presence bitmask** — one bit per field of its class
56+
/// [`ObjectView`](crate::ontology::ObjectView), set iff that field is populated
57+
/// on a given instance.
58+
///
59+
/// The instance's *delta from its class* (`cognitive-risc-classes.md`:48), as
60+
/// **pure presence bits**. Bit position `N` = the `N`-th field in the class's
61+
/// ordered field list — stable + append-only (N3): once instances persist, a
62+
/// field's bit position never moves and retired bits are never reused. Zero-dep
63+
/// (`u64`, no `bitflags`); mask width is bounded by the *class's* field count
64+
/// (dozens), never the entity union.
65+
///
66+
/// **Presence, NEVER semantics (C2).** `has(n)` answers "is field n populated
67+
/// here"; it must never gate "field n means something different here."
68+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
69+
pub struct FieldMask(pub u64);
70+
71+
impl FieldMask {
72+
/// The empty mask (no fields populated).
73+
pub const EMPTY: Self = Self(0);
74+
75+
/// Maximum addressable field positions in one `u64` mask.
76+
pub const MAX_FIELDS: u32 = 64;
77+
78+
/// Build a mask from the populated field positions.
79+
pub const fn from_positions(positions: &[u8]) -> Self {
80+
let mut bits = 0u64;
81+
let mut i = 0;
82+
while i < positions.len() {
83+
bits |= 1u64 << (positions[i] as u64 & 63);
84+
i += 1;
85+
}
86+
Self(bits)
87+
}
88+
89+
/// Set field position `n` as populated.
90+
#[inline]
91+
pub const fn with(self, n: u8) -> Self {
92+
Self(self.0 | (1u64 << (n as u64 & 63)))
93+
}
94+
95+
/// Is field position `n` populated? (presence — C2)
96+
#[inline]
97+
pub const fn has(self, n: u8) -> bool {
98+
self.0 & (1u64 << (n as u64 & 63)) != 0
99+
}
100+
101+
/// Number of populated fields.
102+
#[inline]
103+
pub const fn count(self) -> u32 {
104+
self.0.count_ones()
105+
}
106+
107+
/// Is nothing populated?
108+
#[inline]
109+
pub const fn is_empty(self) -> bool {
110+
self.0 == 0
111+
}
112+
}
113+
114+
/// The class as a **meta lookup that flies above the SoA** — the resolver trait.
115+
///
116+
/// An implementor (in `lance-graph-ontology`, over the OGIT cache) is the
117+
/// "parser+schema": given a `class_id` it resolves the class's ordered field set,
118+
/// labels, DOLCE category, and render template — all LATE-bound from the cache,
119+
/// none stored on the SoA row. The contract owns only the *vocabulary*; the cache
120+
/// owns the *answers* (dependency inversion, like `PlannerContract`/`MailboxSoaView`).
121+
///
122+
/// "Single lookup" (leaf, today) vs "meta lookup" (the class, this trait): a
123+
/// single lookup is `uri → row`; a meta lookup is `class_id → shape`, composing
124+
/// many leaf lookups into one projected view.
125+
pub trait ClassView {
126+
/// The class's ordered field set — the bit basis. Position `i` in this slice
127+
/// is the stable [`FieldMask`] bit `i` (N3 append-only). This IS the
128+
/// per-class [`ObjectView`](crate::ontology::ObjectView)'s `fields`.
129+
fn fields(&self, class: ClassId) -> &[FieldRef];
130+
131+
/// Which askama template renders this class.
132+
fn template(&self, class: ClassId) -> DisplayTemplate;
133+
134+
/// The DOLCE upper-category of this class, RESOLVED from the ontology cache
135+
/// (not a stored enum on the row — OD-DOLCE "use the ontology cache"). Returned
136+
/// as the cache's opaque category id; the consumer maps it to its own enum.
137+
fn dolce_category_id(&self, class: ClassId) -> u8;
138+
139+
/// The label of field position `n` in `class`, resolved late from the cache
140+
/// (locale resolution is the consumer's job). `None` if `n` is out of range.
141+
fn field_label(&self, class: ClassId, n: u8) -> Option<&str> {
142+
self.fields(class).get(n as usize).map(|f| f.label.as_str())
143+
}
144+
145+
/// The class's field count (mask width). Must be `<= FieldMask::MAX_FIELDS`.
146+
#[inline]
147+
fn field_count(&self, class: ClassId) -> usize {
148+
self.fields(class).len()
149+
}
150+
151+
/// Project an instance: iterate `(field, populated?)` pairs in class order,
152+
/// gating each field by the presence `mask`. This is the render surface — the
153+
/// consumer skips off-bits (`cognitive-risc-classes.md`:49). The SoA supplied
154+
/// only `(class, mask)`; the labels come from the cache, above the SoA.
155+
fn project<'a>(&'a self, class: ClassId, mask: FieldMask) -> ClassProjection<'a> {
156+
ClassProjection {
157+
fields: self.fields(class),
158+
mask,
159+
pos: 0,
160+
}
161+
}
162+
}
163+
164+
/// An iterator over a class's fields paired with their presence bit — the
165+
/// projected view a render template consumes (off-bits are still yielded with
166+
/// `present = false` so the template can `{% if present %}`-skip them).
167+
pub struct ClassProjection<'a> {
168+
fields: &'a [FieldRef],
169+
mask: FieldMask,
170+
pos: usize,
171+
}
172+
173+
impl<'a> Iterator for ClassProjection<'a> {
174+
/// `(field, present)` — `present` is the C2 presence bit, never a semantics bit.
175+
type Item = (&'a FieldRef, bool);
176+
177+
fn next(&mut self) -> Option<Self::Item> {
178+
let f = self.fields.get(self.pos)?;
179+
let present = self.mask.has(self.pos as u8);
180+
self.pos += 1;
181+
Some((f, present))
182+
}
183+
}
184+
185+
#[cfg(test)]
186+
mod tests {
187+
use super::*;
188+
use crate::ontology::{DisplayTemplate, FieldRef};
189+
190+
/// A tiny in-contract ClassView fake — proves the trait is satisfiable and the
191+
/// meta-DTO projects above an agnostic (class, mask) input, no labels stored.
192+
struct FakeClasses {
193+
// class 7 = a 3-field shape ("invoice": amount, tax, partner)
194+
invoice: Vec<FieldRef>,
195+
}
196+
197+
impl FakeClasses {
198+
fn new() -> Self {
199+
Self {
200+
invoice: vec![
201+
FieldRef::new("amount_total", "Total"),
202+
FieldRef::new("amount_tax", "Tax"),
203+
FieldRef::new("partner_id", "Partner"),
204+
],
205+
}
206+
}
207+
}
208+
209+
impl ClassView for FakeClasses {
210+
fn fields(&self, class: ClassId) -> &[FieldRef] {
211+
match class {
212+
7 => &self.invoice,
213+
_ => &[],
214+
}
215+
}
216+
fn template(&self, _class: ClassId) -> DisplayTemplate {
217+
DisplayTemplate::Detail
218+
}
219+
fn dolce_category_id(&self, _class: ClassId) -> u8 {
220+
0 // Endurant, resolved from the cache in the real impl
221+
}
222+
}
223+
224+
#[test]
225+
fn field_mask_is_presence_bits() {
226+
let m = FieldMask::from_positions(&[0, 2]); // amount + partner populated, tax absent
227+
assert!(m.has(0) && !m.has(1) && m.has(2));
228+
assert_eq!(m.count(), 2);
229+
assert!(!m.is_empty() && FieldMask::EMPTY.is_empty());
230+
assert_eq!(
231+
FieldMask::EMPTY.with(1).with(1),
232+
FieldMask::from_positions(&[1])
233+
);
234+
}
235+
236+
#[test]
237+
fn meta_dto_projects_above_agnostic_class_mask() {
238+
let classes = FakeClasses::new();
239+
// The SoA supplied ONLY (class_id=7, mask) — no labels. The meta-DTO
240+
// resolves the labels from above.
241+
let mask = FieldMask::from_positions(&[0, 2]); // tax (pos 1) is off
242+
let projected: Vec<(&str, bool)> = classes
243+
.project(7, mask)
244+
.map(|(f, present)| (f.label.as_str(), present))
245+
.collect();
246+
assert_eq!(
247+
projected,
248+
vec![("Total", true), ("Tax", false), ("Partner", true)],
249+
"labels come from the cache above the SoA; presence comes from the mask"
250+
);
251+
// The render template skips off-bits: only present fields surface.
252+
let rendered: Vec<&str> = classes
253+
.project(7, mask)
254+
.filter(|(_, present)| *present)
255+
.map(|(f, _)| f.label.as_str())
256+
.collect();
257+
assert_eq!(rendered, vec!["Total", "Partner"], "off-bit (Tax) skipped");
258+
}
259+
260+
#[test]
261+
fn field_label_resolves_late_from_class_not_row() {
262+
let classes = FakeClasses::new();
263+
assert_eq!(classes.field_label(7, 1), Some("Tax"));
264+
assert_eq!(classes.field_label(7, 9), None); // out of range
265+
assert_eq!(classes.field_count(7), 3);
266+
assert_eq!(classes.field_count(999), 0); // unknown class
267+
}
268+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub mod atoms;
4242
pub mod auth;
4343
pub mod callcenter;
4444
pub mod cam;
45+
pub mod class_view;
4546
pub mod codegen_spine;
4647
pub mod cognitive_shader;
4748
pub mod collapse_gate;
@@ -96,6 +97,7 @@ pub mod world_map;
9697
pub mod world_model;
9798

9899
// Re-exports for the most commonly used collapse_gate types.
100+
pub use class_view::{ClassId, ClassProjection, ClassView, FieldMask};
99101
pub use collapse_gate::{CollapseGateEmission, GateDecision, MailboxId, MergeMode};
100102
pub use kanban::{ExecTarget, KanbanColumn, KanbanMove, RubiconTransitionError};
101103
pub use soa_view::{MailboxSoaOwner, MailboxSoaView};

0 commit comments

Comments
 (0)