Skip to content

feat(ogar): OGAR per-class transpile substrate — lift + mint + emit (+ doc)#132

Merged
AdaWorldAPI merged 4 commits into
mainfrom
claude/odoo-rs-transcode-lf8ya5
Jun 29, 2026
Merged

feat(ogar): OGAR per-class transpile substrate — lift + mint + emit (+ doc)#132
AdaWorldAPI merged 4 commits into
mainfrom
claude/odoo-rs-transcode-lf8ya5

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Jun 29, 2026

Copy link
Copy Markdown
Owner

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:

  1. field projection (Codex P1 follow-up to feat(ogar-from-ruff): Python/Odoo lift consuming ruff_python_spo #131) — project_odoo_fields
    maps Model::fields -> attributes / associations (kind from relation_kind)
    / computed_fields, so the Python lift stops dropping the Odoo schema. Gated
    on Language::Python. Adds ComputedField::new to ogar-vocab.

  2. per-class minting (pull-in addressing)mint_graph<P: PortSpec> mints
    the 16-byte rail facet per node, render classid via render_classid_for::<P>;
    CompiledClass { class, facet } + compile_graph_python<P> pair the schema
    with its address. account.move -> 0x0002_0202; OpenProject WorkPackage -> 0x0001_0102 (same concept, different render skin); unmapped -> bootstrap 0.

  3. pull-back emit (Rust reference)emit_rust(&CompiledClass) -> String
    renders a rail struct using the consumer's WRAPPER-CONTRACT types
    (OgScalar / ToOne<T> / ToMany<T>) + a *_CLASSID const. So "the
    language 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> }.

  4. the architecture docOGAR-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 mint ruff_spo_address (both on ruff
main). OGAR has no committed Cargo.lock, so CI resolves ruff main fresh.

Test Plan

Probe-verified (real ogar-vocab + ruff main content): 36 tests pass,
clippy -D warnings clean. New: field-projection (2), mint (4: account.move
rail 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

…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>

@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: 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".

Comment thread crates/ogar-from-ruff/src/lib.rs
…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>
@claude claude Bot changed the title fix(ogar-from-ruff): project Odoo Model::fields into Class schema (Codex P1 follow-up to #131) feat(ogar-from-ruff): OGAR per-class Odoo transpile — field projection + rail-facet minting Jun 29, 2026
claude added 2 commits June 29, 2026 14:31
… 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>
@claude claude Bot changed the title feat(ogar-from-ruff): OGAR per-class Odoo transpile — field projection + rail-facet minting feat(ogar): OGAR per-class transpile substrate — lift + mint + emit (+ doc) Jun 29, 2026
@AdaWorldAPI AdaWorldAPI merged commit ab165f5 into main Jun 29, 2026
1 check passed
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.
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