Skip to content

Commit 09ac8eb

Browse files
authored
Merge pull request #105 from AdaWorldAPI/claude/medcare-bridge-lance-graph-wmx76z
OGIT 1:1 import + ogar-from-schema producer with reverse-emit and SGO
2 parents 283bd6c + cce8420 commit 09ac8eb

2,010 files changed

Lines changed: 59867 additions & 16 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/board/EPIPHANIES.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,102 @@
1515
1616
## Entries (newest first)
1717

18+
## 2026-06-22 — Schema-vs-source duality: schemas lift structure bijectively; source ASTs lift behaviour best-effort; they cross-validate at the structural boundary
19+
**Status:** FINDING
20+
**Scope:** producer architecture × MARS calibration × Foundry-Odoo lens × the bardioc migration
21+
22+
The work landing this session imported OGIT's MARS taxonomy (NTO/MARS,
23+
SGO upper ontology, root `ogit.ttl`, MARS XSD oracle) and built the
24+
`ogar-from-schema` producer to lift it. In the process the
25+
structural-vs-behavioural arm split — already carved on the **codegen**
26+
side by `SURREAL-AST-AS-ADAPTER.md` — turned out to apply with equal
27+
sharpness on the **producer** side. Schema-driven producers (XSD, TTL,
28+
JSON-Schema, OpenAPI, Prisma) lift the **structural arm** bijectively
29+
because schemas are declarative-by-construction. Source-AST producers
30+
(`ogar-from-rails`, `ogar-from-elixir`, future `ogar-from-python`) lift
31+
the **behavioural arm** best-effort because source code is dynamic
32+
(Ruby `method_missing`, Python decorators, Elixir macros all defeat
33+
static extraction).
34+
35+
The two are not redundant. They cover **disjoint surfaces** that meet
36+
only at the structural arm. At that meeting point they become each
37+
other's **oracle**: emit a schema from a source-lifted `Class`, diff
38+
against the committed schema, every PR catches structural drift on the
39+
way in. **This is exactly what Palantir Foundry charges money for
40+
("ontology change management"); the schema producer + 50 LOC of
41+
reverse-emit gets it for free.**
42+
43+
For bardioc concretely: MARS-Schema XSD + OGIT NTO/MARS TTL are TWO
44+
independent encodings of the same taxonomy. The schema lift's
45+
agreement with the XSD oracle (`ttl::tests::application_class_values_appear_in_xsd_oracle`)
46+
is the chess-grade calibration applied at the schema-vs-schema boundary
47+
— stronger than chess's source-vs-runtime oracle because both witnesses
48+
are frozen schemas.
49+
50+
This finding reshapes every future producer: structural arm gets a
51+
schema front-end first (cheap, bijective); behavioural arm gets a
52+
source-AST front-end second (expensive, best-effort); the cross-check
53+
at the structural boundary is free and replaces a paid platform feature.
54+
55+
Evidence:
56+
- `crates/ogar-from-schema/` (lift) + `ttl_emit::all_mars_ttl_files_roundtrip` (29 MARS TTLs)
57+
- `sgo::all_sgo_verbs_roundtrip` (176 SGO verbs)
58+
- `_oracle/extract_classes.py` (Python 2, runs unchanged on Py3 via mechanical 2to3)
59+
- `vocab/imports/ogit/NTO/MARS/_oracle/classifications.adoc` (XSD-extracted reference)
60+
- `docs/HIRO-IN-CLASSES.md §2` (the framing)
61+
- `docs/MARS-TRANSCODING.md` (the calibration spec)
62+
- `docs/FOUNDRY-ODOO-MARS-LENS.md` (the cross-domain learning)
63+
64+
The funny part: this was already implicit in the carved spine-adapter
65+
split, just on the other side. The session ended with both ends of the
66+
producer↔codegen pipeline using the same structural/behavioural carving.
67+
68+
## 2026-06-22 — Reverse-engineering bijection: OGAR Class structures emit back to OGIT-flavoured TTL with semantic equality
69+
**Status:** FINDING
70+
**Scope:** producer round-trip × bardioc migration safety × no two-way translation tables
71+
72+
The `ogar-from-schema::ttl` parser was made symmetric by adding
73+
`ttl_emit::emit_entity` and `emit_attribute`. The contract is
74+
**semantic bijection**: `parse(emit(parse(src))) == parse(src)` for
75+
every predicate the OGIT TTL dialect uses; whitespace, comment
76+
positions, and `@prefix` declaration order are not preserved (and
77+
should not be — they are not load-bearing for the structural arm).
78+
79+
Pursuing byte-bijection would force the producer to carry raw text
80+
alongside the parsed structure, defeating the "schema as IR" pattern.
81+
The right contract is what survives a meaningful re-emit, not what
82+
survives `diff -q`. Tested on every MARS TTL (29 files) and every SGO
83+
verb TTL (176 files); zero failures.
84+
85+
**Migration consequence:** colleagues can author OGAR `Class`
86+
structures in Rust, emit OGIT-flavoured TTL, and feed it back into
87+
bardioc's existing ingest pipeline. No migration cliff, no two-way
88+
translation table, no separate drift detector to wire up. **The
89+
producer IS the translator.**
90+
91+
## 2026-06-22 — SGO is the AST predicate vocabulary
92+
**Status:** FINDING
93+
**Scope:** AST design × `ogit:allowed` resolution × Foundry-parity
94+
95+
Every NTO entity's `ogit:allowed ([verb target])` block references
96+
verbs that live in OGIT's upper ontology (`SGO/sgo/verbs/`). 176 verb
97+
TTLs — `dependsOn`, `contains`, `runsOn`, `generates`, `relates`,
98+
`causes`, `affects`, `assignedTo`, `audits`, `bornIn`, `bills`, … —
99+
each with a `dcterms:description`, `dcterms:creator`, validity range.
100+
101+
Before this session: those references were captured as raw strings;
102+
no validation that a verb existed or matched its declared semantics.
103+
After this session: `ogar-from-schema::sgo::parse_verb` lifts each
104+
SGO verb TTL into a typed `VerbDecl`, and the NTO `ogit:allowed`
105+
references resolve against a typed registry instead of string
106+
compare. This is the **AST predicate vocabulary** OGAR's `Association`
107+
and `ActionDef` surfaces have been needing — it was sitting in OGIT
108+
the whole time.
109+
110+
The 176 verbs are the same verbs every Foundry "object graph link
111+
type" represents. Foundry curates them as a platform feature; OGIT
112+
ships them as MIT-licensed TTL. OGAR makes them typed Rust.
113+
18114
## 2026-06-04 — Sprint 7 muscle-memory is canonical; the OGAR#7 std::sync correction round-tripped
19115
**Status:** FINDING
20116
**Scope:** Sprint 7 wiring spec × three-way alignment (Kanban/ractor/SurrealQL) × cross-session correction round-trip

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ members = [
1313
"crates/ogar-from-elixir",
1414
"crates/ogar-from-ruff",
1515
"crates/ogar-from-rails",
16+
"crates/ogar-from-schema",
1617
"crates/ogar-class-view",
1718
"crates/ogar-render-askama",
1819
]

