|
| 1 | +# Odoo + Redmine/OpenProject — ERP/planning landing plan (OGAR) |
| 2 | + |
| 3 | +> **Companion to `docs/OGAR-TRANSPILE-SUBSTRATE.md`** (the per-class transpile |
| 4 | +> machine — lift + mint + emit — landed in OGAR #132). This is the durable |
| 5 | +> ERP/planning *landing plan*: how Odoo (ERP) + Redmine/OpenProject (planning) |
| 6 | +> land in OGAR in the *best native-yet-agnostic-yet-truthful* shape — "Odoo"/ |
| 7 | +> "Redmine" are **labels**; the **ontology** is the canonical ERP/planning |
| 8 | +> form, and lance-graph is the Foundry it executes on. (Relocated from |
| 9 | +> `.claude/handovers/` per `.claude/AGENTS.md` §Forbidden — durable docs live |
| 10 | +> in `docs/`.) |
| 11 | +
|
| 12 | +--- |
| 13 | + |
| 14 | +## 0. The frame (do not re-derive) |
| 15 | + |
| 16 | +- **OGAR = the transpiler** (transpile-time): `ogar-from-*` parse a source AST → |
| 17 | + the SPO/`ModelGraph` IR → classids, `ClassView`s, facets. It *produces shapes*. |
| 18 | +- **lance-graph = the Palantir-Foundry-shaped execution** (runtime): where those |
| 19 | + shapes run / store / think. Consumers (medcare-rs, woa, smb, and the |
| 20 | + ERP/planning apps) **execute on lance-graph**. |
| 21 | +- **Connecting tissue** (BBB-allowed in any consumer binary): `lance-graph-contract` |
| 22 | + / `-ontology` / `-ogar` / `-callcenter`. The BBB bars only the **brain/engine** |
| 23 | + crates (planner, cognitive engine). |
| 24 | +- **"Odoo" / "Redmine" are render labels** (the hi-u16 `AppPrefix`): Odoo=`0x0002`, |
| 25 | + OpenProject=`0x0001`, Redmine=`0x0007`. The **lo-u16 canonical concept** is the |
| 26 | + shared, agnostic identity. Same concept across apps ⇒ same lo-u16. |
| 27 | + |
| 28 | +## 1. Mission: native + agnostic + truthful |
| 29 | + |
| 30 | +- **Native:** faithful to the source's real model (Odoo's ORM fields/associations/ |
| 31 | + methods; Rails/AR `belongs_to`/`has_many`/`validates`/`acts_as_*`). |
| 32 | +- **Agnostic:** the landed shape is the **universal ERP/planning ontology**, not an |
| 33 | + Odoo/Rails-specific struct. The label drops; the concept stays. |
| 34 | +- **Truthful (lossless):** nothing dropped. Each member lands in an ontology target |
| 35 | + (below); the 16-byte facet is just the content-addressed index over it. |
| 36 | +- **Adjacent (drift-free):** ontologically-related concepts share a `part_of:is_a` |
| 37 | + prefix; identity is by **reference** (one ClassView / one adapter, never copies); |
| 38 | + the canonical codebook is single-source ("pull, never re-mint"). |
| 39 | + |
| 40 | +## 2. The import contract (per member — the truthful shape) |
| 41 | + |
| 42 | +| source member | lands as | drift-proofing | |
| 43 | +|---|---|---| |
| 44 | +| `has_field` | a `ClassView` slot | reference | |
| 45 | +| `field_type` | the **type's** `ClassView` (recursive — types are classes) | reference, not copy | |
| 46 | +| `has_function` / Odoo method / AR callback | **ActionDef/KausalSpec ontology + a consumer wrapper over reusable OGAR adapters** (logic *extracted as ontology*, not reimplemented; for ERP this is the posting/validation behaviour) | shared adapter | |
| 47 | +| object type | the **shared codebook concept id** | canonical id | |
| 48 | +| ERP ontology glue (the ICD-analogue) | **FIBO** (finance grounding) + **DOLCE** (upper) + **SKR03/04** + l10n_de regulation IRIs (HGB/UStG/GoBD) | hierarchy / pivot | |
| 49 | + |
| 50 | +> Over-cap classes (`>= 256` members — Odoo has god-models) **branch** via the SoC |
| 51 | +> lint (`ruff_spo_address::soc` / `soc_branches`): data → ClassView(s) (paginate via |
| 52 | +> class hierarchy if `> 64` distinct types), behaviour → ActionDefs/adapters. Branch, |
| 53 | +> never widen. |
| 54 | +
|
| 55 | +## 3. What already exists (grounded — build on it, don't reinvent) |
| 56 | + |
| 57 | +**odoo-rs** (private): |
| 58 | +- `od-ontology` — SPO triple corpus (`triple.rs`, ~13 predicates × 4 NARS bands); |
| 59 | + `schema_to_classes` / `emit_via_ogar` (`ogar_bridge.rs`) lower Odoo → `ogar_vocab::Class` |
| 60 | + (pulls classids via `OdooPort`, a pure static lookup — no bridge object/registry). |
| 61 | +- `od-posting` — **GoBD double-entry** posting host (gapless Belegnummer + |
| 62 | + inalterability hash chain). This is the ERP *behaviour* arm. |
| 63 | +- `alignment::ODOO_SEED` (`pivot_uri` + DOLCE + `label_uri`, keyed by odoo_class) + |
| 64 | + `data/ontologies/odoo/alignment/odoo-to-fibo.ttl` — the FIBO pivot already exists |
| 65 | + on the Odoo side. |
| 66 | + |
| 67 | +**lance-graph** (`crates/lance-graph-ontology/src/odoo_blueprint/`): |
| 68 | +- ~404 curated + huge auto-extracted `OdooEntity` consts (`account` 685 KB, `stock` |
| 69 | + 383 KB, `l10n_de_chart` 864 KB / SKR03-04 **2466 rows** + **37 UStVA Kennzahlen**), |
| 70 | + with fields, methods, state machines, German `regulation_iri` anchors. |
| 71 | +- `class_signature.rs` `StructuralSignature` / `shape_families` (FNV-1a histogram) — |
| 72 | + **the route/form duplication clustering** (collapse identical-shape classes to one |
| 73 | + shared ClassView). |
| 74 | +- `hydrators/{odoo,dolce_odoo,fibo}.rs` — Odoo hydrated as `OGIT::ODOO_V1` inheriting |
| 75 | + `FIBOFND_V1` (FIBO Foundations). |
| 76 | + |
| 77 | +**OGAR** (the transpiler): `ogar-from-ruff` (Python/Odoo SPO frontend), |
| 78 | +`ogar-from-rails` (Rails/AR), `docs/{ODOO-TRANSCODING,ODOO-DIGEST-TO-OGIT,FOUNDRY-ODOO-MARS-LENS}.md`. |
| 79 | + |
| 80 | +**ruff** (the SPO frontends + mint): |
| 81 | +- `ruff_spo_triplet` — closed **60-predicate** vocab, the neutral `ModelGraph` IR, |
| 82 | + `expand` ⇄ `reassemble` (NOTE: `reassemble` is **C++-projection-only today** — a |
| 83 | + general reassembler is the first build item if you round-trip Python/Ruby). |
| 84 | +- `ruff_spo_address` — the 16-byte facet mint (`mint_with_classid(triples, classid_of)`), |
| 85 | + `soc` + `soc_branches` (the SoC lint, ruff PR #33). |
| 86 | +- `ruff_ruby_spo` — Ruby/Rails frontend (lib-ruby-parser, `app/models`, **27 |
| 87 | + OpenProject AR predicates**); `NAMESPACE="openproject"`. |
| 88 | +- `ruff_python_dto_check` — Python harvester (→ JSON bundles; needs a `bundle → |
| 89 | + ModelGraph` adapter to enter the same IR door). |
| 90 | + |
| 91 | +**lance-graph-contract** (the tissue): |
| 92 | +- `ogar_codebook.rs` — `CODEBOOK` (`0xDDCC` concept ids), `canonical_concept_id`, |
| 93 | + `AppPrefix`, `render_classid`. **The cross-app join key.** |
| 94 | +- `class_view.rs` — `ClassView` (trait), `FieldMask`, `RenderRow`, `FieldRef`. |
| 95 | +- `facet.rs` + `facet_schema.rs` (lance-graph PR #619, **merged**) — the 16-byte |
| 96 | + facet with classid-selected format (`6×2` cascade | `4×3` SPO | `2×48`). |
| 97 | + |
| 98 | +**Convergence proof (the agnostic identity, already wired):** |
| 99 | +`crates/lance-graph-ogar/tests/bridge_codebook_convergence.rs` — OpenProject |
| 100 | +`WorkPackage` & Redmine `Issue` → `project_work_item 0x0102`; `TimeEntry` / |
| 101 | +`Stundenzettel` / Odoo `account.analytic.line` → `billable_work_entry 0x0103`; |
| 102 | +Odoo `account.move`+`sale.order` → `commercial_document 0x0202`; `product.template` |
| 103 | +→ `product 0x0207`; `account.account` → `accounting_account 0x0208`. |
| 104 | + |
| 105 | +## 4. Redmine / OpenProject (the parallel landing — same methodology) |
| 106 | + |
| 107 | +- Frontend: `ogar-from-rails` / `ruff_ruby_spo` over `app/models` (the AR shape: |
| 108 | + `belongs_to`/`has_many`/`validates`/`acts_as_list`/callbacks → the 27 AR predicates). |
| 109 | +- Identity: `WorkPackage`/`Issue` → `0x0102`; `TimeEntry` → `0x0103` (shared with Odoo |
| 110 | + — that 0x0103 convergence is the planner↔ERP billable-hours pin). |
| 111 | +- Render: the **"17-year Redmine QueryColumn evolution"** lands as the `ClassView` + |
| 112 | + `FieldMask` line-view (`ogar-render-askama::list_view`, `render_rows`) — the same |
| 113 | + field-view primitive medcare's views use. |
| 114 | + |
| 115 | +## 5. The cross-domain map (the Foundry payoff — LATER, easy once truthful) |
| 116 | + |
| 117 | +ERP ↔ planning ↔ health align at the **ontology plane**, by: |
| 118 | +- **shared lo-u16 codebook concept** (the de-facto `owl:equivalentClass`, expressed |
| 119 | + as a u16) — *the only mechanism wired today*; |
| 120 | +- **FIBO/DOLCE pivots** (Odoo already has `odoo-to-fibo.ttl`; author the symmetric |
| 121 | + twins so domains inherit a common FIBO/DOLCE category via `ContextBundle.inherits_from`). |
| 122 | + |
| 123 | +## 6. Gaps the session must close (from the ontology-preservation verification) |
| 124 | + |
| 125 | +1. **Carry the codebook concept through the mint** — always stamp `facet_classid` |
| 126 | + via the OGAR resolver; **never ship `classid=0`** (no join key, no ontology). |
| 127 | +2. **Promote groundings to typed data** — give each `CODEBOOK` row an optional |
| 128 | + `{ogit_uri, dolce_category, fibo_equivalent_class}` (lift today's doc-comment |
| 129 | + annotations), and thread it through the mint (don't drop it at the `_ =>` arm). |
| 130 | +3. **FK facet → registry** — put the concept u16 on `MappingRow` (O(1) facet → |
| 131 | + `OntologyRegistry`; today it's indirect `ogit_uri` string-match). |
| 132 | +4. **Fix the OWL hydrator** — store subject→object edges so `rdfs:subClassOf` is |
| 133 | + reconstructable (today it flattens s/p/o into one `iri_to_id`; TTL export hardcodes |
| 134 | + a flat `rdfs:subClassOf ogit:Entity`). |
| 135 | +5. **Behaviour arm** — land Odoo methods / AR callbacks as `ActionDef` ontology + |
| 136 | + reusable adapter, **not** reimplemented (od-posting's GoBD logic is the test case). |
| 137 | +6. **Land `odoo_blueprint` extracted entities as OGAR `Class`** via |
| 138 | + `od-ontology::schema_to_classes` (the future `od-ontology-bridge` binary). |
| 139 | + |
| 140 | +## 7. Hard constraints |
| 141 | + |
| 142 | +- Lossless + truthful **now**; cross-domain tissue **later** (don't let mapping drive |
| 143 | + the import shape). |
| 144 | +- OGAR = transpiler; lance-graph = Foundry execution. Tissue crates in the binary; |
| 145 | + brain/engine crates out (BBB). |
| 146 | +- Canonical codebook is single-source: **pull, never re-mint** (drift-guarded by |
| 147 | + `codebook_ids_match_ogar_vocab`). |
| 148 | +- SoC lint fires at `>= 256` members → branch. |
| 149 | +- No German PII; **no model/session identifier in any committed artifact**; audit |
| 150 | + never egresses. |
| 151 | + |
| 152 | +## 8. First moves (ordered) |
| 153 | + |
| 154 | +1. Land **one Odoo model end-to-end** (e.g. `account.move` → `0x0202`): SPO → |
| 155 | + `ModelGraph` → `Class` (via `schema_to_classes`) → mint with classid → facet, |
| 156 | + with its FIBO/DOLCE grounding **retained** (Gap #2). Round-trip it. |
| 157 | +2. Do the **OpenProject twin** (`WorkPackage` → `0x0102`) — prove the agnostic |
| 158 | + convergence (same concept, two app labels) on a real model, not just the test. |
| 159 | +3. Promote the grounding tuple on `CODEBOOK` (Gap #2) + the `MappingRow` FK (Gap #3). |
| 160 | +4. Then scale `odoo_blueprint` (the 404 entities) through the same path. |
| 161 | + |
| 162 | +## 9. References |
| 163 | + |
| 164 | +- Merged: lance-graph #619 (`facet_schema`), ruff #32 (polyglot AST RFC), OGAR #130 |
| 165 | + (V3-transpiler ADR), lance-graph #620 (node-block layout — resolved: flexible |
| 166 | + per-classid, first GUID already encodes location+relations). |
| 167 | +- In-flight: ruff #33 (`soc` + `soc_branches`, SoC lint). |
| 168 | +- Verification: ontology-preservation (workflow over ruff/lance-graph/odoo-rs); |
| 169 | + the cross-domain mapping is codebook-keyed today, FIBO/DOLCE pivots aspirational. |
| 170 | +- medcare-rs board `EPIPHANIES` E-7…E-19 (the substrate clarifications; E-18→E-19 |
| 171 | + correct the OGAR-transpiler / lance-graph-Foundry polarity). |
0 commit comments