Skip to content

feat(hpc): widen dn_tree + ogit_bridge surface for pillar drift-checks#189

Merged
AdaWorldAPI merged 2 commits into
masterfrom
feat/dn-tree-ogit-surface-for-drift-checks
May 21, 2026
Merged

feat(hpc): widen dn_tree + ogit_bridge surface for pillar drift-checks#189
AdaWorldAPI merged 2 commits into
masterfrom
feat/dn-tree-ogit-surface-for-drift-checks

Conversation

@AdaWorldAPI
Copy link
Copy Markdown
Owner

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.

File Change Reason
src/hpc/dn_tree.rs fn bundle_intopub(crate) fn bundle_into Lets crate::hpc::pillar::hhtl_contraction import the production bundle for Pillar-13 drift-check
src/hpc/ogit_bridge/schema.rs New pub fn is_ancestor(&self, ancestor, descendant) -> bool Lets crate::hpc::pillar::ogit_lattice verify partial-order axioms on the loaded ontology's actual closure

Design choices

  • bundle_intopub(crate), not pub. No public-API contract change; the function stays internal to the ndarray::hpc::* module tree. The docstring is explicit that it may change without notice.
  • is_ancestor is pub, but with a documented narrow contract. It is reflexive, walks EntityClass.parent (rdfs:subClassOf), and has a defensive MAX_DEPTH = 64 guard 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).
  • No drift-check tests in this PR. Those land separately, either in feat(pillar): substrate-tier Pillars 12-17 + design charter #188 follow-up commits or in a fresh PR, by whichever Claude session picks them up.

Tests

Six unit tests added for is_ancestor:

  • is_ancestor_reflexive — including unknown IRIs
  • is_ancestor_direct_parent
  • is_ancestor_transitive_through_chain — four-tier Heel→Hip→Twig→Leaf
  • is_ancestor_antisymmetric — reverse direction returns false
  • is_ancestor_unknown_descendant_returns_false
  • is_ancestor_unrelated_classes — disjoint chains

bundle_into visibility-only widening needs no new test; existing internal usage covers behaviour.

How to verify

cargo test -p ndarray --lib hpc::ogit_bridge::schema::tests::is_ancestor
cargo build -p ndarray --features pillar  # bundle_into is now reachable from pillar tests

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 8cb40ca on claude/continue-ndarray-x0Oaw) is already wired and passes locally; it imports Spd3::from_scale_quat which has been pub since the splat3d module landed. This PR fills the gap for the two pillars whose production sides were still internal.

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)
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +646 to +647
if parent == ancestor {
return true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants