33> ** For colleagues building anything that needs Odoo as a typed ontology
44> outside the Odoo Python runtime.** This is the architectural shape:
55> digest Odoo source once into OGIT-shaped TTL templates (stored
6- > in-tree at ` vocab/imports /ogit/NTO/<Domain>/ ` ), then relive any model
6+ > in-tree at ` vocab/exports /ogit/NTO/<Domain>/ ` ), then relive any model
77> or workflow action agnostically via ` ogar-render-askama ` .
88>
99> Status: ** FRAMING v0** (2026-06-22). Companion to
1010> ` docs/ODOO-TRANSCODING.md ` (the producer spec) and
1111> ` docs/VERB-AS-CLASS-TEMPLATE.md ` (the askama-template framing this
1212> reuses).
1313
14- The shape in one sentence: ** ` ogar-from-python ` digests Odoo source
15- once into the OGAR IR; ` ttl_emit ` writes the IR back as OGIT-shaped
16- TTL templates stored in the existing OGIT NTO tree; consumers
17- re-instantiate any model or workflow action by rendering against the
18- TTL via ` ogar-render-askama ` , never touching Odoo Python.**
14+ The shape in one sentence: ** the ` ruff_python_spo + ogar-from-ruff `
15+ pipeline digests Odoo source once into the OGAR IR; ` ttl_emit ` writes
16+ the IR back as OGIT-shaped TTL templates stored at
17+ ` vocab/exports/ogit/NTO/<Domain>/ ` ; consumers re-instantiate any model
18+ or workflow action by rendering against the TTL via
19+ ` ogar-render-askama ` , never touching Odoo Python.**
20+
21+ > ** Producer naming correction (2026-06-22):** earlier drafts of this
22+ > doc called the producer "` ogar-from-python ` " — that name is wrong.
23+ > The correct pipeline is the existing ** ` ruff_python_spo ` ** AST
24+ > frontend (sibling of ` ruff_ruby_spo ` / ` ruff_elixir_spo ` in the
25+ > ` ruff/ ` workspace) producing ` ruff_spo_triplet::Model ` , then the
26+ > existing ** ` ogar-from-ruff ` ** crate mechanically projecting that IR
27+ > into ` ogar_vocab::Class ` . A ` ruff_python_spo ` frontend is queued
28+ > (the projector ` ogar-from-ruff ` already exists and works for Ruby).
29+ > Building a new ` ogar-from-python ` from scratch would duplicate the
30+ > projection that's already shipping.
31+ >
32+ > ** Storage location correction (2026-06-22):** earlier drafts said
33+ > digests land in ` vocab/imports/ogit/NTO/<Domain>/ ` . That was wrong
34+ > — it would put OGAR-produced content at re-vendor risk (the
35+ > ` imports/ ` re-vendor recipe is a destructive ` cp -r ` ). Correct
36+ > location: ** ` vocab/exports/ogit/NTO/<Domain>/ ` ** — see
37+ > ` vocab/exports/PROVENANCE.md ` for the split rationale.
1938
2039---
2140
@@ -25,21 +44,26 @@ TTL via `ogar-render-askama`, never touching Odoo Python.**
2544 Odoo Python source
2645 (addons/<module>/models/*.py)
2746 │
28- │ ogar-from-python (AST schema filter)
47+ │ ruff_python_spo (sibling of ruff_ruby_spo / ruff_elixir_spo;
48+ │ queued — Python AST → ruff_spo_triplet::Model)
2949 │ — keeps structural arm: _name, _inherit, fields.*, selections
3050 │ — keeps behavioural-arm SIGNATURES (decorators, action def names)
3151 │ — drops bodies (computed methods, action implementations)
3252 ▼
53+ ruff_spo_triplet::Model
54+ │
55+ │ ogar-from-ruff (existing — mechanical projection)
56+ ▼
3357 OGAR Class IR (in memory)
3458 │
3559 │ ttl_emit::emit_entity (semantic bijection)
3660 │ — entity-as-class for models
3761 │ — verb-as-class for workflow action signatures
3862 ▼
3963 OGIT-shaped TTL templates
40- (vocab/imports /ogit/NTO/<Domain>/<DigestedClass>.ttl)
64+ (vocab/EXPORTS /ogit/NTO/<Domain>/<DigestedClass>.ttl)
4165 — dcterms:creator = bus-compiler (digester provenance)
42- — alongside upstream arago TTL (Viktor Voss et al )
66+ — distinct tree from imports/ ogit/ (re-vendor safety )
4367 │
4468 │ ogar-render-askama (entity render → views; verb render → actions)
4569 ▼
@@ -54,26 +78,36 @@ The Python runtime is **only** touched at digest time. Consumers
5478(` woa-rs ` , ` smb-office-rs ` , ` medcare-rs ` , ` q2 ` , any future renderer)
5579never depend on Odoo Python, only on TTL + the askama renderer.
5680
57- ## §2. Why store digests in OGIT NTO ( not a parallel ` vocab/imports/odoo/ ` )
81+ ## §2. Why digests live in ` vocab/exports/ogit/ ` , not ` vocab/imports/ogit/ `
5882
59- The ` dcterms:creator ` author-scan (`OGIT-DOMAIN-LIFT-CATALOGUE.md
60- § Verifying domain authorship`) gives clean provenance without
61- needing a separate storage namespace:
83+ ` imports/ ` is a READ-ONLY mirror of upstream OGIT (the re-vendor
84+ recipe is a destructive ` cp -r /upstream/. vocab/imports/ogit/ ` ).
85+ Putting OGAR-produced content in ` imports/ ` would silently nuke
86+ those files on the next re-vendor. ** The split exists for re-vendor
87+ safety, license/governance, and upstream-contribution path** — see
88+ ` vocab/exports/PROVENANCE.md ` for the full rationale.
6289
63- | ` dcterms:creator ` value | Meaning | Who can change |
64- | ---| ---| ---|
65- | ` Viktor Voss ` , ` chris.boos@almato.com ` , ` fotto@arago.de ` , … | Upstream arago/almato | Re-vendor requires arago PR; OGAR consumes via the SHA pin |
66- | ` bus-compiler ` , ` family-codec-smith ` , ` Claude (...) ` , … | Internal agent digest | Re-run the digester; no external coordination |
67-
68- The precedent exists today: ` vocab/imports/ogit/NTO/Accounting/ `
69- already has 11 files from a prior `Claude (AdaWorldAPI/lance-graph
70- 3-hop optim)` digest sitting alongside Viktor Voss's 23 originals. The
71- two co-exist, the author-scan distinguishes them, and lifting both
72- into OGAR's ` Class ` IR is symmetric.
90+ The digest ** mirrors the upstream layout** so consumers see one shape:
7391
74- So ** the digest lives where the concept belongs** — ` account.move ` →
75- ` Accounting/ ` , ` sale.order ` → ` SalesDistribution/ ` , ` stock.picking ` →
76- ` Transport/ ` — and the author scan is the discriminator.
92+ | Concept | Upstream OGIT path (READ-only mirror) | Digest target (OGAR-produced) |
93+ | ---| ---| ---|
94+ | ` Accounting ` | ` vocab/imports/ogit/NTO/Accounting/ ` (23 files, Viktor Voss) | ` vocab/exports/ogit/NTO/Accounting/ ` (Odoo-digested) |
95+ | ` SalesDistribution ` | ` vocab/imports/ogit/NTO/SalesDistribution/ ` (23 files, Marek Meyer) | ` vocab/exports/ogit/NTO/SalesDistribution/ ` (Odoo sale.* digest) |
96+ | ` Transport ` | ` vocab/imports/ogit/NTO/Transport/ ` (27 files, chris.boos@almato.com ) | ` vocab/exports/ogit/NTO/Transport/ ` (Odoo stock.* digest) |
97+ | … | upstream-mirrored | OGAR-produced, OGIT-shape-compatible |
98+
99+ ` dcterms:creator ` provenance is now a SECONDARY check (the directory
100+ split is the primary). The `OGIT-DOMAIN-LIFT-CATALOGUE.md §
101+ Verifying domain authorship` scan still runs and still discriminates
102+ authors, but the destructive-overwrite risk is structurally gone.
103+
104+ ** Migration note for the existing 11 stranded files.** A prior
105+ session's ` Claude (AdaWorldAPI/lance-graph 3-hop optim) ` digest left
106+ 11 OGAR-produced files in ` vocab/imports/ogit/NTO/Accounting/ `
107+ alongside Viktor Voss's 23 originals. Those 11 belong in
108+ ` vocab/exports/ogit/NTO/Accounting/ ` . The migration is a separate
109+ PR; ` vocab/exports/PROVENANCE.md § Migration note ` carries the
110+ list.
77111
78112## §3. The four shapes the digester produces
79113
@@ -123,12 +157,14 @@ the v0 producer adds rows; concept-mint passes work in parallel.
123157
124158```
125159Day 1 — digest Odoo at SHA-A
126- ogar-from-python addons/account → vocab/imports/ogit/NTO/Accounting/*.ttl
160+ ruff_python_spo addons/account → ruff_spo_triplet::Model
161+ ogar-from-ruff → Class IR
162+ ttl_emit::emit_entity → vocab/exports/ogit/NTO/Accounting/*.ttl
127163 git commit (the TTL set is the frozen contract)
128164
129165Day N — Odoo upstream releases SHA-B
130- ogar-from-python addons/account → /tmp/odoo-shaB-digest/
131- diff -r vocab/imports /ogit/NTO/Accounting/ /tmp/odoo-shaB-digest/
166+ same pipeline → /tmp/odoo-shaB-digest/
167+ diff -r vocab/exports /ogit/NTO/Accounting/ /tmp/odoo-shaB-digest/
132168
133169 Any output line is a structural change Odoo just made:
134170 - added field → diff shows a new ogit:optional-attributes entry
@@ -148,15 +184,19 @@ license fee.
148184
149185| Piece | Status |
150186| ---| ---|
151- | Storage location (` vocab/imports/ogit/NTO/<Domain>/ ` ) | exists; 72 domains imported, MARS oracle proven |
187+ | Read-only upstream mirror (` vocab/imports/ogit/ ` ) | exists; 72 NTO + SGO + ogit.ttl + SDF imported, MARS oracle proven |
188+ | OGAR-produced export tree (` vocab/exports/ogit/ ` ) | ** skeleton exists** (this commit); content populates as digests run |
152189| TTL emitter for the structural arm | exists (` ttl_emit::emit_entity ` ); semantic bijection proven on 29 MARS + 176 SGO TTLs |
153190| Verb-as-class template surface | exists (WorkOrder convention; ` docs/VERB-AS-CLASS-TEMPLATE.md ` ) |
154- | Author-provenance discriminator | exists (` dcterms:creator ` scan in ` OGIT-DOMAIN-LIFT-CATALOGUE.md ` ) |
191+ | Author-provenance discriminator | exists (` dcterms:creator ` scan in ` OGIT-DOMAIN-LIFT-CATALOGUE.md ` ); now a secondary check behind the directory split |
192+ | ` ogar-from-ruff ` (mechanical projector from ` ruff_spo_triplet::Model ` → ` Class ` ) | exists for Ruby AR; same projector handles Python and Elixir once their ` ruff_*_spo ` frontends ship |
193+ | ` ruff_python_spo ` (Python AST frontend, sibling of ` ruff_ruby_spo ` ) | ** does not exist** — needs ` libcst ` or ` rustpython-parser ` ; ~ 1500 LOC for the structural-arm filter |
194+ | ` ruff_rust_spo ` (Rust AST frontend, for digesting medcare-rs / woa-rs / etc.) | ** does not exist** — needs ` syn ` walker; symmetric with the other ruff frontends |
155195| ` ogar-render-askama::actions ` (verb-as-class render path) | ** does not exist** — ~ 200 LOC mirroring the existing ` views/ ` path |
156- | ` ogar-from-python ` (the digester) | ** does not exist** — needs ` libcst ` or ` rustpython-parser ` to walk Python AST; ~ 1500 LOC for the structural-arm filter |
157196| Concept mints for non-Accounting Odoo models | needs the 5+3 codebook pass per ` APP-CLASS-CODEBOOK-LAYOUT.md ` |
197+ | Migration of the 11 stranded Accounting files (` imports/ ` → ` exports/ ` ) | ** queued** — separate PR (see ` vocab/exports/PROVENANCE.md § Migration note ` ) |
158198
159- ` ogar-from-python ` and ` ogar-render-askama::actions ` are independent
199+ ` ruff_python_spo ` and ` ogar-render-askama::actions ` are independent
160200and can ship in parallel PRs. Concept mints are the slow path
161201(codebook discipline) and don't block the digest — a digested model
162202without a minted concept_id just gets ` Class.name = "sale.order" ` and
@@ -170,8 +210,8 @@ this architecture:
170210
171211| Foundry layer | Vendor cost | Our equivalent | Marginal cost |
172212| ---| ---| ---| ---|
173- | Ingest (vendor pipelines) | $ | ` ogar-from-python ` digest run (one-shot per Odoo upgrade) | engineer-hours per digester ~ 1500 LOC |
174- | Storage (vendor platform) | $$ | ` vocab/imports /ogit/NTO/<Domain>/ ` TTL templates with ` dcterms:creator ` provenance | zero |
213+ | Ingest (vendor pipelines) | $ | ` ruff_python_spo + ogar-from-ruff ` digest (one-shot per Odoo upgrade) | engineer-hours per frontend ~ 1500 LOC (projector exists) |
214+ | Storage (vendor platform) | $$ | ` vocab/exports /ogit/NTO/<Domain>/ ` TTL templates (mirrors upstream OGIT layout) | zero (skeleton shipped) |
175215| Render (vendor UI) | $$ | ` ogar-render-askama::{views, actions} ` | engineer-hours per render path ~ 200 LOC each |
176216| Access control / audit (vendor IAM) | $$$ | verb-as-class ` requires-perm ` slot + ` emits-audit ` + Lance-version-as-audit (ADR-013) | zero (the substrate already does it) |
177217| Ontology change management (vendor feature) | $$$ | ` diff -r ` of digest output (§5) | zero |
0 commit comments