Skip to content

Commit 7410508

Browse files
committed
contract(soa_graph): register Odoo as the third Gotham render domain
Follow-up to PR #557 (SoA-as-graph domain foundation) + the odoo plug-and-play activation. The activation gave odoo a manifest entry + a consumer actor + a namespace bridge but no q2 rendering. PR #557 gave OSINT/FMA a render surface but didn't register odoo. Adding `ODOO_ERP` as a third `DomainSpec` closes the gap: when odoo-rs ships, its NodeRow stream projects into the same Gotham snapshot the q2 cockpit already renders. Three additive surfaces: 1. canonical_node::NodeGuid::CLASSID_ODOO = 0x0000_0050 — third registered domain classid. Chosen to mirror the OGIT G slot (OGIT::ODOO_V1.0 = 50) so classid ↔ G is visually obvious. OSINT picked 0x0007 and FMA 0x0008 (consecutive, not tied to G); ODOO matches G because the W11 activation profile is the source of truth, and 0x0050 doesn't collide with 0x0007/0x0008. 2. canonical_node::ReadMode::ODOO = (Cognitive, CoarseOnly) — hot live- business-reasoning lifecycle. Same hot tenants as OSINT: @api.depends recompute uses the same Meta+Qualia+Fingerprint+Energy+Plasticity carve as NARS hot reasoning; gated account.move.action_post lifecycle reuses Energy + Plasticity columns; NARS truth on reconciliation outcomes fits the Meta+Qualia carve. NOT Compressed (FMA's cold-reference shape), NOT Bootstrap (empty pre-class default), NOT Full (over-materializes the hot path). Registered in BUILTIN_READ_MODES. 3. soa_graph::ODOO_ERP: DomainSpec — classid CLASSID_ODOO, name "Odoo-ERP", in_family_edge "line-of" (account.move ⇆ account.move.line), out_family_edge "references" (account.move → res.partner / account.account / etc.), member_edge "member-of", anchor_families empty (callers supply invoice / journal-entry anchors per workload). Inherits FIBO-FND family slots through lance_graph_callcenter::odoo_alignment per Seam decision 1 / Option B — no new CAM codebook family minted; the DomainSpec is rendering metadata, not a new family. Acceptance: - +2 tests: canonical_node::odoo_classid_resolves_to_its_read_mode + soa_graph::odoo_erp_projects_into_the_same_gotham_surface. Both pass. - 700/700 contract lib tests (was 698 in PR #557; +1 from each new test). - Clippy -D warnings clean (after restructuring the ReadMode::ODOO doc to avoid clippy's `doc list item without indentation` lint — `Meta + Qualia + Fingerprint` looked like a list continuation at line wrap). - Format clean (cargo fmt also picked up a couple of pre-existing comment- alignment + spacing nits in hhtl.rs + nan_projection.rs, included in the commit so the tree stays fmt-clean). The projector is unchanged. project_snapshot(rows, &ODOO_ERP) and nearest_anchor(rows, &ODOO_ERP) work the same as the OSINT/FMA cases — this is consume-don't-duplicate (E-OGAR-AR-SHAPE-REHOME guard). Board hygiene: prepended EPIPHANIES E-ODOO-GOTHAM-VIEW-IS-A-DOMAINSPEC + LATEST_STATE Current Contract Inventory ADDED entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
1 parent 2a0abf5 commit 7410508

6 files changed

Lines changed: 154 additions & 17 deletions

File tree

