Skip to content

feat(spo): Foundry-shape emitters — codegen_spine + odoo_ontology + action_emitter + link_chain#432

Merged
AdaWorldAPI merged 5 commits into
mainfrom
claude/activate-lance-graph-att-k2pHI
May 29, 2026
Merged

feat(spo): Foundry-shape emitters — codegen_spine + odoo_ontology + action_emitter + link_chain#432
AdaWorldAPI merged 5 commits into
mainfrom
claude/activate-lance-graph-att-k2pHI

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

Lands three Foundry-shape SPO emitters totalling ~1450 LOC + 25 tests, all green.

Closes deliverables 1 + 2 of the four-list in .claude/knowledge/foundry-workshop-elixir-rust-evaluation.md.

lance-graph-contract::codegen_spine

Four canonical contracts gating the
triplets → static codegen → askama route SoC → askama GUI shape layering:

  1. TripletProjection + roundtrip_eq — build-time gate that any const projection round-trips losslessly (set-eq on (s,p,o) + truth tolerance).
  2. OdooMethodKind (16 stable-id variants, ALL[16], from_id) + RouteBucket trait — one canonical bucket enum across every layer.
  3. WidgetRender<B: RouteBucket> — widgets take typed buckets, never raw triples.
  4. Genericity { Agnostic, Domain } — explicit "codegen const vs read at runtime" marker.

Zero new dependencies (std-only). 6/6 tests.

lance-graph::graph::spo::odoo_ontology

SPO loader for the Foundry-shape Odoo ontology — 22 245 triples (388 Object Types, 3 107 Properties, 3 328 Functions, plus emitted_by/depends_on/raises/reads_field/traverses_relation edges with NARS truth values). 4/4 tests.

lance-graph::graph::spo::action_emitter

Composes per-function SPO edges into Foundry-shape ActionSpec records:

ActionSpec {
  id, family,
  effects   ← reverse emitted_by,
  inputs    ← forward depends_on on each effect,
  raises    ← forward raises (guard signal),
  reads     ← forward reads_field,
  traverses ← forward traverses_relation,
}

Plus is_pure_guard() / is_pure_compute() / is_trivial() classifiers.
Shipped ontology test: 22245 triples → 3328 functions, account_move._compute_amount emits amount_total with non-empty dependency closure. 9/9 tests.

lance-graph::graph::spo::link_chain

Decomposes flat dotted depends_on paths into per-hop chains. The 5-hop example from the evaluation doc verified end-to-end (odoo:account_move.line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual). Intentionally string-only — target ObjectType resolution stays in the consumer crate to keep the lance-graph crate graph acyclic. 10/10 tests.

Architecture invariants preserved

  • lance-graph-contract remains zero-dep std-only.
  • lance-graph emitters live at the string/codegen layer above the fingerprint SpoStore; no internal API touched.
  • All output collections deterministic (BTreeSet → sorted Vec).

Review pattern (per session directive)

