feat(registry-quality): scope-Achse — 2 out_of_scope + derived_obligation (User Option 2)
User-Entscheidung 2026-07-01 zum Scope-Audit: Adressat der Norm != Handlungspflicht des Herstellers. Neue `scope`-Attribut-Achse (Enum, KEINE neue Objektklasse -> Freeze v1.0 unberuehrt): in_scope (default) / out_of_scope / derived_obligation. - sanctions + market_surveillance_safeguard -> out_of_scope (reine Staats-/Durchsetzungs- bestimmungen; Praezedenz CSIRT/ENISA im CRA-Vuln-Cut). Aus join_keys gefiltert. - notified_body_requirements -> derived_obligation (Norm adressiert primaer die notifizierte Stelle, erzeugt aber mittelbare Herstellerpflichten: NB einbeziehen + Unterlagen + Konformitaetsbewertung) + scope_split_candidate (spaetere Aufspaltung Normadressat <-> abgeleitete Herstellerpflicht). BLEIBT im Set (Prinzip: Wissen nicht zu frueh verwerfen). - export_join_keys.py filtert scope==out_of_scope + fuehrt scope je Eintrag -> join_keys 126->124 (MaschVO 31->29; 123 in_scope + 1 derived_obligation). - scope_audit.py jetzt 3-Wege-klassifikations-bewusst (0 unklassifizierte Reste) + apply_scope_classification.py (deterministisch). Fuer jeden kuenftigen Cut mitlaufen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4125,7 +4125,11 @@
|
||||
"llm_model": "claude-opus-4-8",
|
||||
"synthesis_version": "v1"
|
||||
},
|
||||
"family": "machinery"
|
||||
"family": "machinery",
|
||||
"scope": "derived_obligation",
|
||||
"scope_reason": "Norm adressiert primär die notifizierte Stelle (Unabhängigkeit/Kompetenz/Unparteilichkeit), erzeugt aber mittelbare Hersteller-Pflichten: notifizierte Stelle einbeziehen, erforderliche Unterlagen bereitstellen, Konformitätsbewertung korrekt durchführen.",
|
||||
"scope_split_candidate": true,
|
||||
"scope_split_note": "Kandidat für spätere Aufspaltung: 'Normadressat' (Anforderungen AN die notifizierte Stelle = institutional/out_of_scope) ↔ 'abgeleitete Herstellerpflicht' (NB einbeziehen + Unterlagen + Konformitätsbewertung = in_scope). NICHT vorzeitig festziehen."
|
||||
},
|
||||
{
|
||||
"id": "market_surveillance_safeguard",
|
||||
@@ -4204,7 +4208,9 @@
|
||||
"llm_model": "claude-opus-4-8",
|
||||
"synthesis_version": "v1"
|
||||
},
|
||||
"family": "machinery"
|
||||
"family": "machinery",
|
||||
"scope": "out_of_scope",
|
||||
"scope_reason": "Adressat = Marktüberwachungsbehörden/Kommission (Schutzmaßnahmen, Schutzklauselverfahren); keine Hersteller-Handlungspflicht. Präzedenz CSIRT/ENISA."
|
||||
},
|
||||
{
|
||||
"id": "sanctions",
|
||||
@@ -4268,7 +4274,9 @@
|
||||
"llm_model": "claude-opus-4-8",
|
||||
"synthesis_version": "v1"
|
||||
},
|
||||
"family": "machinery"
|
||||
"family": "machinery",
|
||||
"scope": "out_of_scope",
|
||||
"scope_reason": "Adressat = Mitgliedstaaten (legen Sanktionen fest); keine Hersteller-Handlungspflicht. Präzedenz CSIRT/ENISA (CRA-Vuln-Cut)."
|
||||
},
|
||||
{
|
||||
"id": "scope_transition_application",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,18 @@
|
||||
{
|
||||
"audit": "obligation scope audit (Adressat: Hersteller vs Behörde/notified_body)",
|
||||
"principle": "Registry modelliert Hersteller-Pflichten; Enforcement/Institutions-Recht = out_of_scope-Kandidat",
|
||||
"principle": "Adressat der Norm != Handlungspflicht des Herstellers; scope-Achse in_scope/out_of_scope/derived_obligation",
|
||||
"false_positive_guard": "Melde-AN-Behörde-Pflichten (applicability=domain:products…) bleiben IN-SCOPE",
|
||||
"obligations_scanned": 126,
|
||||
"out_of_scope_candidates": [
|
||||
"classified": [
|
||||
{
|
||||
"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'"
|
||||
"scope": "derived_obligation",
|
||||
"scope_reason": "Norm adressiert primär die notifizierte Stelle (Unabhängigkeit/Kompetenz/Unparteilichkeit), erzeugt aber mittelbare Hersteller-Pflichten: notifizierte Stelle einbeziehen, erforderliche Unterlagen bereitstellen, Konformitätsbewertung korrekt durchführen.",
|
||||
"scope_split_note": "Kandidat für spätere Aufspaltung: 'Normadressat' (Anforderungen AN die notifizierte Stelle = institutional/out_of_scope) ↔ 'abgeleitete Herstellerpflicht' (NB einbeziehen + Unterlagen + Konformitätsbewertung = in_scope). NICHT vorzeitig festziehen."
|
||||
},
|
||||
{
|
||||
"file": "cra_machinery.json",
|
||||
@@ -22,11 +20,8 @@
|
||||
"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'"
|
||||
"scope": "out_of_scope",
|
||||
"scope_reason": "Adressat = Marktüberwachungsbehörden/Kommission (Schutzmaßnahmen, Schutzklauselverfahren); keine Hersteller-Handlungspflicht. Präzedenz CSIRT/ENISA."
|
||||
},
|
||||
{
|
||||
"file": "cra_machinery.json",
|
||||
@@ -34,12 +29,10 @@
|
||||
"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'"
|
||||
"scope": "out_of_scope",
|
||||
"scope_reason": "Adressat = Mitgliedstaaten (legen Sanktionen fest); keine Hersteller-Handlungspflicht. Präzedenz CSIRT/ENISA (CRA-Vuln-Cut)."
|
||||
}
|
||||
],
|
||||
"decision_owner": "User/Registry-Owner — Audit FLAGGT nur, reklassifiziert nicht"
|
||||
"unclassified_candidates": [],
|
||||
"decision_owner": "User/Registry-Owner — Audit FLAGGT nur; für jeden künftigen Cut mitlaufen lassen"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Scope-Klassifikation anwenden (User-Entscheidung 2026-07-01, Option 2 + derived_obligation).
|
||||
|
||||
Neue `scope`-Attribut-Achse (KEINE neue Objektklasse — Enum-Wert, freeze-safe):
|
||||
in_scope (default/implizit) · out_of_scope · derived_obligation
|
||||
|
||||
Prinzip (User): Adressat der Norm ⊥ Handlungspflicht des Herstellers. Reine Staats-/
|
||||
Durchsetzungs-/Institutions-Bestimmungen = out_of_scope. Norm, die primär eine andere Rolle
|
||||
adressiert ABER mittelbar eine Hersteller-Handlungspflicht erzeugt = derived_obligation
|
||||
(bleibt im Hersteller-Set, wird NICHT verworfen — 'im Zweifel nicht zu früh Wissen verwerfen').
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import json
|
||||
|
||||
SCOPE = {
|
||||
"sanctions": {
|
||||
"scope": "out_of_scope",
|
||||
"scope_reason": "Adressat = Mitgliedstaaten (legen Sanktionen fest); keine Hersteller-Handlungspflicht. Präzedenz CSIRT/ENISA (CRA-Vuln-Cut).",
|
||||
},
|
||||
"market_surveillance_safeguard": {
|
||||
"scope": "out_of_scope",
|
||||
"scope_reason": "Adressat = Marktüberwachungsbehörden/Kommission (Schutzmaßnahmen, Schutzklauselverfahren); keine Hersteller-Handlungspflicht. Präzedenz CSIRT/ENISA.",
|
||||
},
|
||||
"notified_body_requirements": {
|
||||
"scope": "derived_obligation",
|
||||
"scope_reason": "Norm adressiert primär die notifizierte Stelle (Unabhängigkeit/Kompetenz/Unparteilichkeit), erzeugt aber mittelbare Hersteller-Pflichten: notifizierte Stelle einbeziehen, erforderliche Unterlagen bereitstellen, Konformitätsbewertung korrekt durchführen.",
|
||||
"scope_split_candidate": True,
|
||||
"scope_split_note": "Kandidat für spätere Aufspaltung: 'Normadressat' (Anforderungen AN die notifizierte Stelle = institutional/out_of_scope) ↔ 'abgeleitete Herstellerpflicht' (NB einbeziehen + Unterlagen + Konformitätsbewertung = in_scope). NICHT vorzeitig festziehen.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
applied = []
|
||||
for f in sorted(glob.glob("obligations/cra*.json")):
|
||||
d = json.load(open(f, encoding="utf-8"))
|
||||
changed = False
|
||||
for o in d.get("obligations", []):
|
||||
spec = SCOPE.get(o.get("id"))
|
||||
if spec:
|
||||
o.update(spec)
|
||||
applied.append((o["id"], spec["scope"]))
|
||||
changed = True
|
||||
if changed:
|
||||
json.dump(d, open(f, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
for oid, sc in applied:
|
||||
print(f" {oid:32} scope={sc}")
|
||||
print(f"angewendet: {len(applied)} (erwartet 3)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -23,12 +23,15 @@ def main() -> None:
|
||||
for path in a.registries:
|
||||
reg = json.load(open(path, encoding="utf-8"))
|
||||
for o in reg.get("obligations", []):
|
||||
if o.get("scope") == "out_of_scope":
|
||||
continue # Institutions-/Enforcement-Recht (Adressat != Hersteller) -> nicht im Join-Vertrag
|
||||
citation_units = [b.get("anchor", "") for b in o.get("legal_basis", []) if b.get("anchor")]
|
||||
keys.append({
|
||||
"obligation_id": o["id"],
|
||||
"regulation": reg.get("regulation", ""),
|
||||
"family": o.get("family", ""),
|
||||
"tier": o.get("tier", ""),
|
||||
"scope": o.get("scope", "in_scope"),
|
||||
"citation_units": citation_units,
|
||||
"source_role": o.get("source_role", ""),
|
||||
})
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
"""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.
|
||||
Prinzip (User 2026-07-01): **Adressat der Norm ⊥ Handlungspflicht des Herstellers.** Die Registry
|
||||
modelliert Hersteller-Pflichten. `scope`-Achse (Attribut-Enum, KEINE neue Objektklasse — freeze-safe):
|
||||
- in_scope : Norm adressiert direkt den Hersteller (default).
|
||||
- out_of_scope : reines Staats-/Durchsetzungs-/Institutions-Recht (Adressat != Hersteller,
|
||||
KEINE mittelbare Herstellerpflicht) — z.B. Sanktionen, Marktüberwachung.
|
||||
Präzedenz CSIRT/ENISA (CRA-Vuln-Cut). Aus join_keys gefiltert.
|
||||
- derived_obligation : Norm adressiert primär eine andere Rolle, erzeugt aber MITTELBAR eine
|
||||
Hersteller-Handlungspflicht — bleibt im Set (Wissen nicht verwerfen),
|
||||
ggf. `scope_split_candidate` (Aufspaltung Normadressat ↔ abgeleitete Pflicht).
|
||||
|
||||
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.
|
||||
False-Positive-Guard: Melde-AN-Behörde-Pflichten (applicability=domain:products…) sind IN-SCOPE
|
||||
(Adressat = Hersteller, Behörde nur Empfänger) — der Audit key't auf `applicability`, nicht auf
|
||||
Behörden-Nennung im Namen. Deterministisch, kein LLM. Audit FLAGGT; Reklassifizierung = User/Owner.
|
||||
"""
|
||||
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",
|
||||
@@ -29,39 +29,45 @@ NON_MANUFACTURER_DOMAINS = {
|
||||
|
||||
|
||||
def main() -> None:
|
||||
findings = []
|
||||
classified = [] # bereits per scope entschieden
|
||||
unclassified = [] # nicht-Hersteller-Adressat OHNE scope -> Review-Kandidat
|
||||
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'",
|
||||
})
|
||||
scope = o.get("scope")
|
||||
rec = {
|
||||
"file": f.split("/")[-1], "id": o.get("id") or o.get("obligation_id"),
|
||||
"name": o.get("name"), "tier": o.get("tier"), "applicability": appl,
|
||||
"scope": scope, "scope_reason": o.get("scope_reason"),
|
||||
}
|
||||
if scope in ("out_of_scope", "derived_obligation"):
|
||||
if o.get("scope_split_candidate"):
|
||||
rec["scope_split_note"] = o.get("scope_split_note")
|
||||
classified.append(rec)
|
||||
elif appl in NON_MANUFACTURER_DOMAINS:
|
||||
rec["reason"] = "Adressat Behörde/notifizierte Stelle/Mitgliedstaat, nicht klassifiziert"
|
||||
rec["recommendation"] = "out_of_scope (reine Durchsetzung) ODER derived_obligation (mittelbare Herstellerpflicht)"
|
||||
unclassified.append(rec)
|
||||
out = {
|
||||
"audit": "obligation scope audit (Adressat: Hersteller vs Behörde/notified_body)",
|
||||
"principle": "Registry modelliert Hersteller-Pflichten; Enforcement/Institutions-Recht = out_of_scope-Kandidat",
|
||||
"principle": "Adressat der Norm != Handlungspflicht des Herstellers; scope-Achse in_scope/out_of_scope/derived_obligation",
|
||||
"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",
|
||||
"classified": classified,
|
||||
"unclassified_candidates": unclassified,
|
||||
"decision_owner": "User/Registry-Owner — Audit FLAGGT nur; für jeden künftigen Cut mitlaufen lassen",
|
||||
}
|
||||
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']}")
|
||||
print(f"gescannt {total} | klassifiziert {len(classified)} | unklassifizierte Kandidaten {len(unclassified)}")
|
||||
for r in classified:
|
||||
extra = " [SPLIT-KANDIDAT]" if r.get("scope_split_note") else ""
|
||||
print(f" [{r['scope']}] {r['id']} appl={r['applicability']}{extra}")
|
||||
for r in unclassified:
|
||||
print(f" [UNKLASSIFIZIERT] {r['id']} appl={r['applicability']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user