Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,31 @@
namespace Argumentum.AssetConverter.Tests.Ontology
{
/// <summary>
/// End-to-end OWL proof for #133 — SURFACES A SECOND SILENT-FALSE-PASS BUG in the production
/// validation path that the #480→#481→#482 lane missed.
/// End-to-end OWL proof for #133 — REGRESSION SUITE for the read-path fix of the SECOND
/// silent-false-pass bug (the one the #480→#481→#482 lane missed because it only exercised the
/// IN-MEMORY path).
///
/// <see cref="OwlValidatorLivePathTests"/> (#482) proved with SYNTHETIC in-memory adapters that the
/// production validator no longer silent-false-passes after the OwlAdapter reader fix (#481). But
/// that lane only exercised the IN-MEMORY path. This class loads the REAL generation output
/// (<c>docs/ontology/argumentum.owl</c>, ~5 MB, produced by <c>OwlDocumentConfig.CreateOwlDocument</c>)
/// and proves the readers are STILL BLIND on a reloaded file — so the production validator
/// (<c>OwlOntologyValidationTests.LoadOntology</c> → <c>OwlAdapter.FromFile</c> → validate) STILL
/// silent-false-passes in the real prod path, for a DIFFERENT root cause than the #480 type-mismatch.
/// HISTORY (the bug this file originally pinned, now fixed): OWLSharp's OWL2XML serializer/reader
/// drops <c>rdf:type</c> from the reloaded annotation stream (rdf:type==0, skos:inScheme==0 among
/// <c>AnnotationAxioms</c> after reload). The OwlAdapter readers located concepts/schemes by
/// scanning <c>AnnotationAxioms</c> for <c>rdf:type</c>, so on any LOADED file they returned empty →
/// <c>ValidateMultilingualAnnotations</c> / <c>ValidateAIFMappings</c> hit their
/// <c>if (concepts.Count == 0) return true;</c> guard and reported PASS without inspecting anything
/// — even though the real content was present (2816 prefLabels, AIF matches, 1510 class declarations).
///
/// ROOT CAUSE (measured on the loaded file): OWLSharp's OWL2XML serializer DROPS the
/// <c>rdf:type</c> and <c>skos:inScheme</c> annotation assertions during serialization — neither
/// survives the round-trip (rdf:type == 0, inScheme == 0 after reload). The OwlAdapter readers find
/// concepts/schemes by scanning <c>AnnotationAxioms</c> for <c>rdf:type</c>, so on any LOADED file
/// they return empty. <c>ValidateMultilingualAnnotations</c> / <c>ValidateAIFMappings</c> then hit
/// their <c>if (concepts.Count == 0) return true;</c> guard → report PASS without inspecting anything.
/// Meanwhile the real content IS present (2816 prefLabels, 10 AIF matches, 1510 class declarations) —
/// the validator just cannot see it.
/// THE FIX (read-path only — the serializer is deliberately untouched, see test 1):
/// <c>OwlAdapter.GetResourcesByType</c> / <c>GetConcepts</c> now fall back, when the rdf:type scan
/// is empty, to locating entities via the SKOS annotations that DO survive the round-trip —
/// concepts are the distinct subjects of <c>skos:prefLabel</c>, the ConceptScheme is the subject of
/// <c>skos:hasTopConcept</c>. In-memory ontologies (rdf:type present) take the original path and are
/// unaffected, so the <see cref="OwlValidatorLivePathTests"/> (#482) in-memory proofs still hold.
///
/// These tests are GREEN by pinning the CURRENT BROKEN behavior (characterization). When the fix
/// lands (readers must locate concepts via surviving annotations — prefLabel/definition/example
/// subjects, or filtered DeclarationAxioms — NOT rdf:type/inScheme), these assertions flip to the
/// honest "detection works" form. This is a coordinator-scope fix (prod behavior + release-gate
/// implications), so the worker surfaces it rather than shipping a unilateral prod change.
///
/// Deterministic, key-free, release-independent. Loads one real file (lazy, shared across the class).
/// NEW additive file (dispatch #133 primaire) — no existing file modified.
/// These tests load the REAL generation output (<c>docs/ontology/argumentum.owl</c>, ~5 MB, produced
/// by <c>OwlDocumentConfig.CreateOwlDocument</c>) and prove: the serializer drop is still real but
/// now benign (1), the readers resolve concepts + scheme on the reloaded file (2), the real content
/// is now reachable (3), and the production validator genuinely inspects and passes rather than
/// skip-false-passing (4). Deterministic, key-free, release-independent; loads one real file (lazy,
/// shared across the class).
/// </summary>
public class OwlE2EGenerationValidationTests
{
Expand Down Expand Up @@ -82,10 +79,11 @@ private static int CountAnnotations(string iriFragment)
}

// ─────────────────────────────────────────────────────────────────────────────
// (1) ROOT CAUSE: rdf:type and skos:inScheme annotations are DROPPED by OWLSharp's
// OWL2XML round-trip. rdf:type==0 on the reloaded ontology (the generator emitted one
// rdf:type=skos:Concept per concept + one for the scheme). inScheme==0 likewise. Contrast:
// prefLabel (literal-valued) survives — so the loss is predicate-selective, not total.
// (1) The serializer drop is REAL and UNFIXED BY DESIGN (read-path-only fix).
// rdf:type==0 and skos:inScheme==0 among the reloaded AnnotationAxioms — OWLSharp's
// OWL2XML round-trip drops/absorbs both. This stays true after the fix: the readers no
// longer depend on them, but the drop itself is not patched (out of scope: write/serialize).
// Contrast: prefLabel (literal-valued) survives — the loss is predicate-selective.
// ─────────────────────────────────────────────────────────────────────────────

[Fact]
Expand All @@ -96,49 +94,52 @@ public void LoadedOntology_RdfTypeAndInScheme_DroppedByOwl2XmlRoundTrip()
int prefLabel = CountAnnotations("skos/core#prefLabel");

rdfType.Should().Be(0,
"BUG: OWLSharp's OWL2XML serializer drops rdf:type annotation assertions — the generator " +
"emitted one rdf:type=skos:Concept per concept (~1400) plus one for the ConceptScheme, but " +
"ZERO survive the round-trip. This is the root cause of the loaded-file reader blindness.");
"OWLSharp's OWL2XML round-trip drops rdf:type annotation assertions (the generator emitted " +
"one rdf:type=skos:Concept per concept plus one for the ConceptScheme, none survive). This " +
"drop is REAL and left in place by the read-path fix — the readers now locate concepts via " +
"surviving prefLabel annotations instead (see test 2), so the drop is benign, not patched.");

inScheme.Should().Be(0,
"BUG: skos:inScheme (resource-valued, emitted per concept by DeclareConcept) is ALSO dropped " +
"by the round-trip — so a fallback fix cannot rely on inScheme either.");
"skos:inScheme is also absent from the reloaded AnnotationAxioms (absorbed elsewhere by the " +
"reader) — a read-path fix cannot rely on it either; the fix uses prefLabel + hasTopConcept.");

prefLabel.Should().BeGreaterThan(0,
"contrast: prefLabel (literal-valued) DOES survive the round-trip — the serialization loss is " +
"predicate-selective, and the real content (concepts' labels) IS present in the reloaded ontology");
"predicate-selective, and the real content (concepts' labels) IS present, which is exactly what " +
"the read-path fix keys on to locate concepts.");
}

// ─────────────────────────────────────────────────────────────────────────────
// (2) CONSEQUENCE: the readers the production validators branch on return EMPTY on the
// reloaded file. GetResourcesByType(Concept) scans AnnotationAxioms for rdf:type, which is
// now absent (test 1). This is the exact precondition for the silent false-pass.
// (2) THE FIX: the readers the production validators branch on now RESOLVE concepts and the
// scheme on the reloaded file — via the surviving prefLabel (concepts) and hasTopConcept
// (scheme) annotations. Before the fix both returned empty (the silent-false-pass precondition).
// ─────────────────────────────────────────────────────────────────────────────

