Skip to content

feat(odoo-spo): FK target/inverse_name (P1) + deep reads_field (P0) corpus enrichment#523

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/odoo-spo-fk-target-deep-reads
Jun 17, 2026
Merged

feat(odoo-spo): FK target/inverse_name (P1) + deep reads_field (P0) corpus enrichment#523
AdaWorldAPI merged 1 commit into
mainfrom
claude/odoo-spo-fk-target-deep-reads

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Jun 17, 2026

Copy link
Copy Markdown
Owner

What

Enriches the Odoo SPO corpus with two predicate families the odoo-rs UPSTREAM_WISHLIST asks for — additive only, lance-graph side, no ClassView (Core-first: emit what the consumer assumes). Serves AdaWorldAPI/odoo-rs as a downstream consumer; no odoo-rs change here (their adoption is their call, per their "silence is alignment").

Predicates added (corpus 22,245 → 23,701)

  • target ×618(odoo:<model>.<rel>, target, "<comodel.dotted>"), the ruff#18 sibling-triple shape. Kills the phantom-target failure: (odoo:account_move.invoice_line_ids, target, "account.move.line") instead of an invented record<invoice_line>.
  • inverse_name ×102 — One2many inverse field.
  • deep reads_field ×736 (2,095 → 2,831) — @api.depends('rel.leaf') now lifts the transitive (method, reads_field, <target_model>.<leaf>) using the new target map (P1 is the prerequisite for P0). 567 dotted-path hops skipped where the relation target is outside the scanned source (counted, never faked).

Where it lives

The corpus's original generator (emit_ontology2.py over methods.parquet) is absent from the tree — only its output is committed. Added a stdlib-only, deterministic, idempotent tools/odoo-blueprint-extractor/.../spo_enrich.py that runs over the shipped corpus + the present Odoo source. The Rust loader (odoo_ontology.rs) gains the matching parse/emit surface.

Honest correction — it delivers recompute ordering, not a cycle

The wishlist (and the original audit) framed MISSED-1 as a "P0 dependency cycle." Local validation against odoo-rs's shipped RecomputeDag (throwaway worktree, nothing committed there) shows:

  • Baseline: the cross-model edge account_move_line._compute_amount_residual → account_move._compute_amount is structurally invisible (0 cross-model compute edges).
  • Enriched: that ordering edge is present (27 compute edges) — so the projection can now emit DEFINE FIELD/DEFINE FUNCTION in the correct topological order, retiring the hand-audit. That is the wishlist's actual P0 value.
  • But it is NOT a cycle. The dependency is unidirectional — no method reads back any field _compute_amount emits. The audit's "circular" framing is a semantic circularity (the body filters on a computed reconciled), not a structural reads_fieldemitted_by back-edge. RecomputeDag correctly sees a line-before-move ordering edge, and its slice_2 no-cycle assertion legitimately still holds. The enrichment was not forced to fake a cycle.

Tests

  • cargo test -p lance-graph --lib odoo_ontology 9/9 (2 new: enrichment_emits_fk_target_and_inverse_name, enrichment_emits_cross_model_deep_reads_field); action_emitter/spo green (function count 3,328 unchanged).
  • Extractor python3 -m unittest tests.test_spo_enrich 14/14 + existing uom smoke.
  • cargo fmt clean; lance-graph crate clippy clean (the workspace -D warnings deprecation errors are pre-existing v2-layout debt in causal-edge/p64-bridge/lance-graph-planner — untouched; this diff has zero CausalEdge64/temporal/inference_type refs).

