Add obligation advisor proof (P3)
Demonstriert den Produktnutzen der Registry: obligation-basierte Antwort statt RAG-Text. Frage → Pflicht (LEGAL_MINIMUM + Rechtsgrundlage + Applicability) ⊥ Best Practice (guidance_basis) ⊥ Nachweise (evidence_facets + member controls) + Beziehungen, deterministisch aus obligations/cra.json (kein LLM, zitierfähig). Beleg (SBOM, Maschinenbauer): JA — 7 CRA-Mindestpflichten + 4 Best-Practice (OWASP/NIST/ENISA); sbom_* supports vuln_identification_inventory. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
"""P3 — Compliance-Advisor-Proof: obligation-basierte Antwort aus der Registry (NICHT RAG-Text,
|
||||||
|
KEIN LLM). Demonstriert den besseren Antworttyp: PFLICHT (LEGAL_MINIMUM + Rechtsgrundlage +
|
||||||
|
Applicability) / BEST PRACTICE (guidance_basis) / NACHWEISE (evidence_facets + member controls)
|
||||||
|
+ Beziehungen. Deterministisch + zitierfähig — das ist der Produktnutzen der Registry.
|
||||||
|
|
||||||
|
python3 scripts/obligation_discovery/advisor_proof.py --registry obligations/cra.json \
|
||||||
|
--topic sbom --has-digital-elements
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def applies(obl: dict, has_digital: bool) -> tuple[bool, str]:
|
||||||
|
a = obl.get("applicability", "universal")
|
||||||
|
if a == "universal":
|
||||||
|
return True, ""
|
||||||
|
if a.startswith("domain:products_with_digital_elements"):
|
||||||
|
return (has_digital, "nur für Produkte mit digitalen Elementen (CRA Art. 3)")
|
||||||
|
if a.startswith("domain:"):
|
||||||
|
return True, a.split(":", 1)[1]
|
||||||
|
if a.startswith("conditional:"):
|
||||||
|
return True, f"bedingt: {a.split(':',1)[1]}"
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--registry", required=True)
|
||||||
|
ap.add_argument("--topic", default="sbom", help="Family/Keyword der Pflicht (z.B. sbom)")
|
||||||
|
ap.add_argument("--has-digital-elements", action="store_true")
|
||||||
|
ap.add_argument("--question", default="Muss ich als Maschinenbauer eine SBOM bereitstellen?")
|
||||||
|
a = ap.parse_args()
|
||||||
|
reg = json.load(open(a.registry, encoding="utf-8"))
|
||||||
|
obls = [o for o in reg["obligations"]
|
||||||
|
if a.topic in o.get("family", "") or a.topic in o["id"] or a.topic in o.get("name", "").lower()]
|
||||||
|
ids = {o["id"] for o in obls}
|
||||||
|
pflicht = [o for o in obls if o["tier"] == "LEGAL_MINIMUM"]
|
||||||
|
best = [o for o in obls if o["tier"] in ("BEST_PRACTICE", "IMPLEMENTATION_GUIDANCE")]
|
||||||
|
|
||||||
|
print(f"FRAGE: {a.question}")
|
||||||
|
print(f"KONTEXT: Hersteller; digitale Elemente = {a.has_digital_elements} → CRA-Geltungsbereich")
|
||||||
|
applicable_pflicht = [(o, applies(o, a.has_digital_elements)) for o in pflicht]
|
||||||
|
any_pflicht = any(ok for _, (ok, _) in applicable_pflicht)
|
||||||
|
n_pflicht = sum(1 for _, (ok, _) in applicable_pflicht if ok)
|
||||||
|
verdict = "JA" if any_pflicht and a.has_digital_elements else "NUR WENN CRA-anwendbar"
|
||||||
|
print(f"\nANTWORT: {verdict} — unter dem CRA bestehen {n_pflicht} gesetzliche "
|
||||||
|
f"Mindestpflichten zum Thema {a.topic}.\n")
|
||||||
|
|
||||||
|
print("── PFLICHT (LEGAL_MINIMUM, Wortlaut im CRA) ──")
|
||||||
|
for o, (ok, note) in applicable_pflicht:
|
||||||
|
if not ok:
|
||||||
|
continue
|
||||||
|
lb = "; ".join(f"{b.get('source','')} {b.get('anchor','')}".strip() for b in o.get("legal_basis", []))
|
||||||
|
print(f" • {o['id']} — {o.get('description','')[:84]}")
|
||||||
|
print(f" Rechtsgrundlage: {lb or '—'}" + (f" | {note}" if note else ""))
|
||||||
|
|
||||||
|
print("\n── BEST PRACTICE (anerkannte Umsetzung, KEINE CRA-Wortlautpflicht) ──")
|
||||||
|
for o in best:
|
||||||
|
gb = "; ".join(b.get("source", "") for b in o.get("guidance_basis", []))
|
||||||
|
print(f" • {o['id']} — {o.get('description','')[:74]} | Guidance: {gb or '—'}")
|
||||||
|
|
||||||
|
print("\n── NACHWEISE (Evidence) ──")
|
||||||
|
for o in pflicht + best:
|
||||||
|
f = o.get("evidence_facets", {})
|
||||||
|
facets = "/".join(k for k in ("governance", "capability", "evidence") if f.get(k)) or "—"
|
||||||
|
print(f" • {o['id']}: {o.get('member_count','?')} Controls · Facetten {facets}")
|
||||||
|
|
||||||
|
print("\n── BEZIEHUNGEN (warum es zählt) ──")
|
||||||
|
for r in reg.get("relationships", []):
|
||||||
|
if r.get("from") in ids and r.get("to") not in ids:
|
||||||
|
print(f" • {r['from']} --{r['type']}--> {r['to']}: {r.get('note','')[:70]}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user