"""Implementation reasoning (spec Modus 3) — Welt 1 only. Maps a free-text claim ("Wir haben SBOMs und machen Updates, wenn Kunden Fehler melden.") onto the product's applicable obligations and reports, per obligation, whether the *claim* potentially/partially/does-not address it — plus the evidence that WOULD be needed to prove real implementation. This is NOT a conformity verdict. It judges the customer's statement, never whether the obligation is met. The real verdict (ComplianceStatus: erfüllt/ offen/unklar from verified evidence) lives in the Compliance Execution Graph. The four reasoning layers: claim -> interpretation (capabilities/topics on the claim) -> potential obligation coverage (`claim_coverage`) -> evidence required. """ from __future__ import annotations from typing import Dict, List from .claim_normalizer import normalize_claim from .enums import ClaimCoverage, Confidence from .obligation_engine import derive_obligations from .schemas import ( ClaimObligationMapping, CustomerImplementationClaim, ImplementationReasoningResponse, ProductProfile, ) from .taxonomy_claims import topics_for DISCLAIMER = ( "Diese Auswertung interpretiert ausschließlich die Kundenaussage (ClaimCoverage, Welt 1). " "Sie ist KEINE Konformitätsaussage — der tatsächliche Compliance-Status (ComplianceStatus, " "Welt 2) ergibt sich erst aus geprüften Nachweisen im Compliance Execution Graph." ) # Typical sub-elements a capability still misses when only partially claimed. STANDARD_GAPS: Dict[str, List[str]] = { "software_bill_of_materials": [ "Vulnerability-Monitoring der Komponenten", "Bewertung betroffener Komponenten", "Lieferantenprozess", ], "secure_updates": [ "aktive Schwachstellenüberwachung", "Patch-Bewertung", "Fristen und Verantwortlichkeiten", "Nachweis der Updatefähigkeit", ], "vulnerability_management": [ "definierter Vulnerability-Handling-Prozess", "Priorisierung und Fristen", ], "authentication": ["MFA für privilegierte Zugänge", "keine Standard-Zugangsdaten"], "security_logging": ["Schutz der Logs vor Manipulation", "Monitoring/Alerting"], "software_integrity": ["Signierung der Updates", "Verifikation der Update-Signatur"], "secure_by_default": ["Härtung der Auslieferungskonfiguration", "Minimierung der Angriffsfläche"], "secure_communication": ["verschlüsselte Übertragung", "Integritätsschutz der Verbindung"], "risk_assessment": ["dokumentierte Risikobewertung", "Aufnahme in die technische Doku"], "technical_documentation": ["vollständige technische Unterlagen", "Aktualisierung über den Lebenszyklus"], } def _missing_for(capabilities: List[str]) -> List[str]: out: List[str] = [] for cap in capabilities: for gap in STANDARD_GAPS.get(cap, []): if gap not in out: out.append(gap) return out def _coverage(required: List[str], claimed: List[str], qualifiers: List[str]) -> ClaimCoverage: if not required: return ClaimCoverage.INSUFFICIENT_INFORMATION req, have = set(required), set(claimed) hit = req & have if not hit: return ClaimCoverage.DOES_NOT_ADDRESS if "absent" in qualifiers or "planned" in qualifiers: return ClaimCoverage.DOES_NOT_ADDRESS if "reactive" in qualifiers and hit & {"secure_updates", "vulnerability_management"}: return ClaimCoverage.PARTIALLY_ADDRESSES if req <= have: return ClaimCoverage.POTENTIALLY_ADDRESSES return ClaimCoverage.PARTIALLY_ADDRESSES def reason_implementation_claim( profile: ProductProfile, customer_claim: str ) -> ImplementationReasoningResponse: claim = normalize_claim(customer_claim) obligations = derive_obligations(profile).applicable_obligations claimed = claim.claimed_capability claim_topics = set(claim.related_topics) | set(claimed) mappings: List[ClaimObligationMapping] = [] missing_evidence: List[str] = [] for ob in obligations: from .rules_obligations import obligation_rule rule = obligation_rule(ob.obligation_id) required_caps = rule.required_capabilities if rule else [] ob_topics = set(topics_for(required_caps)) | set(required_caps) directly_claimed = bool(set(required_caps) & set(claimed)) related = bool(ob_topics & claim_topics) if not directly_claimed and not related: continue # unrelated to the claim -> don't reason about it coverage = _coverage(required_caps, claimed, claim.qualifiers) missing = [] if coverage == ClaimCoverage.POTENTIALLY_ADDRESSES else _missing_for(required_caps) if coverage != ClaimCoverage.POTENTIALLY_ADDRESSES: for ev in ob.required_evidence: if ev not in missing_evidence: missing_evidence.append(ev) mappings.append( ClaimObligationMapping( claim_id=claim.claim_id, obligation_id=ob.obligation_id, claim_coverage=coverage, missing_elements=missing, required_evidence=ob.required_evidence, explanation=_explain(coverage, ob.title, claim.qualifiers), confidence=Confidence.MEDIUM, ) ) return ImplementationReasoningResponse( claim=claim, mappings=mappings, missing_evidence=missing_evidence, summary=_summary(claim, mappings), disclaimer=DISCLAIMER, ) def _explain(coverage: ClaimCoverage, title: str, qualifiers: List[str]) -> str: if coverage == ClaimCoverage.POTENTIALLY_ADDRESSES: return "Die Aussage adressiert die Pflicht '%s' direkt — Nachweise erforderlich für eine Bewertung der Umsetzung." % title if coverage == ClaimCoverage.PARTIALLY_ADDRESSES: extra = " Der beschriebene Prozess wirkt reaktiv." if "reactive" in qualifiers else "" return "Die Aussage adressiert die Pflicht '%s' nur teilweise.%s" % (title, extra) if coverage == ClaimCoverage.DOES_NOT_ADDRESS: return "Die Aussage adressiert die Pflicht '%s' nicht." % title return "Zur Pflicht '%s' liegen zu wenige Angaben für eine Einordnung vor." % title def _summary(claim: CustomerImplementationClaim, mappings: List[ClaimObligationMapping]) -> str: if not claim.claimed_capability: return "Die Aussage ist zu unspezifisch — bitte konkretisieren, was umgesetzt wurde." full = sum(1 for m in mappings if m.claim_coverage == ClaimCoverage.POTENTIALLY_ADDRESSES) partial = sum(1 for m in mappings if m.claim_coverage == ClaimCoverage.PARTIALLY_ADDRESSES) none = sum(1 for m in mappings if m.claim_coverage == ClaimCoverage.DOES_NOT_ADDRESS) return ( "Die beschriebene Maßnahme adressiert wahrscheinlich %d Pflicht(en) direkt und %d " "teilweise; %d werden durch die Aussage nicht berührt. Für eine Bewertung der tatsächlichen " "Umsetzung sind Nachweise erforderlich. Dies ist keine Konformitätsaussage." % (full, partial, none) )