Board: EPIPHANIES.md (E-ODOO-FK-DEEP-READS — the P1→P0 coupling, the ruff#18-ratified shape, and the cycle-vs-ordering-edge correction), AGENT_LOG.md, LATEST_STATE.md — same commit.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced relationship mapping for database models with improved field dependency tracking across related entities.
  • Tests

    • Added comprehensive test suite for relationship enrichment validation and output verification.
  • Documentation

    • Updated internal progress logs documenting relationship corpus enrichment and system improvements.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@AdaWorldAPI, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 54 minutes and 54 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 10c50540-4415-4230-89e3-b3e15d6d6631

📥 Commits

Reviewing files that changed from the base of the PR and between 37b827f and 69a3b0a.

📒 Files selected for processing (7)
  • .claude/board/AGENT_LOG.md
  • .claude/board/EPIPHANIES.md
  • .claude/board/LATEST_STATE.md
  • crates/lance-graph/src/graph/spo/odoo_ontology.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson
  • tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py
  • tools/odoo-blueprint-extractor/tests/test_spo_enrich.py
📝 Walkthrough

Walkthrough

A new stdlib-only Python module spo_enrich.py is introduced that AST-scans Odoo addons to build a relation map and enriches an SPO ndjson corpus with target/inverse_name FK triples (P1) and deep reads_field triples lifted across @api.depends dotted paths (P0). A hermetic unittest suite covers all enrichment behaviors. The Rust odoo_ontology.rs module documentation and tests are updated to reflect the enriched corpus counts and new predicate families. Agent board logs record the session.

Changes

Odoo SPO Corpus Enrichment

Layer / File(s) Summary
spo_enrich.py: relation-map builder, core enrichment logic, and CLI
tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py
Defines RELATIONAL_KINDS and truth-weight constants; builds a (model_us, field) → (comodel, inverse) map via AST scanning; implements enrich() to emit P1 target/inverse_name and P0 deep reads_field triples with dedup, self-loop dropping, and unknown-hop skipping; wires run() orchestrator and main() CLI entrypoint.
test_spo_enrich.py: hermetic unit tests
tools/odoo-blueprint-extractor/tests/test_spo_enrich.py
Covers model_to_underscore, resolve_path traversal, P1 and P0 enrich behaviors (including many2one, dotted-path-only relations, deep lifting, unknown-hop skipping, self-loop dropping), idempotence/dedup, and JSON output shape with lexicographic sort.
odoo_ontology.rs: enriched corpus docs and test assertions
crates/lance-graph/src/graph/spo/odoo_ontology.rs
Extends module docs to describe the two new predicate families and updated corpus statistics; bumps parses_all_triples count to 23,701; adds target and inverse_name to predicate histogram assertions; adds two new Rust tests for account_move.line_ids FK triples and cross-model deep reads_field edges.
Agent board log entries
.claude/board/AGENT_LOG.md, .claude/board/EPIPHANIES.md, .claude/board/LATEST_STATE.md
Prepends AGENT_LOG with the enrichment session record; adds EPIPHANIES entry noting P1→P0 ordering requirement and MISSED-1 ordering-vs-cycle clarification; updates LATEST_STATE with the in-PR branch summary.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • AdaWorldAPI/lance-graph#432: Directly modifies the same odoo_ontology.rs test surface, updating predicate histogram expectations and triple count assertions against the same SPO ndjson corpus.

Poem

🐇 Hop along the dotted path,
Through model fields and FK math,
target, inverse_name appear—
Deep reads_field triples now are here!
Deduped and sorted, self-loops gone,
The corpus grows, the graph rolls on. ✨

🚥 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 title accurately summarizes the main change: adding FK target/inverse_name (P1) and deep reads_field (P0) predicates to the Odoo SPO corpus enrichment, which directly reflects the primary objective and file changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

…orpus enrichment

Implements the odoo-rs UPSTREAM_WISHLIST P1 (FK target override) + coupled P0
(deep reads_field) enrichment of the Odoo SPO corpus, lance-graph only.

The coupling: P0 is built ON P1 — lifting @api.depends('line_ids.amount_residual')
into a deep read requires resolving line_ids → account.move.line, which IS the P1
target map. So P1 first (relation map), then P0 resolves dotted depends through it.

New stdlib-only generator
tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py:
- builds (model, field) → (comodel, inverse) from /home/user/odoo/addons via ast
  (the same source the ORM extractor parses);
- P1: emits target/inverse_name sibling triples keyed by the relation IRI,
  matching the cross-language-ratified shape from AdaWorldAPI/ruff#18
  (WorkPackage.owner, class_name, "User") → (account_move.line_ids, target,
  "account.move.line") + (…, inverse_name, "move_id"). Object is the raw dotted
  comodel string so od-ontology RelationMap::from_corpus reads it directly;
- P0: resolves each dotted @api.depends path through the target map and lifts a
  deep reads_field onto the field's emitting method, in addition to the existing
  shallow relation read. Self-loops dropped; unknown-hop paths skipped + counted.
Additive, deterministic, idempotent ((s,p,o) dedup; re-run adds 0).

Corpus odoo_ontology.spo.ndjson regenerated: 22 245 → 23 701 triples
(+618 target, +102 inverse_name, +736 deep reads_field; 567 unknown-hop skips).
odoo_ontology.rs: module-doc + provenance updated, count test 22 245→23 701,
histogram extended (target/inverse_name/reads_field), 2 new enrichment tests.
14 new spo_enrich unit tests.

Generation-path finding: the corpus's documented original generator
(emit_ontology2.py over methods.parquet) is absent from the tree — only its
output is committed; the blueprint extractor emits typed Rust OdooEntity consts
(a separate artifact). Enrichment runs over the shipped corpus + present Odoo
source, the correct additive stage.

Cross-repo validation (local only, no odoo-rs commit): od_ontology::RecomputeDag
on a slice-2-scoped enriched fixture. Baseline 0 cross-model compute edges →
enriched 27, with the edge account_move_line._compute_amount_residual →
account_move._compute_amount now PRESENT (the wishlist's P0 ask delivered). But
the audit's MISSED-1 is a unidirectional ORDERING edge, not a cycle (move depends
on line; line does not depend back on move's totals), so odoo-rs's
slice_2_compute_subset_no_cross_model_cycle no-cycle assertion legitimately still
holds. Reported, not faked.

Tests: cargo test -p lance-graph --lib odoo_ontology 9/9 + action_emitter/spo
green; python -m unittest tests.test_spo_enrich 14/14 + uom smoke green.
cargo fmt clean; lance-graph crate clippy clean (the -D warnings deprecation
errors are pre-existing v2-layout debt in causal-edge/p64-bridge/lance-graph-
planner, untouched here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
@AdaWorldAPI AdaWorldAPI force-pushed the claude/odoo-spo-fk-target-deep-reads branch from 37b827f to 69a3b0a Compare June 17, 2026 21:43

@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: 37b827faca

ℹ️ 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".

Comment on lines +273 to +276
# field IRI → emitting method IRI (for the deep-read lift target)
field_emitter: Dict[str, str] = {
t["s"]: t["o"] for t in triples if t["p"] == "emitted_by"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve all emitters when lifting deep reads

When a field has multiple emitted_by triples, this dict keeps only the last method, so each deep depends_on path is lifted to one emitter and the other methods lose their reads_field edges. The shipped corpus has such cases: stock_move.quantity is emitted by both _compute_quantity and _onchange_product_uom_qty, but only the latter gets the deep reads for move_line_ids.quantity/product_uom_id, leaving downstream recompute ordering incomplete for the computed method. Store all emitters and emit the deep read for each one.

Useful? React with 👍 / 👎.

Comment on lines +166 to +171
if model_name is not None:
mu = model_to_underscore(model_name)
for field_name, (comodel, inverse) in local_fields.items():
# Last write wins across _inherit reopenings of the same model;
# comodel for a given (model, field) is stable in practice.
relmap[(mu, field_name)] = (comodel, inverse)

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 Honor _inherit-only model extensions

When an Odoo addon extends an existing model with _inherit = "some.model" and no _name, which is the common extension form, model_name remains None and the relational fields collected in local_fields are discarded here. Those fields then never receive target/inverse_name, and deep depends paths through them are skipped as unknown hops; the existing class parser in this package already maps _inherit to the model name, so the enrichment should do the same.

Useful? React with 👍 / 👎.

@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: 3

🧹 Nitpick comments (1)
tools/odoo-blueprint-extractor/tests/test_spo_enrich.py (1)

131-233: ⚡ Quick win

Add a regression test for _inherit-only model extensions.

Current tests validate enrich/resolve_path, but not the AST extraction path that builds relmap from _inherit-only classes. A focused fixture test would prevent silent regressions in relation discovery.

🤖 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 `@tools/odoo-blueprint-extractor/tests/test_spo_enrich.py` around lines 131 -
233, Add a new test class or test method to validate the AST extraction path for
_inherit-only model extensions. The test should create a fixture that represents
a model using only _inherit (without any new fields), then verify that the
relmap is correctly built from this inheritance-only class definition and that
relation discovery works properly. This will prevent regressions in the AST
extraction and relmap building logic for models that extend other models through
inheritance alone.
🤖 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/board/AGENT_LOG.md:
- Around line 3-10: The scope description in the first line stating the work is
"lance-graph only" is inaccurate because the shipped components include both the
Python extractor module
(tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py) and its
test suite (tools/odoo-blueprint-extractor/tests/test_spo_enrich.py) in addition
to the lance-graph changes. Update the scope text to accurately reflect that
this work spans both the Python extraction tooling and the lance-graph corpus
enrichment, removing the "lance-graph only" restriction so the board log matches
the actual diff surface.

In @.claude/board/EPIPHANIES.md:
- Around line 9-12: The document claims 27 compute edges and no-cycle acyclicity
that were verified locally but the verification cannot be reproduced. Add a
persistent test fixture or validation mechanism: either create a slice-2-scoped
enriched corpus within this repo with a validation script that runs the
enrichment generator and asserts both the edge count and acyclicity using
RecomputeDag, or add a documented cross-repo reference to odoo-rs's test suite
that validates these claims against the no-cycle assertion. This makes the claim
auditable and re-runnable by future reviewers instead of relying on discarded
local worktree validation.

In `@tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py`:
- Around line 124-129: The _scan_file function currently only binds relational
fields when the _name attribute is present, causing fields declared on extension
classes that use _inherit without _name to be dropped from the relation map. Add
handling for the _inherit attribute similar to the existing _name handling: when
target_name equals _inherit, extract the inherited model name(s) using
_const_str and store them appropriately so that relational fields on
inheritance-only classes are properly captured. Apply this fix in the code block
checking target_name == "_name" and also in the additional location mentioned at
lines 166-171 where similar model name binding occurs.

---

Nitpick comments:
In `@tools/odoo-blueprint-extractor/tests/test_spo_enrich.py`:
- Around line 131-233: Add a new test class or test method to validate the AST
extraction path for _inherit-only model extensions. The test should create a
fixture that represents a model using only _inherit (without any new fields),
then verify that the relmap is correctly built from this inheritance-only class
definition and that relation discovery works properly. This will prevent
regressions in the AST extraction and relmap building logic for models that
extend other models through inheritance alone.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bb59f93e-15b6-40d1-a829-2d3e64419e26

📥 Commits

Reviewing files that changed from the base of the PR and between 620bd8e and 37b827f.

📒 Files selected for processing (7)
  • .claude/board/AGENT_LOG.md
  • .claude/board/EPIPHANIES.md
  • .claude/board/LATEST_STATE.md
  • crates/lance-graph/src/graph/spo/odoo_ontology.rs
  • crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson
  • tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py
  • tools/odoo-blueprint-extractor/tests/test_spo_enrich.py

Comment on lines +3 to +10
**Main thread (Opus) — single implementer**, branch `claude/odoo-spo-fk-target-deep-reads`. Implements the odoo-rs `UPSTREAM_WISHLIST` P1 (FK `target`/`inverse_name`) + coupled P0 (deep `reads_field`) corpus enrichment, lance-graph only.

**Shipped:**
- `tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py` (new, stdlib-only): builds a `(model, field) → (comodel, inverse)` relation map from `/home/user/odoo/addons` via `ast`, then (P1) emits `target`/`inverse_name` sibling triples keyed by the relation IRI (ruff#18 shape, raw dotted comodel object) for every relational field on a corpus-declared model, and (P0) resolves each dotted `@api.depends` path through the map and lifts a deep `reads_field` onto the field's emitting method. Additive, deterministic, idempotent (`(s,p,o)` dedup); self-loops dropped; unknown-hop paths skipped + counted. CLI: `python3 -m odoo_blueprint_extractor.spo_enrich --corpus … --addons …`.
- `crates/lance-graph/src/graph/spo/odoo_ontology.spo.ndjson` regenerated: 22 245 → **23 701** triples (**+618 `target`, +102 `inverse_name`, +736 deep `reads_field`**; 567 dotted-path skips for unknown hops).
- `crates/lance-graph/src/graph/spo/odoo_ontology.rs`: module-doc updated (new predicates + provenance/regeneration note), triple-count test 22 245→23 701, histogram test extended (`target`=618, `inverse_name`=102, `reads_field`=2 831), 2 new tests (`enrichment_emits_fk_target_and_inverse_name`, `enrichment_emits_cross_model_deep_reads_field`).
- `tools/odoo-blueprint-extractor/tests/test_spo_enrich.py` (new): 14 unittest cases (path resolution, P1/P0 emission, dedup/idempotence, self-loop drop, unknown-model guard).

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

Scope claim is too narrow.

This entry says the PR is "lance-graph only", but the shipped list also includes the Python extractor and its test suite. Please widen the scope text so the board log matches the actual diff surface.

🤖 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/board/AGENT_LOG.md around lines 3 - 10, The scope description in the
first line stating the work is "lance-graph only" is inaccurate because the
shipped components include both the Python extractor module
(tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py) and its
test suite (tools/odoo-blueprint-extractor/tests/test_spo_enrich.py) in addition
to the lance-graph changes. Update the scope text to accurately reflect that
this work spans both the Python extraction tooling and the lance-graph corpus
enrichment, removing the "lance-graph only" restriction so the board log matches
the actual diff surface.

Comment on lines +9 to +12
**The honest correction — MISSED-1 is an ordering edge, not a cycle (cross-repo-verified).** The task framing expected the deep-read lift to make odoo-rs's `slice_2_compute_subset_no_cross_model_cycle` test's no-cycle assertion *fail* (a cycle appears). It does **NOT**, and faking it would be wrong. Verified locally against `od_ontology::RecomputeDag` on a slice-2-scoped enriched fixture:
- **Baseline (surface reads):** 0 cross-model compute edges. `_compute_amount`'s only relevant read is the relation `account_move.line_ids` (no emitter → no edge). The line→move dependency is structurally invisible.
- **Enriched (deep reads):** the edge `account_move_line._compute_amount_residual → account_move._compute_amount` IS present (27 compute edges total). This is **exactly the wishlist's P0 acceptance** — the cross-model recompute-ordering dependency becomes a structural annotation, retiring the hand-audit ritual.
- **But still acyclic.** The dependency is *unidirectional*: `account.move._compute_amount` reads line fields; `account.move.line._compute_amount_residual` reads only same-model line fields and never reads back any field `_compute_amount` emits (verified: no method reads any `_compute_amount` output). The audit's "logically circular" framing is a *semantic* circularity (filtering by a computed `reconciled` field inside the body), not a `reads_field`↔`emitted_by` structural back-edge. So `RecomputeDag` correctly sees an ordering edge (line-before-move), not a cycle, and the no-cycle assertion legitimately still holds.

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "account_move_line\\._compute_amount_residual|account_move\\._compute_amount|RecomputeDag|slice-2|27 compute edges|MISSED-1" \
  crates tools .claude/board

Repository: AdaWorldAPI/lance-graph

Length of output: 14845


🏁 Script executed:

# Check if the 27-edge count is validated in any test
rg -n "27.*compute.*edge|edge.*count|compute_edges|RecomputeDag" \
  --type rust --type python crates tools .claude/board -A 2 | head -100

Repository: AdaWorldAPI/lance-graph

Length of output: 98


🏁 Script executed:

# Look for test files related to RecomputeDag in odoo-rs (might be referenced)
fd -type f \( -name "*test*" -o -name "*spec*" \) crates tools | grep -i odoo

Repository: AdaWorldAPI/lance-graph

Length of output: 238


🏁 Script executed:

# Check if spo_enrich.py or its tests validate the edge enrichment
cat -n tools/odoo-blueprint-extractor/tests/test_spo_enrich.py | grep -i "27\|edge\|comput" | head -20

Repository: AdaWorldAPI/lance-graph

Length of output: 618


🏁 Script executed:

# Search for edge count validation in tests and source
rg "27" tools/odoo-blueprint-extractor/tests/test_spo_enrich.py

Repository: AdaWorldAPI/lance-graph

Length of output: 49


🏁 Script executed:

# Check the actual test file to understand what it validates
head -50 tools/odoo-blueprint-extractor/tests/test_spo_enrich.py

Repository: AdaWorldAPI/lance-graph

Length of output: 1570


🏁 Script executed:

# Search for RecomputeDag tests in odoo-rs references or any test that validates the graph
rg -i "no.*cycle|acyclic|topolog" crates tools .claude/board

Repository: AdaWorldAPI/lance-graph

Length of output: 50380


🏁 Script executed:

# Check if there's a reference to the odoo-rs repository commit or test in the board
rg -i "d8a270d|odoo-rs.*commit" .claude/board

Repository: AdaWorldAPI/lance-graph

Length of output: 5307


Point to a reproducible test or fixture that validates the 27-edge + no-cycle claim.

The enrichment generator (tools/odoo-blueprint-extractor/spo_enrich.py) and its unit tests are auditable, but the entry's "verified locally" claim lacks a persistent reference. The local worktree validation was discarded. Add either:

  • A test fixture within this repo (slice-2-scoped enriched corpus + a validation script that asserts edge count and acyclicity), or
  • A cross-repo reference to odoo-rs's test suite (once they adopt the enriched corpus) that validates the no-cycle assertion against RecomputeDag.

The board doc (AGENT_LOG.md) captures the findings, but not in a form a reviewer can re-run.

🤖 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/board/EPIPHANIES.md around lines 9 - 12, The document claims 27
compute edges and no-cycle acyclicity that were verified locally but the
verification cannot be reproduced. Add a persistent test fixture or validation
mechanism: either create a slice-2-scoped enriched corpus within this repo with
a validation script that runs the enrichment generator and asserts both the edge
count and acyclicity using RecomputeDag, or add a documented cross-repo
reference to odoo-rs's test suite that validates these claims against the
no-cycle assertion. This makes the claim auditable and re-runnable by future
reviewers instead of relying on discarded local worktree validation.

Comment on lines +124 to +129
# _name = 'account.move.line'
if target_name == "_name":
s = _const_str(stmt.value)
if s is not None:
model_name = s
continue

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

Handle _inherit-only model classes in relation-map extraction.

_scan_file only binds fields when _name exists, so relational fields declared on extension classes that define _inherit (without _name) are dropped from relmap. That causes missing enrichment output and incomplete deep-hop resolution.

Proposed fix
-        model_name: Optional[str] = None
+        model_names: List[str] = []
         local_fields: Dict[str, Tuple[str, Optional[str]]] = {}

@@
-            # _name = 'account.move.line'
-            if target_name == "_name":
+            # _name = 'account.move.line'
+            if target_name == "_name":
                 s = _const_str(stmt.value)
                 if s is not None:
-                    model_name = s
+                    model_names = [s]
+                continue
+
+            # _inherit = 'account.move' OR _inherit = ['account.move', ...]
+            if target_name == "_inherit" and not model_names:
+                s = _const_str(stmt.value)
+                if s is not None:
+                    model_names = [s]
+                elif isinstance(stmt.value, (ast.List, ast.Tuple)):
+                    vals = [
+                        _const_str(elt)
+                        for elt in stmt.value.elts
+                        if _const_str(elt) is not None
+                    ]
+                    if vals:
+                        model_names = vals
                 continue

@@
-        if model_name is not None:
-            mu = model_to_underscore(model_name)
-            for field_name, (comodel, inverse) in local_fields.items():
-                # Last write wins across _inherit reopenings of the same model;
-                # comodel for a given (model, field) is stable in practice.
-                relmap[(mu, field_name)] = (comodel, inverse)
+        for model_name in model_names:
+            mu = model_to_underscore(model_name)
+            for field_name, (comodel, inverse) in local_fields.items():
+                relmap[(mu, field_name)] = (comodel, inverse)

Also applies to: 166-171

🤖 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 `@tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py` around
lines 124 - 129, The _scan_file function currently only binds relational fields
when the _name attribute is present, causing fields declared on extension
classes that use _inherit without _name to be dropped from the relation map. Add
handling for the _inherit attribute similar to the existing _name handling: when
target_name equals _inherit, extract the inherited model name(s) using
_const_str and store them appropriately so that relational fields on
inheritance-only classes are properly captured. Apply this fix in the code block
checking target_name == "_name" and also in the additional location mentioned at
lines 166-171 where similar model name binding occurs.

@AdaWorldAPI AdaWorldAPI merged commit 64c77ad into main Jun 17, 2026
6 checks passed
AdaWorldAPI pushed a commit that referenced this pull request Jun 17, 2026
…t-only

Post-merge follow-up to #523 (merged at 69a3b0a before these landed). Both
codex findings were real correctness gaps in the enrichment:

1. P1 multi-emitter deep-reads: the field→emitter index kept only the LAST
   emitted_by method, so a field emitted by several methods (e.g.
   stock_move.quantity ← _compute_quantity AND _onchange_product_uom_qty)
   lifted the deep reads_field to only one — the _compute_* lost its
   recompute-ordering edge. Now field → sorted set of emitters; deep read
   emitted per emitter (self-loop drop preserved).
2. P2/Major _inherit-only model extensions: _scan_file bound fields only when
   _name was present, dropping the COMMON Odoo extension form
   (_inherit="x" / ["a","b"] with no _name) — those relational fields never
   got target/inverse_name and deep hops through them were skipped. Now
   model_names binds from _name OR _inherit (string/list), mapping local
   fields onto each.

Corpus regenerated: target 618→842, inverse_name 102→144, reads_field
2831→3030 (the previously-dropped _inherit fields + per-emitter deep reads).

Doc nits: AGENT_LOG scope widened (Python extractor + tests, not "lance-graph
only"); EPIPHANIES "verified locally" claim now cites an in-repo fixture test
(test_spo_enrich) instead of the discarded worktree.

Tests: extractor 14→20 (6 new fixture tests for both cases), lance-graph
odoo_ontology 9→11, fmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
AdaWorldAPI added a commit that referenced this pull request Jun 18, 2026
…ep-reads

fix(odoo-spo): #523 review follow-up — multi-emitter deep-reads + _inherit-only models
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