"""Certification Capability Hypotheses — capability-centric library + empirical confidence. Pins the reuse design (one capability, many supporting certs -> ~40-60 hypotheses, not ~300), the automatic multi-certification merge, the empirical (computed) confidence loop, and the Welt-1 guarantee that capabilities NO cert suggests (SBOM, signed updates, CVD) are never inferred -> they stay in the delta and get asked. Then the Advisor consumes the resolved library end-to-end. """ from __future__ import annotations import os import yaml from compliance.onboarding import ( CapabilityHypothesis, Observation, ObservationType, OnboardingInput, advisor_start, empirical_confidence, empirical_distribution, inferred_hypotheses, resolve_for_certifications, ) from compliance.transition_reasoning import TargetRequirement _DIR = os.path.dirname(__file__) _LIB = [CapabilityHypothesis(**h) for h in yaml.safe_load( open(os.path.join(_DIR, "..", "knowledge", "certification_hypotheses", "hypotheses.yaml"), encoding="utf-8"))["hypotheses"]] def test_library_is_capability_centric_and_reuses_certs(): # the shared core is small (reuse, not 30-per-cert) and document control is supported by many certs doc = next(h for h in _LIB if h.capability == "document_and_change_control") assert len(doc.supported_by) >= 4 assert len(_LIB) <= 60 # whole library, not ~300 def test_multi_certification_merges_automatically(): # a company with ISO9001 + ISO14001 + TISAX gets the UNION of their hypotheses, deduped merged = inferred_hypotheses(["ISO9001", "ISO14001", "TISAX"], _LIB) caps = {h.capability for h in merged} assert "document_and_change_control" in caps # ISO9001 + TISAX assert "information_security_management" in caps # TISAX assert "environmental_management_documentation" in caps # ISO14001 # SBOM / signed updates are suggested by NO certificate -> never inferred assert "sbom_creation" not in caps and "secure_signed_update_distribution" not in caps def test_observations_are_richer_than_binary_and_review_gated(): # the learning unit is the QUESTION; an answer can be partial with a scope note, not just yes/no raw = [Observation(hypothesis_id="HYP-supplier", observation_type=ObservationType.CONFIRMED)] assert empirical_confidence(raw) is None # unreviewed -> does NOT calibrate (review gate) obs = [ Observation(hypothesis_id="HYP-supplier", observation_type=ObservationType.CONFIRMED, reviewed=True), Observation(hypothesis_id="HYP-supplier", observation_type=ObservationType.PARTIAL, scope_note="nur kritische Lieferanten", reviewed=True), Observation(hypothesis_id="HYP-supplier", observation_type=ObservationType.REFUTED, reviewed=True), Observation(hypothesis_id="HYP-supplier", observation_type=ObservationType.NOT_APPLICABLE, reviewed=True), ] dist = empirical_distribution(obs) # a DISTRIBUTION, not a single percentage assert dist["confirmed"] == 1 and dist["partial"] == 1 and dist["refuted"] == 1 and dist["not_applicable"] == 1 # confidence = (confirmed + 0.5*partial) / (confirmed+partial+refuted); n.a. excluded from the base assert empirical_confidence(obs) == 0.5 def test_resolve_adapts_to_advisor_input(): res = resolve_for_certifications(["ISO27001", "ISO9001"], _LIB) assert "incident_management" in res["ISO27001"] assert "document_and_change_control" in res["ISO9001"] def test_advisor_consumes_the_library_end_to_end(): cra = yaml.safe_load(open(os.path.join(_DIR, "..", "knowledge", "transition_patterns", "transition_pattern_iso27001_to_cra_maschinenvo_v1.yaml"), encoding="utf-8")) req = [TargetRequirement(capability_id=a["capability"]) for a in cra["likely_covered"]] req += [TargetRequirement(capability_id=d["capability"], expected_evidence=d.get("expected_evidence", [])) for d in cra["delta_requirements"]] inp = OnboardingInput(company="x", certifications=["ISO27001", "TISAX", "ISO9001", "ISO14001"], target=["CRA"]) hyp = resolve_for_certifications(inp.certifications, _LIB) # library -> advisor input res = advisor_start(inp, hyp, req, target_id="CRA", corpus_status={"CRA": "validated"}) assert res.inferred_assumptions and res.next_best_questions assert any(r.certification == "ISO14001" for r in res.rejected_assumptions) # not relevant to CRA