5e5002c883
Architecture-validation finding: the implementation mode produced compliance-
flavored output ("teilweise erfüllt", "covered") from a mere customer claim,
blurring the line to the Execution layer. This is a design decision, not a text
fix — the reasoning layer judges only the customer's STATEMENT, never conformity.
- CoverageStatus -> ClaimCoverage; values are claim-relative + carry "potential":
potentially_addresses / partially_addresses / does_not_address /
insufficient_information.
- ImplementationAssessment -> ClaimObligationMapping (coverage_status ->
claim_coverage); ImplementationResponse -> ImplementationReasoningResponse
(assessments -> mappings, + explicit `disclaimer`); request renamed; engine
entry assess_implementation -> reason_implementation_claim.
- Endpoint /reasoning/implementation-assessment -> /reasoning/implementation-reasoning.
- Summary/explanations reworded: "adressiert wahrscheinlich N Pflichten … für
eine Bewertung der tatsächlichen Umsetzung sind Nachweise erforderlich (keine
Konformitätsaussage)". No "erfüllt"/"abgedeckt" leaks.
- New guard test asserts no compliance verdict leaks (no "erfüllt"; disclaimer
separates ClaimCoverage from ComplianceStatus). 23 tests green, mypy clean.
Discovery (scope/obligations) was already structurally claim-free and unaffected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
159 lines
7.0 KiB
Python
159 lines
7.0 KiB
Python
"""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' wahrscheinlich vollständig — Nachweise erforderlich." % 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) vollständig und %d "
|
|
"teilweise; %d werden nicht berührt. Für eine Bewertung der tatsächlichen Umsetzung sind "
|
|
"Nachweise erforderlich (keine Konformitätsaussage)." % (full, partial, none)
|
|
)
|