diff --git a/README.de.md b/README.de.md new file mode 100644 index 0000000..1aa8766 --- /dev/null +++ b/README.de.md @@ -0,0 +1,189 @@ +# OGAR — Open Graph of Active Record + +> [English](README.md) · **Deutsch** · [Philosophie](docs/PHILOSOPHIE.md) + +Das [Active-Record-Muster](https://martinfowler.com/eaaCatalog/activeRecord.html) +(Martin Fowler, 2003) als kanonische 32-Bit-`classid`. Ein Codebook; +jeder Consumer zieht, prägt niemals neu. + +```rust +let cid: u16 = HealthcarePort::class_id("Patient"); // 0x0901 +let render = HealthcarePort::APP_PREFIX | (cid as u32); // 0x0005_0901 +authorize(actor, cid, Op::Read); // geteilter Grant auf lo u16 +// // lokal anreichern — deins +``` + +``` +classid : u32 = 0xAAAA ‖ 0xDDCC beide Hälften sind ADRESSE + │ │ + │ └─ lo u16 — WELCHES KONZEPT (geteilte Identität) + └─────────── hi u16 — WESSEN RENDER (App-eigene Haut) + + ──────► löst auf zu ──────► + ├─ ClassView die HAUT (pro App) + ├─ Class die FORM (kanonisch) + └─ ActionDef + KausalSpec die MAGIE (Verhalten — im Core, + nie in der Adresse) +``` + +## Active-Record-Querverweis + +| OGAR-Vokabular | Rails ActiveRecord | Odoo `models.Model` | +|-------------------------------|------------------------------------------|------------------------------------------------------| +| `ogar/Class` | `class WorkPackage < ApplicationRecord` | `class sale_order(models.Model): _name='sale.order'` | +| `ogar/Association(BelongsTo)` | `belongs_to :project` | `fields.Many2one('res.partner')` | +| `ogar/Association(HasMany)` | `has_many :line_items` | `fields.One2many('sale.order.line', 'order_id')` | +| `ogar/Association(HabTm)` | `has_and_belongs_to_many :tags` | `fields.Many2many('res.partner.category')` | +| `ogar/Mixin` | `include Mentionable` | `_inherit = 'mail.thread'` | +| `ogar/Enum` | `enum status: { open: 0, closed: 1 }` | `fields.Selection([('draft','Draft'), ...])` | +| `ogar/Field` | `t.string :subject` (in Migration) | `subject = fields.Char(required=True)` | +| `ogar/Validation` ¹ | `validates :subject, presence: true` | `@api.constrains('subject')` | +| `ogar/Callback` ¹ | `before_save :touch_parent` | `@api.depends('field_x')` / `@api.onchange` | +| `ogar/Scope` | `scope :open, -> { where(state:'open')}` | Such-Domain `[('state','=','open')]` | + +¹ Validation/Callback sind **Verhalten** — `ActionDef` + `KausalSpec`, +erreicht *über* die classid, nie in ihr. Rails, Odoo, Sequel, Django, +Prisma — gleiches Vokabular, ein IR. + +## Render-Linse — ein Konzept, viele Apps + +| classid | Konzept (lo u16) | Render (hi u16) | App | +|---------------|-----------------------------|--------------------------|----------------------| +| `0x0001_0102` | `0x0102` project_work_item | `0x0001` OpenProject | openproject-nexgen-rs | +| `0x0007_0102` | `0x0102` project_work_item | `0x0007` Redmine | (Consumer TBD) | +| `0x0005_0901` | `0x0901` patient | `0x0005` Medcare | medcare-rs | +| `0x0003_0103` | `0x0103` billable_work_entry | `0x0003` WoA | woa-rs | +| `0x0004_0103` | `0x0103` billable_work_entry | `0x0004` SMB | smb-office-rs | +| `0x0001_0103` | `0x0103` billable_work_entry | `0x0001` OpenProject | openproject-nexgen-rs | +| `0x0007_0103` | `0x0103` billable_work_entry | `0x0007` Redmine | (Consumer TBD) | +| `0x0002_0103` | `0x0103` billable_work_entry | `0x0002` Odoo | odoo-rs | + +Gleiche `0x0103` → gleicher RBAC-Grant, gleiche Ontologie, gleiche +Cross-Fork-Identität. Fünf Apps rendern *abrechenbare Zeit* auf fünf +Arten; Planer-Stunden stimmen mit der Abrechnung per Codebook-Lookup +überein, nicht per Übersetzung. + +## Consumer-Muster — vier Züge, nur einer ist deiner + +| Zug | Aufruf | Deiner? | +|-----------|--------------------------------------------|-----------| +| pull | `Port::class_id(name) -> Option` | nein — reine Funktion | +| render | `Port::APP_PREFIX \| (cid as u32)` | nein — typisierter Helfer | +| authorize | `authorize(actor, cid, op)` ² | nein — geteiltes Grant-Gitter | +| enrich | deine Domänen-Logik, an `cid` verschlüsselt | **ja** | + +² `lance-graph-rbac::authorize` ist `[H]`, gegated auf +`PROBE-OGAR-RBAC-AUTHORIZE`. Bis es ausliefert, behalte die bestehende +Auth — führe KEINE `*Bridge` als Notlösung wieder ein. + +## Design-Wächter + +| Wächter | Lesen vor | +|---|---| +| [Consumer Best Practices](docs/OGAR-CONSUMER-BEST-PRACTICES.md) | jeder Consumer-Aufrufstelle (classid · `APP_PREFIX` · `ClassView` · `*Bridge`) | +| [SurrealQL-AST-Fallen-Pre-Flight](docs/SURREAL-AST-TRAP-PREFLIGHT.md) | Producer→IR · Transcode · Codegen · `.surql`-Authoring | +| [SurrealQL-AST als Adapter](docs/SURREAL-AST-AS-ADAPTER.md) | Entscheidung Rückgrat vs. Adapter | +| [Die Firewall](docs/THE-FIREWALL.md) | jeder Hot-Path-Serialisierung (ADR-022/023) | +| [APP‖class-Codebook-Layout](docs/APP-CLASS-CODEBOOK-LAYOUT.md) | Prägen einer classid oder eines App-Präfix | +| [classid-RBAC-Keystone](docs/CLASSID-RBAC-KEYSTONE-SPEC.md) | Verdrahten der Autorisierung | +| [Consumer-Migrations-Anleitung](docs/CONSUMER-MIGRATION-HOWTO.md) | Umzug eines Consumers weg von einer `*Bridge` | + +Der Fallen-Pre-Flight (Producer-seitig) und der Best-Practices-Leitfaden +(Consumer-seitig) sind die zwei Arme einer Grenze: der Pre-Flight hält +einen *Producer* davon ab, DDL für den Core einzusetzen; der Best- +Practices-Leitfaden hält einen *Consumer* davon ab, den Core lokal neu +zu implementieren. + +## Architektur + +``` + ─── Quell-ASTs (Producer) ───────────────────────────────────── + Ruby AR Python Odoo SQL DDL ... + lib-ruby-parser libcst / ast sqlparser-rs + │ │ │ + └──────────┬─────────┴──────────┬─────────┘ + ▼ ▼ + OGAR IR (kanonisch) ⟷ ogar-extensions/* + │ Class · ActionDef · Identity · KausalSpec + │ + │ validiert von lance-graph-ontology (+ Cache) + │ geplant von lance-graph-planner + │ gespeichert als SoA-spaltenförmig (Arrow IPC, append-only Lance) + ▼ + lance-graph-Tripel + │ + ├─ Projektionen (Consumer — gleiches IR, andere Ziele): ← ADAPTER + │ • SurrealQL DDL AST (DEFINE TABLE / FIELD) nur struktureller Arm; + │ • PostgreSQL DDL (CREATE TABLE / Migration) Verhaltens-Arm + │ • OpenAPI / JSON-Schema (API-Verträge) lebt im Core + │ • TypeScript-Typen (Frontend-Interfaces) (Verhaltens-Arm: nur Core) + │ • Prisma / Drizzle (andere ORMs) + │ + └─ Laufzeit (Aktor-Schicht — Pragmatik): + lance-graph-callcenter (BEAM-Äquivalent über OGAR) +``` + +### Voller Schlüssel (16-Byte-Zoom) + +``` +key = classid(4) │ HEEL · HIP · TWIG (6) │ family(3) │ identity(3) + ▲ ▲ ▲ ▲ + Klassen- HHTL-Kaskaden-Tiers Basin- Instanz + Adresse (256×256 Zentroid- Gruppierung im Basin + (dieses Doc) Kacheln, O(1)-Distanz) +``` + +## Repository-Layout + +``` +OGAR/ +├── crates/ +│ ├── ogar-vocab/ — Rust-Typen: kanonisches IR + Codebook (class_ids, ports, APP_PREFIX) +│ └── ogar-ontology/ — Präfix-Konventionen, NiblePath-kompatible Identität +├── vocab/ +│ ├── ogar.ttl — Turtle/RDF-Kanonform +│ ├── ogar.json-ld — JSON-LD-Kanonform (geplant) +│ └── ogar.surql — SurrealQL-DDL-Projektion (nur struktureller Arm) +└── docs/ — die sieben Wächter oben + das Discovery- / Integration- / ADR-Ledger +``` + +## Producer / Consumer + +**Producer** (Quell-AST → OGAR IR): `ruff_ruby_spo` (Ruby AR, liefert in +[`openproject-nexgen-rs`](https://github.com/AdaWorldAPI/openproject-nexgen-rs) +aus) · `ogar-python` (Django + Odoo, geplant) · `ogar-sql-ddl` (geplant) +· `ogar-typescript` (geplant). + +**Consumer** (OGAR IR → Ziel): `op-codegen-pipeline` (liefert aus) · +`ogar-to-postgres` · `ogar-to-surrealql` · `ogar-to-openapi` · +`ogar-to-typescript` (alle geplant). Alle folgen dem Consumer-Muster: +**pull · render · authorize · enrich**. + +## Laufzeit — lance-graph-callcenter + +OGIT ↔ HIRO ↔ OTP/BEAM ist *Ontologie ↔ Laufzeit ↔ Aktor-Substrat*. Das +OGAR-Analogon ist der Vier-Crate-`lance-graph`-Stack: + +| Schicht | OGIT-Welt | OGAR-Welt | +|---------------------------|-------------------------|--------------------------------------------------------------------| +| Substrat-Primitive | (roher Graph) | **lance-graph-contract** — NiblePath, Identität, Versionen, Codebook | +| Ontologie-Schicht + Cache | OGIT-Vokabular + Ext. | **lance-graph-ontology** — OGAR registriert; schnelle Typ-Auflösung | +| Query / Plan | HIRO-Query-Planner | **lance-graph-planner** — ontologie-bewusste Traversierungs-Optimierung | +| Aktor-Laufzeit | HIRO-Automation + BEAM | **lance-graph-callcenter** — Dispatch an `ogar/Class`-Aktoren | + +`subClassOf` ist der Supervisions-Baum; Ontologie-Updates sind +Hot-Code-Reload. Die Klasse ist die Aktor-Spezifikation; der Aktor ist +die laufende Klasse. + +## Status + +**v0 — auslieferndes Codebook.** [`crates/ogar-vocab`](crates/ogar-vocab) +ist der sprachneutrale Lift der stabilen C17a–c-Form aus +`ruff_ruby_spo`. `class_ids`, `ports::*Port`, `APP_PREFIX` sind live; +die TTL- / SurrealQL-Projektionen in [`vocab/`](vocab) sind generierte +Artefakte (stabiles URI-Präfix `ogar/`). Vokabular-Repo zuerst, +Code-Crate zweitens — wie FOAF oder SKOS. + +## Lizenz + +MIT — siehe [`LICENSE`](LICENSE). diff --git a/README.md b/README.md index ddb7b46..7d076e6 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,34 @@ # OGAR — Open Graph of Active Record -The canonical graph-ontology and Rust toolkit for the [Active Record -pattern](https://martinfowler.com/eaaCatalog/activeRecord.html) — a -language-independent vocabulary that expresses any AR-shaped data model -(Rails, Odoo, Sequel, Django, Prisma, …) as a stable set of triples. +> **English** · [Deutsch](README.de.md) · [Philosophy](docs/PHILOSOPHY.md) -OGAR sits next to [OGIT](https://www.arago.co/ogit/) (Ontology for Graph -IT) in the same prefix-radix namespace: where OGIT covers IT-operations -semantics, OGAR covers the schema-as-class-declaration pattern that -underpins ERPs, project trackers, and any record-oriented business -application. +The [Active Record pattern](https://martinfowler.com/eaaCatalog/activeRecord.html) +(Martin Fowler, 2003) as a canonical 32-bit `classid`. One codebook; +every consumer pulls, never re-mints. -## Status - -**v0 — vocabulary stub.** The Rust types in [`crates/ogar-vocab`](crates/ogar-vocab) -are the language-neutral lift of the C17a–c stable shape currently -shipping inside [`AdaWorldAPI/openproject-nexgen-rs`](https://github.com/AdaWorldAPI/openproject-nexgen-rs)'s -`ruff_ruby_spo`. The TTL / SurrealQL projections in [`vocab/`](vocab) are -generated from those types and intended as the cite-able canonical -artifacts (stable URI prefix `ogar/`). - -This is a **vocabulary repo first, code crate second** — like FOAF or -SKOS, the ontology files are the public contract; the crate is a -producer/consumer convenience. +```rust +let cid: u16 = HealthcarePort::class_id("Patient"); // 0x0901 +let render = HealthcarePort::APP_PREFIX | (cid as u32); // 0x0005_0901 +authorize(actor, cid, Op::Read); // shared grant on lo u16 +// // enrich locally — yours +``` -## Why OGAR +``` +classid : u32 = 0xAAAA ‖ 0xDDCC both halves are ADDRESS + │ │ + │ └─ lo u16 — WHICH CONCEPT (shared identity) + └─────────── hi u16 — WHOSE RENDER (per-app skin) + + ──────► resolves to ──────► + ├─ ClassView the SKIN (per-app) + ├─ Class the SHAPE (canonical) + └─ ActionDef + KausalSpec the MAGIC (behaviour — in the Core, + never in the address) +``` -Active Record isn't a Ruby thing — it's a pattern Martin Fowler named in -2003, and every modern record-class ORM is a re-implementation of it. -Compare: +## Active Record cross-walk -| OGAR vocabulary | Rails ActiveRecord | Odoo `models.Model` | +| OGAR | Rails ActiveRecord | Odoo `models.Model` | |-------------------------------|------------------------------------------|------------------------------------------------------| | `ogar/Class` | `class WorkPackage < ApplicationRecord` | `class sale_order(models.Model): _name='sale.order'` | | `ogar/Association(BelongsTo)` | `belongs_to :project` | `fields.Many2one('res.partner')` | @@ -38,46 +36,72 @@ Compare: | `ogar/Association(HabTm)` | `has_and_belongs_to_many :tags` | `fields.Many2many('res.partner.category')` | | `ogar/Mixin` | `include Mentionable` | `_inherit = 'mail.thread'` | | `ogar/Enum` | `enum status: { open: 0, closed: 1 }` | `fields.Selection([('draft','Draft'), ...])` | -| `ogar/Validation` | `validates :subject, presence: true` | `@api.constrains('subject')` | -| `ogar/Callback` | `before_save :touch_parent` | `@api.depends('field_x')` / `@api.onchange` | -| `ogar/Scope` | `scope :open, -> { where(state: 'open')}`| search-domain `[('state','=','open')]` | -| `ogar/Field` | `t.string :subject` (in migration) | `subject = fields.Char(required=True)` | - -The same vocabulary handles all of them. Application-specific overlays -(`ogit-op/*` for OpenProject, `ogit-erp/*` for Odoo business semantics, -`ogit-gitlab/*` for GitLab) extend OGAR with domain identifiers under -the same prefix-radix routing. - -## Architecture layer-stack - -``` - lance-graph (append-only Arrow/Lance storage) - ▲ - ┌───────────────────┼───────────────────┐ - │ │ │ - ogit/IT/* ogar/ + ogit-op/* ogit-erp/* (Odoo) - (HIRO baseline) (Rails apps) (ERP business) - ▲ ▲ ▲ - │ │ │ - ITOM tools OGAR crate ERP extractors - (this repo) - ▲ - ┌─────────┴──────────┐ - │ │ - ruff_ruby_spo ogar-python (planned, for - (Ruby AR AST) Django + Odoo models) -``` - -### Universal AST ↔ OGAR ↔ DLL AST +| `ogar/Field` | `t.string :subject` (migration) | `subject = fields.Char(required=True)` | +| `ogar/Validation` ¹ | `validates :subject, presence: true` | `@api.constrains('subject')` | +| `ogar/Callback` ¹ | `before_save :touch_parent` | `@api.depends('field_x')` / `@api.onchange` | +| `ogar/Scope` | `scope :open, -> { where(state:'open')}` | search-domain `[('state','=','open')]` | + +¹ Validation/Callback are **behaviour** — `ActionDef` + `KausalSpec`, +reached *through* the classid, never inside it. Rails, Odoo, Sequel, +Django, Prisma — same vocabulary, one IR. + +## Render lens — one concept, many apps + +| classid | concept (lo u16) | render (hi u16) | app | +|---------------|-----------------------------|--------------------------|----------------------| +| `0x0001_0102` | `0x0102` project_work_item | `0x0001` OpenProject | openproject-nexgen-rs | +| `0x0007_0102` | `0x0102` project_work_item | `0x0007` Redmine | (consumer TBD) | +| `0x0005_0901` | `0x0901` patient | `0x0005` Medcare | medcare-rs | +| `0x0003_0103` | `0x0103` billable_work_entry | `0x0003` WoA | woa-rs | +| `0x0004_0103` | `0x0103` billable_work_entry | `0x0004` SMB | smb-office-rs | +| `0x0001_0103` | `0x0103` billable_work_entry | `0x0001` OpenProject | openproject-nexgen-rs | +| `0x0007_0103` | `0x0103` billable_work_entry | `0x0007` Redmine | (consumer TBD) | +| `0x0002_0103` | `0x0103` billable_work_entry | `0x0002` Odoo | odoo-rs | + +Same `0x0103` → same RBAC grant, same ontology, same cross-fork +identity. Five apps render *billable time* five ways; planner hours +align with billing by codebook lookup, not translation. + +## Consumer pattern — four moves, only one is yours + +| Move | Call | Yours? | +|-----------|--------------------------------------------|-----------| +| pull | `Port::class_id(name) -> Option` | no — pure function | +| render | `Port::APP_PREFIX \| (cid as u32)` | no — typed helper | +| authorize | `authorize(actor, cid, op)` ² | no — shared grant lattice | +| enrich | your domain logic keyed on `cid` | **yes** | + +² `lance-graph-rbac::authorize` is `[H]`, gated on `PROBE-OGAR-RBAC-AUTHORIZE`. +Until it ships, keep existing auth — do NOT re-introduce a `*Bridge` as a stopgap. + +## Design guards + +| Guard | Read before | +|---|---| +| [Consumer best practices](docs/OGAR-CONSUMER-BEST-PRACTICES.md) | any consumer call site (classid · `APP_PREFIX` · `ClassView` · `*Bridge`) | +| [SurrealQL-AST trap pre-flight](docs/SURREAL-AST-TRAP-PREFLIGHT.md) | producer→IR · transcode · codegen · `.surql` authoring | +| [SurrealQL AST as adapter](docs/SURREAL-AST-AS-ADAPTER.md) | deciding spine vs. adapter | +| [The Firewall](docs/THE-FIREWALL.md) | any hot-path serialization (ADR-022/023) | +| [APP‖class codebook layout](docs/APP-CLASS-CODEBOOK-LAYOUT.md) | minting a classid or app prefix | +| [classid-RBAC keystone](docs/CLASSID-RBAC-KEYSTONE-SPEC.md) | wiring authorization | +| [Consumer migration how-to](docs/CONSUMER-MIGRATION-HOWTO.md) | moving a consumer off a `*Bridge` | + +The trap pre-flight (producer-side) and the best-practices guide +(consumer-side) are the two arms of one boundary: the pre-flight stops +a *producer* substituting DDL for the Core; the best-practices guide +stops a *consumer* re-implementing the Core locally. + +## Architecture ``` ─── source ASTs (producers) ────────────────────────────────── Ruby AR Python Odoo SQL DDL ... - lib-ruby-parser libcst/ast sqlparser-rs + lib-ruby-parser libcst / ast sqlparser-rs │ │ │ └──────────┬─────────┴──────────┬─────────┘ ▼ ▼ OGAR IR (canonical) ⟷ ogar-extensions/* + │ Class · ActionDef · Identity · KausalSpec │ │ validated by lance-graph-ontology (+ cache) │ planned by lance-graph-planner @@ -85,80 +109,76 @@ the same prefix-radix routing. ▼ lance-graph triples │ - ├─ projections (consumers — same IR, other targets): - │ • SurrealQL DDL AST (DEFINE TABLE / FIELD) - │ • PostgreSQL DDL (CREATE TABLE / migration) - │ • OpenAPI / JSON-Schema (API contracts) - │ • TypeScript types (frontend interfaces) + ├─ projections (consumers — same IR, other targets): ← ADAPTERS + │ • SurrealQL DDL AST (DEFINE TABLE / FIELD) structural arm only; + │ • PostgreSQL DDL (CREATE TABLE / migration) behavioural arm + │ • OpenAPI / JSON-Schema (API contracts) lives in the Core + │ • TypeScript types (frontend interfaces) (behaviour arm: Core only) │ • Prisma / Drizzle (other ORMs) │ └─ runtime (actor layer — pragmatics): lance-graph-callcenter (BEAM-equivalent over OGAR) ``` -The SurrealQL DLL AST is **both producer and consumer**: source-of-truth -in SurrealQL → OGAR → Rails/Odoo/PG, or source-of-truth in Rails AR → -OGAR → SurrealQL/PG. The IR is the meeting point. +### Full key (16-byte zoom) + +``` +key = classid(4) │ HEEL · HIP · TWIG (6) │ family(3) │ identity(3) + ▲ ▲ ▲ ▲ + class addr HHTL cascade tiers basin instance + (this doc) (256×256 centroid tiles, grouping within basin + O(1) distance lookup) +``` ## Repository layout ``` OGAR/ ├── crates/ -│ ├── ogar-vocab/ — Rust types: the canonical IR -│ └── ogar-ontology/ — prefix conventions, NiblePath-compatible identity +│ ├── ogar-vocab/ — Rust types: canonical IR + codebook (class_ids, ports, APP_PREFIX) +│ └── ogar-ontology/ — prefix conventions, NiblePath-compatible identity ├── vocab/ -│ ├── ogar.ttl — Turtle/RDF canonical form -│ ├── ogar.json-ld — JSON-LD canonical form (planned) -│ └── ogar.surql — SurrealQL DDL projection -└── docs/ - └── ARCHITECTURE.md — the layer-stack writeup +│ ├── ogar.ttl — Turtle/RDF canonical form +│ ├── ogar.json-ld — JSON-LD canonical form (planned) +│ └── ogar.surql — SurrealQL DDL projection (structural arm only) +└── docs/ — the seven guards above + the discovery / integration / ADR ledger ``` -## Producers (current and planned) +## Producers / consumers -- **`ruff_ruby_spo`** (lives in `AdaWorldAPI/openproject-nexgen-rs`, - C17a–c stable): parses Ruby ActiveRecord models with - `lib-ruby-parser`, emits OGAR-shaped IR. Closes 17/21 coverage-probe - gaps over real OpenProject source. -- **`ogar-python`** (planned): Django + Odoo `models.Model` extractor - via `libcst` / Python `ast`. -- **`ogar-sql-ddl`** (planned): SQL DDL via `sqlparser-rs`. -- **`ogar-typescript`** (planned): Prisma + TypeORM + Drizzle schema - extractor. +**Producers** (source AST → OGAR IR): `ruff_ruby_spo` (Ruby AR, shipping +in [`openproject-nexgen-rs`](https://github.com/AdaWorldAPI/openproject-nexgen-rs)) +· `ogar-python` (Django + Odoo, planned) · `ogar-sql-ddl` (planned) · +`ogar-typescript` (planned). -## Consumers (current and planned) +**Consumers** (OGAR IR → target): `op-codegen-pipeline` (shipping) · +`ogar-to-postgres` · `ogar-to-surrealql` · `ogar-to-openapi` · +`ogar-to-typescript` (all planned). All follow the consumer pattern: +**pull · render · authorize · enrich**. -- **`op-codegen-pipeline`** (lives in `AdaWorldAPI/openproject-nexgen-rs`): - consumes OGAR IR, emits `ogit-op/*` triples into lance-graph. -- **`ogar-to-postgres`** (planned): OGAR → PostgreSQL DDL / Rails - migrations. -- **`ogar-to-surrealql`** (planned): OGAR → SurrealQL `DEFINE TABLE` / - `DEFINE FIELD`. -- **`ogar-to-openapi`** (planned): OGAR → OpenAPI / JSON:API contracts. -- **`ogar-to-typescript`** (planned): OGAR → `interface` declarations - with zod schemas. - -## The runtime — lance-graph-callcenter +## Runtime — lance-graph-callcenter OGIT ↔ HIRO ↔ OTP/BEAM is *ontology ↔ runtime ↔ actor substrate*. The OGAR analogue is the four-crate `lance-graph` stack: -| Layer | OGIT-world | OGAR-world (planned) | -|-------------------------|-------------------------|---------------------------------------------------------------------------------------| -| Substrate primitives | (raw graph) | **lance-graph-contract** — NiblePath, identity, versions, append-only | -| Ontology layer + cache | OGIT vocab + extensions | **lance-graph-ontology** — OGAR + extensions registered; fast type-resolution | -| Query / plan | HIRO query planner | **lance-graph-planner** — ontology-aware traversal optimisation | -| Actor runtime | HIRO automation + BEAM | **lance-graph-callcenter** — dispatch messages to `ogar/Class` actors | - -Each `ogar/Class` (e.g. `ogit-op/WorkPackage`) registers actors that -handle messages addressed to instances. `subClassOf` is the supervision -tree; ontology updates are hot-code reload. - -**Same form, two modes**: business-data-classes (semantics — what is -true) and actors (pragmatics — what is done) are two views on the same -append-only log. The class is the actor spec; the actor is the class -running. +| Layer | OGIT-world | OGAR-world | +|-------------------------|-------------------------|--------------------------------------------------------------------| +| Substrate primitives | (raw graph) | **lance-graph-contract** — NiblePath, identity, versions, codebook | +| Ontology layer + cache | OGIT vocab + extensions | **lance-graph-ontology** — OGAR registered; fast type-resolution | +| Query / plan | HIRO query planner | **lance-graph-planner** — ontology-aware traversal optimisation | +| Actor runtime | HIRO automation + BEAM | **lance-graph-callcenter** — dispatch to `ogar/Class` actors | + +`subClassOf` is the supervision tree; ontology updates are hot-code +reload. The class is the actor spec; the actor is the class running. + +## Status + +**v0 — shipping codebook.** [`crates/ogar-vocab`](crates/ogar-vocab) is +the language-neutral lift of the C17a–c stable shape from +`ruff_ruby_spo`. `class_ids`, `ports::*Port`, `APP_PREFIX` are live; the +TTL/SurrealQL projections in [`vocab/`](vocab) are generated artifacts +(stable URI prefix `ogar/`). Vocabulary repo first, code crate second — +like FOAF or SKOS. ## License diff --git a/docs/PHILOSOPHIE.md b/docs/PHILOSOPHIE.md new file mode 100644 index 0000000..f5b5e9b --- /dev/null +++ b/docs/PHILOSOPHIE.md @@ -0,0 +1,124 @@ +# OGAR — Philosophie + +> [English](PHILOSOPHY.md) · **Deutsch** · [↑ README (Spezifikation)](../README.de.md) + +Das ist das *Warum* vor dem *Was*. Für die Spezifikation siehe die +[README](../README.de.md). Für die operative Disziplin siehe die +[sieben Design-Wächter](../README.de.md#design-wächter) in `docs/`. + +--- + +### Frag eine Fechterin mit dreißig Jahren Erfahrung, was Fechten ist — und sie greift zur Kelle und schöpft Wasser. + +Eine Bewegung. Ruhig, exakt, nichts verschüttet. Die ganze Kunst steckt +darin — die Ökonomie, die Linie, das Gewicht, das schon vor dem Heben +bekannt ist. Es sieht aus wie nichts. Das *sind* die dreißig Jahre. + +OGAR ist eine Kelle. + +```rust +let cid = HealthcarePort::class_id("Patient"); // Some(0x0901) +``` + +Ein Aufruf. Du ziehst die classid, und alles, was das Konzept braucht, +ist bereits da — seine Form, sein Grant, sein Lebenszyklus, sein Platz +in einem Adressraum von 2,8 × 10¹⁴ Zellen, den sich jede App teilt, die +je einen Patienten benannt hat. Du baust es nicht. Du prägst es nicht. +Du **schöpfst**. + +Der Rest dieses Dokuments sind die dreißig Jahre. + +--- + +## Die drei Erkenntnisse + +Alles andere ist Detail. Wenn du dir nur drei Dinge merkst, dann diese — +sie sind das Muskelgedächtnis, das der ganze Stack automatisch machen +soll. + +### 1 · Die classid ist reine Adresse. Die Magie ist das, worauf sie auflöst. + +Eine classid sind 32 Bit *Adresse* — mehr nicht. Sie trägt kein +Verhalten. Sie zu kennen sagt dir *welcher* Knoten, *wessen* Haut, +*welche* Form — nie *wie er sich verhält*. + +``` +classid : u32 = 0xAAAA ‖ 0xDDCC beide Hälften sind ADRESSE + │ │ + │ └─ lo u16 — WELCHES KONZEPT (geteilte Identität) + └─────────── hi u16 — WESSEN RENDER (App-eigene Haut) + + ──────► löst auf zu ──────► + ├─ ClassView die HAUT (Render — pro App) + ├─ Class die FORM (strukturell — geteilt) + └─ ActionDef + KausalSpec die MAGIE (Verhalten — Lebenszyklus, + Callbacks, Validierungen; + immer im Core, + nie in der Adresse) +``` + +Der Rails-Block `class Patient < ApplicationRecord; validates …; +before_save …; end` — die *Klassen-Magie* — steckt **nicht** in der ID. +Er ist das, worauf die ID verweist. Die ID adressiert; der Core verhält +sich. + +### 2 · Ein Konzept, viele Render. + +Die **unteren** 16 Bit benennen das Konzept — geteilt von jeder App, mit +einem RBAC-Grant und einer Ontologie. Die **oberen** 16 Bit benennen die +Render-Linse — das eigene Template jeder App, ihre eigene Haut. Gleiches +Konzept, andere Kleidung: + +``` +0x0001_0102 ─ OpenProjects WorkPackage ┐ gleiche lo 0x0102 = project_work_item +0x0007_0102 ─ Redmines Issue ┘ → EIN Grant, EINE Ontologie, ZWEI Templates +``` + +Fünf Apps können *abrechenbare Zeit* auf fünf Arten rendern und lösen +trotzdem auf dieselbe `0x0103` auf — „Planer-Stunden stimmen mit der +Abrechnung überein" ist also ein Codebook-Lookup, keine +Übersetzungsschicht. + +### 3 · Der Core verhält sich; der Producer speist ihn; der Adapter hallt nur wider. + +Verhalten fließt **Producer → OGAR `Class` + `ActionDef` → Adapter** — +nie Producer → DDL. SurrealQL, PostgreSQL, OpenAPI, TypeScript sind +**Adapter**: verlustbehaftete Echos des Core, *aus* ihm emittiert, +niemals das Rückgrat. Lebenszyklus in `DEFINE EVENT … WHEN … THEN …` zu +kodieren heißt, den Wert in den Schlüssel zu legen — die Falle, gegen +die die [Design-Wächter](../README.de.md#design-wächter) existieren. + +--- + +## Das Muskelgedächtnis, in einem Atemzug + +> **Zieh die classid. Präge niemals den Core neu.** + +Ein Consumer (medcare-rs, woa-rs, smb-office-rs, odoo-rs, …) ist +*Anreicherung über einer classid* — mehr nicht. Vier Züge, und nur einer +davon ist je deiner: + +| Zug | Die Geste | Deiner? | +|---|---|---| +| **Pull** | `Port::class_id("Patient")` → `0x0901` | nein — reine Funktion | +| **Render** | `APP_PREFIX \| concept` → `0x0005_0901` | nein — typisierter Helfer | +| **Authorize** | `authorize(actor, concept, op)` | nein — das geteilte Grant-Gitter | +| **Enrich** | deine Domänen-Logik, an der classid verschlüsselt | **ja** — das ist der Teil, der legitim deiner ist | + +Du kopierst nie das Codebook. Du konstruierst nie eine `*Bridge`. Du +prägst nie ein paralleles Register. Der Core besitzt Form und Verhalten; +du hältst die classid und fügst deine Domäne hinzu. Das ist das +Schöpfen. + +--- + +## Wohin als Nächstes + +- [**README**](../README.de.md) — die Spezifikation (umgekehrte Pyramide, mustergedrängt) +- [**OGAR-CONSUMER-BEST-PRACTICES.md**](OGAR-CONSUMER-BEST-PRACTICES.md) — das Muskelgedächtnis, durchgearbeitet mit Beispielen für jeden Consumer +- [**SURREAL-AST-TRAP-PREFLIGHT.md**](SURREAL-AST-TRAP-PREFLIGHT.md) — der Producer-seitige Wächter gegen die Negative-Schönheit-Falle aus Erkenntnis 3 +- [**APP-CLASS-CODEBOOK-LAYOUT.md**](APP-CLASS-CODEBOOK-LAYOUT.md) — Erkenntnis 2 in formaler Spezifikationsform +- [**THE-FIREWALL.md**](THE-FIREWALL.md) — der strukturelle Grund, warum Adapter nicht das Rückgrat sein können + +Die README ist die Spezifikation. Die sieben Design-Wächter in `docs/` +sind die operative Disziplin. Dieses Dokument ist nur das Warum. diff --git a/docs/PHILOSOPHY.md b/docs/PHILOSOPHY.md new file mode 100644 index 0000000..fab680c --- /dev/null +++ b/docs/PHILOSOPHY.md @@ -0,0 +1,119 @@ +# OGAR — Philosophy + +> **English** · [Deutsch](PHILOSOPHIE.md) · [↑ README (spec)](../README.md) + +This is the *why* before the *what*. For the spec, see the +[README](../README.md). For the operational discipline, see the +[seven design guards](../README.md#design-guards) in `docs/`. + +--- + +### Ask a fencer with thirty years what fencing is, and she picks up a ladle and scoops water. + +One motion. Unhurried, exact, nothing spilled. The whole art is in it — +the economy, the line, the weight known before the lift. It looks like +nothing. That *is* the thirty years. + +OGAR is a ladle. + +```rust +let cid = HealthcarePort::class_id("Patient"); // Some(0x0901) +``` + +One call. You pull the classid, and everything the concept needs is +already there — its shape, its grant, its lifecycle, its place in a +2.8 × 10¹⁴-cell address space shared by every app that ever named a +patient. You don't build it. You don't mint it. You **scoop**. + +The rest of this document is the thirty years. + +--- + +## The three epiphanies + +Everything else is detail. If you only remember three things, remember +these — they are the muscle memory the whole stack is built to make +automatic. + +### 1 · The classid is pure address. The magic is what it resolves to. + +A classid is 32 bits of *address* — nothing more. It carries no +behaviour. Knowing it tells you *which* node, *whose* skin, *what* +shape — never *how it acts*. + +``` +classid : u32 = 0xAAAA ‖ 0xDDCC both halves are ADDRESS + │ │ + │ └─ lo u16 — WHICH CONCEPT (shared identity) + └─────────── hi u16 — WHOSE RENDER (per-app skin) + + ──────► resolves to ──────► + ├─ ClassView the SKIN (render — per-app) + ├─ Class the SHAPE (structural — shared) + └─ ActionDef + KausalSpec the MAGIC (behaviour — lifecycle, + callbacks, validations; + always in the Core, + never in the address) +``` + +The rails `class Patient < ApplicationRecord; validates …; before_save +…; end` blob — the *class magic* — is **not** in the id. It is what the +id keys into. The id addresses; the Core behaves. + +### 2 · One concept, many renders. + +The **low** 16 bits name the concept — shared by every app, with one +RBAC grant and one ontology. The **high** 16 bits name the render lens — +each app's own template, its own skin. Same concept, different dress: + +``` +0x0001_0102 ─ OpenProject's WorkPackage ┐ same lo 0x0102 = project_work_item +0x0007_0102 ─ Redmine's Issue ┘ → ONE grant, ONE ontology, TWO templates +``` + +Five apps can render *billable time* five ways and still resolve to the +**same** `0x0103` — so "planner hours align with billing" is a codebook +lookup, not a translation layer. + +### 3 · The Core behaves; the producer feeds it; the adapter only echoes. + +Behaviour flows **producer → OGAR `Class` + `ActionDef` → adapter** — +never producer → DDL. SurrealQL, PostgreSQL, OpenAPI, TypeScript are +**adapters**: lossy echoes of the Core, emitted *from* it, never the +spine. Trying to encode lifecycle in `DEFINE EVENT … WHEN … THEN …` is +putting the value into the key — the trap the +[design guards](../README.md#design-guards) exist to stop. + +--- + +## The muscle memory, in one breath + +> **Pull the classid. Never re-mint the Core.** + +A consumer (medcare-rs, woa-rs, smb-office-rs, odoo-rs, …) is +*enrichment over a classid* — nothing more. Four moves, and only one of +them is ever yours to write: + +| Move | The gesture | Yours? | +|---|---|---| +| **Pull** | `Port::class_id("Patient")` → `0x0901` | no — a pure function | +| **Render** | `APP_PREFIX \| concept` → `0x0005_0901` | no — a typed helper | +| **Authorize** | `authorize(actor, concept, op)` | no — the shared grant lattice | +| **Enrich** | your domain logic keyed on the classid | **yes** — this is the part that's legitimately yours | + +You never copy the codebook. You never construct a `*Bridge`. You never +mint a parallel registry. The Core owns the shape and the behaviour; you +hold the classid and add your domain. That is the scoop. + +--- + +## Where to next + +- [**README**](../README.md) — the spec (reverse-pyramid, pattern-dense) +- [**OGAR-CONSUMER-BEST-PRACTICES.md**](OGAR-CONSUMER-BEST-PRACTICES.md) — the muscle memory drilled with worked examples for every consumer +- [**SURREAL-AST-TRAP-PREFLIGHT.md**](SURREAL-AST-TRAP-PREFLIGHT.md) — the producer-side guard against epiphany 3's negative-beauty trap +- [**APP-CLASS-CODEBOOK-LAYOUT.md**](APP-CLASS-CODEBOOK-LAYOUT.md) — epiphany 2 in formal spec form +- [**THE-FIREWALL.md**](THE-FIREWALL.md) — the structural reason adapters can't be the spine + +The README is the spec. The seven design guards in `docs/` are the +operational discipline. This document is just the why.