[Fact]
public void LoadedOntology_ConceptAndSchemeReaders_ReturnEmpty_BugPinned()
public void LoadedOntology_ConceptAndSchemeReaders_ResolveViaSurvivingAnnotations_AfterReadPathFix()
{
var concepts = RealOntology.GetResourcesByType(SKOSVocabulary.Concept);
var schemes = RealOntology.GetResourcesByType(SKOSVocabulary.ConceptScheme);

concepts.Should().BeEmpty(
"BUG PINNED: on the reloaded real ontology GetResourcesByType(Concept) returns empty because " +
"rdf:type is absent (test 1). In-memory (post-#481) this resolves concepts, but on a LOADED " +
"file the reader is blind — so the production validators see zero concepts.");
concepts.Should().NotBeEmpty().And.HaveCountGreaterThan(1000,
"FIX: GetResourcesByType(Concept) now resolves the ~1408 concepts on the reloaded ontology by " +
"falling back to the distinct subjects of skos:prefLabel when the rdf:type scan is empty. " +
"Before the fix this returned empty (the silent-false-pass precondition); the early-return " +
"'No concepts → skip → PASS' in the validators is now unreachable.");

schemes.Should().BeEmpty(
"same root cause for ConceptScheme: rdf:type=skos:ConceptScheme was dropped on round-trip");
schemes.Should().NotBeEmpty(
"FIX: GetResourcesByType(ConceptScheme) now resolves the scheme as the subject of the surviving " +
"skos:hasTopConcept annotation. Before the fix this returned empty (same rdf:type-drop root cause).");
}

// ─────────────────────────────────────────────────────────────────────────────
// (3) SMOKING GUN: the reloaded ontology DOES contain real content — 2816 prefLabels,
// 10 AIF match mappings, 1510 class declarations. The concepts exist; the readers simply
// cannot locate them via the dropped rdf:type. This proves the empty reader is a reader
// defect, not a data defect.
// (3) The reloaded ontology DOES contain real content — 2816 prefLabels, AIF match mappings,
// 1510 class declarations — and the readers now RESOLVE that content. (Before the fix the
// concepts existed but the readers could not locate them via the dropped rdf:type.)
// ─────────────────────────────────────────────────────────────────────────────

[Fact]
public void LoadedOntology_ContainsRealContent_ReadersCannotSee()
public void LoadedOntology_ContainsRealContent_NowResolvableByReaders()
{
int prefLabel = CountAnnotations("skos/core#prefLabel");
int matches = CountAnnotations("#exactMatch")
Expand All @@ -147,42 +148,54 @@ public void LoadedOntology_ContainsRealContent_ReadersCannotSee()
int classDecls = RealOntology.GetOntology().DeclarationAxioms.Count(d => d.Entity is OWLClass);

prefLabel.Should().BeGreaterThan(1000,
"the reloaded ontology carries ~2816 prefLabels (one fr+en pair per concept) — real content " +
"is present");
"the reloaded ontology carries ~2816 prefLabels (one fr+en pair per concept) — real content");
matches.Should().BeGreaterThan(0,
"the reloaded ontology carries AIF match mappings (exactMatch/closeMatch/relatedMatch) — " +
"real content is present, yet ValidateAIFMappings will still skip because concepts resolves empty");
"the reloaded ontology carries AIF match mappings (exactMatch/closeMatch/relatedMatch, plus " +
"broadMatch/narrowMatch) — real content that a LIVE validator now inspects");
classDecls.Should().BeGreaterThan(1000,
"~1510 OWL class declarations survive (concepts + scheme + AIF classes) — an alternative fix " +
"could locate concepts via DeclarationAxioms rather than the dropped rdf:type");
"~1510 OWL class declarations survive (concepts + scheme + AIF classes)");

