diff --git a/docs-src/development/obligation_registry_v1.md b/docs-src/development/obligation_registry_v1.md index ea566a96..d9482726 100644 --- a/docs-src/development/obligation_registry_v1.md +++ b/docs-src/development/obligation_registry_v1.md @@ -128,3 +128,31 @@ SBOM ✓ → Vuln ✓ → Registry v1 (DIESE Spec) → Ontologie/Beziehung Begründung: Schema jetzt billig änderbar; bei 300–1000 Obligations wird jede Schemaänderung teuer. Fortschritt wird daran gemessen, ob jede neue Obligation die Registry besser macht — nicht an neuen Controls. + +## Scope-Audit (Review-Step, PFLICHT je Cut) + +Die Registry modelliert **Hersteller-Pflichten**. Bestimmungen, die an Behörden / notifizierte +Stellen / Mitgliedstaaten adressiert sind (Sanktionen, Marktüberwachung, Anforderungen an +Konformitätsbewertungsstellen), sind Enforcement-/Institutions-Recht. **Prinzip: Adressat der Norm +⊥ Handlungspflicht des Herstellers.** `scope`-Attribut-Achse (Enum, KEINE neue Objektklasse): + +- `in_scope` — Norm adressiert direkt den Hersteller (Default). +- `out_of_scope` — reines Staats-/Durchsetzungs-/Institutions-Recht (Adressat ≠ Hersteller, KEINE + mittelbare Herstellerpflicht). Aus `obligation_join_keys.json` gefiltert. Präzedenz CSIRT/ENISA. +- `derived_obligation` — Norm adressiert primär eine andere Rolle, erzeugt aber MITTELBAR eine + Hersteller-Handlungspflicht → **bleibt im Set** (`scope_split_candidate` markiert spätere + Aufspaltung Normadressat ↔ abgeleitete Pflicht; nicht vorzeitig festziehen). + +**Gate-Regel:** +``` +Jeder neue Obligation-Cut muss durch Scope-Audit laufen. +Findings mit authority-/institution-addressed obligations werden dokumentiert. +Automatische Reclassification ist verboten, solange kein explizites Review-Go vorliegt. +``` + +**Werkzeug-Trennung (FLAG ⊥ MUTATE):** +- `scope_audit.py` — **flaggt nur** (scannt alle Registries → `scope_audit_findings.json`; mutiert nie). +- `validate_registry.py` — surfaced pro Cut unklassifizierte authority-/institution-Obligations als + **non-fatal Warnung** (blockt nicht, mutiert nicht). +- `apply_scope_classification.py` — **mutiert** (setzt `scope`), läuft NUR nach explizitem Review-Go + (ändert `join_keys` + Compliance-Execution-Sync → menschlich/koordiniert). diff --git a/scripts/obligation_discovery/validate_registry.py b/scripts/obligation_discovery/validate_registry.py index bd0707a4..fde574ea 100644 --- a/scripts/obligation_discovery/validate_registry.py +++ b/scripts/obligation_discovery/validate_registry.py @@ -11,6 +11,7 @@ import json import sys from _core import validate_registry +from scope_audit import NON_MANUFACTURER_DOMAINS def main() -> None: @@ -28,6 +29,15 @@ def main() -> None: print(f" out_of_scope: {v['out_of_scope']}") print(f" semantische Kanten: {v['semantic_edges']}") print(f" PASSED: {v['passed']}") + # Scope-Audit (Review-Step, FLAG-ONLY — mutiert nie, blockt nie, ändert Exit-Code nicht): + unclassified = [o.get("id") or o.get("obligation_id") for o in reg.get("obligations", []) + if (o.get("applicability") or "").strip() in NON_MANUFACTURER_DOMAINS and not o.get("scope")] + if unclassified: + print(f" ⚠ SCOPE-AUDIT: {len(unclassified)} authority-/institution-adressierte Obligation(s) OHNE scope:") + print(f" {', '.join(str(x) for x in unclassified)}") + print(" → Review-Pflicht: scope=out_of_scope|derived_obligation (apply_scope_classification.py, NUR mit Review-Go). Auto-Reclassification VERBOTEN.") + else: + print(" Scope-Audit: keine unklassifizierten authority-/institution-Obligations") sys.exit(0 if v["passed"] else 1)