.claude/board/EPIPHANIES.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
## 2026-06-20 — E-ODOO-GOTHAM-VIEW-IS-A-DOMAINSPEC — extending the plug-and-play activation to PR #557's q2 render surface = registering one more `DomainSpec`; classid `0x0050` mirrors the G slot, `ReadMode::ODOO = Cognitive ⊕ CoarseOnly` matches the hot ERP lifecycle
2+
3+
**Status:** FINDING (additive; the consumption side of E-ODOO-ACTIVATION-PROFILE, on the q2-graph-render axis).
4+
5+
**The shape of the move.** PR #557 ("SoA-as-graph domain foundation for OSINT/Gotham + FMA") shipped `contract::soa_graph::{DomainSpec, project_snapshot, nearest_anchor, OSINT_GOTHAM, FMA_ANATOMY}` + `NodeGuid::{CLASSID_OSINT (0x0007), CLASSID_FMA (0x0008)}` + `ReadMode::{OSINT, FMA}` + the q2 cockpit's rendering surface keyed off the 32-byte HEAD (zero value decode, `E-ANCHOR-IS-A-HEAD-FIELD-NOT-A-VALUE-TYPE`). The plug-and-play activation that just landed (`E-ODOO-ACTIVATION-PROFILE`) gives odoo a manifest entry + a consumer actor + a namespace bridge, but no q2 rendering. The natural close: one more `DomainSpec` const + one more `CLASSID_ODOO` + one more `ReadMode::ODOO` — the projector is domain-agnostic and re-used unchanged.
6+
7+
**What landed.**
8+
9+
1. **`canonical_node::NodeGuid::CLASSID_ODOO = 0x0000_0050`** — the third registered domain classid, chosen to mirror the OGIT G slot (`OGIT::ODOO_V1.0 = 50`) so `classid ↔ G` is visually obvious. OSINT picked `0x0007` and FMA `0x0008` (consecutive, not tied to G); ODOO matches G because the W11 activation profile is the source of truth for this consumer, and `0x0050 ≠ 0x0007/0x0008` so no classid collision.
10+
11+
2. **`canonical_node::ReadMode::ODOO = ReadMode { value_schema: Cognitive, edge_codec: CoarseOnly }`** — the hot live-business-reasoning lifecycle (same as OSINT: `@api.depends` recompute is structurally identical to NARS reasoning's hot tenants; gated `account.move.action_post` lifecycle reuses `Cognitive`'s Energy + Plasticity columns; NARS truth on reconciliation outcomes fits the existing `Meta + Qualia` carve). NOT Compressed (that's FMA's cold-reference shape — ERP is not cold), NOT Bootstrap (that's the empty pre-class default), NOT Full (over-materializes for the hot path). Registered in `BUILTIN_READ_MODES` next to OSINT and FMA.
12+
13+
3. **`soa_graph::ODOO_ERP = DomainSpec`** — `classid: CLASSID_ODOO`, `name: "Odoo-ERP"`, `anchor_families: &[]` (callers supply invoice / journal-entry anchor families per workload), `in_family_edge: "line-of"` (the dominant intra-family adjacency: `account.move.line ⇆ account.move`), `out_family_edge: "references"` (cross-model: `account.move → res.partner`, etc.), `member_edge: "member-of"`. Inherits FIBO-FND family slots through `lance_graph_callcenter::odoo_alignment` per Seam decision 1 / Option B — no new CAM codebook family minted; the `DomainSpec` is rendering metadata, not a new family.
14+
15+
**Acceptance.** +2 tests (`canonical_node::odoo_classid_resolves_to_its_read_mode` + `soa_graph::odoo_erp_projects_into_the_same_gotham_surface`). Both pass. Total contract lib: 700 tests (was 698 in PR #557; +1 from `odoo_classid_*`, +1 from `odoo_erp_projects_*`). Clippy `-D warnings` clean (after restructuring the `ReadMode::ODOO` doc to avoid clippy's `doc list item without indentation` lint, which fired because `Meta + Qualia + Fingerprint` looked like a markdown list continuation at line-wrap). Format clean.
16+
17+
**Why this is a closure, not an extension.** The W11 plug-and-play activation was the *consumer-side* wire-in (manifest → bridge → actor). PR #557 was the *renderer-side* wire-in for OSINT/FMA. These two arcs were independent until now — the q2 cockpit could render OSINT/FMA but had no way to render odoo's family-grouped partner / accounting / product / stock graph. Adding ODOO_ERP closes that gap: when odoo-rs ships, its NodeRow stream projects into the same Gotham snapshot the q2 cockpit already renders. **No new layer; no new bridge; no new projection function** — the same `project_snapshot(rows, &ODOO_ERP)` call the OSINT case uses.
18+
19+
**Cross-refs:** `E-ODOO-ACTIVATION-PROFILE` (the activation arc this closes); PR #557 / `E-ANCHOR-IS-A-HEAD-FIELD-NOT-A-VALUE-TYPE` (the render surface this consumes); `modules/odoo/manifest.yaml` (the activation profile that named G=50); `E-OGAR-AR-SHAPE-REHOME` (the construction-error guard — this consumes existing PR #557 types, does not duplicate them); `lance_graph_callcenter::odoo_alignment` (the FIBO-FND family-slot inheritance the DomainSpec rides on, Seam decision 1 / Option B).
20+
21+
---
22+
123
## 2026-06-20 — E-ANCHOR-IS-A-HEAD-FIELD-NOT-A-VALUE-TYPE — graph STRUCTURE (domain, family grouping, hierarchy, stability anchors, adjacency) must key off the 32-byte HEAD (classid / family / HHTL path), never the value slab; only then does the whole neo4j/Gotham view — and "FMA bones as stability anchor" — stay zero-value-decode at memory-scan speed
224

325
**Status:** FINDING (perennial; shipped `contract::soa_graph` + `NiblePath::family_hop_count`, 2026-06-20).

.claude/board/LATEST_STATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@
115115

116116
## Current Contract Inventory (lance-graph-contract)
117117

118+
> **2026-06-20 — ADDED (Odoo domain on the q2 Gotham render surface, follow-up of #557)**: `lance_graph_contract::canonical_node::{NodeGuid::CLASSID_ODOO = 0x0000_0050, ReadMode::ODOO = (Cognitive, CoarseOnly)}` + `BUILTIN_READ_MODES` entry + `soa_graph::ODOO_ERP: DomainSpec` (classid CLASSID_ODOO, name "Odoo-ERP", in_family_edge "line-of", out_family_edge "references", member_edge "member-of", anchor_families empty). Third registered graph domain after `OSINT_GOTHAM` / `FMA_ANATOMY` from PR #557; the projector (`project_snapshot` / `nearest_anchor`) is domain-agnostic and re-used unchanged. Classid `0x0050` mirrors the OGIT G slot (`OGIT::ODOO_V1.0 = 50`) so the manifest → domain wiring is visually obvious; OSINT/FMA picked consecutive `0x0007`/`0x0008` independent of their G slots. `ReadMode::ODOO = Cognitive ⊕ CoarseOnly` matches the hot live-business-reasoning lifecycle (`@api.depends` recompute uses the same Meta+Qualia+Fingerprint+Energy+Plasticity carve as NARS hot reasoning; gated `account.move.action_post` lifecycle reuses Energy + Plasticity columns). +2 tests (`odoo_classid_resolves_to_its_read_mode` + `odoo_erp_projects_into_the_same_gotham_surface`); 700/700 contract lib tests pass; clippy `-D warnings` + fmt clean. Closes the q2-render side of the plug-and-play arc started in `E-ODOO-ACTIVATION-PROFILE`: when odoo-rs ships, its NodeRow stream projects into the same Gotham snapshot the q2 cockpit already renders, via `project_snapshot(rows, &ODOO_ERP)`. EPIPHANIES `E-ODOO-GOTHAM-VIEW-IS-A-DOMAINSPEC`. Branch `claude/hydrate-dolce-dul-owl-Ce9Oa`.
119+
118120
> **2026-06-20 — ACTIVATED (odoo plug-and-play profile, W11 D-RACTOR-SUPERVISOR consumer slot 50)**: `modules/odoo/manifest.yaml` flipped from inert to active, registering `OGIT::ODOO_V1 = (50, 1)` with: `rbac_policy: odoo_policy`, `stack_profile {audit_retention_days: 3650 (§147 AO 10-year accounting retention), requires_fail_closed: true, escalation: llm}`, 15 `action_capabilities` (the odoo DO arm — `post_journal_entry`/`validate_invoice`/`reconcile_payment`/`delete_partner`/`export_xml_invoice` as `escalate`; partner+journal+PDF+email writes as `permit_with_audit`; read paths as `permit`), `actor: {crate: odoo-rs, type: OdooActor, message_type: OdooMessage}`, `inherits_from: fibofnd`. The build-script's `non_inert_no_actor` strict validator passes; `MANIFEST_METADATA[ODOO_V1]` now carries `inert: false, rbac_policy: Some("odoo_policy"), actor_crate: Some("odoo-rs"), actor_type: Some("OdooActor")`. Companion skeletons land additively in two crates: (1) `lance_graph_supervisor::actors::OdooConsumerActor` (~140 LOC, 1:1 sibling of `MedcareConsumerActor`, `ODOO_G = 50` / `ODOO_VERSION = 1`, reads `ODOO_AUDIT_SALT` env var, handles all 8 `ConsumerEnvelope` arms with TODO markers awaiting `odoo-rs`); (2) `lance_graph_ontology::bridges::OdooBridge` (~50 LOC, `bridge_id = "odoo"`, locks to namespace `"Odoo"`, sibling of `MedcareBridge`/`WoaBridge`). Per `hydrators/odoo.rs` Seam decision 1 / Option B, odoo classes inherit existing FIBO/SKR family slots via `owl:equivalentClass`-routing — no new CAM codebook family minted. Foundry-family resolution stays in `lance_graph_callcenter::odoo_alignment`; `OdooBridge` is the public-name → OGIT URI translator the consumer crate dispatches through. Build clean, 8/8 `manifest_codegen` tests pass (incl. `test_non_inert_no_actor_rejected` regression-proves the activated manifest), supervisor + ontology lib tests pass. Closes the precise gap in the plug-and-play seven-component request (ontology / callcenter / contract / rbac / surrealdb-on-kvs-lance / OGAR / ractor) — odoo is now "drop the odoo-rs crate and go" through the existing CallcenterSupervisor + GenericBridge surface. EPIPHANIES `E-ODOO-ACTIVATION-PROFILE`. Branch `claude/hydrate-dolce-dul-owl-Ce9Oa`.
119121

120122
> **2026-06-18 — ADDED (probe-excel-compute-dag-v1 Inc 0, the `compute_dag` Core gap)**: `lance_graph_contract::class_view::{ComputeEdge, compute_dag_is_acyclic}` + `ClassView::compute_dag(class) -> &[ComputeEdge]` (default `&[]`, zero-fallback). `ComputeEdge {target: u8, inputs: &'static [u8]}` is the harvest-sourced recompute edge (`emitted_by` target ← `depends_on` inputs; field positions index the class `FieldMask`), `const`-constructible like `MethodSig`/`ActionDef` (the harvest IS the manifest). `compute_dag_is_acyclic` is the **registry-build gate** — a cyclic recompute DAG (formula loop / `@api.depends` cycle / self-loop) is rejected at build (Kahn over ≤64 positions, allocation-free; out-of-range positions ignored, no panic, mirrors `FieldMask::from_positions`). This is the Core home for computed-field recompute *dispatch* that EVERY computed-field AR consumer needs (Odoo `@api.depends`, Excel formulas, medcare lab-trends, woa calc, q2 cells — they reduce to a sheet; `E-EXCEL-SHADER-PROJECTION`) and the NNUE-incremental existence-proof shape (`E-CHESS-TENSOR-PROVEN`). **Layout-preserving**: a default trait method + a free fn, resolution metadata ABOVE the SoA, stores nothing on the row, zero `NODE_ROW_STRIDE`/`ENVELOPE_LAYOUT_VERSION` impact (core-gap-auditor's EXTEND-CORE, never an adapter-state hack). The instance recompute that consumes it is gated per-cell by the cycle-aware `write_row` (`E-SOA-CYCLE-OWNERSHIP`). Additive, zero-dep; +4 tests (default-empty, acyclic-chain, cycle/self-loop/3-cycle rejected, out-of-range ignored); 10/10 class_view, clippy/fmt clean. Sibling `ClassView::constraints` (`validation_kind`-sourced) deferred to Inc-follow-up. Plan: `.claude/plans/probe-excel-compute-dag-v1.md`. Branch `claude/particle-wave-click-epiphany`.

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

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ impl NodeGuid {
4949
/// anchors). Resolves to [`ReadMode::FMA`] (cold `Compressed` reference value +
5050
/// `CoarseOnly` part-of adjacency).
5151
pub const CLASSID_FMA: u32 = 0x0000_0008;
52+
/// OGAR class for the **Odoo ERP** domain — partners (res.partner family) +
53+
/// accounting (account.move / account.move.line) + product (product.template
54+
/// / product.product) + stock (stock.move / stock.picking), inheriting
55+
/// FIBO-FND via `modules/odoo/manifest.yaml`'s `inherits_from: fibofnd`.
56+
/// Resolves to [`ReadMode::ODOO`] (hot `Cognitive` value — ERP is live
57+
/// business reasoning, not cold reference data — over `CoarseOnly` adjacency
58+
/// edges: in-family links account.move.line → account.move, out-of-family
59+
/// links account.move → res.partner / account.account / account.journal).
60+
/// Value chosen to match the G slot in `lance-graph-contract::manifest::OGIT`
61+
/// (`ODOO_V1 = (50, 1)`), keeping the classid ↔ G visually obvious.
62+
pub const CLASSID_ODOO: u32 = 0x0000_0050;
5263

5364
/// Construct from the six canonical groups. `family`/`identity` use their low 3 bytes.
5465
///
@@ -688,6 +699,20 @@ impl ReadMode {
688699
edge_codec: EdgeCodecFlavor::CoarseOnly,
689700
};
690701

702+
/// The **Odoo ERP** read-mode ([`NodeGuid::CLASSID_ODOO`]): a *hot* business
703+
/// reasoning graph — [`ValueSchema::Cognitive`] (the live-lifecycle tenants,
704+
/// for `@api.depends` recompute, gated `account.move.action_post`
705+
/// transitions, and NARS truth on reconciliation outcomes) over
706+
/// [`EdgeCodecFlavor::CoarseOnly`] adjacency (`account.move ⇆ account.move.line`
707+
/// in-family; `account.move → res.partner / account.account / account.journal`
708+
/// out-of-family). Same hot lifecycle as OSINT; the difference is the
709+
/// family-codebook inheritance (FIBO-FND for ODOO, none-explicit for OSINT)
710+
/// and the `DomainSpec` edge labelling in `soa_graph::ODOO_ERP`.
711+
pub const ODOO: ReadMode = ReadMode {
712+
value_schema: ValueSchema::Cognitive,
713+
edge_codec: EdgeCodecFlavor::CoarseOnly,
714+
};
715+
691716
/// Both axes are layout-preserving (a preset/flavor re-interprets reserved
692717
/// bytes, never a stride change), so adopting any read-mode needs no
693718
/// `ENVELOPE_LAYOUT_VERSION` bump.
@@ -709,11 +734,13 @@ static BUILTIN_READ_MODES: LazyLock<HashMap<u32, ReadMode>> = LazyLock::new(|| {
709734
let mut m = HashMap::new();
710735
// The canon default class materialises the POC-Full slab (see ReadMode::DEFAULT).
711736
m.insert(NodeGuid::CLASSID_DEFAULT, ReadMode::DEFAULT);
712-
// OSINT/Gotham (hot entity graph) + FMA anatomy (cold structural reference) —
713-
// the two registered graph domains (see `soa_graph`). Both read edges as
714-
// CoarseOnly adjacency; they differ in the value schema (hot vs cold).
737+
// OSINT/Gotham (hot entity graph) + FMA anatomy (cold structural reference)
738+
// + Odoo ERP (hot business reasoning, FIBO-FND-inheriting) — the three
739+
// registered graph domains (see `soa_graph`). All read edges as `CoarseOnly`
740+
// adjacency; they differ in the value schema (hot vs cold).
715741
m.insert(NodeGuid::CLASSID_OSINT, ReadMode::OSINT);
716742
m.insert(NodeGuid::CLASSID_FMA, ReadMode::FMA);
743+
m.insert(NodeGuid::CLASSID_ODOO, ReadMode::ODOO);
717744
m
718745
});
719746

@@ -1284,4 +1311,25 @@ mod tests {
12841311
);
12851312
assert!(osint.is_layout_preserving() && fma.is_layout_preserving());
12861313
}
1314+
1315+
#[test]
1316+
fn odoo_classid_resolves_to_its_read_mode() {
1317+
// The third registered graph domain: Odoo ERP — hot business reasoning
1318+
// (Cognitive value, same as OSINT) over CoarseOnly adjacency. The
1319+
// classid IS the G slot (0x0050 = 80 = ODOO_V1 G slot in
1320+
// `manifest::OGIT::ODOO_V1`), keeping the classid ↔ G visually obvious.
1321+
let odoo = classid_read_mode(NodeGuid::CLASSID_ODOO);
1322+
assert_eq!(odoo, ReadMode::ODOO);
1323+
assert_eq!(odoo.value_schema, ValueSchema::Cognitive);
1324+
assert_eq!(odoo.edge_codec, EdgeCodecFlavor::CoarseOnly);
1325+
assert_eq!(NodeGuid::CLASSID_ODOO, 0x0000_0050);
1326+
assert_eq!(
1327+
NodeGuid::new(NodeGuid::CLASSID_ODOO, 1, 2, 3, 0xAB, 0xCD).read_mode(),
1328+
ReadMode::ODOO
1329+
);
1330+
assert!(odoo.is_layout_preserving());
1331+
// Distinct from the two sibling domains (no classid collision).
1332+
assert_ne!(NodeGuid::CLASSID_ODOO, NodeGuid::CLASSID_OSINT);
1333+
assert_ne!(NodeGuid::CLASSID_ODOO, NodeGuid::CLASSID_FMA);
1334+
}
12871335
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ mod tests {
682682
let sib = NiblePath::root(0x1).child(0x2).child(0x3).child(0x9);
683683
assert_eq!(a.family_hop_count(sib), 2);
684684
assert_eq!(sib.family_hop_count(a), 2); // symmetric
685-
// parent = 1 hop
685+
// parent = 1 hop
686686
let parent = NiblePath::root(0x1).child(0x2).child(0x3);
687687
assert_eq!(a.family_hop_count(parent), 1);
688688
// cousins: share (1)(2), differ from depth 3 down → (4-2)+(4-2) = 4

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ mod tests {
125125
#[test]
126126
fn subnormal_and_zero_are_finite() {
127127
// exponent-zero patterns (zero, subnormals) must NOT be flagged
128-
let rows = vec![board_with(0.0), board_with(-0.0), board_with(f32::MIN_POSITIVE)];
128+
let rows = vec![
129+
board_with(0.0),
130+
board_with(-0.0),
131+
board_with(f32::MIN_POSITIVE),
132+
];
129133
assert!(project_energy_nonfinite(&rows).is_clean());
130134
}
131135
}

0 commit comments

Comments
 (0)