Skip to content

Commit 21c37eb

Browse files
committed
fix(#441): CodeRabbit — STATUS_BOARD #440->#441 + signature/object_view as OdooEntity carrier methods
Two valid CodeRabbit findings: 1. STATUS_BOARD D-CLS rows referenced the plan PR #440; the change set is #441 — updated the 5 mutable PR/Evidence cells (sanctioned: that column is the mutable field). 2. signature(&OdooEntity)/object_view(&OdooEntity) were free functions over the carrier; CLAUDE.md 'The Click' litmus says method-on-the-carrier. Refactored into ; bodies unchanged (entity->self), all 7 call sites -> e.signature()/e.object_view(). 240 ontology lib green; clippy+fmt clean. https://claude.ai/code/session_01R9AWgFa65uPnLyS2my2d2R
1 parent 250b66f commit 21c37eb

2 files changed

Lines changed: 72 additions & 67 deletions

File tree

.claude/board/STATUS_BOARD.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -568,11 +568,11 @@ 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 |
572-
| D-CLS-RES | `class_resolver`: `RegistryClassView` impls `ClassView` over the live OntologyRegistry — the ontology-side 'parser' (class_id -> shape, DOLCE resolved LATE via classify_odoo from the cache URI, memoized over the O(n) registry scan) | lance-graph-ontology | 200 | LOW | **In PR** | #440 D-CLS; makes the contract trait live; field-set supplied (D-CLS audit deferred); 4 teeth-tests |
573-
| D-CLS-SIG | `class_signature`: deterministic structural-signature audit of curated OdooEntity consts (FNV-1a over kind+field-hist+method-hist+state-machine) -> shape-family group-by + `object_view()` derives the real ObjectView bit-basis (fills the D-CLS-RES placeholder) | lance-graph-ontology | 230 | LOW | **In PR** | #440 D-CLS; the HONEST D-CLS-3 (group-by-on-structural-hash, NOT aerial-cluster vaporware, classes.md:43); 4 teeth-tests over real l1 data |
574-
| D-CLS-AUDIT | `class_signature` corpus audit: `curated_entities()` (all 15 l-lanes, 64 consts) + `corpus_summary()` + falsifiable test that the real curated corpus collapses entities->fewer shape-families (classes.md:42 CONFIRMED on real data, not asserted) | lance-graph-ontology | 90 | LOW | **In PR** | #440 D-CLS Wave-2 input; +clippy fix (unused FieldMask import in class_resolver) |
575-
| D-CLS-RENDER | `ClassView::render_rows` + `RenderRow{label,predicate}` — the off-bits-skipped render surface (C2 presence-only; template-agnostic, askama engine deferred to its own crate-Wave) | lance-graph-contract | 50 | LOW | **In PR** | #440 D-CLS; the render LOGIC (classes.md:49), not the engine; +doc-lint fix |
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** | #441 D-CLS contract foundation; OD-gates ratified; presence!=semantics (C2); N3 stable positions; 3 teeth-tests |
572+
| D-CLS-RES | `class_resolver`: `RegistryClassView` impls `ClassView` over the live OntologyRegistry — the ontology-side 'parser' (class_id -> shape, DOLCE resolved LATE via classify_odoo from the cache URI, memoized over the O(n) registry scan) | lance-graph-ontology | 200 | LOW | **In PR** | #441 D-CLS; makes the contract trait live; field-set supplied (D-CLS audit deferred); 4 teeth-tests |
573+
| D-CLS-SIG | `class_signature`: deterministic structural-signature audit of curated OdooEntity consts (FNV-1a over kind+field-hist+method-hist+state-machine) -> shape-family group-by + `object_view()` derives the real ObjectView bit-basis (fills the D-CLS-RES placeholder) | lance-graph-ontology | 230 | LOW | **In PR** | #441 D-CLS; the HONEST D-CLS-3 (group-by-on-structural-hash, NOT aerial-cluster vaporware, classes.md:43); 4 teeth-tests over real l1 data |
574+
| D-CLS-AUDIT | `class_signature` corpus audit: `curated_entities()` (all 15 l-lanes, 64 consts) + `corpus_summary()` + falsifiable test that the real curated corpus collapses entities->fewer shape-families (classes.md:42 CONFIRMED on real data, not asserted) | lance-graph-ontology | 90 | LOW | **In PR** | #441 D-CLS Wave-2 input; +clippy fix (unused FieldMask import in class_resolver) |
575+
| D-CLS-RENDER | `ClassView::render_rows` + `RenderRow{label,predicate}` — the off-bits-skipped render surface (C2 presence-only; template-agnostic, askama engine deferred to its own crate-Wave) | lance-graph-contract | 50 | LOW | **In PR** | #441 D-CLS; the render LOGIC (classes.md:49), not the engine; +doc-lint fix |
576576

577577
---
578578

crates/lance-graph-ontology/src/odoo_blueprint/class_signature.rs

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -69,69 +69,74 @@ fn method_kind_bucket(k: OdooMethodKind) -> usize {
6969
}
7070
}
7171

