From e54d07f2d92522d879271c976e124ae857e72139 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Wed, 1 Jul 2026 13:12:40 +0200 Subject: [PATCH] =?UTF-8?q?feat(registry-quality):=20Scope-Audit-Tool=20?= =?UTF-8?q?=E2=80=94=20Adressaten-Pr=C3=BCfung=20(Hersteller=20vs=20Beh?= =?UTF-8?q?=C3=B6rde)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generalisiert den Zitierfähigkeits-Befund (2 Kapitel-Obligations authority-facing) zu einem wiederverwendbaren Review-Stage-Werkzeug: flaggt Obligations, deren applicability an Behörde/notifizierte Stelle/Mitgliedstaat adressiert ist = out_of_scope-Kandidaten (Registry modelliert Hersteller-Pflichten; Präzedenz CSIRT/ENISA im CRA-Vuln-Cut). - scope_audit.py: deterministisch, key't auf applicability (Adressat), NICHT auf Behörden-Nennung im Namen → Melde-AN-Behörde-Pflichten bleiben korrekt IN-SCOPE (False-Positive-Guard, verifiziert an exploited_vuln_reporting_authorities). - 126 Obligations gescannt → 3 Kandidaten (alle MaschVO LEGAL_MINIMUM): notified_body_requirements (domain:notified_body) · market_surveillance_safeguard (domain:authority) · sanctions (domain:authority). - scope_audit_findings.json = Findings-Artefakt. Audit FLAGGT nur; Reklassifizierung = User/Owner-Entscheidung (ändert join_keys, cross-session). Co-Authored-By: Claude Opus 4.7 --- obligations/scope_audit_findings.json | 45 ++++++++++++++ scripts/obligation_discovery/scope_audit.py | 68 +++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 obligations/scope_audit_findings.json create mode 100644 scripts/obligation_discovery/scope_audit.py diff --git a/obligations/scope_audit_findings.json b/obligations/scope_audit_findings.json new file mode 100644 index 00000000..af4ff308 --- /dev/null +++ b/obligations/scope_audit_findings.json @@ -0,0 +1,45 @@ +{ + "audit": "obligation scope audit (Adressat: Hersteller vs Behörde/notified_body)", + "principle": "Registry modelliert Hersteller-Pflichten; Enforcement/Institutions-Recht = out_of_scope-Kandidat", + "false_positive_guard": "Melde-AN-Behörde-Pflichten (applicability=domain:products…) bleiben IN-SCOPE", + "obligations_scanned": 126, + "out_of_scope_candidates": [ + { + "file": "cra_machinery.json", + "id": "notified_body_requirements", + "name": "Anforderungen an notifizierte Stellen", + "tier": "LEGAL_MINIMUM", + "applicability": "domain:notified_body", + "subdomain": "notified_body", + "member_count": 11, + "reason": "Adressat ist Behörde/notifizierte Stelle/Mitgliedstaat, nicht Hersteller", + "precedent": "CRA-Vuln-Cut: CSIRT/ENISA out_of_scope (Adressat != Hersteller)", + "recommendation": "out_of_scope ODER eigene Kategorie 'institutional/enforcement'" + }, + { + "file": "cra_machinery.json", + "id": "market_surveillance_safeguard", + "name": "Marktüberwachung, nationale Schutzmaßnahmen und Korrekturmaßnahmen", + "tier": "LEGAL_MINIMUM", + "applicability": "domain:authority", + "subdomain": "market_surveillance", + "member_count": 30, + "reason": "Adressat ist Behörde/notifizierte Stelle/Mitgliedstaat, nicht Hersteller", + "precedent": "CRA-Vuln-Cut: CSIRT/ENISA out_of_scope (Adressat != Hersteller)", + "recommendation": "out_of_scope ODER eigene Kategorie 'institutional/enforcement'" + }, + { + "file": "cra_machinery.json", + "id": "sanctions", + "name": "Sanktionen für Verstöße gegen die Maschinenverordnung", + "tier": "LEGAL_MINIMUM", + "applicability": "domain:authority", + "subdomain": "sanctions", + "member_count": 19, + "reason": "Adressat ist Behörde/notifizierte Stelle/Mitgliedstaat, nicht Hersteller", + "precedent": "CRA-Vuln-Cut: CSIRT/ENISA out_of_scope (Adressat != Hersteller)", + "recommendation": "out_of_scope ODER eigene Kategorie 'institutional/enforcement'" + } + ], + "decision_owner": "User/Registry-Owner — Audit FLAGGT nur, reklassifiziert nicht" +} \ No newline at end of file diff --git a/scripts/obligation_discovery/scope_audit.py b/scripts/obligation_discovery/scope_audit.py new file mode 100644 index 00000000..4595eddf --- /dev/null +++ b/scripts/obligation_discovery/scope_audit.py @@ -0,0 +1,68 @@ +"""Scope-Audit: Adressaten-Prüfung der Obligation-Registry (Review-Stage-Werkzeug). + +Prinzip (etabliert im CRA-Vuln-Cut, CSIRT/ENISA out_of_scope): die Registry modelliert +**Hersteller-Pflichten**. Bestimmungen, die an BEHÖRDEN / notifizierte Stellen / Mitgliedstaaten +adressiert sind (Marktüberwachung, Sanktionen, Anforderungen an Konformitätsbewertungsstellen), +sind Enforcement-/Institutions-Recht → out_of_scope-KANDIDATEN. + +WICHTIG (False-Positive-Abgrenzung): eine Hersteller-Pflicht, Etwas AN eine Behörde zu MELDEN +(z.B. `exploited_vuln_reporting_authorities`, applicability=products_with_digital_elements) ist +IN-SCOPE — Adressat der Pflicht = Hersteller, Behörde = nur Empfänger. Der Audit key't daher auf +`applicability` (Adressat), NICHT auf Behörden-Nennung im Namen. + +Deterministisch, kein LLM. Reklassifizierung = Owner-/User-Entscheidung (dieser Audit FLAGGT nur). +Für jeden künftigen Regulierungs-Cut mitlaufen lassen. +""" +from __future__ import annotations + +import glob +import json + +# applicability-Präfixe, die einen NICHT-Hersteller-Adressaten bezeichnen +NON_MANUFACTURER_DOMAINS = { + "domain:authority", + "domain:notified_body", + "domain:market_surveillance", + "domain:member_state", + "domain:commission", +} + + +def main() -> None: + findings = [] + total = 0 + for f in sorted(glob.glob("obligations/cra*.json")): + d = json.load(open(f, encoding="utf-8")) + for o in d.get("obligations", []): + total += 1 + appl = (o.get("applicability") or "").strip() + if appl in NON_MANUFACTURER_DOMAINS: + findings.append({ + "file": f.split("/")[-1], + "id": o.get("id") or o.get("obligation_id"), + "name": o.get("name"), + "tier": o.get("tier"), + "applicability": appl, + "subdomain": o.get("subdomain"), + "member_count": o.get("member_count"), + "reason": "Adressat ist Behörde/notifizierte Stelle/Mitgliedstaat, nicht Hersteller", + "precedent": "CRA-Vuln-Cut: CSIRT/ENISA out_of_scope (Adressat != Hersteller)", + "recommendation": "out_of_scope ODER eigene Kategorie 'institutional/enforcement'", + }) + out = { + "audit": "obligation scope audit (Adressat: Hersteller vs Behörde/notified_body)", + "principle": "Registry modelliert Hersteller-Pflichten; Enforcement/Institutions-Recht = out_of_scope-Kandidat", + "false_positive_guard": "Melde-AN-Behörde-Pflichten (applicability=domain:products…) bleiben IN-SCOPE", + "obligations_scanned": total, + "out_of_scope_candidates": findings, + "decision_owner": "User/Registry-Owner — Audit FLAGGT nur, reklassifiziert nicht", + } + json.dump(out, open("obligations/scope_audit_findings.json", "w", encoding="utf-8"), + ensure_ascii=False, indent=1) + print(f"gescannt {total} Obligations | out_of_scope-Kandidaten: {len(findings)}") + for fnd in findings: + print(f" [{fnd['file']}] {fnd['id']:32} tier={fnd['tier']} appl={fnd['applicability']}") + + +if __name__ == "__main__": + main()