From 5fad558d09cf4a485de190591e1b56647952f8f8 Mon Sep 17 00:00:00 2001 From: AdaWorldAPI Date: Fri, 7 Aug 2026 14:52:21 +0200 Subject: [PATCH] ogar-elk: drop the serialization surface, name the pre-bake stage The joins are pre-bake. Reconciling independently authored sources is the work that must finish BEFORE anything is baked, because the bake is what freezes the answer into positions. That makes this crate a stage, not a layer -- and two things in it were written as if it were a layer. - Remove the `serde` feature and all three Serialize/Deserialize derives. A join validator that can serialize its verdict invites someone to ship the verdict instead of the bake. There is now no serialization surface at all, not behind a feature, not optionally. The crate has zero dependencies again. - `ClassAddr` was documented as "the ABI-shaped address a baked row already carries." It is not; it is the join key the joiner has already agreed on for the two sides it joins. What a baked row carries is a matter of position, resolved by the class, and this crate is finished long before that question is asked. The old wording invited exactly the leak the doc now names. The types themselves stay: types exist before the bake, and a join needs a key and a directed edge. What the crate doc now states explicitly is that nothing it defines survives the bake -- if one of these types appears in a post-bake read path, that is the leak. 8 tests green, clippy clean. --- crates/ogar-elk/Cargo.toml | 7 +------ crates/ogar-elk/src/lib.rs | 40 +++++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/crates/ogar-elk/Cargo.toml b/crates/ogar-elk/Cargo.toml index d2a3eaa..323b7a4 100644 --- a/crates/ogar-elk/Cargo.toml +++ b/crates/ogar-elk/Cargo.toml @@ -6,11 +6,6 @@ license.workspace = true repository.workspace = true authors.workspace = true rust-version.workspace = true -description = "The EL subsumption closure as a FACTFINDER over ABI-shaped addressed edges. Answers two questions and no others: does `A ⊑ B` follow from what is asserted, and is adding a set of axioms to an existing closure sound (does it introduce an equivalence cycle). Entailments are facts — they follow necessarily — so nothing here is graded, ranked, or scored. Zero-dependency; consumes `(classid, identity)` addresses, never a legacy ontology file." - -[features] -default = [] -serde = ["dep:serde"] +description = "The EL subsumption closure as a PRE-BAKE join validator. Answers two questions and no others: does `A ⊑ B` follow from what is asserted, and is adding a set of axioms to an existing closure sound (does it introduce an equivalence cycle). Entailments are facts — they follow necessarily — so nothing here is graded, ranked, or scored. Runs once, upstream of the bake, over the joined sources; nothing it defines survives into the baked substrate. Zero dependencies, and no serialization surface at all." [dependencies] -serde = { workspace = true, optional = true } diff --git a/crates/ogar-elk/src/lib.rs b/crates/ogar-elk/src/lib.rs index c41194b..334a57a 100644 --- a/crates/ogar-elk/src/lib.rs +++ b/crates/ogar-elk/src/lib.rs @@ -18,14 +18,29 @@ //! [`ogar-obo`]: https://docs.rs/ogar-obo //! [`ogar-ro`]: https://docs.rs/ogar-ro //! -//! # Addresses, never a file +//! # Where this runs: BEFORE the bake, never after //! -//! Input is `(classid, identity)` — the ABI-shaped address a baked row already -//! carries. This crate never parses an ontology document, never resolves a -//! CURIE, and never looks at a label. That is deliberate: reasoning over the -//! addressed form is the whole point of having addressed it, and a reasoner -//! that reached back for the source file would re-introduce the coupling the -//! bake exists to remove. +//! **The joins are pre-bake.** Reconciling independently authored sources — +//! deciding which assertions corroborate, which enrich, and which contradict — +//! is exactly the work that must finish before anything is baked, because the +//! bake is what freezes the answer into positions. So this crate is a **stage**, +//! not a layer: it runs once, upstream, and then it is done. +//! +//! Three consequences follow, and they are the whole design: +//! +//! 1. **Types are legal here.** [`ClassAddr`] and [`Subsumption`] exist because +//! a join needs a key and a directed edge. They are pre-bake scaffolding. +//! **Nothing this crate defines survives the bake** — afterwards there are +//! only classes, and a class is resolved by position, not by a type declared +//! here. If one of these types ever appears in a post-bake read path, that is +//! the leak, and it is this doc that says so. +//! 2. **Nothing here is in the hot path**, so nothing here may pretend to be. +//! There is no serialization surface — not behind a feature, not optionally. +//! A join validator that could serialize its verdict would invite someone to +//! ship the verdict instead of the bake. +//! 3. **No file, no CURIE, no label.** Input is already-joined addressed edges. +//! Reaching back for the source document would put parsing inside the +//! validator and re-introduce the coupling the bake exists to remove. //! //! # The fragment, stated precisely //! @@ -70,13 +85,18 @@ use std::collections::{HashMap, HashSet, VecDeque}; -/// A class address: `(classid, identity)`, exactly as a baked row carries it. +/// The **join key** the pre-bake reconciliation uses: `(classid, identity)`. +/// +/// Not an ABI address, and deliberately not documented as one — it is the pair +/// the joiner has already agreed on for the two sides it is joining, which is +/// all a closure needs to index by. What a *baked* row carries is a matter of +/// position, resolved by the class, and this crate is finished long before that +/// question is asked. /// /// Ordered and hashable so a closure can index by it without a side table. It /// carries no namespace, no CURIE and no label — resolving those is the /// caller's business and none of this crate's. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ClassAddr { /// The row's classid. pub classid: u32, @@ -98,7 +118,6 @@ impl ClassAddr { /// cannot hand this crate a `part_of` edge by accident — see the crate doc's /// false-ancestor hazard. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Subsumption { /// The subclass. pub sub: ClassAddr, @@ -242,7 +261,6 @@ impl Closure { /// decides whether to accept the merge after seeing the verdict, which is the /// only order that makes the verdict useful. #[derive(Debug, Clone, PartialEq, Eq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MergeVerdict { /// Axioms the closure already entailed. These corroborate the existing set /// and add nothing — the strongest possible outcome for an axiom, and the