crates/ogar-from-schema/Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "ogar-from-schema"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
authors.workspace = true
8+
rust-version.workspace = true
9+
description = "Schema-as-input producer family for OGAR IR. Lifts the STRUCTURAL ARM of a domain from declarative schemas (OGIT TTL today; XSD/JSON-Schema/OpenAPI/Prisma queued) into ogar-vocab Class/Attribute/Association/EnumSource without parsing source code. Pair with source-AST producers (ogar-from-rails, ogar-from-elixir) which carry the BEHAVIORAL arm — see docs/HIRO-IN-CLASSES.md §2 'Schema lifts structure; source lifts behavior'."
10+
11+
[features]
12+
default = []
13+
serde = ["dep:serde", "ogar-vocab/serde"]
14+
15+
[dependencies]
16+
ogar-vocab = { path = "../ogar-vocab" }
17+
serde = { workspace = true, optional = true }
18+
19+
# No external TTL/XSD parser pulled in yet: the v0 reader uses a
20+
# narrow line-oriented walker that handles the shapes OGIT's TTL
21+
# actually emits (a tiny, machine-stable subset of full Turtle).
22+
# When the surface grows (Wikidata-shaped TTL, full RDF/XML, OWL
23+
# imports), swap in `oxttl` / `oxrdf` here without touching the
24+
# Class/Attribute target shape this crate produces.