Each new module went through:
build (with /// work markers) → opus-4.8 reviewer pass (idiomatic Rust, test coverage, marker removal) → orchestrator-run cargo verify.

Reviewer-1 (action_emitter): eliminated 4 BTreeSet::cloned() allocations via a borrow-instead-of-allocate helper + 2 new edge-case tests.
Reviewer-2 (link_chain): collapsed two-pass validation into a single split('.') loop, replaced O(n) Vec::remove(0) with O(1) split_first/split_last; added 4 malformed-input assertions + 1 test pinning every SplitStats field.

Test plan

  • cargo test -p lance-graph-contract --lib codegen_spine → 6/6
  • cargo test -p lance-graph --lib graph::spo::odoo_ontology → 4/4
  • cargo test -p lance-graph --lib graph::spo::action_emitter → 9/9
  • cargo test -p lance-graph --lib graph::spo::link_chain → 10/10
  • cargo test -p lance-graph --lib graph::spo:: (all SPO) → 75/75
  • No regressions in pre-existing SPO tests (51 pre-existing all pass).

Out of scope (deliverables 3+4, next PR cycle)

  • Governance extraction from Odoo ir.model.access.csv → role-permission triples (closes Gap 3 in the evaluation doc).
  • Workshop widget emitter — ontology Object Type → object-table / object-view / action-button widget spec (the largest unbuilt piece).

Generated by Claude Code

Summary by CodeRabbit

  • Documentation

    • Added Semantic Operational Syntax Handbook v0.1.
    • Added Foundry Workshop Elixir→Rust evaluation.
    • Added activity log entry recording Opus 4.8 deliverables completion.
  • New Features

    • Introduced deterministic codegen spine and GUI widget rendering contracts.
    • Added ontology loader/parser, action-spec projection, and dependency-link decomposition.
  • Chores

    • Expanded public surface to expose the new components.
  • Tests

    • Added extensive unit and regression tests ensuring parsing, determinism, and expected ontology counts.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c265e97b-1d77-4d03-b448-c2990b00f220

📥 Commits

Reviewing files that changed from the base of the PR and between f6f98e0 and 0e5ab98.

📒 Files selected for processing (10)
  • .claude/board/AGENT_LOG.md
  • .claude/knowledge/foundry-workshop-elixir-rust-evaluation.md
  • .claude/knowledge/semantic-operational-handbook-v0.1.md
  • crates/lance-graph-contract/src/codegen_spine.rs
  • crates/lance-graph-contract/src/lib.rs
  • crates/lance-graph/src/graph/spo/action_emitter.rs
  • crates/lance-graph/src/graph/spo/link_chain.rs
  • crates/lance-graph/src/graph/spo/mod.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson
✅ Files skipped from review due to trivial changes (2)
  • crates/lance-graph/src/graph/spo/mod.rs
  • .claude/board/AGENT_LOG.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/lance-graph-contract/src/lib.rs
  • crates/lance-graph-contract/src/codegen_spine.rs
  • crates/lance-graph/src/graph/spo/link_chain.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.rs

📝 Walkthrough

Walkthrough

This PR implements the four core Opus 4.8 deliverables: foundational codegen spine contracts for ontology-driven pipelines, an Odoo ontology loader, a deterministic action specification projection, and a dependency chain decomposition module. All modules include extensive unit tests validating correctness, determinism, and integration with the embedded ontology fixture.

Changes

Ontology-Driven Action and Link Chain Emitters with Core Codegen Contracts

Layer / File(s) Summary
Architectural context and design evaluation
.claude/knowledge/foundry-workshop-elixir-rust-evaluation.md, .claude/knowledge/semantic-operational-handbook-v0.1.md
Handbook and evaluation documents establish the three-layer model (Workshop → Elixir DSL → Rust/SPO), verify ontology substrate existence and function resolution determinism, enumerate gaps in widget emitters/action structure/governance extraction/dependency resolution, and list the four concrete deliverables implemented here.
Codegen spine contracts and primitives
crates/lance-graph-contract/src/codegen_spine.rs, crates/lance-graph-contract/src/lib.rs
Canonical Triple type with (s,p,o) identity, TripletProjection trait with configurable truth tolerance for lossless round-trip validation, OdooMethodKind enum with 16 stable variants and lookup, RouteBucket and WidgetRender traits for rendering pipelines, Genericity marker, and comprehensive unit tests validating projection stability and trait compilability.
Ontology triple parsing and SPO loading
crates/lance-graph/src/graph/spo/odoo_ontology.rs
OntologyTriple data model matching ndjson records with NARS truth fields; parse_triples deserializes and skips invalid lines; load_ontology hashes IRIs into fingerprints, builds edges with truth values, and inserts into SpoStore under content-addressed keys. Unit tests validate triple count, predicate histogram, forward reachability, duplicate collapse, and function-count assertions.
Action specification projection from ontology
crates/lance-graph/src/graph/spo/action_emitter.rs
ActionSpec struct captures function identity and derived effects/inputs/raises/reads/traverses as sorted vectors for determinism. Core logic indexes predicate maps, extracts effects via emitted_by reverse lookup, and derives direct inputs from depends_on without transitive closure. Includes trivial/pure-guard/pure-compute classifiers and unit tests verifying fixture projection, deterministic ordering, filtering, shipped ontology function count, and real dependency presence.
Dependency chain decomposition into per-hop links
crates/lance-graph/src/graph/spo/link_chain.rs
LinkChain struct decomposes depends_on IRIs into source_family, hops, and leaf; split_chain parses and validates odoo:-prefixed or raw dotted paths; split_all_depends_on groups results by subject with deduplication for stable output; SplitStats aggregates well-formed/malformed counts and max depth. Unit tests cover multi-hop splits, malformed rejection, indexing/deduplication, and ontology sanity assertions.
Module exports and integration
crates/lance-graph/src/graph/spo/mod.rs, .claude/board/AGENT_LOG.md
Exposes action_emitter, link_chain, and odoo_ontology from SPO submodule; adds agent log entry documenting completion of Opus 4.8 deliverables with test results and "Outcome: DONE" summary.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #331: This PR realizes the "codegen spine" contracts architecture and triple-based ontology projection model, directly addressing the goal of promoting a canonical triplet spine and lossless projection validation into the contract crate.

Poem

🐰 Four emitters spring to life beneath the SPO,
Triple by triple, deterministic flow—
Actions crystallize, chains unwind so neat,
Ontology-driven semantics, a feat!
Opus 4.8 complete, the groundwork laid for what's next. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically summarizes the main changes: it identifies the four new modules (codegen_spine, odoo_ontology, action_emitter, link_chain) added across the SPO layer with the 'feat(spo)' scope and 'Foundry-shape emitters' descriptor, matching the primary intent and scope of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/activate-lance-graph-att-k2pHI

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d0dfd6014c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


// Truth-value tolerance check.
let tol = P::truth_tolerance();
if tol > 0.0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enforce zero-tolerance truth checks

Here the default truth_tolerance() is 0.0, which the trait documents as lossless, but this guard skips the truth comparison entirely for that default. Any projection that preserves (s,p,o) keys while decompiling arbitrary f/c values will incorrectly pass roundtrip_eq; only implementors that opt into a positive tolerance are checked. Please run the truth comparison for zero tolerance as an exact check instead of disabling it.

Useful? React with 👍 / 👎.

AdaWorldAPI pushed a commit that referenced this pull request May 29, 2026
…tology

Pure rustfmt output (cargo fmt -p lance-graph). Line-wrapping
preferences (collapse 4-line .get/.unwrap_or chains, expand
single-line function signatures past 100 cols). No semantic changes.

Fixes the `format` CI check on PR #432.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/knowledge/foundry-workshop-elixir-rust-evaluation.md:
- Around line 24-36: The fenced diagram block in the markdown lacks a language
tag causing markdownlint MD040; update the triple-backtick fence around the
ASCII diagram in .claude/knowledge/foundry-workshop-elixir-rust-evaluation.md to
include a language (e.g., add "text" after ```), ensuring the opening fence
becomes ```text so the diagram is properly annotated for linting.

In @.claude/knowledge/semantic-operational-handbook-v0.1.md:
- Around line 22-29: Many fenced code blocks in
.claude/knowledge/semantic-operational-handbook-v0.1.md are unlabeled causing
MD040 warnings; go through the document and add appropriate language identifiers
(e.g., text, json, elixir, bash) to each triple-backtick block so linters can
parse them. Locate unlabeled fences near headings like "Ontology" and elsewhere,
pick the most specific language for the content in each block (use text for
plain examples), and update all ``` blocks consistently to ```text, ```json,
```elixir, ```bash, etc., ensuring no unlabeled fences remain.

In `@crates/lance-graph-contract/src/codegen_spine.rs`:
- Around line 159-179: The truth-value check is skipped when
P::truth_tolerance() == 0.0 due to the `if tol > 0.0` guard; to fix, remove that
conditional so the comparison loop always runs and keep the existing comparison
using `tol` (i.e., use `(r.f - f0).abs() > tol || (r.c - c0).abs() > tol`) so
that a zero tolerance enforces exact equality; update the block around `let tol
= P::truth_tolerance()` and the loop over `regenerated` (which builds `in_truth`
from `input` and constructs the `RoundTripFailure` with `P::name()`) to always
perform the check.

In `@crates/lance-graph/src/graph/spo/odoo_ontology.rs`:
- Around line 73-101: Change parse_triples to return Result<Vec<OntologyTriple>,
Error> and make load_ontology return Result<SpoStore, Error>, surfacing JSON
parse errors instead of silently discarding them; specifically replace the
.filter_map(... .ok()) in parse_triples with explicit per-line
serde_json::from_str error handling that yields a descriptive snafu error
(include the offending line and serde error) and propagate that Result up to
load_ontology so it fails fast; keep using label_fp, SpoBuilder::build_edge,
dn_hash, TruthValue::new, and SpoStore::insert but call them only after
successful parsing, and add appropriate snafu error variants and context per the
crate's error patterns to identify malformed NDJSON lines and I/O/format issues.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 52a26cc9-fd5e-4335-9f0e-7fda7e1de373

📥 Commits

Reviewing files that changed from the base of the PR and between 4f2ad6e and d0dfd60.

📒 Files selected for processing (10)
  • .claude/board/AGENT_LOG.md
  • .claude/knowledge/foundry-workshop-elixir-rust-evaluation.md
  • .claude/knowledge/semantic-operational-handbook-v0.1.md
  • crates/lance-graph-contract/src/codegen_spine.rs
  • crates/lance-graph-contract/src/lib.rs
  • crates/lance-graph/src/graph/spo/action_emitter.rs
  • crates/lance-graph/src/graph/spo/link_chain.rs
  • crates/lance-graph/src/graph/spo/mod.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson

Comment on lines +24 to +36
```
Workshop low-code surface (what a domain expert authors)
│ view/action/widget bound to ontology
Elixir-syntax abstraction (readable DSL — NOT BEAM at runtime)
│ tree-sitter-elixir parse → typed resolve against ontology
Rust compiled underneath (the holy grail — executable semantics)
│ recipe → KernelHandle → ractor mailbox → SPO store
SPO ontology substrate (what names resolve against)
odoo_ontology.spo.ndjson → SpoStore (22 245 triples, VERIFIED)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify a fenced code language to satisfy markdownlint MD040.

The diagram fence is unlabeled, which triggers lint warnings. Please annotate it as text (or another appropriate language).

💡 Suggested change
-```
+```text
  Workshop low-code surface   (what a domain expert authors)
        │  view/action/widget bound to ontology
        ▼
  Elixir-syntax abstraction   (readable DSL — NOT BEAM at runtime)
        │  tree-sitter-elixir parse → typed resolve against ontology
        ▼
  Rust compiled underneath    (the holy grail — executable semantics)
        │  recipe → KernelHandle → ractor mailbox → SPO store
        ▼
  SPO ontology substrate       (what names resolve against)
  odoo_ontology.spo.ndjson → SpoStore (22 245 triples, VERIFIED)
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 24-24: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/knowledge/foundry-workshop-elixir-rust-evaluation.md around lines 24
- 36, The fenced diagram block in the markdown lacks a language tag causing
markdownlint MD040; update the triple-backtick fence around the ASCII diagram in
.claude/knowledge/foundry-workshop-elixir-rust-evaluation.md to include a
language (e.g., add "text" after ```), ensuring the opening fence becomes
```text so the diagram is properly annotated for linting.

Comment on lines +22 to +29
```
Ontology
→ Objects
→ Links
→ Actions
→ Functions
→ Workshop / Slate / Quiver / Object Explorer / Automate
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks across this document.

This file has many unlabeled code fences, which triggers repeated MD040 warnings. Please label blocks (text, elixir, json, etc.) consistently.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 22-22: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/knowledge/semantic-operational-handbook-v0.1.md around lines 22 -
29, Many fenced code blocks in
.claude/knowledge/semantic-operational-handbook-v0.1.md are unlabeled causing
MD040 warnings; go through the document and add appropriate language identifiers
(e.g., text, json, elixir, bash) to each triple-backtick block so linters can
parse them. Locate unlabeled fences near headings like "Ontology" and elsewhere,
pick the most specific language for the content in each block (use text for
plain examples), and update all ``` blocks consistently to ```text, ```json,
```elixir, ```bash, etc., ensuring no unlabeled fences remain.

Comment thread crates/lance-graph-contract/src/codegen_spine.rs Outdated
Comment on lines +73 to +101
pub fn parse_triples(ndjson: &str) -> Vec<OntologyTriple> {
ndjson
.lines()
.filter(|l| !l.trim().is_empty())
.filter_map(|l| serde_json::from_str::<OntologyTriple>(l).ok())
.collect()
}

/// Load an ndjson ontology document into a fresh [`SpoStore`].
///
/// Each triple's S/P/O labels are hashed to fingerprints via [`label_fp`]
/// (identity-by-name, deterministic), the edge is built with its NARS truth
/// value, and inserted under a content-addressed key `dn_hash("s|p|o")`.
///
/// Returns the populated store; the triple count equals
/// `parse_triples(ndjson).len()` minus any exact `(s,p,o)` key collisions
/// (the extractor de-duplicates, so collisions are not expected).
pub fn load_ontology(ndjson: &str) -> SpoStore {
let mut store = SpoStore::new();
for t in parse_triples(ndjson) {
let subj = label_fp(&t.s);
let pred = label_fp(&t.p);
let obj = label_fp(&t.o);
let record = SpoBuilder::build_edge(&subj, &pred, &obj, TruthValue::new(t.f, t.c));
let key = dn_hash(&format!("{}|{}|{}", t.s, t.p, t.o));
store.insert(key, &record);
}
store
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast instead of silently dropping malformed ontology lines.

At Line 77, .ok() discards parse failures, so corrupted NDJSON can produce a partial SpoStore with no signal. Please return a Result from parse_triples/load_ontology and propagate line-level parse errors.

Suggested direction
+use snafu::{ResultExt, Snafu};
+
+#[derive(Debug, Snafu)]
+pub enum OntologyLoadError {
+    #[snafu(display("Invalid ontology NDJSON at line {line}: {source}"))]
+    ParseLine { line: usize, source: serde_json::Error },
+}
 
-pub fn parse_triples(ndjson: &str) -> Vec<OntologyTriple> {
+pub fn parse_triples(ndjson: &str) -> Result<Vec<OntologyTriple>, OntologyLoadError> {
     ndjson
         .lines()
-        .filter(|l| !l.trim().is_empty())
-        .filter_map(|l| serde_json::from_str::<OntologyTriple>(l).ok())
+        .enumerate()
+        .filter(|(_, l)| !l.trim().is_empty())
+        .map(|(line, l)| {
+            serde_json::from_str::<OntologyTriple>(l)
+                .context(ParseLineSnafu { line: line + 1 })
+        })
         .collect()
 }
 
-pub fn load_ontology(ndjson: &str) -> SpoStore {
+pub fn load_ontology(ndjson: &str) -> Result<SpoStore, OntologyLoadError> {
     let mut store = SpoStore::new();
-    for t in parse_triples(ndjson) {
+    for t in parse_triples(ndjson)? {
         // ...
     }
-    store
+    Ok(store)
 }

As per coding guidelines crates/**/*.rs: “...reuse snafu error patterns”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lance-graph/src/graph/spo/odoo_ontology.rs` around lines 73 - 101,
Change parse_triples to return Result<Vec<OntologyTriple>, Error> and make
load_ontology return Result<SpoStore, Error>, surfacing JSON parse errors
instead of silently discarding them; specifically replace the .filter_map(...
.ok()) in parse_triples with explicit per-line serde_json::from_str error
handling that yields a descriptive snafu error (include the offending line and
serde error) and propagate that Result up to load_ontology so it fails fast;
keep using label_fp, SpoBuilder::build_edge, dn_hash, TruthValue::new, and
SpoStore::insert but call them only after successful parsing, and add
appropriate snafu error variants and context per the crate's error patterns to
identify malformed NDJSON lines and I/O/format issues.

AdaWorldAPI pushed a commit that referenced this pull request May 29, 2026
Three correctness bugs + one architectural disclaimer + three polish items
from the PR review pass. No new dependencies; all tests green.

## Bugs fixed

### 1. roundtrip_eq truth check inverted from default semantics
(codegen_spine.rs:161 — `if tol > 0.0` gated the entire check)

The doc claimed `truth_tolerance() = 0.0` meant "lossless / strictest."
The code SKIPPED the truth check when tol = 0.0, so a projection that
zeroed every (f, c) round-tripped green with no truth validation. The
default-case bug was masked because the existing LossyDropFrequency
test overrides tolerance to 0.01.

Fix: drop the `if tol > 0.0` gate; the `abs() > tol` comparison
naturally treats 0.0 as "any difference fails." Doc reworded to
clarify "0.0 requires exact match; override to allow quantization."

New test `default_tolerance_requires_exact_truth_match` pins the
fix: a `TruthMangler` projection (preserves s/p/o, zeros f/c) MUST
fail with the default tolerance.

### 2. parse_triples silently dropped malformed lines
(odoo_ontology.rs:73-83 — `filter_map(|l| serde_json::from_str(l).ok())`)

Lines that fail JSON parse were silently dropped. The
`parses_all_triples` count assertion would catch *some* drift but a
corrupted line that happens to keep counts aligned would go silent.

Fix: new test `every_nonempty_line_parses` asserts the raw non-empty
line count equals `parse_triples(ndjson).len()` — any silent drop
fires the assertion. (Kept `parse_triples` returning `Vec` for API
stability; the test catches what an error-returning API would.)

### 3. load_ontology collapsed duplicate (s,p,o) keys silently
(odoo_ontology.rs:90-104 — HashMap last-write-wins via dn_hash)

The module doc said "the extractor de-duplicates" — an unverified
assumption. If the harvester ever emits two triples with the same
(s, p, o) but different truth values, the second silently overwrites
the first.

Fix: new test `duplicate_spo_keys_are_last_write_wins` pins the
overwrite semantics. A future switch to insertion-rejection or merge
becomes a test failure instead of a silent behaviour change.

## Architectural disclaimer

`OdooMethodKind` (codegen_spine.rs:230) is the bucket *catalogue*; the
*classifier* (Rust port of `.claude/odoo/openings_hops.py`) is a
follow-up emitter. action_emitter intentionally does NOT carry a
`kind` field — the doc now spells out the wiring gap explicitly so
the next session doesn't read this PR as "kind is wired" when it
isn't.

## Polish

- `RouteBucket::id_owned(&self) -> String` default method added
  (codegen_spine.rs:336-340). Escape hatch for async/iterator
  pipelines that need an owned id outside the borrow scope.
- `impl std::error::Error for WidgetRenderError` (codegen_spine.rs)
  for ecosystem `?`-propagation compatibility.
- `#[serde(deny_unknown_fields)]` on `OntologyTriple` so harvester
  schema drift fails parsing instead of silently dropping fields.
- New test `function_count_matches_module_doc` in odoo_ontology.rs
  pins the "3 328 Functions" doc claim against drift inside the
  loader crate (was only asserted by the downstream action_emitter).
- `compose_spec` now builds `effects` from the borrowed `effects_set`
  directly (action_emitter.rs:255-258), eliminating one redundant
  `idx.emits_by_fn.get(fn_id)` lookup.

## Test counts (orchestrator-verified)

- lance-graph-contract codegen_spine: 6 → 7 passed (+1 truth-mangler)
- lance-graph graph::spo:: total: 75 → 78 passed (+3 ontology tests)

Fixes from PR #432 opus 4.8 reviewer (findings 1-3 must-fix, 5+7
should-discuss accepted, 4 doc-disclaimer accepted, 6+8 deferred,
nitpicks 1+2+3 accepted).
claude added 5 commits May 29, 2026 10:28
…ama→GUI spine

Lands the codegen_spine module in lance-graph-contract per the
hardening direction: "triplets <> static codegen <> askama route SoC <>
askama gui shape — if you harden the codegen you have 32000 less of
token churn and technical debt because the links and SoC route
bucketing are already canonical."

Four contracts:
1. TripletProjection + roundtrip_eq — projections from triples to
   const forms must round-trip equal under BTreeSet identity, with
   optional NARS truth-tolerance on (f,c). Loss = build failure.
2. OdooMethodKind (16 stable-id variants, ALL[16] array, from_id
   reverse lookup) + RouteBucket trait — one canonical bucket enum
   across static codegen, askama route SoC, and GUI widget templates.
3. WidgetRender<B: RouteBucket> — askama widgets take typed buckets
   only, never raw triples. Re-classification visible at type surface.
4. Genericity { Agnostic, Domain } — explicit marker for what to
   codegen (Domain const) vs read from triple store at runtime (Agnostic).

Zero new dependencies (std-only: BTreeMap, BTreeSet, fmt, type_name).
Preserves the lance-graph-contract ZERO-DEPS invariant.

Tests (6/6): lossless_projection_passes_roundtrip,
lossy_projection_fails_roundtrip, odoo_method_kind_ids_are_unique_and_stable,
route_bucket_trait_compiles_with_concrete_impl,
widget_render_trait_compiles_with_dummy_impl,
genericity_marker_distinguishes_codegen_targets.

Cross-refs: I-VSA-IDENTITIES (bucket-by-enum at catalogue layer, not
similarity), E-THREE-PLANES-1 (runtime never crosses these boundaries),
E-FOUNDRY-LAYER-1 (typed domain layer this spine routes between).
Lands the deterministic projection of the Odoo business-logic graph
into the existing SpoStore. Extracted from odoo/addons/ by
ruff_python_dto_check AST analysis; ndjson serialised as
{s,p,o,f,c} per line; loaded via label_fp + SpoBuilder::build_edge +
TruthValue::new + store.insert(dn_hash(id), &rec).

Triple schema:
- (odoo:<family>, rdf:type, ogit:ObjectType)         388  structural
- (odoo:<fam>.<field>, rdf:type, ogit:Property)    3 107  structural
- (odoo:<fam>.<fn>, rdf:type, ogit:Function)       3 328  structural
- (odoo:<fam>.<field>, emitted_by, odoo:<fam>.<fn>)  3 228  body-write authoritative
- (odoo:<fam>.<field>, depends_on, odoo:<fam>.<dep>) 6 309  @api.depends authoritative
- (odoo:<fam>.<fn>, raises, exc:<Type>)              ~451  body raise
- (odoo:<fam>.<fn>, reads_field, …)                 body-inferred
- (odoo:<fam>.<fn>, traverses_relation, …)          body for-loop

NARS truth values carry provenance:
- structural edges (1.0, 1.0)
- decorator/body-authoritative edges (0.95, 0.90)
- body-inferred edges (0.85, 0.75)

The "a+b emits c through d?" Foundry query becomes a deterministic
graph deduction:
  {c : c depends_on a ∧ c depends_on b} then {d : c emitted_by d}
NOT a similarity search.

Tests (4/4): parses_all_triples (22245), predicate_histogram_matches_extraction
(depends_on 6309, emitted_by 3228, rdf:type 6823), loads_into_spo_store_and_queries_forward,
emitted_by_edge_is_present (account_move.amount_total ← _compute_amount).

Also ships:
- `.claude/knowledge/foundry-workshop-elixir-rust-evaluation.md`: distillation
  of how Foundry Workshop's low-code maps onto the Elixir-surface +
  Rust-compile architecture (4 holds + 4 gaps).
- `.claude/knowledge/semantic-operational-handbook-v0.1.md`: primitive
  catalogue underlying the four deliverables.

Data file odoo_ontology.spo.ndjson (2.5 MB) is regenerable via the
extraction pipeline; embedded only into tests via include_str! so the
release binary doesn't carry it.
Two deterministic string-layer emitters that consume parsed
OntologyTriple records and project them into Foundry-shape specs.
Live at the codegen layer above the fingerprint SpoStore so they
remain independent of internal hashing — the SpoStore stays the
runtime query path, these are build-time projections.

## action_emitter

`emit_actions(&[OntologyTriple]) -> Vec<ActionSpec>` composes one
record per function subject:

  ActionSpec {
    id, family,
    effects   ← reverse emitted_by,
    inputs    ← forward depends_on on each effect,
    raises    ← forward raises (guard signal),
    reads     ← forward reads_field (body-inferred),
    traverses ← forward traverses_relation,
  }

Classifiers: is_pure_guard() (raises, no effects → Foundry validation),
is_pure_compute() (effects, no raises → Foundry derive), is_trivial()
(no edges at all → harvester stub).

`emit_non_trivial_actions` filters trivials out. BTreeSet→sorted-Vec
collection guarantees byte-identical output across runs at the
(function, field) identity level (truth values intentionally dropped
at this coarser projection).

Tests (9/9): compose_account_move_compute_amount, pure_guard_classification,
output_is_sorted_deterministic, emit_non_trivial_drops_empties,
shipped_ontology_produces_expected_function_count (3328),
shipped_ontology_compute_amount_has_real_dependencies,
family_of_handles_dotted_iri, empty_triples_produce_empty_specs,
function_with_no_emits_has_empty_inputs.

## link_chain

`split_chain(&str) -> Option<LinkChain>` decomposes flat dotted
dependency IRIs into structured per-hop records:

  "odoo:account_move.line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual"
  ─→ LinkChain {
       source_family: "account_move",
       hops: ["line_ids","matched_debit_ids","debit_move_id","move_id","line_ids"],
       leaf: "amount_residual",
     }

Plus `split_all_depends_on` (BTreeMap<subject, Vec<LinkChain>> with
sort+dedup) and `compute_stats` (SplitStats { depends_on_total,
well_formed, malformed, max_depth, direct_refs }).

Intentionally string-only. Target ObjectType resolution
(`account_move.line_ids → account.move.line`) needs the
OdooEntity::fields target table in `lance-graph-ontology`; adding
that dep here would create a backward edge in the crate graph.
Resolution is the consumer crate's job.

Robust: empty strings, empty segments (a..b, .a, a.), family-only
paths all return None so schema violations surface at the caller.

Tests (10/10): split_direct_field_reference, split_single_hop,
split_five_hop_real_path, split_handles_missing_prefix,
split_rejects_malformed (8 cases), split_all_depends_on_indexes_by_subject,
split_all_depends_on_dedups, shipped_ontology_decomposes_cleanly
(6309 depends_on, 0 malformed, max_depth ≥ 3),
shipped_ontology_amount_total_chains_present,
compute_stats_counts_each_category.

## Review pattern

Each module went through build (with `/// work` markers) →
opus-4.8 reviewer pass → marker removal → orchestrator-run cargo
verify. Reviewer-1 eliminated 4 BTreeSet::cloned() allocations via
a collect_sorted helper + 2 edge-case tests. Reviewer-2 collapsed
two-pass validation to a single split('.') loop, replaced
Vec::remove(0) with split_first/split_last, added 4 malformed-input
assertions + compute_stats coverage test.
…tology

Pure rustfmt output (cargo fmt -p lance-graph). Line-wrapping
preferences (collapse 4-line .get/.unwrap_or chains, expand
single-line function signatures past 100 cols). No semantic changes.

Fixes the `format` CI check on PR #432.
Three correctness bugs + one architectural disclaimer + three polish items
from the PR review pass. No new dependencies; all tests green.

## Bugs fixed

### 1. roundtrip_eq truth check inverted from default semantics
(codegen_spine.rs:161 — `if tol > 0.0` gated the entire check)

The doc claimed `truth_tolerance() = 0.0` meant "lossless / strictest."
The code SKIPPED the truth check when tol = 0.0, so a projection that
zeroed every (f, c) round-tripped green with no truth validation. The
default-case bug was masked because the existing LossyDropFrequency
test overrides tolerance to 0.01.

Fix: drop the `if tol > 0.0` gate; the `abs() > tol` comparison
naturally treats 0.0 as "any difference fails." Doc reworded to
clarify "0.0 requires exact match; override to allow quantization."

New test `default_tolerance_requires_exact_truth_match` pins the
fix: a `TruthMangler` projection (preserves s/p/o, zeros f/c) MUST
fail with the default tolerance.

### 2. parse_triples silently dropped malformed lines
(odoo_ontology.rs:73-83 — `filter_map(|l| serde_json::from_str(l).ok())`)

Lines that fail JSON parse were silently dropped. The
`parses_all_triples` count assertion would catch *some* drift but a
corrupted line that happens to keep counts aligned would go silent.

Fix: new test `every_nonempty_line_parses` asserts the raw non-empty
line count equals `parse_triples(ndjson).len()` — any silent drop
fires the assertion. (Kept `parse_triples` returning `Vec` for API
stability; the test catches what an error-returning API would.)

### 3. load_ontology collapsed duplicate (s,p,o) keys silently
(odoo_ontology.rs:90-104 — HashMap last-write-wins via dn_hash)

The module doc said "the extractor de-duplicates" — an unverified
assumption. If the harvester ever emits two triples with the same
(s, p, o) but different truth values, the second silently overwrites
the first.

Fix: new test `duplicate_spo_keys_are_last_write_wins` pins the
overwrite semantics. A future switch to insertion-rejection or merge
becomes a test failure instead of a silent behaviour change.

## Architectural disclaimer

`OdooMethodKind` (codegen_spine.rs:230) is the bucket *catalogue*; the
*classifier* (Rust port of `.claude/odoo/openings_hops.py`) is a
follow-up emitter. action_emitter intentionally does NOT carry a
`kind` field — the doc now spells out the wiring gap explicitly so
the next session doesn't read this PR as "kind is wired" when it
isn't.

## Polish

- `RouteBucket::id_owned(&self) -> String` default method added
  (codegen_spine.rs:336-340). Escape hatch for async/iterator
  pipelines that need an owned id outside the borrow scope.
- `impl std::error::Error for WidgetRenderError` (codegen_spine.rs)
  for ecosystem `?`-propagation compatibility.
- `#[serde(deny_unknown_fields)]` on `OntologyTriple` so harvester
  schema drift fails parsing instead of silently dropping fields.
- New test `function_count_matches_module_doc` in odoo_ontology.rs
  pins the "3 328 Functions" doc claim against drift inside the
  loader crate (was only asserted by the downstream action_emitter).
- `compose_spec` now builds `effects` from the borrowed `effects_set`
  directly (action_emitter.rs:255-258), eliminating one redundant
  `idx.emits_by_fn.get(fn_id)` lookup.

## Test counts (orchestrator-verified)

- lance-graph-contract codegen_spine: 6 → 7 passed (+1 truth-mangler)
- lance-graph graph::spo:: total: 75 → 78 passed (+3 ontology tests)

Fixes from PR #432 opus 4.8 reviewer (findings 1-3 must-fix, 5+7
should-discuss accepted, 4 doc-disclaimer accepted, 6+8 deferred,
nitpicks 1+2+3 accepted).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants