feat(ogar-obo): OBO-core reference bake → 512-byte SoA + EL saturation - #230
Conversation
New public reference crate (sibling of ogar-fma/ogar-cpic). Parses the OBO Foundry core — MONDO (disease) · HPO (phenotype) · Uberon (anatomy) · PATO (quality) · RO (relations) — into the canon 512-byte NodeRow geometry (key|edges|value LE) the lance-graph loader reads zero-copy via node_rows_from_le_bytes. Byte compatibility proven by the round-trip test (as_le_bytes ↔ rows_from_le_bytes, 512×N, 64-align gate); no cross-repo compile coupling. Classids canon-high (concept<<16 | app): Uberon rides the 0x0A Anatomy domain, disease/phenotype/quality/relations the 0x0B OBO-clinical-reference domain. Preserved, never truncated: is_a/part_of rails, has_phenotype/location convergence, HP anatomy:quality grounding from hp-base.owl logical defs, and all external xrefs (MeSH · UMLS · OMIM · Orphanet · SNOMED · ICD) — the projection-join / multilateration bearings and the MeSH guideline-spider path. reason.rs: the OWL-EL completion subset the OBO EL profile exercises, excavated to plain Rust — is_a transitivity, transitive-role part_of, and R∃ existential-through-spine (grounding inherited up). Iterative Tarjan cycle check + Kahn topo-DP closures. Full bake on the pinned sources: 68,797 rows (35 MB), 0 cycles, 0 dangling, backbone MONDO→HP/Uberon + HP→Uberon/PATO all resolving, 212k xrefs (9.2k MeSH). Sources pinned in manifest.json (PURL·version·sha256·SPDX); the .soa artifact is a gitignored release asset. Pure public CC-BY; zero PHI. 8 tests, clippy-clean.
…ows) Registers the OBO reference domain in the canonical codebook as a RESERVED, zero-vocabulary-row block — the same posture as 0x07 OSINT and 0x0E Genetics. ConceptDomain::Ontology + the 0x03 → Ontology routing arm give consumers a stable domain tag from `id >> 8`, while the concept ids themselves live in the producer crate ogar-obo (Namespace::concept_id). This keeps the OBO reference PLUG-AND-PLAY: only a consumer that deps ogar-obo compiles the concepts; ERP / project consumers (odoo-rs, openproject-nexgen-rs, …) never pull them into their concept space. Public reference, firewall-separated from 0x09 Health PHI (reference ≠ PHI, same split as Anatomy 0x0A). Fixes two classid collisions in ogar-obo's first cut: 0x0B was already Auth (auth_store…) and 0x0A02 was `skeleton`. All five OBO namespaces now sit in the collision-free 0x03 domain: mondo 0x0301 · hpo 0x0302 · uberon 0x0303 · pato 0x0304 · ro 0x0305. Uberon cross-references FMA (ogar-fma, 0x0A Anatomy) by edge, not by shared domain byte. Bake re-verified (68,797 rows, loader contract green, 212k xrefs / 9.2k MeSH preserved); manifest artifact sha refreshed. ogar-vocab 141 tests, ogar-obo 8 tests, clippy-clean.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9a2e45c94
ℹ️ 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".
| (Mondo, Hpo) => Predicate::HasPhenotype, | ||
| (Mondo, Uberon) => Predicate::HasLocation, | ||
| (Hpo, Uberon) => Predicate::HasAnatomy, | ||
| (Hpo, Pato) => Predicate::HasQuality, | ||
| (Uberon, Uberon) => Predicate::PartOf, |
There was a problem hiding this comment.
Classify relationships from their predicate CURIE
For any intra-Uberon relationship whose predicate is not part_of, this namespace-only mapping still emits Predicate::PartOf; the parser has the actual relation CURIE in the first token of relationship: but discards it. reason::saturate then treats the mislabeled edge as transitive and adds it to the anatomy spine, producing false part_of and existential closure results for the pinned Uberon bake. Parse known predicate CURIEs such as BFO:0000050 directly and preserve unsupported relations as Other.
Useful? React with 👍 / 👎.
| // Write the artifact + verify it round-trips through the loader contract. | ||
| let bytes = as_le_bytes(&baked.rows); | ||
| std::fs::write(&out, bytes).unwrap_or_else(|e| panic!("write {out}: {e}")); |
There was a problem hiding this comment.
Serialize triples and xrefs with the release artifact
In the advertised bake_obo release path, the only persisted data is baked.rows; baked.triples, baked.xrefs, and all inferred closure data are merely counted and then discarded. Because each row contains only a degree histogram and namespace, the resulting obo-core.soa has neither edge destinations nor xref IDs, so a consumer loading the release asset cannot perform the documented graph traversal, MeSH lookup, or use the claimed EL saturation. Persist these tables in the artifact or deterministic sidecars and include them in the release contract.
Useful? React with 👍 / 👎.
| for (i, d) in deg.iter().enumerate() { | ||
| row.0[EDGES_OFFSET + i] = (*d).min(255) as u8; |
There was a problem hiding this comment.
Keep degree counts out of EdgeBlock slots
Whenever a node has any parsed edge, these bytes populate the canonical EdgeBlock's 12 in-family/4 out-of-family adjacency slots with degree counts rather than edge references. A lance-graph consumer accepts the row as a NodeRow and interprets values such as 1 or 2 as adjacency entries, yielding bogus neighbors rather than a histogram. The block must remain zero until valid adjacency is encoded, or the histogram needs a registered value-tenant/sidecar representation.
Useful? React with 👍 / 👎.
| let id = identity.to_le_bytes(); // low 3 bytes are the 24-bit identity | ||
| k[13] = id[0]; | ||
| k[14] = id[1]; | ||
| k[15] = id[2]; |
There was a problem hiding this comment.
Encode new ontology rows with the V3 facet key
Every newly baked OBO row is born with the retired V1 family:u24 | identity:u24 tail, despite the repository canon in CLAUDE.md:9-20 forbidding that shape for new units in favor of the V3 content-blind 4+12 facet. A V3 reader resolves the 12 payload bytes through the class's facet interpretation, so placing the CURIE identity at bytes 13–15 without a registered legacy-outlier interpretation does not provide the promised V3 address and can be decoded as facet-axis data. Mint an approved V3 payload, or explicitly register and encode a sanctioned compatibility variant.
Useful? React with 👍 / 👎.
New public reference crate
ogar-obo(sibling ofogar-fma/ogar-cpic) + the0x03Ontology domain reservation inogar-vocab.What it is
Parses the OBO Foundry biomedical core — MONDO (disease) · HPO (phenotype) · Uberon (anatomy) · PATO (quality) · RO (relations) — into the canon 512-byte SoA
NodeRowgeometry (key(16)|edges(16)|value(480)LE) the lance-graph loader reads zero-copy, plus the OWL-EL completion subset the OBO EL profile actually exercises.Loader connection = byte-layout contract (not a code dep)
ogar-oboemits the exactlance_graph_contract::canonical_node::NodeRowbytes;node_rows_from_le_bytesreads them back as&[NodeRow]with no deserialize. Proven by the round-trip test (as_le_bytes↔rows_from_le_bytes, 512×N, 64-align gate, byte-identical, refuses misaligned). Crate stays lean (deps:ogar-vocabonly).Classids — plug-and-play
0x03Ontology domainReserved in
ogar-vocabasConceptDomain::Ontologywith zero shared CODEBOOK rows (same posture as OSINT0x07/ Genetics0x0E). The concept ids live inogar-obo(mondo 0x0301 · hpo 0x0302 · uberon 0x0303 · pato 0x0304 · ro 0x0305), so only consumers that depogar-obocompile them — ERP / project consumers never pull the OBO concepts into their concept space. Public reference, firewall-separated from0x09Health PHI. Fixes two collisions the first cut would have hit (0x0B= Auth,0x0A02=skeleton).Preserved, never truncated
is_a/part_ofrails ·has_phenotype/has_locationconvergence · HPanatomy:qualitygrounding fromhp-base.owllogical defs · all external xrefs (MeSH · UMLS · OMIM · Orphanet · SNOMED · ICD) — the projection-join / multilateration bearings and the MeSH clinical-guideline lookup path.EL saturation (
reason.rs)ELK subset excavated to plain Rust —
is_atransitivity, transitive-rolepart_of, and the R∃ existential-through-spine propagation (grounding inherited up). Iterative Tarjan cycle check + Kahn topo-DP closures.Full bake on the pinned sources
68,797 rows (35 MB) · 0 cycles · 0 dangling · backbone MONDO→HP/Uberon + HP→Uberon/PATO resolving · 212k xrefs (9.2k MeSH) · EL 738k is_a / 1.6M part_of / 9.4M existential-inferred.
Sources pinned in
manifest.json(PURL · version · sha256 · SPDX); the derived.soa+ frozen CC-BY sources ship as theogar-obo-v0.1.0release. Pure public reference; zero PHI.ogar-obo8 tests ·ogar-vocab141 tests · clippy-clean.Generated by Claude Code