feat(hpc): widen dn_tree + ogit_bridge surface for pillar drift-checks#189
Conversation
Two minimum-surface additions that enable Pillar-13 and Pillar-14 drift-check tests to compare the substrate-tier pillars (PR #188) against the production code paths they certify, without coupling production code to pillar code. src/hpc/dn_tree.rs - bundle_into: fn -> pub(crate) fn - No behavioural change; visibility only. - Lets crate::hpc::pillar::hhtl_contraction import the production bundle for cross-checking against its Bernoulli-mixture reference. src/hpc/ogit_bridge/schema.rs - New: OntologySchema::is_ancestor(&self, ancestor, descendant) -> bool - Walks the EntityClass.parent chain (rdfs:subClassOf), reflexive, with a defensive depth cap of 64 against any cycle that might slip past upstream antisymmetry checks. - Six unit tests: reflexivity, direct parent, transitive chain, antisymmetry, unknown descendant, disjoint chains. - Lets crate::hpc::pillar::ogit_lattice verify the same three partial-order axioms it certifies on synthetic DAGs also hold on the production schema's actual closure. Both additions are pub(crate) / pub-with-documented-instability — they do not commit to a long-term public API surface. The drift-check tests that depend on them live in crate::hpc::pillar::* and are gated under the existing pillar feature. Replies to #188 (comment)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c4607d7f7
ℹ️ 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".
| if parent == ancestor { | ||
| return true; |
There was a problem hiding this comment.
Reject unknown ancestors in ancestry checks
is_ancestor returns true as soon as entity.parent == ancestor, even if that parent IRI is not present in self.entities. Because from_triples stores rdfs:subClassOf targets without requiring them to be declared classes, a partial/malformed ontology can make is_ancestor("missing:Class", "ogit:Leaf") succeed for a non-reflexive unknown ancestor. This contradicts the method’s documented contract (“unknown IRIs” are only true in the reflexive case) and can produce false positives in drift checks or type-gated propagation when schemas are incomplete.
Useful? React with 👍 / 👎.
Codex flagged on PR #189 that `is_ancestor` returned true as soon as `entity.parent == ancestor`, even when `ancestor` was not a declared rdfs:Class in `self.entities`. Because `from_triples` accepts `rdfs:subClassOf` targets without requiring them to be declared, a partial or malformed ontology could make `is_ancestor("missing:Class", "ogit:Leaf")` succeed for a non-reflexive unknown ancestor — directly contradicting the method's documented contract that unknown IRIs are only true in the reflexive case. Fix: after the reflexive `ancestor == descendant` shortcut, early-return false if `ancestor` is not present in `self.entities`. Reflexivity on unknown IRIs is preserved (still defined on the full IRI space), but no entity's parent field can now project a phantom IRI into the ancestor closure. Added regression test `is_ancestor_unknown_ancestor_returns_false_when_non_reflexive` that parses a turtle source where `ogit:Leaf rdfs:subClassOf ogit:Phantom` but `ogit:Phantom` is never declared as a class. Asserts both: - `is_ancestor("ogit:Phantom", "ogit:Phantom")` → true (reflexive) - `is_ancestor("ogit:Phantom", "ogit:Leaf")` → false (was previously true — the bug) All 7 `is_ancestor` tests pass; lib clippy + fmt clean.
Summary
Two minimum-surface additions that enable the Pillar-13 and Pillar-14 drift-check tests requested in #188 (comment) to compare the substrate-tier pillars in #188 against the production code paths they certify — without coupling production code to pillar code.
src/hpc/dn_tree.rsfn bundle_into→pub(crate) fn bundle_intocrate::hpc::pillar::hhtl_contractionimport the production bundle for Pillar-13 drift-checksrc/hpc/ogit_bridge/schema.rspub fn is_ancestor(&self, ancestor, descendant) -> boolcrate::hpc::pillar::ogit_latticeverify partial-order axioms on the loaded ontology's actual closureDesign choices
bundle_into→pub(crate), notpub. No public-API contract change; the function stays internal to thendarray::hpc::*module tree. The docstring is explicit that it may change without notice.is_ancestorispub, but with a documented narrow contract. It is reflexive, walksEntityClass.parent(rdfs:subClassOf), and has a defensiveMAX_DEPTH = 64guard against any cycle that might slip past upstream antisymmetry checks. The docstring covers all four edge cases (reflexive on unknown IRIs, unknown descendant, depth cap, cycle backstop).Tests
Six unit tests added for
is_ancestor:is_ancestor_reflexive— including unknown IRIsis_ancestor_direct_parentis_ancestor_transitive_through_chain— four-tier Heel→Hip→Twig→Leafis_ancestor_antisymmetric— reverse direction returns falseis_ancestor_unknown_descendant_returns_falseis_ancestor_unrelated_classes— disjoint chainsbundle_intovisibility-only widening needs no new test; existing internal usage covers behaviour.How to verify
Relationship to #188
PR #188 introduces the substrate-tier Pillars 12–17 with the explicit non-coupling design: pillars re-derive their math independently from production. This PR enables the complementary drift-check tests that compare both implementations on shared seeded inputs — the test sits in the pillar module, the production code stays untouched in
src/hpc/*, and CI fails visibly if the two ever diverge.The Pillar-12 drift-check (commit
8cb40caonclaude/continue-ndarray-x0Oaw) is already wired and passes locally; it importsSpd3::from_scale_quatwhich has beenpubsince the splat3d module landed. This PR fills the gap for the two pillars whose production sides were still internal.