// The readers now resolve the real content (the smoking gun of the fix): a substantial set
// of distinct concept subjects (~1305) is located via prefLabel. (Not exactly prefLabel/2
// because the concept URI derives from GetId(TextEn) — fallacy rows whose TextEn collides
// aggregate onto one subject, so distinct subjects < fr+en label count.)
int conceptCount = RealOntology.GetResourcesByType(SKOSVocabulary.Concept).Count;
conceptCount.Should().BeGreaterThan(1000,
"the reader-resolved distinct concept subjects are substantial on the reloaded ontology — " +
"the content is no longer invisible to the readers");
}

// ─────────────────────────────────────────────────────────────────────────────
// (4) DECISIVE PROOF: the PRODUCTION validator (OwlOntologyValidationTests, injected via
// reflection) returns TRUE for both annotation and AIF validation on the reloaded ontology —
// NOT because the content is valid, but because the concept reader is empty and the methods
// early-return true ("No concepts to validate — skipping"). The silent false-pass is STILL
// ACTIVE in the production load-and-validate path, contradicting #133's premise.
// (4) DECISIVE: the PRODUCTION validator (OwlOntologyValidationTests, injected via reflection)
// now GENUINELY inspects the concepts and passes on the reloaded ontology — NOT a skip
// false-pass. Before the fix it returned true because GetResourcesByType(Concept) was empty
// and both methods early-returned; now the concepts resolve, the inspection actually runs,
// finds the present prefLabels/definitions and AIF mappings, and passes for the right reason.
// ─────────────────────────────────────────────────────────────────────────────

[Fact]
public async Task ProdValidator_SilentFalsePass_StillActiveOnLoadedOntology()
public async Task ProdValidator_InspectsConceptsAndGenuinelyPassesOnLoadedOntology()
{
// Precondition that makes the 'concepts.Count == 0 → return true' skip guard UNREACHABLE.
// Asserting it here proves the passes below are GENUINE, not silent false-passes.
RealOntology.GetResourcesByType(SKOSVocabulary.Concept)
.Should().NotBeEmpty(
"the validators' silent-false-pass early-return triggers only when this is empty; with the " +
"read-path fix the concepts resolve, so any PASS from the validators is a genuine inspection " +
"pass, not a skip");

var validator = BuildProdValidator(RealOntology);
bool annotationsOk = await InvokeValidate(validator, "ValidateMultilingualAnnotations");
bool aifOk = await InvokeValidate(validator, "ValidateAIFMappings");

// These return TRUE — but that is the SILENT FALSE-PASS, not a genuine pass: the concept
// reader is empty (tests 1+2), so both validators hit `if (concepts.Count == 0) return true;`
// and skip inspection entirely. The ontology demonstrably contains prefLabels + AIF matches
// (test 3) that a LIVE validator would inspect. Pinning this FALSE pass until the reader fix.
annotationsOk.Should().BeTrue(
"ValidateMultilingualAnnotations returns TRUE on the reloaded ontology — the silent false-pass " +
"is still alive in production. It returns true because GetResourcesByType(Concept) is empty " +
"(test 2) so the method early-returns, NOT because the (present) annotations were inspected");
"Genuine pass: with the concepts resolved, ValidateMultilingualAnnotations inspects them and " +
"finds the present skos:prefLabel + skos:definition annotations — it returns true because the " +
"annotations ARE there and were inspected, not because the concept list was empty");
aifOk.Should().BeTrue(
"ValidateAIFMappings returns TRUE on the reloaded ontology for the same reason — silent " +
"false-pass. #133's 'confidence restored' premise does not hold for the prod load path");
"Genuine pass: with the concepts resolved, ValidateAIFMappings inspects them and finds AIF match " +
"mappings (closeMatch/broadMatch/narrowMatch) on at least some concepts — it returns true because " +
"the mappings were inspected and present, not because the concept list was empty");
}

private static object BuildProdValidator(OwlAdapter ontology)
Expand Down
Loading
Loading