From 6c619ecc42ac94aefa802c267b424c942de859e3 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 16 Jun 2026 07:44:13 +0200 Subject: [PATCH] =?UTF-8?q?feat(cra):=20kuratierte=20Ma=C3=9Fnahmen-Biblio?= =?UTF-8?q?thek=20=E2=80=94=20alle=2040=20CRA-Anforderungen=20belegt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - data/measures_curated.json: 24 deduplizierte, standard-gestützte Maßnahmen (9 bestehende M540-548 + 15 neue M600-614), Volltext + norm_refs + multi-reg covers. Deckt alle 40 CRA-AI-x (vorher nur 17). - cra_annex_i_data lädt die Bibliothek defensiv: MEASURES=Superset, MEASURE_DETAILS (Volltext), mapped_measures aus covers abgeleitet. Fallback = hartkodierte 9. - Mapper: open_measures tragen jetzt name+description+norm_refs (echte Volltexte). - useCRA: merge nutzt Backend-Volltexte statt Demo-Lookup. - Tests: Coverage (40/40) + Volltext im Assessment. Quelle: extern handkuratiert/recherchiert, hier dedupliziert + gemappt. Maschinen- VO/NIS2/IEC-Maßnahmen folgen, sobald deren Spine existiert. Co-Authored-By: Claude Opus 4.7 --- .../sdk/iace/[projectId]/cra/_hooks/useCRA.ts | 10 +- .../compliance/api/cra_annex_i_data.py | 30 +++++ .../compliance/data/measures_curated.json | 121 ++++++++++++++++++ .../compliance/services/cra_finding_mapper.py | 14 +- .../tests/test_cra_measures_library.py | 34 +++++ 5 files changed, 203 insertions(+), 6 deletions(-) create mode 100644 backend-compliance/compliance/data/measures_curated.json create mode 100644 backend-compliance/tests/test_cra_measures_library.py diff --git a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts index 2baeac6c..1bf24c77 100644 --- a/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts +++ b/admin-compliance/app/sdk/iace/[projectId]/cra/_hooks/useCRA.ts @@ -67,10 +67,12 @@ function merge(live: any): CRADemo { } as CRAFinding }) - const open_measures: Measure[] = (live.open_measures || []).map((om: any) => { - const detail = DEMO_SCENARIO.open_measures.find((d) => d.id === om.id) - return detail || { id: om.id, name: om.id, description: om.description || '', norm_refs: [] } - }) + const open_measures: Measure[] = (live.open_measures || []).map((om: any) => ({ + id: om.id, + name: om.name || DEMO_SCENARIO.open_measures.find((d) => d.id === om.id)?.name || om.id, + description: om.description || '', + norm_refs: om.norm_refs || [], + })) return { scenario: DEMO_SCENARIO.scenario, diff --git a/backend-compliance/compliance/api/cra_annex_i_data.py b/backend-compliance/compliance/api/cra_annex_i_data.py index 6e175924..8ff8f517 100644 --- a/backend-compliance/compliance/api/cra_annex_i_data.py +++ b/backend-compliance/compliance/api/cra_annex_i_data.py @@ -258,3 +258,33 @@ SEVERITY_WEIGHT = { "MEDIUM": 30, "LOW": 10, } + + +# ── Kuratierte Maßnahmen-Bibliothek (Source of Truth: data/measures_curated.json) ── +# Deduplizierte, standard-gestützte Maßnahmen. Beim Import: MEASURES (id->Name) wird +# Superset, MEASURE_DETAILS trägt den Volltext, und jede CRA-Anforderung bekommt +# mapped_measures aus dem `covers`-Feld. Defensiv: bei fehlender/kaputter JSON bleibt +# das hartkodierte 9er-Set + Mapping erhalten. +import json as _json +import os as _os + +MEASURE_DETAILS: dict = {} + +try: + _MEAS_PATH = _os.path.join(_os.path.dirname(__file__), "..", "data", "measures_curated.json") + with open(_MEAS_PATH, encoding="utf-8") as _fh: + _curated = _json.load(_fh) + MEASURE_DETAILS = {m["id"]: m for m in _curated if m.get("id")} + for _m in _curated: + if _m.get("id") and _m.get("name"): + MEASURES[_m["id"]] = _m["name"] + _cover_map: dict = {} + for _m in _curated: + for _c in _m.get("covers", []): + _cover_map.setdefault(_c, []).append(_m["id"]) + for _r in ANNEX_I_REQUIREMENTS: + _ids = _cover_map.get(_r["req_id"]) + if _ids: + _r["mapped_measures"] = _ids +except Exception: # noqa: BLE001 — Bibliothek optional; Fallback = hartkodiertes Set + pass diff --git a/backend-compliance/compliance/data/measures_curated.json b/backend-compliance/compliance/data/measures_curated.json new file mode 100644 index 00000000..a0143808 --- /dev/null +++ b/backend-compliance/compliance/data/measures_curated.json @@ -0,0 +1,121 @@ +[ + {"id": "M540", "name": "Software Bill of Materials (SBOM) erstellen und mitliefern", + "description": "Für Software, Firmware und relevante Drittkomponenten wird eine SBOM erstellt und gepflegt. Sie enthält mindestens Komponente, Version, Herkunft und Abhängigkeit und wird für das Schwachstellenmanagement genutzt.", + "sub_topic": "supply_chain", "evidence_type": "code", "covers": ["CRA-AI-23", "CRA-AI-33"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-218", "NTIA SBOM"], "alternatives": ["SPDX", "CycloneDX"]}, + + {"id": "M541", "name": "Signierte Software- und Firmware-Updates mit Rollback-Schutz", + "description": "Updates werden ausschließlich kryptographisch signiert ausgeliefert; die Steuerung prüft die Signatur vor Installation und verweigert unsignierte Pakete. Ein Rollback-Schutz verhindert das Downgraden auf nachweislich verwundbare Versionen.", + "sub_topic": "updates", "evidence_type": "code", "covers": ["CRA-AI-29", "CRA-AI-5", "CRA-AI-28"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "IEC 62443-4-1", "NIST SP 800-53: SI-7"], "alternatives": []}, + + {"id": "M542", "name": "Initiale Default-Passwörter beim ersten Start erzwungen ändern", + "description": "Die Maschine fordert beim ersten Hochfahren zwingend die Änderung aller werkseitigen Default-Passwörter (Bediener, Wartung, Admin). Default-Credentials werden nicht im Klartext veröffentlicht; eine Wiederherstellung auf Default ist nur über physischen Zugriff möglich.", + "sub_topic": "authentication", "evidence_type": "code", "covers": ["CRA-AI-8", "CRA-AI-9"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "ETSI EN 303 645", "NIST SP 800-53: IA-5"], "alternatives": []}, + + {"id": "M543", "name": "Coordinated-Vulnerability-Disclosure-Policy veröffentlichen und betreiben", + "description": "Der Hersteller stellt einen klaren Meldeweg für Schwachstellen bereit. Meldungen werden bestätigt, bewertet, priorisiert und bis zur Behebung nachvollziehbar bearbeitet.", + "sub_topic": "vuln_handling", "evidence_type": "document", "covers": ["CRA-AI-35"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "ISO/IEC 29147", "ISO/IEC 30111"], "alternatives": []}, + + {"id": "M544", "name": "Patch-SLA mit Severity-Tiers dokumentieren und Lifecycle-Support festlegen", + "description": "Für kritische, hohe, mittlere und niedrige Schwachstellen sind Bewertungs- und Behebungsfristen definiert. Supportzeitraum und Updateversorgung pro Produkt sind festgelegt; Abweichungen werden risikobasiert begründet und dokumentiert.", + "sub_topic": "vuln_handling", "evidence_type": "process", "covers": ["CRA-AI-31", "CRA-AI-34", "CRA-AI-39"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "ISO/IEC 30111", "NIST SP 800-53: SI-2"], "alternatives": []}, + + {"id": "M545", "name": "Secure-by-Default-Konfiguration und Cybersecurity-Hardening-Guide beilegen", + "description": "Das Produkt wird mit sicheren Standardeinstellungen ausgeliefert (keine offenen Debug-Schnittstellen, keine unnötigen Dienste). Die Doku enthält einen gepflegten Hardening-Guide: Netzwerk-Segmentierung, deaktivierbare Dienste, sichere Konfiguration der Komponenten und Benutzerverwaltung.", + "sub_topic": "secure_design", "evidence_type": "hybrid", "covers": ["CRA-AI-1", "CRA-AI-40"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I/II", "IEC 62443-3-3", "ETSI EN 303 645"], "alternatives": []}, + + {"id": "M546", "name": "Incident-Meldeprozess an ENISA / nationale CSIRT definieren", + "description": "Für aktiv ausgenutzte Schwachstellen und erhebliche Sicherheitsvorfälle ist ein Meldeprozess mit Rollen, Fristen (24h/72h), Freigaben und Kommunikationswegen definiert. Zuständigkeiten für ENISA, CSIRT, Kundeninformation und interne Eskalation sind festgelegt.", + "sub_topic": "vuln_handling", "evidence_type": "process", "covers": ["CRA-AI-36", "CRA-AI-37", "CRA-AI-38"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "ISO/IEC 27035"], "alternatives": []}, + + {"id": "M547", "name": "Updates über authentisierten Kanal mit Integritätsprüfung", + "description": "Der Update-Kanal ist manipulationssicher: TLS 1.3 mit Zertifikatsprüfung bei Online-Updates, Hash-Prüfung der Update-Datei vor dem Anwenden. Der Update-Prozess ist atomar — bei Abbruch bleibt die alte Version lauffähig.", + "sub_topic": "updates", "evidence_type": "code", "covers": ["CRA-AI-30", "CRA-AI-28", "CRA-AI-6"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "IEC 62443-4-2", "NIST SP 800-53: SI-2"], "alternatives": []}, + + {"id": "M548", "name": "Sicherheitsbewertung / Penetrationstest vor Inverkehrbringen durchführen", + "description": "Vor Inverkehrbringen werden Architektur, Konfiguration, Schnittstellen, Update-Prozess, Authentisierung und bekannte Schwachstellen bewertet. Je nach Risiko erfolgt zusätzlich ein Penetrationstest oder eine unabhängige technische Prüfung.", + "sub_topic": "ssdlc", "evidence_type": "hybrid", "covers": ["CRA-AI-20"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "IEC 62443-4-1", "OWASP ASVS"], "alternatives": ["Penetrationstest", "Security-Review", "Red-Team-Test"]}, + + {"id": "M600", "name": "Nicht benötigte Dienste, Ports und Protokolle standardmäßig deaktivieren", + "description": "Auf SPS, HMI, IPC, Gateway und Fernwartung werden nur die für den Einsatzzweck erforderlichen Dienste aktiviert. Nicht benötigte Ports, Konten, Webdienste und Diagnose-Schnittstellen bleiben deaktiviert; Aktivierungen werden dokumentiert.", + "sub_topic": "secure_design", "evidence_type": "code", "covers": ["CRA-AI-2"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "IEC 62443-3-3", "NIST SP 800-53: CM-7", "ETSI EN 303 645"], "alternatives": []}, + + {"id": "M601", "name": "Sichere Systemarchitektur mit Netzwerkzonen und Conduits umsetzen", + "description": "Produktionsnetz, Fernwartung, Office-IT, Cloud-Anbindung und Safety-Funktionen werden logisch oder physisch getrennt. Sicherheitskritische Funktionen sind nicht direkt aus untrusted Segmenten erreichbar; Kommunikationspfade sind explizit erlaubt und dokumentiert.", + "sub_topic": "secure_design", "evidence_type": "hybrid", "covers": ["CRA-AI-3"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "IEC 62443-3-2", "IEC 62443-3-3", "NIST SP 800-53: SC-7"], "alternatives": []}, + + {"id": "M602", "name": "Least-Privilege und rollenbasierte Autorisierung erzwingen", + "description": "Benutzer, Dienste und Komponenten erhalten nur die für ihre Aufgabe erforderlichen Rechte. Bediener, Instandhaltung, Hersteller-Service und Admin haben getrennte Rollen; Schreibrechte auf sicherheitsrelevante Parameter sind beschränkt und werden regelmäßig überprüft.", + "sub_topic": "authentication", "evidence_type": "code", "covers": ["CRA-AI-4", "CRA-AI-12"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: AC-6", "NIST SP 800-53: AC-2", "IEC 62443-3-3"], "alternatives": []}, + + {"id": "M603", "name": "Starke Authentifizierung für privilegierte Zugänge einsetzen", + "description": "Administrations-, Wartungs- und Fernwartungszugänge werden durch starke Authentisierung geschützt. Für besonders kritische Zugänge wird ein zweiter Faktor, ein Client-Zertifikat oder ein Hardware-Token verlangt.", + "sub_topic": "authentication", "evidence_type": "code", "covers": ["CRA-AI-7"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: IA-2", "OWASP ASVS V2", "ETSI EN 303 645"], "alternatives": ["Client-Zertifikat", "Hardware-Token", "TOTP"]}, + + {"id": "M604", "name": "Credential- und Schlüsselmanagement etablieren", + "description": "Passwörter, API-Keys, Zertifikate und private Schlüssel werden nicht im Klartext oder Quellcode gespeichert und nicht über Kunden/Maschinen wiederverwendet. Schlüssel werden sicher erzeugt, getrennt verwahrt, rotiert und bei Kompromittierung ersetzt.", + "sub_topic": "authentication", "evidence_type": "hybrid", "covers": ["CRA-AI-9", "CRA-AI-16"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: IA-5", "NIST SP 800-57", "IEC 62443-4-2"], "alternatives": ["HSM", "Secure Element", "TPM"]}, + + {"id": "M605", "name": "Sitzungen automatisch absichern und beenden", + "description": "Authentisierte Sitzungen besitzen eindeutige Session-IDs, begrenzte Lebensdauer und automatische Sperrung bei Inaktivität. Logout, Rollenwechsel oder Passwortänderung machen bestehende Sitzungen ungültig.", + "sub_topic": "authentication", "evidence_type": "code", "covers": ["CRA-AI-10"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "OWASP ASVS V3", "NIST SP 800-53: AC-12"], "alternatives": []}, + + {"id": "M606", "name": "Brute-Force-Angriffe begrenzen und Anmeldungen protokollieren", + "description": "Wiederholte Fehlanmeldungen führen zu Verzögerungen, temporären Sperren oder zusätzlichen Prüfungen, ohne reguläre Benutzer unangemessen einzuschränken. Anmeldeversuche an HMI, Web, API, Fernwartung und Servicekonten werden protokolliert.", + "sub_topic": "authentication", "evidence_type": "code", "covers": ["CRA-AI-11", "CRA-AI-24"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: AC-7", "OWASP ASVS V2"], "alternatives": []}, + + {"id": "M607", "name": "Vertrauliche Daten im Gerät und in Backups verschlüsseln", + "description": "Personenbezogene, sicherheitsrelevante und betriebskritische Daten werden auf Datenträgern, Speichermedien und Backups kryptographisch geschützt. Schlüsselmaterial wird getrennt von den Daten verwaltet.", + "sub_topic": "cryptography", "evidence_type": "code", "covers": ["CRA-AI-13", "CRA-AI-14"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: SC-28", "NIST SP 800-57", "IEC 62443-3-3"], "alternatives": []}, + + {"id": "M608", "name": "Externe Kommunikation kryptographisch absichern", + "description": "Fernwartung, Cloud-Anbindung, API-Verkehr, Telemetrie und Update-Downloads erfolgen über authentisierte und verschlüsselte Verbindungen. Zertifikate werden geprüft, unsichere Protokolle und schwache Cipher sind deaktiviert.", + "sub_topic": "cryptography", "evidence_type": "code", "covers": ["CRA-AI-15"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-52", "IEC 62443-4-2"], "alternatives": []}, + + {"id": "M609", "name": "Datenminimierung für Erhebung, Telemetrie und Logs umsetzen", + "description": "Personenbezogene, betriebliche und diagnostische Daten werden auf das notwendige Maß beschränkt. Aufbewahrungsfristen und Löschprozesse sind definiert; nicht mehr benötigte Daten werden entfernt, pseudonymisiert oder anonymisiert.", + "sub_topic": "secure_design", "evidence_type": "process", "covers": ["CRA-AI-17"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "ISO/IEC 27002", "NIST Privacy Framework"], "alternatives": []}, + + {"id": "M610", "name": "Secure Software Development Lifecycle mit Code Reviews betreiben", + "description": "Bedrohungsanalyse, Sicherheitsanforderungen, sichere Implementierung, Schwachstellenprüfungen und Freigaben sind feste Bestandteile der Entwicklung. Änderungen an Authentisierung, Kryptographie, Updates und Safety-Parametern werden vor Freigabe systematisch reviewt und dokumentiert.", + "sub_topic": "ssdlc", "evidence_type": "process", "covers": ["CRA-AI-18", "CRA-AI-19"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-218", "IEC 62443-4-1", "OWASP SAMM"], "alternatives": []}, + + {"id": "M611", "name": "Schwachstellen identifizieren und Abhängigkeiten kontinuierlich überwachen", + "description": "Eigenentwicklungen, Bibliotheken, Container und Firmware werden fortlaufend und in CI/CD gegen Schwachstellendatenbanken geprüft. Treffer werden bewertet, priorisiert und ins Patch-Management überführt; kritische Treffer erzeugen Tickets oder blockieren Releases.", + "sub_topic": "supply_chain", "evidence_type": "code", "covers": ["CRA-AI-22", "CRA-AI-32"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-218", "OWASP Dependency-Check", "SLSA"], "alternatives": []}, + + {"id": "M612", "name": "Supply-Chain-Security: Drittkomponenten vor Integration bewerten", + "description": "Open-Source-Komponenten, Lieferantenbibliotheken, Container und Firmware-Bausteine werden vor Einsatz auf Herkunft, Wartungsstatus, Lizenz, Schwachstellen und Integrität geprüft. Unsichere oder nicht nachvollziehbare Komponenten werden nicht freigegeben.", + "sub_topic": "supply_chain", "evidence_type": "process", "covers": ["CRA-AI-21"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-218", "IEC 62443-4-1"], "alternatives": []}, + + {"id": "M613", "name": "Sicherheitsereignisse protokollieren, überwachen und auf Anomalien prüfen", + "description": "Anmeldungen, Konfigurations- und Parameteränderungen, Fernwartung und Update-Vorgänge werden nachvollziehbar protokolliert. Ereignisse werden überwacht und auf Auffälligkeiten ausgewertet; kritische Ereignisse lösen definierte Reaktionen aus.", + "sub_topic": "logging", "evidence_type": "process", "covers": ["CRA-AI-24", "CRA-AI-25", "CRA-AI-26"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: AU-6", "IEC 62443-3-3"], "alternatives": []}, + + {"id": "M614", "name": "Log-Integrität, Aufbewahrung und Vorfalldokumentation sicherstellen", + "description": "Sicherheitsprotokolle werden gegen nachträgliche Änderung geschützt und für definierte Zeiträume aufbewahrt. Sicherheitsvorfälle, Ursachenanalysen und Maßnahmen werden nachvollziehbar dokumentiert; Erkenntnisse fließen in Produkt- und Prozessverbesserung.", + "sub_topic": "logging", "evidence_type": "document", "covers": ["CRA-AI-27", "CRA-AI-40"], + "norm_refs": ["Verordnung (EU) 2024/2847 (CRA), Anhang I", "NIST SP 800-53: AU-9", "ISO/IEC 27035"], "alternatives": []} +] diff --git a/backend-compliance/compliance/services/cra_finding_mapper.py b/backend-compliance/compliance/services/cra_finding_mapper.py index 027b20d2..f3105f5a 100644 --- a/backend-compliance/compliance/services/cra_finding_mapper.py +++ b/backend-compliance/compliance/services/cra_finding_mapper.py @@ -13,11 +13,21 @@ compliance.api.cra_annex_i_data (pure data, no FastAPI dependency). from dataclasses import dataclass, field, asdict from typing import Optional -from compliance.api.cra_annex_i_data import ANNEX_I_REQUIREMENTS, MEASURES, DEADLINES +from compliance.api.cra_annex_i_data import ANNEX_I_REQUIREMENTS, MEASURES, MEASURE_DETAILS, DEADLINES from compliance.services.cra_security_crosswalk import security_refs_for from compliance.services.cra_prioritizer import prioritize, OBJECTIVES from compliance.services.cra_safety_bridge import build_cross_links +def _measure_obj(mid: str) -> dict: + """Full curated measure (name/description/norm_refs) for the assessment output, + falling back to just the name when only the thin id->name map has it.""" + d = MEASURE_DETAILS.get(mid) + if d: + return {"id": mid, "name": d.get("name", ""), "description": d.get("description", ""), + "norm_refs": d.get("norm_refs", [])} + return {"id": mid, "name": MEASURES.get(mid, ""), "description": MEASURES.get(mid, ""), "norm_refs": []} + + _REQ_INDEX = {r["req_id"]: r for r in ANNEX_I_REQUIREMENTS} _SEV_ORDER = {"LOW": 1, "MEDIUM": 2, "HIGH": 3, "CRITICAL": 4} _SEV_BY_RANK = {v: k for k, v in _SEV_ORDER.items()} @@ -249,7 +259,7 @@ def assess_findings(findings: list, weights=None, safety_functions=None) -> CRAA mapped=mapped, by_risk=by_risk, requirements_touched=sorted(reqs_touched), - open_measures=[{"id": mid, "description": MEASURES.get(mid, "")} for mid in measure_ids], + open_measures=[_measure_obj(mid) for mid in measure_ids], unmapped_findings=unmapped, coverage_pct=round(100.0 * covered / total, 1) if total else 0.0, quick_wins=[m.finding_id for m in mapped if m.quick_win], diff --git a/backend-compliance/tests/test_cra_measures_library.py b/backend-compliance/tests/test_cra_measures_library.py new file mode 100644 index 00000000..94907717 --- /dev/null +++ b/backend-compliance/tests/test_cra_measures_library.py @@ -0,0 +1,34 @@ +"""The curated measures library (data/measures_curated.json) must load, cover all +40 CRA Annex I requirements, and surface full text (name + norm_refs) in the +assessment output.""" +from compliance.api.cra_annex_i_data import ( + ANNEX_I_REQUIREMENTS, + MEASURES, + MEASURE_DETAILS, +) +from compliance.services.cra_finding_mapper import assess_findings_payload + + +def test_library_loaded(): + assert len(MEASURE_DETAILS) >= 24 + # MEASURES (id->name) is the superset incl. the original M540-M548. + assert "M540" in MEASURES and "M600" in MEASURES + + +def test_all_40_requirements_have_a_measure(): + uncovered = [r["req_id"] for r in ANNEX_I_REQUIREMENTS if not r.get("mapped_measures")] + assert uncovered == [], f"uncovered: {uncovered}" + + +def test_mapped_measures_resolve_to_known_ids(): + for r in ANNEX_I_REQUIREMENTS: + for mid in r.get("mapped_measures", []): + assert mid in MEASURES, f"{r['req_id']} -> unknown measure {mid}" + + +def test_assessment_open_measures_carry_full_text(): + res = assess_findings_payload({"findings": [{"id": "x", "cwe": "CWE-79", "severity": "high"}]}) + assert res["open_measures"], "expected at least one measure" + om = res["open_measures"][0] + assert om.get("name") + assert "norm_refs" in om