crates/ogar-from-schema/src/lib.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//! `ogar-from-schema` — schema-as-input producer family.
2+
//!
3+
//! Sibling to source-AST producers (`ogar-from-rails`, `ogar-from-elixir`,
4+
//! `ogar-from-ruff`). The pair carves the producer surface along the same
5+
//! split [`SURREAL-AST-AS-ADAPTER.md`] already carved on the codegen side:
6+
//!
7+
//! ```text
8+
//! STRUCTURAL ARM ──► ogar-from-schema (this crate)
9+
//! (XSD, TTL, schemas are declarative & bijective
10+
//! JSON-Schema, → round-trip provable
11+
//! OpenAPI, Prisma) → 80 OGIT domains for free
12+
//!
13+
//! BEHAVIORAL ARM ──► ogar-from-{rails,elixir,ruff,…}
14+
//! (callbacks, FSMs, source code only — schemas can't carry it
15+
//! @api.depends, → best-effort, language-specific
16+
//! gen_statem,…) → adds what schemas can't see
17+
//! ```
18+
//!
19+
//! **Why both — the funny insight** (`docs/HIRO-IN-CLASSES.md` §2):
20+
//!
21+
//! A schema gets you LESS, more RELIABLY. A source AST gets you MORE,
22+
//! less reliably. The two are not redundant — they cover disjoint surfaces
23+
//! that meet only at the structural arm, and at that meeting point they
24+
//! become each other's oracle:
25+
//!
26+
//! - Schema lift produces a `Class` set that is **byte-exact** (the schema
27+
//! IS the contract).
28+
//! - Source lift produces a `Class` set that is **best-effort** (Ruby is
29+
//! dynamic; `method_missing` defeats static extraction).
30+
//! - Where both cover the same domain, emitting a schema from the
31+
//! source-lifted `Class` and diffing against the committed schema is a
32+
//! **drift detector** for every PR. That is what Palantir Foundry charges
33+
//! for as "ontology change management" — we get it from this crate
34+
//! plus `extract_classes.py`.
35+
//!
36+
//! # v0 scope
37+
//!
38+
//! - `ttl` front-end: reads the line-oriented OGIT TTL dialect into
39+
//! [`Class`]. Demo target: `vocab/imports/ogit/MARS/`.
40+
//! - Cross-check: the **fixed-enum agreement test** asserts the TTL
41+
//! `ogit:validation-parameter` set matches the XSD oracle's extracted
42+
//! classifications (the chess-grade bijection, applied at the schema
43+
//! level — see `docs/calibration/mars/README.md`).
44+
//!
45+
//! # Out of v0 (queued)
46+
//!
47+
//! - `xsd` front-end (lift `MARSSchema2015.xsd` directly; cross-check vs TTL).
48+
//! - `json_schema`, `openapi`, `prisma` front-ends.
49+
//! - Full Turtle / RDF-XML / OWL import via `oxttl`/`oxrdf`.
50+
//! - Behavioral-arm fields on `Class` are intentionally left empty by this
51+
//! crate — schemas can't carry them; source-AST producers fill them in.
52+
//!
53+
//! [`SURREAL-AST-AS-ADAPTER.md`]: ../docs/SURREAL-AST-AS-ADAPTER.md
54+
55+
#![warn(missing_docs)]
56+
#![forbid(unsafe_code)]
57+
58+
use ogar_vocab::{Attribute, Class, EnumDecl, EnumSource, Language};
59+
60+
pub mod sgo;
61+
pub mod ttl;
62+
pub mod ttl_emit;
63+
64+
/// What a single TTL file describes — exactly one of: an entity (`Class`),
65+
/// a datatype attribute (`Attribute`), or a verb (`Association` shape).
66+
#[derive(Debug, Clone, PartialEq, Eq)]
67+
#[non_exhaustive]
68+
pub enum TtlDeclaration {
69+
/// An `rdfs:Class` declaration (`entities/<Name>.ttl`).
70+
Entity(EntityDecl),
71+
/// An `owl:DatatypeProperty` declaration (`<Entity>/attributes/<name>.ttl`).
72+
DatatypeAttribute(AttributeDecl),
73+
}
74+
75+
/// Lifted shape of a single OGIT entity TTL file (e.g.
76+
/// `entities/Machine.ttl`). The shape captures every predicate the OGIT
77+
/// TTL dialect uses on `rdfs:Class` subjects, so that round-tripping
78+
/// (`parse → emit → parse`) preserves the semantic content. Mapping into
79+
/// the full [`ogar_vocab::Class`] surface happens in [`into_class`];
80+
/// emitting back to TTL happens in [`crate::ttl_emit::emit_entity`].
81+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
82+
pub struct EntityDecl {
83+
/// The CURIE-form name (`ogit.MARS:Machine`).
84+
pub curie: String,
85+
/// The local name (`Machine`).
86+
pub name: String,
87+
/// `rdfs:label` text (often equal to `name`).
88+
pub label: String,
89+
/// `dcterms:description` text — multi-line preserved verbatim.
90+
pub description: String,
91+
/// `rdfs:subClassOf` target as written (`ogit:Entity`).
92+
pub parent: Option<String>,
93+
/// `dcterms:valid` validity-range string (`"start=2018-06-01;"`).
94+
pub dcterms_valid: Option<String>,
95+
/// `dcterms:creator` text (`"fotto@arago.de"` / `"FCO"`).
96+
pub dcterms_creator: Option<String>,
97+
/// `ogit:scope` text (`"NTO"` / `"SGO"`).
98+
pub ogit_scope: Option<String>,
99+
/// `ogit:parent` token (`ogit:Node`) — separate from
100+
/// [`Self::parent`] (`rdfs:subClassOf`) — both can be present and
101+
/// carry different information.
102+
pub ogit_parent: Option<String>,
103+
/// `ogit:mandatory-attributes` list, as written.
104+
pub mandatory_attributes: Vec<String>,
105+
/// `ogit:optional-attributes` list, as written.
106+
pub optional_attributes: Vec<String>,
107+
/// `ogit:indexed-attributes` list, as written.
108+
pub indexed_attributes: Vec<String>,
109+
/// `ogit:allowed (...)` block — each entry is `(verb, target)` as
110+
/// written (`(ogit:dependsOn, ogit.MARS:Resource)`).
111+
pub allowed: Vec<(String, String)>,
112+
}
113+
114+
/// Lifted shape of a single OGIT attribute TTL file (e.g.
115+
/// `Application/attributes/class.ttl`).
116+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
117+
pub struct AttributeDecl {
118+
/// The CURIE-form name (`ogit.MARS.Application:class`).
119+
pub curie: String,
120+
/// The local name (`class`).
121+
pub name: String,
122+
/// `rdfs:label` text.
123+
pub label: String,
124+
/// `dcterms:description` text.
125+
pub description: String,
126+
/// `dcterms:valid` validity-range string.
127+
pub dcterms_valid: Option<String>,
128+
/// `dcterms:creator` text.
129+
pub dcterms_creator: Option<String>,
130+
/// `ogit:validation-type` value (e.g. `"fixed"`) when present.
131+
pub validation_type: Option<String>,
132+
/// `ogit:validation-parameter` value — when `validation_type` is
133+
/// `"fixed"`, this is the comma-separated set of allowed values.
134+
pub validation_parameter: Option<String>,
135+
}
136+
137+
impl AttributeDecl {
138+
/// When this attribute is `validation-type="fixed"`, split the
139+
/// comma-separated parameter into a sorted, de-duplicated value set —
140+
/// the shape that **must agree with the XSD-extracted classification
141+
/// set** for the same `(entity, attribute)` pair.
142+
///
143+
/// Returns `None` when the attribute is not fixed-enum.
144+
#[must_use]
145+
pub fn fixed_enum_values(&self) -> Option<Vec<String>> {
146+
if self.validation_type.as_deref() != Some("fixed") {
147+
return None;
148+
}
149+
let raw = self.validation_parameter.as_deref()?;
150+
let mut values: Vec<String> = raw
151+
.split(',')
152+
.map(str::trim)
153+
.filter(|s| !s.is_empty())
154+
.map(str::to_owned)
155+
.collect();
156+
values.sort();
157+
values.dedup();
158+
Some(values)
159+
}
160+
}
161+
162+
/// Lower a lifted [`EntityDecl`] into the canonical [`ogar_vocab::Class`].
163+
/// Behavior-arm fields ([`Class::callbacks`], etc.) stay empty — this is
164+
/// the structural arm only, per the §`# v0 scope` invariant.
165+
#[must_use]
166+
pub fn into_class(entity: &EntityDecl, attributes: &[(&str, &AttributeDecl)]) -> Class {
167+
let enums: Vec<EnumDecl> = attributes
168+
.iter()
169+
.filter_map(|(col, attr)| {
170+
let values = attr.fixed_enum_values()?;
171+
// OGIT TTL carries the value list flat (`ogit:validation-parameter
172+
// "Data,Development,..."`) with no separate label vocabulary, so the
173+
// lowering renders each value as a self-labeled `(value, value)`
174+
// pair. A future XSD lift will source labels from
175+
// `<xs:documentation>` per `<xs:enumeration>`.
176+
let pairs: Vec<(String, String)> = values.into_iter().map(|v| (v.clone(), v)).collect();
177+
let mut decl = EnumDecl::default();
178+
decl.column = (*col).to_owned();
179+
decl.source = EnumSource::Static(pairs);
180+
Some(decl)
181+
})
182+
.collect();
183+
184+
let attrs: Vec<Attribute> = attributes
185+
.iter()
186+
.map(|(col, _attr)| {
187+
let mut a = Attribute::default();
188+
a.name = (*col).to_owned();
189+
a
190+
})
191+
.collect();
192+
193+
let mut cls = Class::default();
194+
cls.name = entity.name.clone();
195+
cls.parent = entity.parent.clone();
196+
cls.language = Language::Unknown;
197+
cls.attributes = attrs;
198+
cls.enums = enums;
199+
cls
200+
}

0 commit comments

Comments
 (0)