From 77459d06d6948ea8a4be9ecc5f54b97de25e20cf Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sun, 28 Jun 2026 16:18:28 +0200 Subject: [PATCH] fix(onboarding): apply hypothesis/vocabulary review decisions (ISO13485, patch-policy rationale, summary) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two reviewed knowledge decisions (2026-06-28) + the deferred cosmetic counter, before #59. 1. ISO13485 removed from the incident_management hypothesis. ISO 13485 CAPA / quality-safety incident handling is NOT security incident management — the mapping was too broad and would seed false hypotheses for the empirical loop. A dedicated manage_quality_and_safety_incidents capability can come later IF a target needs it; not forced now. (ISO27001/TISAX/IEC62443 keep incident_management.) 2. patch_policy_doc -> secure_signed_update_distribution stays `partial`, but the curated rationale is sharpened: "indicates update governance, does not evidence signed distribution" (a patch policy is not proof of SIGNED distribution). New optional SignalMapping.rationale field carries the curated note. (github_actions_ci -> SDL and dependency_scanning -> vuln-mgmt reviewed and APPROVED as-is.) 3. Cosmetic (folded in since we touched the file): the silent-intake summary now counts detected and indications SEPARATELY ("N automatisch erkannt, M Indikation(en)") instead of lumping partial signals into "automatisch erkannt" — consistent with the three-state model just shipped. Tests: ISO13485 no longer resolves to incident_management; summary counts split correctly. 29 onboarding tests pass, mypy --strict clean, demo runs, check-loc 0. Runtime-visible (hypothesis resolution + summary text) -> deploy + smoke. --- .../compliance/onboarding/silent_intake.py | 9 ++++++--- .../knowledge/certification_hypotheses/hypotheses.yaml | 5 ++++- .../knowledge/onboarding/intake_signal_map.yaml | 3 ++- .../tests/test_certification_hypotheses.py | 9 +++++++++ backend-compliance/tests/test_silent_intake.py | 2 ++ 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/backend-compliance/compliance/onboarding/silent_intake.py b/backend-compliance/compliance/onboarding/silent_intake.py index 8d91338e..c3d3055b 100644 --- a/backend-compliance/compliance/onboarding/silent_intake.py +++ b/backend-compliance/compliance/onboarding/silent_intake.py @@ -41,6 +41,7 @@ class SignalMapping(BaseModel): evidence: Optional[str] = None # the artifact found (already in hand -> no upload needed) product_fact: Optional[str] = None # e.g. "connected_to_internet" fact_value: str = "true" + rationale: str = "" # curated note: WHY only indicative (esp. for partial mappings) class DetectedCapability(BaseModel): @@ -111,10 +112,12 @@ def silent_intake( detected = [caps[k] for k in sorted(caps)] product_facts = [facts[k] for k in sorted(facts)] requirements_seen = sorted(requirements) + n_detected = sum(1 for d in detected if d.relationship == "detected") # concrete artifacts -> auto-detected + n_indication = len(detected) - n_detected # partial -> indication, still asked summary = ( - "Stille Vorbefüllung: %d Fähigkeit(en) automatisch erkannt, %d Produktfakt(en), %d Nachweis(e) " - "bereits vorhanden, %d Anforderung(en) erkannt (nicht als vorhanden gewertet)." - % (len(detected), len(product_facts), len(evidence), len(requirements_seen)) + "Stille Vorbefüllung: %d Fähigkeit(en) automatisch erkannt, %d Indikation(en), %d Produktfakt(en), " + "%d Nachweis(e) bereits vorhanden, %d Anforderung(en) erkannt (nicht als vorhanden gewertet)." + % (n_detected, n_indication, len(product_facts), len(evidence), len(requirements_seen)) ) return SilentIntakeResult( detected_capabilities=detected, product_facts=product_facts, diff --git a/backend-compliance/knowledge/certification_hypotheses/hypotheses.yaml b/backend-compliance/knowledge/certification_hypotheses/hypotheses.yaml index ca4ac312..dc127c74 100644 --- a/backend-compliance/knowledge/certification_hypotheses/hypotheses.yaml +++ b/backend-compliance/knowledge/certification_hypotheses/hypotheses.yaml @@ -22,8 +22,11 @@ hypotheses: - {id: HYP-document_control, capability: document_and_change_control, relationship: supports, kind: shared, supported_by: [ISO9001, ISO13485, ISO27001, TISAX, ASPICE, IATF16949], verification_required: true, question_intent: verify_existence, expected_evidence: [document_control_procedure]} + # NOTE: ISO13485 deliberately NOT here — its CAPA / quality-safety incident handling is not security + # incident management; that mapping was too broad (would seed false hypotheses). A dedicated + # manage_quality_and_safety_incidents capability can be added later if a target actually needs it. - {id: HYP-incident_management, capability: incident_management, relationship: supports, kind: shared, - supported_by: [ISO27001, TISAX, IEC62443, ISO13485], + supported_by: [ISO27001, TISAX, IEC62443], verification_required: true, question_intent: verify_existence, expected_evidence: [incident_procedure]} - {id: HYP-supplier_security, capability: supplier_security, relationship: supports, kind: shared, supported_by: [ISO27001, TISAX, IEC62443], diff --git a/backend-compliance/knowledge/onboarding/intake_signal_map.yaml b/backend-compliance/knowledge/onboarding/intake_signal_map.yaml index 770adc10..45358848 100644 --- a/backend-compliance/knowledge/onboarding/intake_signal_map.yaml +++ b/backend-compliance/knowledge/onboarding/intake_signal_map.yaml @@ -23,7 +23,8 @@ mappings: # ── documents ───────────────────────────────────────────────────────────────────────────── - {signal: ce_conformity_doc, capability: ce_conformity_assessment_and_technical_documentation, relationship: detected, evidence: technical_documentation} - {signal: product_risk_assessment_doc, capability: product_cyber_risk_assessment, relationship: detected, evidence: product_risk_assessment} - - {signal: patch_policy_doc, capability: secure_signed_update_distribution, relationship: partial, evidence: patch_policy} + - {signal: patch_policy_doc, capability: secure_signed_update_distribution, relationship: partial, evidence: patch_policy, + rationale: "indicates update governance, does not evidence signed distribution"} - {signal: incident_response_plan_doc, capability: incident_management, relationship: detected, evidence: incident_procedure} # ── product facts (drive scope / target applicability) ────────────────────────────────────── - {signal: cloud_connectivity, product_fact: connected_to_internet} diff --git a/backend-compliance/tests/test_certification_hypotheses.py b/backend-compliance/tests/test_certification_hypotheses.py index e2aec149..08d5f0d5 100644 --- a/backend-compliance/tests/test_certification_hypotheses.py +++ b/backend-compliance/tests/test_certification_hypotheses.py @@ -71,6 +71,15 @@ def test_resolve_adapts_to_advisor_input(): assert "document_and_change_control" in res["ISO9001"] +def test_iso13485_does_not_suggest_security_incident_management(): + # ISO 13485 CAPA / quality-safety incident handling is NOT security incident management -> too broad, + # removed from the incident_management hypothesis (review decision 2026-06-28). + res = resolve_for_certifications(["ISO13485"], _LIB) + assert "incident_management" not in res.get("ISO13485", []) + inc = next(h for h in _LIB if h.capability == "incident_management") + assert "ISO13485" not in inc.supported_by + + 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")) diff --git a/backend-compliance/tests/test_silent_intake.py b/backend-compliance/tests/test_silent_intake.py index 161776a9..54ea7996 100644 --- a/backend-compliance/tests/test_silent_intake.py +++ b/backend-compliance/tests/test_silent_intake.py @@ -85,6 +85,8 @@ def test_partial_signal_is_indicative_not_detected(): res = silent_intake([IntakeSignal(source="repository", signal="github_actions_ci")], _MAP) assert "secure_development_lifecycle" not in res.capability_ids() # not counted as present assert res.indicative_capability_ids() == ["secure_development_lifecycle"] # surfaced as an indication + # the summary counts detected and indications SEPARATELY (no over-claim of "automatisch erkannt") + assert "0 Fähigkeit(en) automatisch erkannt, 1 Indikation(en)" in res.summary def test_partial_indication_does_not_remove_the_question():