feat(ogar): OGAR per-class transpile substrate — lift + mint + emit (+ doc)#132
Merged
Conversation
…odex P1 #131) The Python lift previously only set the language discriminant; it read the Rails-side AR-DSL vectors (model.attributes / model.associations / …), which are empty for an Odoo model. An Odoo model carries its schema in the core-7 `Model::fields` vector, so the lifted class came out with empty attributes / associations / computed_fields — downstream consumers got a schema-less class (Codex P1 on #131). Add a Python-only `project_odoo_fields` pass mapping `Model::fields` onto the existing Class columns: - relational field (target set) -> Association; kind from `relation_kind` (many2one->BelongsTo, one2many->HasMany, many2many->HasAndBelongsToMany), class_name = raw comodel, inverse_of = One2many inverse. - non-relational field -> Attribute. - compute field (emitted_by set) -> ComputedField (method + @api.depends), in addition to its Attribute/Association. Gated on Language::Python: Rails ALSO populates Model::fields (DB columns), so projecting them for Rails would double-count its AR-DSL surface. Adds `ComputedField::new(field, compute_method)` to ogar-vocab (it lacked a constructor; #[non_exhaustive] makes a struct literal non-constructible cross-crate — parallels Association::new / Attribute::new). Consumes ruff's `relation_kind` predicate (AdaWorldAPI/ruff#35): target + inverse_name alone can't separate a Many2one from a Many2many (both comodel-only, no inverse), so the kind is required to pick the right AssociationKind. Tests: lift_model_python_projects_odoo_fields (scalar/relational/compute, incl. the M2O-vs-M2M case) + lift_model_ruby_does_not_project_fields (Ruby gating). Verified via probe (local ruff w/ relation_kind + real ogar-vocab): 30 tests pass, clippy -D warnings clean. NOTE: not yet pushed — gated on ruff#35 landing on ruff main + an OGAR Cargo.lock ruff-commit bump. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 984f787977
ℹ️ 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".
…dressing
Per the operator's framing: OGAR is the transpile substrate that holds the
~85% mechanical business logic as per-class minted, rail-shaped,
language-agnostic compiled classes; consumers (odoo-rs, medcare-rs, …)
collapse to thin compiler-store callers + a few adapters for the
"impossible" 15% (intrusive/stateful logic → custom adapter + ClassView +
ontological grounding). Each language adds only a thin wrapper contract
(lance-graph-contract is Rust's).
This adds the substrate's addressing step — the other half of the lift:
- `mint_graph<P: PortSpec>(&ModelGraph) -> Mint` — mints the 16-byte rail
facet for every node, resolving each render classid through a language/app
port (OdooPort / OpenProjectPort / …) via the canonical
`ogar_vocab::app::render_classid_for`. Frontend-agnostic: every
ogar-from-<lang> yields the same ModelGraph.
- `CompiledClass { class, facet }` + `compile_graph_python<P>` — pairs the
lifted schema with its rail address: the unit a consumer pulls back.
- classid = (APP_PREFIX << 16) | concept. The low u16 is the shared concept
(PortSpec::class_id → OGAR codebook); the high u16 is the app render skin.
account.move → 0x0002_0202 (Odoo | commercial_document); OpenProject
WorkPackage → 0x0001_0102 (OpenProject | project_work_item) — different
skins, the same identity where they converge. Unmapped model → classid 0
(bootstrap address), never mis-stamped.
Consumes ruff's mint (ruff_spo_address, on ruff main). Members inherit
their class's render classid.
Tests: account.move compiles to a commercial_document rail class (schema +
facet 0x0002_0202); member facets inherit the class id; port-agnostic mint
(WorkPackage → 0x0001_0102); unmapped model → bootstrap 0. Verified via
probe (local ruff + real ogar-vocab): 34 tests pass, clippy -D warnings
clean. OGAR workspace can't resolve offline (surrealdb 403); CI builds it.
Co-Authored-By: Claude <noreply@anthropic.com>
… one doc For any future session to understand the power. Names the whole machine: - The two legs of the bidirectional transpiler: PULL-IN (source → ogar-from-<lang> → ModelGraph → lift + mint → CompiledClass) and PULL-BACK (a consumer obtains the CompiledClass via a thin runtime wrapper contract — lance-graph-contract is Rust's — or a codegen emit adapter — ogar-adapter-surrealql is the DDL reference). - The addressing that makes it a substrate, not a dump: classid = (APP_PREFIX<<16)|concept (cross-app join key, the low u16 is the shared concept, the high u16 the render skin), the 16-byte FacetCascade (V3 6×part_of:is_a), cross-app convergence (account.move ↔ WorkPackage ↔ TimeEntry/Stundenzettel/account.analytic.line). - The 85/15 split: ~85% mechanical logic minted into OGAR (consumer = a compile_graph caller); the "impossible" 15% = a per-language adapter + ClassView + ontological grounding (od-posting GoBD is the example). - Grounding = resolve, don't store (classid → ClassView → OGIT; FIBO/DOLCE never copied onto rows). - A worked account.move example (→ 0x0002_0202), the built/next state, and the four moves to extend (source lang / target lang / concept / port). Indexed in CLAUDE.md's doc family so a future session finds it at session start. Companion to OGAR-AS-IR.md (the compiler framing) and the #133 handover (the ERP/planning landing plan). Co-Authored-By: Claude <noreply@anthropic.com>
…edClass -> Rust)
The transpiler's output leg. `mint` is pull-in (source -> CompiledClass);
`emit` is pull-back (CompiledClass -> a target language). Reference target
is Rust, mirroring `ogar-adapter-surrealql`'s role for SurrealQL DDL.
`emit_rust(&CompiledClass) -> String` renders a rail struct whose fields use
the consumer's WRAPPER-CONTRACT types — so "the language just needs a wrapper
contract akin to lance-graph" is literal:
- scalar attribute -> `OgScalar`
- Many2one/HasOne -> `ToOne<Comodel>`
- One2many/Many2many-> `ToMany<Comodel>`
- the rail address travels as `pub const <NAME>_CLASSID: u32`
- computed fields -> doc lines (the compute behaviour is the "impossible"
15% — it lands as an adapter, not inline codegen)
account.move -> `struct AccountMove { name: OgScalar, partner_id:
ToOne<ResPartner>, line_ids: ToMany<AccountMoveLine> }` + ACCOUNT_MOVE_CLASSID
= 0x00020202. Scalars emit `OgScalar` uniformly until the `field_type`
capture lands (ruff follow-up); the seam doesn't change when it does.
Doc (OGAR-TRANSPILE-SUBSTRATE.md) updated: both transpile legs (pull-in
lift+mint, pull-back emit) now shipped + probe-verified.
Verified via probe (real ogar-vocab + ruff main): 36 tests pass (2 new emit
tests), clippy -D warnings clean.
Co-Authored-By: Claude <noreply@anthropic.com>
AdaWorldAPI
pushed a commit
that referenced
this pull request
Jun 29, 2026
The handover landed (#133) under `.claude/handovers/`, but `.claude/AGENTS.md` Forbidden bars sprint-local planning docs from `.claude/` ("PLAN.md is the live roadmap; sprint-local notes belong in PR descriptions"). Codex flagged it P1. Resolution: move it to `docs/ERP-PLANNING-FOUNDRY-LANDING.md` as a DURABLE architecture doc (its frame — the native/agnostic/truthful landing, the FIBO/ DOLCE grounding, the Redmine/OpenProject parallel — is durable, not transient). The sprint-local "first moves" are now executed and cross-referenced: OGAR #132 (the per-class transpile substrate) + odoo-rs #19 (Phase-2 thinning plan); the live substrate reference is docs/OGAR-TRANSPILE-SUBSTRATE.md. A status header records this. No content lost; `.claude/` is no longer cluttered with a planning doc. Co-Authored-By: Claude <noreply@anthropic.com>
AdaWorldAPI
pushed a commit
that referenced
this pull request
Jun 29, 2026
…ions (#2 Stage A) W3.3's "One2many/Many2many array<record>" emitter gap: the shared SurrealQL emitter rendered only the owning side (BelongsTo → record<X>) and dropped to-many associations to a comment marker. Now that the lift carries the association (OGAR #132 `relation_kind` → HasMany/HasAndBelongsToMany + comodel), `emit_field_assoc` renders both as `array<record<comodel>>`: - BelongsTo (Many2one) -> record<X> / option<record<X>> (unchanged) - HasMany (One2many) -> array<record<X>> (NEW) - HasAndBelongsToMany (Many2many) -> array<record<X>> (NEW) - HasOne (Rails-only, non-owning) -> comment marker (unchanged) Target handling mirrors the owning-side record<X> exactly (dotted Odoo comodels are quoted), so no new normalization. The One2many *computed* `VALUE <-comodel.<inverse> READONLY` reverse-link is the separate W3.3 "computed VALUE" gap and is deferred; this lands the structural array TYPE. Parse-back of `array<record<…>>` is the companion `surrealdb-parser` follow-up — today's `walk` recovers the owning-side record<X> only (same emit-richer-than-parse asymmetry the prior comment-marker had; the gated round-trip tests cover BelongsTo + primitives, unaffected). This is #2 Stage A from odoo-rs #19 — it unblocks deleting od-ontology's native emit fork (W3.3) once the shared emitter covers the schema. Verified via probe (default features, no surrealdb): 22 tests pass (3 new: has_many/has_and_belongs_to_many → array<record>, has_one → comment), clippy -D warnings clean. The emit side is pure string-gen (no surrealdb); the OGAR workspace's surrealdb-parser leg builds on CI. Co-Authored-By: Claude <noreply@anthropic.com>
AdaWorldAPI
pushed a commit
that referenced
this pull request
Jun 30, 2026
…sembler
Adds the keystone gap named in E-KEEP-AR-REMOVE-ORM / the OP convergence
assessment: a Rails-correct sibling of compile_graph_python. Identical
shape (mint_graph::<P> + per-class facet resolution) but routes through
the existing lift_model_graph (Language::Ruby) instead of
lift_model_graph_python — pure operator-reuse, no new lift, and
project_odoo_fields is correctly never invoked for Rails (it would
double-count; lift_model_graph_python's own doc-comment says so).
Proves the convergence claim in code: compile_graph_ruby::<OpenProjectPort>
on a WorkPackage graph and compile_graph_ruby::<RedminePort> on an Issue
graph mint to the SAME low-u16 concept (0x0102 project_work_item) and
DIFFERENT high-u16 render prefixes (0x0001 vs 0x0007) — one canonical
concept, two render skins, machine-checked rather than asserted.
Drive-by fix: 3 pre-existing Function{...} literal constructions
(emit.rs, mint.rs's account_move fixture, lib.rs) broke against the
already-merged ruff#38 (writes/calls fields) because this crate's
ruff_spo_triplet dep floats on branch=main. Added ..Default::default()
to each — no behavior change, restores compilation.
Verification: standalone probe workspace (path-dep ogar-vocab +
ogar-from-ruff, git-dep ruff branch=main) — the OGAR workspace itself
can't resolve in-sandbox (ogar-adapter-surrealql's surrealdb-ast git dep
403s), the same pattern prior PRs (#131/#132/#136/#138/#141) used.
44/44 tests pass (3 new + 41 pre-existing unbroken); clippy --no-deps
-D warnings clean at the pinned 1.95.0 toolchain (ogar-vocab itself has
pre-existing unrelated clippy debt from never being --workspace-gated,
out of scope here); doc-links resolve.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The OGAR per-class transpile substrate, both legs, for Odoo — building on
ruff#34 (ruff_python_spo) + ruff#35 (relation_kind). A class becomes
language-agnostic compiled business logic, addressed by a rail facet, pullable
into any consumer at the cost of an import. Architecture doc:
docs/OGAR-TRANSPILE-SUBSTRATE.md.Four commits:
field projection (Codex P1 follow-up to feat(ogar-from-ruff): Python/Odoo lift consuming ruff_python_spo #131) —
project_odoo_fieldsmaps
Model::fields-> attributes / associations (kind fromrelation_kind)/ computed_fields, so the Python lift stops dropping the Odoo schema. Gated
on
Language::Python. AddsComputedField::newto ogar-vocab.per-class minting (pull-in addressing) —
mint_graph<P: PortSpec>mintsthe 16-byte rail facet per node, render classid via
render_classid_for::<P>;CompiledClass { class, facet }+compile_graph_python<P>pair the schemawith its address.
account.move -> 0x0002_0202; OpenProjectWorkPackage -> 0x0001_0102(same concept, different render skin); unmapped -> bootstrap 0.pull-back emit (Rust reference) —
emit_rust(&CompiledClass) -> Stringrenders a rail struct using the consumer's WRAPPER-CONTRACT types
(
OgScalar/ToOne<T>/ToMany<T>) + a*_CLASSIDconst. So "thelanguage just needs a wrapper contract akin to lance-graph" is literal.
account.move -> struct AccountMove { name: OgScalar, partner_id: ToOne<ResPartner>, line_ids: ToMany<AccountMoveLine> }.the architecture doc —
OGAR-TRANSPILE-SUBSTRATE.md(+ CLAUDE.md doc-family index): the two legs, the rail addressing + cross-app convergence,
the 85/15 split, grounding (resolve-don't-store), a worked
account.move,and the four moves to extend.
Dependency
Consumes ruff
relation_kind(#35) + the mintruff_spo_address(both on ruffmain). OGAR has no committedCargo.lock, so CI resolves ruffmainfresh.Test Plan
Probe-verified (real
ogar-vocab+ ruffmaincontent): 36 tests pass,clippy -D warningsclean. New: field-projection (2), mint (4: account.moverail class, member inheritance, port-agnostic WorkPackage, unmapped bootstrap),
emit (2: wrapper-contract struct, pascal/snake casing). The OGAR workspace
can't resolve in-sandbox (surrealdb git dep 403); CI builds it fully.
🤖 Generated with Claude Code
Generated by Claude Code