72-
/// Compute the [`StructuralSignature`] of an entity (deterministic, name-independent).
73-
pub fn signature(entity: &OdooEntity) -> StructuralSignature {
74-
// Canonical tuple: [kind_disc, field_hist x6, method_hist x5, has_state_machine].
75-
// Counts are saturated to u8 so the byte layout is stable + the hash deterministic.
76-
let mut buf = [0u8; 1 + 6 + 5 + 1];
77-
buf[0] = entity.kind as u8;
72+
/// Structural-derivation methods on the [`OdooEntity`] carrier (the carrier owns
73+
/// the state these read — `entity.signature()` / `entity.object_view()`, not free
74+
/// functions over it; CLAUDE.md "The Click" litmus: method-on-the-carrier).
75+
impl OdooEntity {
76+
/// Compute this entity's [`StructuralSignature`] (deterministic, name-independent).
77+
pub fn signature(&self) -> StructuralSignature {
78+
// Canonical tuple: [kind_disc, field_hist x6, method_hist x5, has_state_machine].
79+
// Counts are saturated to u8 so the byte layout is stable + the hash deterministic.
80+
let mut buf = [0u8; 1 + 6 + 5 + 1];
81+
buf[0] = self.kind as u8;
7882

79-
let mut field_hist = [0u32; 6];
80-
for f in entity.fields {
81-
field_hist[field_kind_bucket(f.kind)] += 1;
82-
}
83-
for (i, c) in field_hist.iter().enumerate() {
84-
buf[1 + i] = (*c).min(255) as u8;
85-
}
83+
let mut field_hist = [0u32; 6];
84+
for f in self.fields {
85+
field_hist[field_kind_bucket(f.kind)] += 1;
86+
}
87+
for (i, c) in field_hist.iter().enumerate() {
88+
buf[1 + i] = (*c).min(255) as u8;
89+
}
8690

87-
let mut method_hist = [0u32; 5];
88-
for m in entity.methods {
89-
method_hist[method_kind_bucket(m.kind)] += 1;
90-
}
91-
for (i, c) in method_hist.iter().enumerate() {
92-
buf[7 + i] = (*c).min(255) as u8;
93-
}
91+
let mut method_hist = [0u32; 5];
92+
for m in self.methods {
93+
method_hist[method_kind_bucket(m.kind)] += 1;
94+
}
95+
for (i, c) in method_hist.iter().enumerate() {
96+
buf[7 + i] = (*c).min(255) as u8;
97+
}
9498

95-
buf[12] = u8::from(entity.state_machine.is_some());
99+
buf[12] = u8::from(self.state_machine.is_some());
96100

97-
StructuralSignature(fnv1a(&buf))
98-
}
101+
StructuralSignature(fnv1a(&buf))
102+
}
99103

100-
/// Derive the per-class [`ObjectView`] **field-set** from an entity's declared
101-
/// fields — the real bit-basis. Field position `i` here is the stable
102-
/// [`FieldMask`](lance_graph_contract::class_view::FieldMask) bit `i` (N3).
103-
///
104-
/// Field order = declaration order (append-only stability: new fields append at
105-
/// higher positions, existing positions never move). The first textual/`Char`
106-
/// field becomes the `primary_label`; the `DisplayTemplate` is chosen by size
107-
/// (`<= 4` fields → `Card`, else `Detail`). Capped at
108-
/// [`FieldMask::MAX_FIELDS`](lance_graph_contract::class_view::FieldMask::MAX_FIELDS)
109-
/// (64) — the mask cannot address beyond a `u64`.
110-
pub fn object_view(entity: &OdooEntity) -> ObjectView {
111-
use lance_graph_contract::class_view::FieldMask;
104+
/// Derive this entity's per-class [`ObjectView`] **field-set** — the real
105+
/// bit-basis. Field position `i` here is the stable
106+
/// [`FieldMask`](lance_graph_contract::class_view::FieldMask) bit `i` (N3).
107+
///
108+
/// Field order = declaration order (append-only stability: new fields append at
109+
/// higher positions, existing positions never move). The first textual/`Char`
110+
/// field becomes the `primary_label`; the `DisplayTemplate` is chosen by size
111+
/// (`<= 4` fields → `Card`, else `Detail`). Capped at
112+
/// [`FieldMask::MAX_FIELDS`](lance_graph_contract::class_view::FieldMask::MAX_FIELDS)
113+
/// (64) — the mask cannot address beyond a `u64`.
114+
pub fn object_view(&self) -> ObjectView {
115+
use lance_graph_contract::class_view::FieldMask;
112116

113-
let cap = FieldMask::MAX_FIELDS as usize;
114-
let fields: Vec<FieldRef> = entity
115-
.fields
116-
.iter()
117-
.take(cap)
118-
.map(|f| FieldRef::new(f.name, f.name)) // label defaults to name; OGIT resolves the display label late
119-
.collect();
117+
let cap = FieldMask::MAX_FIELDS as usize;
118+
let fields: Vec<FieldRef> = self
119+
.fields
120+
.iter()
121+
.take(cap)
122+
.map(|f| FieldRef::new(f.name, f.name)) // label defaults to name; OGIT resolves the display label late
123+
.collect();
120124

121-
let template = if fields.len() <= 4 {
122-
DisplayTemplate::Card
123-
} else {
124-
DisplayTemplate::Detail
125-
};
125+
let template = if fields.len() <= 4 {
126+
DisplayTemplate::Card
127+
} else {
128+
DisplayTemplate::Detail
129+
};
126130

127-
let mut view = ObjectView::new(template, fields);
128-
// primary_label = the first textual field (the headline), if any.
129-
view.primary_label = entity
130-
.fields
131-
.iter()
132-
.find(|f| field_kind_bucket(f.kind) == 0)
133-
.map(|f| f.name.to_string());
134-
view
131+
let mut view = ObjectView::new(template, fields);
132+
// primary_label = the first textual field (the headline), if any.
133+
view.primary_label = self
134+
.fields
135+
.iter()
136+
.find(|f| field_kind_bucket(f.kind) == 0)
137+
.map(|f| f.name.to_string());
138+
view
139+
}
135140
}
136141

137142
/// One audited entity row: its name, ORM kind, derived signature, field count.
@@ -150,7 +155,7 @@ pub fn audit(entities: &[OdooEntity]) -> Vec<AuditRow> {
150155
.iter()
151156
.map(|e| AuditRow {
152157
model_name: e.model_name,
153-
signature: signature(e),
158+
signature: e.signature(),
154159
field_count: e.fields.len(),
155160
method_count: e.methods.len(),
156161
has_state_machine: e.state_machine.is_some(),
@@ -166,7 +171,7 @@ pub fn shape_families(entities: &[OdooEntity]) -> Vec<(StructuralSignature, Vec<
166171
let mut families: BTreeMap<u32, Vec<&'static str>> = BTreeMap::new();
167172
for e in entities {
168173
families
169-
.entry(signature(e).0)
174+
.entry(e.signature().0)
170175
.or_default()
171176
.push(e.model_name);
172177
}
@@ -237,21 +242,21 @@ mod tests {
237242
#[test]
238243
fn signature_is_deterministic_and_name_independent() {
239244
// Same entity → same signature on repeat (deterministic).
240-
let a = signature(&l1::ACCOUNT_MOVE);
241-
let b = signature(&l1::ACCOUNT_MOVE);
245+
let a = l1::ACCOUNT_MOVE.signature();
246+
let b = l1::ACCOUNT_MOVE.signature();
242247
assert_eq!(a, b, "signature must be deterministic");
243248
// account.move and account.move.line have DIFFERENT structure (one is a
244249
// stateful header, the other a line) → different signatures.
245250
assert_ne!(
246-
signature(&l1::ACCOUNT_MOVE),
247-
signature(&l1::ACCOUNT_MOVE_LINE),
251+
l1::ACCOUNT_MOVE.signature(),
252+
l1::ACCOUNT_MOVE_LINE.signature(),
248253
"structurally different entities must not collide"
249254
);
250255
}
251256

252257
#[test]
253258
fn object_view_derives_the_bit_basis_from_fields() {
254-
let v = object_view(&l1::ACCOUNT_MOVE);
259+
let v = l1::ACCOUNT_MOVE.object_view();
255260
// Field count = the entity's declared fields (capped at 64), in order.
256261
assert_eq!(v.fields.len(), l1::ACCOUNT_MOVE.fields.len().min(64));
257262
// Position 0 is the first declared field (stable bit 0).

0 commit comments

Comments
 (0)