feat(ogar-vocab): agnostic vocab — Inheritance + domain tag + synergies#56
Conversation
…tion
Item 8 of the agnostic flip, as code. Rails conflates three distinct
things across parent + abstract_model + inheritance_column_disabled; this
collapses them into one typed, curator-agnostic slot.
- ogar-vocab: add Inheritance { Root, Concrete{parent}, Abstract,
RootedAt{root} } (non_exhaustive, Default=Root, serde) + a
Class.inheritance field (serde(default)). Additive: parent /
abstract_model / inheritance_column_disabled retained one cycle; all
existing consumers keep building unchanged.
- ogar-from-ruff: lift_inheritance(model) metabolizes StiInfo —
inherits_from -> Concrete; abstract_class -> Abstract;
inheritance_column (no parent) -> RootedAt(self); else Root. Mixins are
NOT consulted (concerns are a separate axis).
Mixins/concerns stay separate (the doctrine: STI parent and 'include
Journalized' are not the same organ). ogar-vocab 9, ogar-from-ruff 18
green; all 5 other vocab consumers build; clippy clean.
…erp) One tiny classifier, not a metabolization: tag each lifted Class with its curator domain so the agnostic shape carries 'what kind of system' without leaking the namespace. - ogar-vocab: add Class.source_domain: Option<String> (serde default) — a coarse curator-agnostic tag and a ClassFingerprint component. - ogar-from-ruff: classify_domain(namespace) maps openproject/redmine -> 'project', odoo -> 'erp', else None (no guessing); lift_model_graph tags every class from graph.namespace. ogar-vocab 9, ogar-from-ruff 19 green; clippy clean; additive.
Once domains are named, wire the synergies: the same canonical concept
surfacing in 2+ domains (OpenProject User <-> Odoo res.users => concept
'user') is what makes the unified vocab worth more than its curators.
- Synergy { concept, members: [SynergyMember{domain, class_name}] }.
- wire_synergies(&[Class]) groups by canonical_concept, keeps only
concepts spanning 2+ distinct source_domains; deterministic order;
undomained classes skipped.
- canonical_concept: lowercase, last dotted segment (res.users->users),
drop a trailing plural s except after ss. Coarse v1 bridge, not a
thesaurus.
ogar-vocab 11 green; clippy clean.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9378578522
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let mut class = Class::new(&model.name); | ||
| class.language = Language::Ruby; | ||
| class.parent = model.sti.as_ref().and_then(sti_parent); | ||
| class.inheritance = lift_inheritance(model); |
There was a problem hiding this comment.
Preserve lifted inheritance in graph output
When StiInfo represents abstract_class or an STI root, this assignment is the only place that records the fact: parent stays None and the legacy booleans are not updated. I checked TripleEmitter::emit_class in crates/ogar-emitter/src/lib.rs, and it still emits only parent, abstract_model, and inheritance_column_disabled while never reading Class::inheritance, so those newly lifted abstract/root inheritance facts disappear as soon as these classes go through the existing graph/Turtle path. Please either keep the retained legacy fields in sync for this migration cycle or add emission/parsing for the new enum before relying on it downstream.
Useful? React with 👍 / 👎.
…sition Companion to the typed PortSpec::APP_PREFIX in this branch: adds `ogar_vocab::app`, the central high-u16 composition machinery from APP-CLASS-CODEBOOK-LAYOUT.md §0/§4, so consumers re-export ONE source of the bit math instead of each re-implementing `(prefix << 16) | concept` locally. - render_classid(prefix, concept) -> u32 compose the full classid - render_classid_for::<P: PortSpec>(concept) compose from P::APP_PREFIX - app_of(classid) -> u16 high half (render lens, §4 key) - concept_of(classid) -> u16 low half (shared RBAC+ontology) This resolves the duplication where each consumer (op-canon #56, the redmine-canon twin) defines its own `render_classid` over a local 0x000N literal: they now compose via `render_classid_for::<OpenProjectPort>(concept)` reading the typed PortSpec::APP_PREFIX — one source of truth, same discipline as class_ids. Composing the high half is reserving/stamping, not minting a class_id (§2); concept (low-u16) mints stay gated on the 5+3 pass. Core (hi=0x0000) is bit-identical to the bare concept (I-APP1/I-APP5), pinned by test. cargo +1.95 test -p ogar-vocab --lib app:: -> 6 passed; ports:: -> 27 passed; check --workspace --all-targets clean.
Agnostic vocab — three concrete actions (code, not framing)
1.
Inheritancemetabolization (item 8)Rails conflates STI parent / abstract base / STI root across
parent+abstract_model+inheritance_column_disabled. Collapsed into one typed slot:ogar-vocab:enum Inheritance { Root, Concrete{parent}, Abstract, RootedAt{root} }(#[non_exhaustive], serde, names-as-String) +Class.inheritance(#[serde(default)]). Additive — old fields kept one cycle; all consumers build unchanged.ogar-from-ruff:lift_inheritancemapsStiInfo-> the slot. Mixins not consulted (separate axis).2. Name the curator domain (one tiny classifier)
ogar-vocab:Class.source_domain: Option<String>— coarse curator-agnostic tag +ClassFingerprintcomponent.ogar-from-ruff:classify_domain(namespace)-> OpenProject/Redmine =project, Odoo =erp, elseNone;lift_model_graphtags every class.3. Wire synergies (cross-domain concept bridges)
ogar-vocab:Synergy { concept, members:[SynergyMember{domain, class_name}] }+wire_synergies(&[Class])— groups bycanonical_concept, keeps concepts spanning 2+ distinct domains. OpenProjectUser<-> Odoores.users-> conceptuser. The unified vocab made worth more than its curators.Green
ogar-vocab11 ·ogar-from-ruff19 · all 5 other vocab consumers build (additive) · clippy clean.🤖 Generated with Claude Code