diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index c215a26..360951c 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -15,6 +15,79 @@ ## Entries (newest first) +## 2026-06-22 — Three corrections to the Odoo digest framing: producer name, storage location, latent re-vendor bug +**Status:** FINDING +**Scope:** producer architecture × vocab/ tree layout × re-vendor safety × digest-to-OGIT + +Three corrections to the framing in `docs/ODOO-DIGEST-TO-OGIT.md` +(originally landed in commit `7d68042`) surfaced from operator +questions on which producer to use and where digests should live. + +**Correction 1 — producer name.** The doc named the producer +"`ogar-from-python`" (a crate that doesn't exist and that we'd be +duplicating effort to build). The actual pipeline is the existing +**`ruff_python_spo`** (Python AST frontend, sibling of +`ruff_ruby_spo` / `ruff_elixir_spo` in the `ruff/` workspace) +producing `ruff_spo_triplet::Model`, then the existing +**`ogar-from-ruff`** crate mechanically projecting that IR into +`ogar_vocab::Class`. `ogar-from-ruff` already exists and works for +Ruby; what's missing is the `ruff_python_spo` frontend itself. + +Same correction applies to medcare-rs digestion: the right pipeline +is **`ruff_rust_spo` (queued) + `ogar-from-ruff`**, not a fictional +`ogar-from-rust`. Symmetric with the other frontends; the projector +is shared. + +Lesson for the next architecture-doc draft: NAME THE ACTUAL CRATE +that exists, don't invent producer names. The cross-repo `ruff` → +`ogar-from-ruff` projection pattern is the standard; any new source +language goes through it. + +**Correction 2 — `lance-graph-arm-discovery` is not a producer.** +The "lancegraph arm crate" the operator asked about is +`lance-graph-arm-discovery`, which is a streaming Association Rule +Mining engine (Aerial+ paper transcode) that DISCOVERS new SPO rules +from tabular data via NARS revision. It is **orthogonal** to schema +digestion. The lance-graph-side OGAR bridge is +`lance-graph-ogar` (re-export + activation, consumer-side wiring). +Neither is a digester for source code or schemas. + +**Correction 3 — digests belong in `vocab/exports/`, not +`vocab/imports/`.** The original doc said digests land in +`vocab/imports/ogit/NTO//` — **wrong**. The `imports/` +re-vendor recipe is a destructive `cp -r /upstream/. vocab/imports/` +that would silently nuke any OGAR-produced content sitting there. +The fix is a sibling tree `vocab/exports/ogit/` mirroring the +upstream layout 1:1; digests land in `exports/`, mirror stays +read-only in `imports/`. + +The split exists for three reasons: +- **Re-vendor safety** — `cp -r` to `imports/` can't clobber what's + in `exports/`. Structural fix, not a discipline fix. +- **License/governance** — `imports/` inherits MIT from arago/almato; + `exports/` inherits OGAR's own license. +- **Upstream-contribution path** — files in `exports/` are PR + candidates back to OGIT upstream; files in `imports/` are + immutable. + +**Latent bug surfaced.** The current `vocab/imports/ogit/NTO/Accounting/` +carries 11 OGAR-produced TTLs from a prior `Claude (AdaWorldAPI/lance-graph +3-hop optim)` session sitting alongside Viktor Voss's 23 originals. +Those 11 are at re-vendor-overwrite risk today. Migration to +`vocab/exports/ogit/NTO/Accounting/` is queued — `vocab/exports/PROVENANCE.md +§ Migration note` carries the file list. + +This session lands the scaffold (empty `exports/` skeleton + +provenance doc + doc corrections); the 11-file migration is a +separate PR (operator decision: do we keep the original commit +hashes for those files via `git mv`, or re-author them under the +current author? — migration approach decides). + +Evidence: `vocab/exports/PROVENANCE.md` (the split rationale), +`vocab/exports/ogit/README.md` (the layout), `docs/ODOO-DIGEST-TO-OGIT.md` +(updated with all three corrections + producer pipeline + storage +path + blocker table). + ## 2026-06-22 — extract_classes.py transcoded to Rust byte-faithfully; XSD↔TTL bijection closed; Python dependency removed from the oracle **Status:** FINDING **Scope:** XSD front-end × calibration self-containment × the queued bijection diff --git a/docs/ODOO-DIGEST-TO-OGIT.md b/docs/ODOO-DIGEST-TO-OGIT.md index a76ee20..0be6c81 100644 --- a/docs/ODOO-DIGEST-TO-OGIT.md +++ b/docs/ODOO-DIGEST-TO-OGIT.md @@ -3,7 +3,7 @@ > **For colleagues building anything that needs Odoo as a typed ontology > outside the Odoo Python runtime.** This is the architectural shape: > digest Odoo source once into OGIT-shaped TTL templates (stored -> in-tree at `vocab/imports/ogit/NTO//`), then relive any model +> in-tree at `vocab/exports/ogit/NTO//`), then relive any model > or workflow action agnostically via `ogar-render-askama`. > > Status: **FRAMING v0** (2026-06-22). Companion to @@ -11,11 +11,30 @@ > `docs/VERB-AS-CLASS-TEMPLATE.md` (the askama-template framing this > reuses). -The shape in one sentence: **`ogar-from-python` digests Odoo source -once into the OGAR IR; `ttl_emit` writes the IR back as OGIT-shaped -TTL templates stored in the existing OGIT NTO tree; consumers -re-instantiate any model or workflow action by rendering against the -TTL via `ogar-render-askama`, never touching Odoo Python.** +The shape in one sentence: **the `ruff_python_spo + ogar-from-ruff` +pipeline digests Odoo source once into the OGAR IR; `ttl_emit` writes +the IR back as OGIT-shaped TTL templates stored at +`vocab/exports/ogit/NTO//`; consumers re-instantiate any model +or workflow action by rendering against the TTL via +`ogar-render-askama`, never touching Odoo Python.** + +> **Producer naming correction (2026-06-22):** earlier drafts of this +> doc called the producer "`ogar-from-python`" — that name is wrong. +> The correct pipeline is the existing **`ruff_python_spo`** AST +> frontend (sibling of `ruff_ruby_spo` / `ruff_elixir_spo` in the +> `ruff/` workspace) producing `ruff_spo_triplet::Model`, then the +> existing **`ogar-from-ruff`** crate mechanically projecting that IR +> into `ogar_vocab::Class`. A `ruff_python_spo` frontend is queued +> (the projector `ogar-from-ruff` already exists and works for Ruby). +> Building a new `ogar-from-python` from scratch would duplicate the +> projection that's already shipping. +> +> **Storage location correction (2026-06-22):** earlier drafts said +> digests land in `vocab/imports/ogit/NTO//`. That was wrong +> — it would put OGAR-produced content at re-vendor risk (the +> `imports/` re-vendor recipe is a destructive `cp -r`). Correct +> location: **`vocab/exports/ogit/NTO//`** — see +> `vocab/exports/PROVENANCE.md` for the split rationale. --- @@ -25,11 +44,16 @@ TTL via `ogar-render-askama`, never touching Odoo Python.** Odoo Python source (addons//models/*.py) │ - │ ogar-from-python (AST schema filter) + │ ruff_python_spo (sibling of ruff_ruby_spo / ruff_elixir_spo; + │ queued — Python AST → ruff_spo_triplet::Model) │ — keeps structural arm: _name, _inherit, fields.*, selections │ — keeps behavioural-arm SIGNATURES (decorators, action def names) │ — drops bodies (computed methods, action implementations) ▼ + ruff_spo_triplet::Model + │ + │ ogar-from-ruff (existing — mechanical projection) + ▼ OGAR Class IR (in memory) │ │ ttl_emit::emit_entity (semantic bijection) @@ -37,9 +61,9 @@ TTL via `ogar-render-askama`, never touching Odoo Python.** │ — verb-as-class for workflow action signatures ▼ OGIT-shaped TTL templates - (vocab/imports/ogit/NTO//.ttl) + (vocab/EXPORTS/ogit/NTO//.ttl) — dcterms:creator = bus-compiler (digester provenance) - — alongside upstream arago TTL (Viktor Voss et al) + — distinct tree from imports/ ogit/ (re-vendor safety) │ │ ogar-render-askama (entity render → views; verb render → actions) ▼ @@ -54,26 +78,36 @@ The Python runtime is **only** touched at digest time. Consumers (`woa-rs`, `smb-office-rs`, `medcare-rs`, `q2`, any future renderer) never depend on Odoo Python, only on TTL + the askama renderer. -## §2. Why store digests in OGIT NTO (not a parallel `vocab/imports/odoo/`) +## §2. Why digests live in `vocab/exports/ogit/`, not `vocab/imports/ogit/` -The `dcterms:creator` author-scan (`OGIT-DOMAIN-LIFT-CATALOGUE.md -§ Verifying domain authorship`) gives clean provenance without -needing a separate storage namespace: +`imports/` is a READ-ONLY mirror of upstream OGIT (the re-vendor +recipe is a destructive `cp -r /upstream/. vocab/imports/ogit/`). +Putting OGAR-produced content in `imports/` would silently nuke +those files on the next re-vendor. **The split exists for re-vendor +safety, license/governance, and upstream-contribution path** — see +`vocab/exports/PROVENANCE.md` for the full rationale. -| `dcterms:creator` value | Meaning | Who can change | -|---|---|---| -| `Viktor Voss`, `chris.boos@almato.com`, `fotto@arago.de`, … | Upstream arago/almato | Re-vendor requires arago PR; OGAR consumes via the SHA pin | -| `bus-compiler`, `family-codec-smith`, `Claude (...)`, … | Internal agent digest | Re-run the digester; no external coordination | - -The precedent exists today: `vocab/imports/ogit/NTO/Accounting/` -already has 11 files from a prior `Claude (AdaWorldAPI/lance-graph -3-hop optim)` digest sitting alongside Viktor Voss's 23 originals. The -two co-exist, the author-scan distinguishes them, and lifting both -into OGAR's `Class` IR is symmetric. +The digest **mirrors the upstream layout** so consumers see one shape: -So **the digest lives where the concept belongs** — `account.move` → -`Accounting/`, `sale.order` → `SalesDistribution/`, `stock.picking` → -`Transport/` — and the author scan is the discriminator. +| Concept | Upstream OGIT path (READ-only mirror) | Digest target (OGAR-produced) | +|---|---|---| +| `Accounting` | `vocab/imports/ogit/NTO/Accounting/` (23 files, Viktor Voss) | `vocab/exports/ogit/NTO/Accounting/` (Odoo-digested) | +| `SalesDistribution` | `vocab/imports/ogit/NTO/SalesDistribution/` (23 files, Marek Meyer) | `vocab/exports/ogit/NTO/SalesDistribution/` (Odoo sale.* digest) | +| `Transport` | `vocab/imports/ogit/NTO/Transport/` (27 files, chris.boos@almato.com) | `vocab/exports/ogit/NTO/Transport/` (Odoo stock.* digest) | +| … | upstream-mirrored | OGAR-produced, OGIT-shape-compatible | + +`dcterms:creator` provenance is now a SECONDARY check (the directory +split is the primary). The `OGIT-DOMAIN-LIFT-CATALOGUE.md § +Verifying domain authorship` scan still runs and still discriminates +authors, but the destructive-overwrite risk is structurally gone. + +**Migration note for the existing 11 stranded files.** A prior +session's `Claude (AdaWorldAPI/lance-graph 3-hop optim)` digest left +11 OGAR-produced files in `vocab/imports/ogit/NTO/Accounting/` +alongside Viktor Voss's 23 originals. Those 11 belong in +`vocab/exports/ogit/NTO/Accounting/`. The migration is a separate +PR; `vocab/exports/PROVENANCE.md § Migration note` carries the +list. ## §3. The four shapes the digester produces @@ -123,12 +157,14 @@ the v0 producer adds rows; concept-mint passes work in parallel. ``` Day 1 — digest Odoo at SHA-A - ogar-from-python addons/account → vocab/imports/ogit/NTO/Accounting/*.ttl + ruff_python_spo addons/account → ruff_spo_triplet::Model + ogar-from-ruff → Class IR + ttl_emit::emit_entity → vocab/exports/ogit/NTO/Accounting/*.ttl git commit (the TTL set is the frozen contract) Day N — Odoo upstream releases SHA-B - ogar-from-python addons/account → /tmp/odoo-shaB-digest/ - diff -r vocab/imports/ogit/NTO/Accounting/ /tmp/odoo-shaB-digest/ + same pipeline → /tmp/odoo-shaB-digest/ + diff -r vocab/exports/ogit/NTO/Accounting/ /tmp/odoo-shaB-digest/ Any output line is a structural change Odoo just made: - added field → diff shows a new ogit:optional-attributes entry @@ -148,15 +184,19 @@ license fee. | Piece | Status | |---|---| -| Storage location (`vocab/imports/ogit/NTO//`) | exists; 72 domains imported, MARS oracle proven | +| Read-only upstream mirror (`vocab/imports/ogit/`) | exists; 72 NTO + SGO + ogit.ttl + SDF imported, MARS oracle proven | +| OGAR-produced export tree (`vocab/exports/ogit/`) | **skeleton exists** (this commit); content populates as digests run | | TTL emitter for the structural arm | exists (`ttl_emit::emit_entity`); semantic bijection proven on 29 MARS + 176 SGO TTLs | | Verb-as-class template surface | exists (WorkOrder convention; `docs/VERB-AS-CLASS-TEMPLATE.md`) | -| Author-provenance discriminator | exists (`dcterms:creator` scan in `OGIT-DOMAIN-LIFT-CATALOGUE.md`) | +| Author-provenance discriminator | exists (`dcterms:creator` scan in `OGIT-DOMAIN-LIFT-CATALOGUE.md`); now a secondary check behind the directory split | +| `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 | +| `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 | +| `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 | | `ogar-render-askama::actions` (verb-as-class render path) | **does not exist** — ~200 LOC mirroring the existing `views/` path | -| `ogar-from-python` (the digester) | **does not exist** — needs `libcst` or `rustpython-parser` to walk Python AST; ~1500 LOC for the structural-arm filter | | Concept mints for non-Accounting Odoo models | needs the 5+3 codebook pass per `APP-CLASS-CODEBOOK-LAYOUT.md` | +| Migration of the 11 stranded Accounting files (`imports/` → `exports/`) | **queued** — separate PR (see `vocab/exports/PROVENANCE.md § Migration note`) | -`ogar-from-python` and `ogar-render-askama::actions` are independent +`ruff_python_spo` and `ogar-render-askama::actions` are independent and can ship in parallel PRs. Concept mints are the slow path (codebook discipline) and don't block the digest — a digested model without a minted concept_id just gets `Class.name = "sale.order"` and @@ -170,8 +210,8 @@ this architecture: | Foundry layer | Vendor cost | Our equivalent | Marginal cost | |---|---|---|---| -| Ingest (vendor pipelines) | $ | `ogar-from-python` digest run (one-shot per Odoo upgrade) | engineer-hours per digester ~1500 LOC | -| Storage (vendor platform) | $$ | `vocab/imports/ogit/NTO//` TTL templates with `dcterms:creator` provenance | zero | +| Ingest (vendor pipelines) | $ | `ruff_python_spo + ogar-from-ruff` digest (one-shot per Odoo upgrade) | engineer-hours per frontend ~1500 LOC (projector exists) | +| Storage (vendor platform) | $$ | `vocab/exports/ogit/NTO//` TTL templates (mirrors upstream OGIT layout) | zero (skeleton shipped) | | Render (vendor UI) | $$ | `ogar-render-askama::{views, actions}` | engineer-hours per render path ~200 LOC each | | 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) | | Ontology change management (vendor feature) | $$$ | `diff -r` of digest output (§5) | zero | diff --git a/vocab/exports/PROVENANCE.md b/vocab/exports/PROVENANCE.md new file mode 100644 index 0000000..3b653ad --- /dev/null +++ b/vocab/exports/PROVENANCE.md @@ -0,0 +1,95 @@ +# PROVENANCE — `vocab/exports/` + +> **OGAR-produced TTL templates, in OGIT-compatible shape.** Distinct +> from `vocab/imports/` (read-only mirror of upstream OGIT, MIT +> licensed by arago/almato). Files in this tree are AUTHORED in OGAR +> — either digested from source by a producer (`ruff_*_spo + +> ogar-from-ruff`, future `ruff_rust_spo`, etc.) or hand-authored — +> and the OGAR license applies. +> +> Status: **SKELETON v0** (2026-06-22). Scaffold lands empty; content +> populates as producers digest into it. + +## Why the split + +`vocab/imports/` and `vocab/exports/` exist for three reasons: + +1. **Re-vendor safety.** The `imports/` re-vendor recipe is a + destructive `cp -r /upstream/. vocab/imports/...`. Putting + OGAR-produced content in `imports/` would be silently nuked on + the next re-vendor. The split makes the producer-output tree + immune. + +2. **License + governance.** `imports/` inherits MIT (Almato AI GmbH, + 2013–2024) from OGIT upstream. `exports/` inherits OGAR's own + license. Authorship discriminator (`dcterms:creator`) becomes a + secondary check rather than the primary one. + +3. **Upstream-contribution path.** Files in `exports/` are candidates + for PR back to OGIT upstream (or onward distribution to consumers + expecting OGIT shape). Files in `imports/` are immutable mirrors. + The directory split makes the contribution flow a one-line check. + +## Layout + +``` +vocab/exports/ +└── ogit/ ← OGIT-shape (consumer-compat) + ├── NTO/ + │ ├── / ← mirrors upstream OGIT NTO layout + │ │ ├── entities/ ← entity TTLs (a rdfs:Class) + │ │ ├── attributes/ ← datatype-property TTLs (a owl:DatatypeProperty) + │ │ ├── verbs/ ← verb TTLs (a owl:ObjectProperty + │ │ │ OR a rdfs:Class for askama-template verbs) + │ │ └── PROVENANCE.md ← per-domain source provenance (which + │ │ producer ran, which upstream input, + │ │ which Odoo/Medcare/etc. revision) + │ ├── ... + └── PROVENANCE.md (this file) +``` + +The layout intentionally mirrors `imports/ogit/` 1:1 so consumers +discovering both trees see one shape; the choice between +"upstream-mirrored" and "OGAR-produced" is the path prefix, nothing +else. + +## What lives here today + +Empty. Producers haven't run yet; content populates as digests land: + +| Source | Producer (planned) | Lands at | +|---|---|---| +| Odoo `addons/account/*` | `ruff_python_spo + ogar-from-ruff` | `exports/ogit/NTO/Accounting/` | +| Odoo `addons/sale/*` | same | `exports/ogit/NTO/SalesDistribution/` | +| Odoo `addons/stock/*` | same | `exports/ogit/NTO/Transport/` | +| Odoo workflow `def action_*` | same (verb-as-class shape) | `exports/ogit/NTO//verbs/` | +| Medcare-rs domain types | `ruff_rust_spo + ogar-from-ruff` (queued) | `exports/ogit/NTO/Healthcare/` | +| Medcare-rs MongoDB schemas | `ogar-from-schema` (XSD/JSON-Schema) | `exports/ogit/NTO/Healthcare/` | +| Hand-authored OGAR Class views | direct authoring | `exports/ogit/NTO//` | + +## Migration note — the 11 stranded Accounting files + +The current `vocab/imports/ogit/NTO/Accounting/` carries 11 TTLs +authored by a prior session (`dcterms:creator = "Claude +(AdaWorldAPI/lance-graph 3-hop optim)"`) sitting alongside 23 upstream +files by Viktor Voss. **Those 11 belong in `exports/ogit/NTO/Accounting/`** +— at re-vendor risk where they sit today. Migration is a separate +decision and a separate PR; the scaffold here doesn't move them yet. +The list: + +``` +vocab/imports/ogit/NTO/Accounting/verbs/hasProductCategory.ttl +vocab/imports/ogit/NTO/Accounting/verbs/hasPickingType.ttl +vocab/imports/ogit/NTO/Accounting/verbs/hasFiscalCountry.ttl +vocab/imports/ogit/NTO/Accounting/attributes/productCategoryComplete.ttl +vocab/imports/ogit/NTO/Accounting/attributes/iso3166Alpha2.ttl ++ 6 more (run the dcterms:creator scan in + docs/OGIT-DOMAIN-LIFT-CATALOGUE.md § Verifying domain authorship + to list all 11) +``` + +## License + contribution + +OGAR repository license (see top-level `LICENSE`). Files here are +authored by OGAR — re-publishing back to OGIT upstream requires +explicit relicensing or arago/almato acceptance. diff --git a/vocab/exports/ogit/README.md b/vocab/exports/ogit/README.md new file mode 100644 index 0000000..666a174 --- /dev/null +++ b/vocab/exports/ogit/README.md @@ -0,0 +1,12 @@ +# `vocab/exports/ogit/` — OGAR-produced TTL in OGIT-compatible shape + +Mirrors the upstream OGIT layout (`NTO//{entities,attributes,verbs}/`, +`SGO/sgo/verbs/`) so consumers see one shape across `imports/` and +`exports/`. The choice between "upstream-mirrored" and "OGAR-produced" +is the path prefix (`vocab/imports/ogit/` vs `vocab/exports/ogit/`), +nothing else. + +See `vocab/exports/PROVENANCE.md` for the split rationale (re-vendor +safety, license/governance, upstream-contribution path) and the +migration note on the 11 stranded Accounting files currently sitting +in `imports/`.