Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 623d80b6c8 | |||
| 8609b696c9 | |||
| 207fc9cb56 | |||
| fdaf547b06 | |||
| fa536f9714 | |||
| cba066f49b | |||
| 75f7bd8de4 | |||
| f85fff4398 | |||
| 3bcffaf52c | |||
| 3a19affb67 | |||
| 2b985ad526 | |||
| 4e761c1363 | |||
| 9aef5ecf6c | |||
| f6c5f4e0a9 | |||
| c72fd3eb5a | |||
| b0435f9885 | |||
| 2341bda621 | |||
| 4634cc09d0 | |||
| d4df1e01df | |||
| ed31fdc0df | |||
| 5412bf0ba3 | |||
| 8a9d5e7c4d | |||
| 01956ee690 | |||
| e46e74ddbb | |||
| 63d65af41b | |||
| 8937f105ea | |||
| 1584b8fb2f |
@@ -33,6 +33,14 @@ COPY migrations/ ./migrations/
|
||||
# Copy policy files (YAML rules)
|
||||
COPY policies/ ./policies/
|
||||
|
||||
# Copy Compliance Execution Graph data (file-backed: Registry join-key copy + accepted control
|
||||
# mappings + evidence requirements) consumed by GET /sdk/v1/compliance/obligation-status.
|
||||
# data/obligations/obligation_join_keys.json is a synced copy of the repo-root Registry contract
|
||||
# (the Obligation Registry owns the canonical file) — re-sync it when the Registry grows.
|
||||
COPY data/control_mappings/ ./data/control_mappings/
|
||||
COPY data/evidence_requirements/ ./data/evidence_requirements/
|
||||
COPY data/obligations/ ./data/obligations/
|
||||
|
||||
# Create non-root user
|
||||
RUN adduser -D -u 1000 appuser
|
||||
USER appuser
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Control-Mapping: CRA Annex I -> NIST SP 800-53 Rev. 5. Eine Zeile = ein Mapping (Schema: ControlMapping).
|
||||
// Reviewt 2026-06-25 (benjamin): 3 accepted, mapping_type=primary_implementation (kanonische Primaer-Control je Anforderung).
|
||||
// Heimat der OWASP-Rejects (2)(e)/(2)(l)/(2)(i): dort war OWASP nicht der Zielstandard ("Mapping ueber NIST/BSI erforderlich").
|
||||
// related-Controls (SC-3(3), RA-5, AC-6, SI-16, ...) folgen separat als mapping_type=supports — hier nur der kanonische Einstieg.
|
||||
// obligation_id (Registry-Handoff #4 adoptiert, #6 auf CORE re-pointet 2026-06-26): SI-7->software_integrity_protection (CORE (2)(f)), SI-2->provide_security_updates, CM-7->attack_surface_minimization (CORE (2)(j)). Join exakt. Die domaenen-scoped IDs (signed_update_integrity, remote_access_attack_surface_min) bleiben gueltige Obligations und zeigen per specializes->CORE auf diese Ziele.
|
||||
{"source_norm": "CRA Annex I Part I (2)(e) — Integritaet", "source_role": "operational_requirement", "target_framework": "NIST SP 800-53", "target_control": "SI-7", "mapping_type": "primary_implementation", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "NIST SI-7 = Software, Firmware, and Information Integrity — kanonische Integritaetskontrolle (Signaturpruefung, Manipulationserkennung).", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Primaere Implementierung der CRA-Integritaetsanforderung; OWASP war hier kein passender Treffer. Related (spaeter, supports): SA-10, CM-14.", "version": "2026-06-25", "obligation_id": "software_integrity_protection"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates", "source_role": "operational_requirement", "target_framework": "NIST SP 800-53", "target_control": "SI-2", "mapping_type": "primary_implementation", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "NIST SI-2 = Flaw Remediation — kanonische Update-/Patch-Kontrolle.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Primaere Implementierung der CRA-Update-Anforderung. Related (spaeter, supports): RA-5, CM-3, SA-11.", "version": "2026-06-25", "obligation_id": "provide_security_updates"}
|
||||
{"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren", "source_role": "operational_requirement", "target_framework": "NIST SP 800-53", "target_control": "CM-7", "mapping_type": "primary_implementation", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "NIST CM-7 = Least Functionality — Deaktivierung nicht benoetigter Ports/Dienste/Funktionen.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "CM-7 als Primaer-Control fuer Angriffsflaeche (nicht SC-3(3)). Related (spaeter, supports): SC-3(3), AC-6, SI-16.", "version": "2026-06-25", "obligation_id": "attack_surface_minimization"}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Evidence-Requirements je NIST-SP-800-53-Control (Schema: EvidenceRequirement). Eine Zeile = eine geforderte Evidenz.
|
||||
// WICHTIG: evidence_type ist FRAMEWORK-AGNOSTISCH (geteilter Katalog config_export/test_report/repo_scan/sbom/...) —
|
||||
// dieselben Typen tragen CRA, NIST, ISO 27001, IEC 62443, BSI. (framework, control) ist nur der Verweis, nicht der Typ.
|
||||
// Stand 2026-06-25, Basis: die 3 accepted CRA->NIST primary_implementation-Mappings (SI-7 Integritaet, SI-2 Updates, CM-7 Angriffsflaeche).
|
||||
{"framework": "NIST SP 800-53", "control": "SI-7", "evidence_type": "sbom", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "SBOM weist die Integritaet/Herkunft der Software-Bestandteile nach (bekannte, unmanipulierte Komponenten).", "version": "2026-06-25"}
|
||||
{"framework": "NIST SP 800-53", "control": "SI-7", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Secure-Boot-/Code-Signing-Konfiguration als Nachweis der Integritaetspruefung.", "version": "2026-06-25"}
|
||||
{"framework": "NIST SP 800-53", "control": "SI-2", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Konfiguration des sicheren Update-/Patch-Mechanismus (signierte/automatische Updates) als technischer Nachweis.", "version": "2026-06-25"}
|
||||
{"framework": "NIST SP 800-53", "control": "SI-2", "evidence_type": "test_report", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "Update-/Patch-Verifikationstest (CI) belegt, dass Sicherheitsupdates greifen.", "version": "2026-06-25"}
|
||||
{"framework": "NIST SP 800-53", "control": "CM-7", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Konfiguration deaktivierter Ports/Dienste/Funktionen als Nachweis minimierter Angriffsflaeche.", "version": "2026-06-25"}
|
||||
{"framework": "NIST SP 800-53", "control": "CM-7", "evidence_type": "repo_scan", "evidence_source": "scanner", "freshness_requirement": "per_release", "required": true, "rationale": "Angriffsflaechen-Scan (offene Ports/Dienste) als Nachweis tatsaechlich minimierter Angriffsflaeche.", "version": "2026-06-25"}
|
||||
@@ -0,0 +1,846 @@
|
||||
{
|
||||
"schema_version": "obligation_join_keys_v1",
|
||||
"contract": "obligation_id ist der stabile Join-Key. Legal Knowledge Graph haengt citation_spans an obligation_id; Compliance Execution Graph mappt control_mapping.source_norm -> obligation_id. Interim-Bruecke = citation_units. obligation_id NIE neu vergeben (re-link).",
|
||||
"count": 95,
|
||||
"obligation_ids": [
|
||||
{
|
||||
"obligation_id": "sbom_creation",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_dependency_coverage",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 3(36) i.V.m. Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_format_standard",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_maintenance_update",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_completeness_verification",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_tooling_automation",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_access_provision",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_authority_provision",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 31 / Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_confidentiality",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 31(4)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_supply_chain_contracts",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "sbom_technical_documentation",
|
||||
"regulation": "CRA",
|
||||
"family": "sbom",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 31 i.V.m. Annex VII"
|
||||
],
|
||||
"source_role": "EVIDENCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_identification_inventory",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_assessment_prioritization",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_remediation_patching",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (2) & (8)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_handling_process",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Article 13(8) & Annex VII"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "coordinated_vulnerability_disclosure",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (5)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "exploited_vuln_reporting_authorities",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Article 14 & Article 16"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "vuln_info_dissemination_users",
|
||||
"regulation": "CRA",
|
||||
"family": "vuln",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part II (4) & (6)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "attack_surface_minimization",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(j)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "software_integrity_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "user_authentication_required",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "authentication_policy_documented",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "auth_exceptions_documented",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "mfa_required",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "step_up_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "privileged_op_reauth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "strong_crypto_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(e)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "credential_lifecycle_management",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "credential_confidentiality_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(e)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "password_policy",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "no_default_credentials",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(a)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "account_lockout_failed_attempts",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "server_side_validation",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "session_binding_management",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "reauth_after_inactivity",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "token_validation_lifecycle",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "mutual_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "revocation_check",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "encrypted_auth_channel",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(e)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "tls_certificate_auth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "service_to_service_auth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "auth_key_management",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "biometric_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "federated_auth_assertions",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "separate_authn_authz",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "supplier_access_auth",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "personal_admin_accounts",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "firmware_software_authentication",
|
||||
"regulation": "CRA",
|
||||
"family": "authentication",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "event_logging_security_events",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "access_control_event_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "audit_trail_admin_actions",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_integrity_immutability",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_access_control_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_retention_archival",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "centralized_log_management",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_monitoring_alerting",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(k)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_data_minimization_privacy",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_format_standardization",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_timestamp_synchronization",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_availability_resilience",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_thread_safety_correctness",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_library_supply_chain",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_config_management",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "logging_governance_roles",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "incident_response_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "log_transmission_security",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "network_traffic_logging",
|
||||
"regulation": "CRA",
|
||||
"family": "logging",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_control_least_privilege",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_confidentiality_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(b)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_session_management",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_mfa",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_encryption",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "reject_insecure_remote_protocols",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_logging_audit",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(g)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_user_validation_ot",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_training",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_architecture_design",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_attack_surface_min",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(a)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_vuln_patch_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_threat_detection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_maintenance_governance",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "temporary_remote_access_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_data_export_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "component_remote_interface_security",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_fallback_concept",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "provide_security_updates",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)",
|
||||
"Art. 13"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "support_period_maintenance",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 13(8)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "signed_update_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "trusted_update_source",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_testing_validation",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_rollback",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "automatic_updates_optout",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_risk_assessment",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "secure_modification_control",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/breakpilot/ai-compliance-sdk/internal/ucca"
|
||||
)
|
||||
|
||||
// ComplianceGraphHandlers serves the read-only Compliance Execution Graph
|
||||
// (Regulation -> Obligation -> Control -> Evidence) over the file-backed bridge artifacts.
|
||||
// It is intentionally SEPARATE from the DB-backed ObligationsHandlers: this is the curated
|
||||
// cross-session graph (Registry join keys + accepted control mappings + evidence requirements),
|
||||
// loaded once at startup. Fail-closed: if the graph could not load, every request answers 503.
|
||||
type ComplianceGraphHandlers struct {
|
||||
joins *ucca.ObligationJoinKeys
|
||||
mappings *ucca.ControlMappingSet
|
||||
evidence *ucca.EvidenceRequirementSet
|
||||
loadErr error
|
||||
}
|
||||
|
||||
// NewComplianceGraphHandlers loads the graph once. Construction never fails; a load error is
|
||||
// retained and surfaced as 503 per request (matches the codebase's load-warn-continue startup).
|
||||
func NewComplianceGraphHandlers() *ComplianceGraphHandlers {
|
||||
joins, mappings, evidence, err := ucca.LoadComplianceGraph()
|
||||
return &ComplianceGraphHandlers{joins: joins, mappings: mappings, evidence: evidence, loadErr: err}
|
||||
}
|
||||
|
||||
// LoadError exposes a startup load failure so the wiring can log a warning.
|
||||
func (h *ComplianceGraphHandlers) LoadError() error { return h.loadErr }
|
||||
|
||||
// RegisterRoutes mounts the compliance-graph routes under /compliance.
|
||||
func (h *ComplianceGraphHandlers) RegisterRoutes(r *gin.RouterGroup) {
|
||||
g := r.Group("/compliance")
|
||||
g.GET("/obligation-status", h.ObligationStatus)
|
||||
}
|
||||
|
||||
type cgControlDTO struct {
|
||||
Framework string `json:"framework"`
|
||||
Control string `json:"control"`
|
||||
MappingType string `json:"mapping_type"`
|
||||
EvidenceRequired []string `json:"evidence_required"`
|
||||
EvidenceStatus string `json:"evidence_status"` // missing | partial | present | none_required
|
||||
}
|
||||
|
||||
type cgStatusResponse struct {
|
||||
ObligationID string `json:"obligation_id"`
|
||||
OverallStatus string `json:"overall_status"` // unknown_obligation | unmapped | not_assessed | open | met
|
||||
LegalBasis []string `json:"legal_basis,omitempty"`
|
||||
CitationSpans string `json:"citation_spans"` // "pending" until the Legal-KG attaches spans
|
||||
Controls []cgControlDTO `json:"controls"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// ObligationStatus answers GET /sdk/v1/compliance/obligation-status?obligation_id=...
|
||||
//
|
||||
// It NEVER asserts fulfillment automatically. With no evidence collection wired (MVP), a mapped
|
||||
// obligation is "not_assessed" and every required evidence is "missing" — the honest picture is
|
||||
// "required vs present evidence", not "a document exists". Fail-closed otherwise:
|
||||
// - no obligation_id -> 400
|
||||
// - graph not loaded -> 503
|
||||
// - id not in the Registry -> 200 overall_status=unknown_obligation
|
||||
// - mapped but no control yet -> 200 overall_status=unmapped
|
||||
func (h *ComplianceGraphHandlers) ObligationStatus(c *gin.Context) {
|
||||
if h.loadErr != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "compliance graph unavailable", "detail": h.loadErr.Error()})
|
||||
return
|
||||
}
|
||||
obID := strings.TrimSpace(c.Query("obligation_id"))
|
||||
if obID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "obligation_id query parameter required"})
|
||||
return
|
||||
}
|
||||
resp := cgStatusResponse{ObligationID: obID, CitationSpans: "pending", Controls: []cgControlDTO{}}
|
||||
|
||||
if h.joins.FindObligation(obID) == nil {
|
||||
resp.OverallStatus = "unknown_obligation"
|
||||
resp.Note = "obligation_id not in the Registry join-key contract"
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// MVP: hasEvidence=nil -> no collection wired -> all required evidence counts as missing.
|
||||
st := ucca.AssessObligationStatus(h.joins, h.mappings, h.evidence, obID, nil)
|
||||
resp.LegalBasis = st.LegalBasis
|
||||
|
||||
if len(st.Controls) == 0 {
|
||||
resp.OverallStatus = "unmapped"
|
||||
resp.Note = "no accepted control maps to this obligation yet"
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
|
||||
for _, cs := range st.Controls {
|
||||
types := make([]string, 0, len(cs.RequiredEvidence))
|
||||
for _, e := range cs.RequiredEvidence {
|
||||
types = append(types, e.EvidenceType)
|
||||
}
|
||||
resp.Controls = append(resp.Controls, cgControlDTO{
|
||||
Framework: cs.Framework,
|
||||
Control: cs.Control,
|
||||
MappingType: cs.MappingType,
|
||||
EvidenceRequired: types,
|
||||
EvidenceStatus: cgEvidenceStatus(len(cs.RequiredEvidence), len(cs.MissingEvidence)),
|
||||
})
|
||||
}
|
||||
// No fulfillment claim without real evidence collection.
|
||||
resp.OverallStatus = "not_assessed"
|
||||
resp.Note = "evidence collection not wired (MVP) — fulfillment not asserted"
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func cgEvidenceStatus(required, missing int) string {
|
||||
switch {
|
||||
case required == 0:
|
||||
return "none_required"
|
||||
case missing == 0:
|
||||
return "present"
|
||||
case missing == required:
|
||||
return "missing"
|
||||
default:
|
||||
return "partial"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func newComplianceGraphTestRouter(t *testing.T) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
h := NewComplianceGraphHandlers()
|
||||
if err := h.LoadError(); err != nil {
|
||||
t.Fatalf("compliance graph failed to load (candidate paths): %v", err)
|
||||
}
|
||||
r := gin.New()
|
||||
h.RegisterRoutes(r.Group("/sdk/v1"))
|
||||
return r
|
||||
}
|
||||
|
||||
func getObligationStatus(t *testing.T, r *gin.Engine, query string) (int, cgStatusResponse) {
|
||||
t.Helper()
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest(http.MethodGet, "/sdk/v1/compliance/obligation-status"+query, nil)
|
||||
r.ServeHTTP(w, req)
|
||||
var resp cgStatusResponse
|
||||
if w.Code == http.StatusOK {
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("decode body %q: %v", w.Body.String(), err)
|
||||
}
|
||||
}
|
||||
return w.Code, resp
|
||||
}
|
||||
|
||||
func TestObligationStatus(t *testing.T) {
|
||||
r := newComplianceGraphTestRouter(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
query string
|
||||
wantHTTP int
|
||||
wantOverall string
|
||||
wantControls bool // expect >=1 control
|
||||
}{
|
||||
{"missing param -> 400", "", http.StatusBadRequest, "", false},
|
||||
{"unknown id -> unknown_obligation", "?obligation_id=does_not_exist", http.StatusOK, "unknown_obligation", false},
|
||||
{"mapped (OWASP V6) -> not_assessed", "?obligation_id=user_authentication_required", http.StatusOK, "not_assessed", true},
|
||||
{"NIST adopted (SI-2) -> not_assessed", "?obligation_id=provide_security_updates", http.StatusOK, "not_assessed", true},
|
||||
{"CORE attack_surface_minimization -> CM-7", "?obligation_id=attack_surface_minimization", http.StatusOK, "not_assessed", true},
|
||||
{"CORE software_integrity_protection -> SI-7", "?obligation_id=software_integrity_protection", http.StatusOK, "not_assessed", true},
|
||||
{"in registry, no control -> unmapped", "?obligation_id=sbom_creation", http.StatusOK, "unmapped", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
code, resp := getObligationStatus(t, r, tt.query)
|
||||
if code != tt.wantHTTP {
|
||||
t.Fatalf("http %d, want %d", code, tt.wantHTTP)
|
||||
}
|
||||
if tt.wantHTTP != http.StatusOK {
|
||||
return
|
||||
}
|
||||
if resp.OverallStatus != tt.wantOverall {
|
||||
t.Errorf("overall_status=%q, want %q", resp.OverallStatus, tt.wantOverall)
|
||||
}
|
||||
if tt.wantControls && len(resp.Controls) == 0 {
|
||||
t.Error("expected >=1 control")
|
||||
}
|
||||
if !tt.wantControls && len(resp.Controls) != 0 {
|
||||
t.Errorf("expected 0 controls, got %d", len(resp.Controls))
|
||||
}
|
||||
if resp.CitationSpans != "pending" {
|
||||
t.Errorf("citation_spans=%q, want pending", resp.CitationSpans)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The MVP must NEVER auto-assert fulfillment: with no evidence collection wired, every required
|
||||
// evidence is "missing" and the overall status stays "not_assessed".
|
||||
func TestObligationStatus_NoFulfillmentClaim(t *testing.T) {
|
||||
r := newComplianceGraphTestRouter(t)
|
||||
code, resp := getObligationStatus(t, r, "?obligation_id=user_authentication_required")
|
||||
if code != http.StatusOK {
|
||||
t.Fatalf("http %d", code)
|
||||
}
|
||||
if resp.OverallStatus == "met" || resp.OverallStatus == "erfuellt" {
|
||||
t.Fatalf("MVP must not assert fulfillment, got overall_status=%q", resp.OverallStatus)
|
||||
}
|
||||
for _, ctl := range resp.Controls {
|
||||
if len(ctl.EvidenceRequired) > 0 && ctl.EvidenceStatus != "missing" {
|
||||
t.Errorf("control %s/%s evidence_status=%q, want missing (no collection wired)", ctl.Framework, ctl.Control, ctl.EvidenceStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pin the curated evidence_required set per NIST obligation. A required:false row silently
|
||||
// drops from evidence_required, which the table test above (control-count only) would miss.
|
||||
func TestObligationStatus_NISTEvidenceTypes(t *testing.T) {
|
||||
r := newComplianceGraphTestRouter(t)
|
||||
want := map[string][]string{
|
||||
"attack_surface_minimization": {"config_export", "repo_scan"},
|
||||
"software_integrity_protection": {"sbom", "config_export"},
|
||||
"provide_security_updates": {"config_export", "test_report"},
|
||||
}
|
||||
for ob, exp := range want {
|
||||
_, resp := getObligationStatus(t, r, "?obligation_id="+ob)
|
||||
if len(resp.Controls) != 1 {
|
||||
t.Fatalf("%s: want 1 control, got %d", ob, len(resp.Controls))
|
||||
}
|
||||
if got := resp.Controls[0].EvidenceRequired; !sameStringSet(got, exp) {
|
||||
t.Errorf("%s evidence_required = %v, want %v", ob, got, exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sameStringSet(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
m := make(map[string]bool, len(a))
|
||||
for _, x := range a {
|
||||
m[x] = true
|
||||
}
|
||||
for _, x := range b {
|
||||
if !m[x] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -153,6 +153,12 @@ func buildRouter(cfg *config.Config, pool *pgxpool.Pool) *gin.Engine {
|
||||
ragHandlers := handlers.NewRAGHandlers(corpusVersionStore)
|
||||
obligationsHandlers := handlers.NewObligationsHandlersWithStore(obligationsStore)
|
||||
|
||||
// Compliance Execution Graph (file-backed: Registry join keys + accepted control mappings + evidence)
|
||||
complianceGraphHandlers := handlers.NewComplianceGraphHandlers()
|
||||
if err := complianceGraphHandlers.LoadError(); err != nil {
|
||||
log.Printf("WARNING: compliance graph not loaded (obligation-status -> 503): %v", err)
|
||||
}
|
||||
|
||||
// Regulatory News
|
||||
allV2Regs, err := ucca.LoadAllV2Regulations()
|
||||
if err != nil {
|
||||
@@ -201,7 +207,8 @@ func buildRouter(cfg *config.Config, pool *pgxpool.Pool) *gin.Engine {
|
||||
uccaHandlers, escalationHandlers, obligationsHandlers, ragHandlers,
|
||||
roadmapHandlers, workshopHandlers, portfolioHandlers,
|
||||
academyHandlers, trainingHandlers, whistleblowerHandlers, iaceHandler,
|
||||
gapHandler, maximizerHandlers, regulatoryNewsHandlers, useCaseHandler)
|
||||
gapHandler, maximizerHandlers, regulatoryNewsHandlers, useCaseHandler,
|
||||
complianceGraphHandlers)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ func registerRoutes(
|
||||
maximizerHandlers *handlers.MaximizerHandlers,
|
||||
regulatoryNewsHandlers *handlers.RegulatoryNewsHandlers,
|
||||
useCaseHandler *handlers.UseCaseHandler,
|
||||
complianceGraphHandlers *handlers.ComplianceGraphHandlers,
|
||||
) {
|
||||
v1 := router.Group("/sdk/v1")
|
||||
{
|
||||
@@ -54,6 +55,7 @@ func registerRoutes(
|
||||
registerMaximizerRoutes(v1, maximizerHandlers)
|
||||
registerUseCaseRoutes(v1, useCaseHandler)
|
||||
v1.GET("/regulatory-news", regulatoryNewsHandlers.GetNews)
|
||||
complianceGraphHandlers.RegisterRoutes(v1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ var domains = []domainDef{
|
||||
{"data_protection",
|
||||
[]string{"DSGVO", "GDPR", "BDSG", "EDPB", "DSK", "BfDI", "BayLfD", "DPF"},
|
||||
[]string{"personenbezogen", "betroffene", "datenschutz", "datenschutzbeauftrag", "dsb",
|
||||
"datenpanne", "auskunft", "loesch", "lösch", "einwilligung", "besondere kategorien", "auftragsverarbeiter"}},
|
||||
"datenpanne", "auskunft", "loesch", "lösch", "einwilligung", "besondere kategorien", "auftragsverarbeit"}},
|
||||
{"cyber",
|
||||
[]string{"CRA", "NIS2", "NIS-2", "ENISA", "DORA", "EUCC"},
|
||||
[]string{"security update", "sicherheitsupdate", "sicherheitsaktualisierung", "schwachstelle", "sbom",
|
||||
@@ -126,6 +126,16 @@ var domains = []domainDef{
|
||||
nil},
|
||||
}
|
||||
|
||||
// euPrimaryDomains are domains whose PRIMARY binding act is an EU regulation/directive
|
||||
// (DSGVO, CRA/NIS2, AI Act, MaschinenVO). In these domains a NATIONAL implementing law
|
||||
// (e.g. BDSG) is subsidiary for general questions — see nationalSubsidiarityPenalty.
|
||||
var euPrimaryDomains = map[string]bool{
|
||||
"data_protection": true,
|
||||
"cyber": true,
|
||||
"ai": true,
|
||||
"product_safety": true,
|
||||
}
|
||||
|
||||
func queryDomain(query string) string {
|
||||
ql := strings.ToLower(query)
|
||||
for _, d := range domains {
|
||||
@@ -135,6 +145,16 @@ func queryDomain(query string) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: an explicit regulation mention (e.g. "DSGVO", "BDSG", "CRA") also signals the
|
||||
// domain — so a question phrased around the act ("... gilt die DSGVO ...") is scoped even
|
||||
// without a topical keyword. Keyword match wins first (more specific).
|
||||
for _, d := range domains {
|
||||
for _, reg := range d.regs {
|
||||
if strings.Contains(ql, strings.ToLower(reg)) {
|
||||
return d.name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const (
|
||||
domainMatchGain = 0.15
|
||||
offDomainPenalty = 0.10 // off-domain binding (demoted, not removed)
|
||||
scopePenalty = 0.25 // BDSG Teil 3 (law enforcement) on a general DP question
|
||||
subsidiarityPen = 0.18 // national implementing law (BDSG) on a general EU-primary question: SOFT demote, not exclusion
|
||||
topicGain = 0.18 // amplifier only
|
||||
supersededPenalty = 0.50 // superseded Alt-Quelle (pre-eu-v1): demoted, nicht versteckt
|
||||
intentLiftGain = 0.10 // epsilon a qualifying interpretative source is lifted ABOVE the best binding
|
||||
@@ -102,6 +103,15 @@ func authorityScore(query string, r LegalSearchResult, qDomain string, qForeign
|
||||
if qDomain == "data_protection" && scopeClass(r) == "law_enforcement" {
|
||||
score -= scopePenalty
|
||||
}
|
||||
// Subsidiarity: a national implementing law (DE binding, e.g. BDSG) is subsidiary to the
|
||||
// primary EU act for GENERAL questions in an EU-primary domain — UNLESS the query hits a
|
||||
// topic where the national norm is co-primary (DSB §38, special categories §22, ...). The
|
||||
// topic boost below lifts those; here we only SOFT-demote the non-topic national norm, so
|
||||
// it stays visible and can still win on a strongly matching topic. No hard exclusion.
|
||||
if euPrimaryDomains[qDomain] && info.sourceClass == "binding_law" &&
|
||||
info.jurisdiction == "DE" && !resultMatchesTopic(query, r) {
|
||||
score -= subsidiarityPen
|
||||
}
|
||||
if resultMatchesTopic(query, r) {
|
||||
score += topicGain // Verstaerker, kein Override
|
||||
}
|
||||
|
||||
@@ -72,6 +72,73 @@ func TestRerankByAuthority_Acceptance(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Subsidiarity (KB-2026.1 BDSG-pilot regression): a national implementing § that is NOT a
|
||||
// co-primary topic norm must not outrank the primary DSGVO article on a general question.
|
||||
t.Run("subsidiarity dp_05: BDSG §23 below DSGVO Art.6 (Rechtsgrundlage)", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 23 BDSG", "BDSG", "DE", 0.70),
|
||||
bindingRes("Art. 6 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
out := rerankByAuthority("Welche Rechtsgrundlagen erlauben eine Verarbeitung personenbezogener Daten?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO Art.6 must beat general BDSG §, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
if len(out) != 2 {
|
||||
t.Fatalf("BDSG must stay visible (soft demote), got len=%d", len(out))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("subsidiarity dp_08: BDSG §70 below DSGVO Art.28 (Auftragsverarbeitung)", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 70 BDSG", "BDSG", "DE", 0.70), // Teil 3 → scope + subsidiarity
|
||||
bindingRes("Art. 28 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
out := rerankByAuthority("Was muss ein Auftragsverarbeitungsvertrag enthalten?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO Art.28 must beat BDSG §70, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("subsidiarity dp_11: BDSG §22 below DSGVO Art.32 on a TOM question", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 22 BDSG", "BDSG", "DE", 0.70),
|
||||
bindingRes("Art. 32 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
out := rerankByAuthority("Welche technischen und organisatorischen Massnahmen verlangt das Datenschutzrecht?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO Art.32 must beat BDSG §22 on a non-topic TOM question, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cr_07: a 'DSGVO' mention scopes the domain so BDSG Teil-3 §64 is demoted", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 64 BDSG", "BDSG", "DE", 0.70), // Teil 3 (law enforcement)
|
||||
bindingRes("Art. 32 DSGVO", "DSGVO", "EU", 0.66),
|
||||
}
|
||||
// Query has no DP keyword but names the DSGVO → domain fallback scopes it data_protection,
|
||||
// so scope+subsidiarity demote the law-enforcement § below the primary norm.
|
||||
out := rerankByAuthority("Welche rechtliche Grundlage gilt fuer technische und organisatorische Massnahmen - DSGVO oder ein Standard?", in)
|
||||
if out[0].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("DSGVO must win on a DSGVO-mention question, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("co-primary dp_01: BDSG §38 stays top on a DSB question (national special rule)", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
bindingRes("§ 38 BDSG", "BDSG", "DE", 0.66),
|
||||
bindingRes("Art. 37 DSGVO", "DSGVO", "EU", 0.64),
|
||||
}
|
||||
out := rerankByAuthority("Ab wann muss ein Datenschutzbeauftragter benannt werden?", in)
|
||||
// DSB topic → §38 is co-primary (topic-matched, NOT subsidiarity-demoted) and keeps its
|
||||
// semantic lead; Art. 37 stays a close second. Both remain top-2.
|
||||
if out[0].RegulationShort != "BDSG" {
|
||||
t.Fatalf("BDSG §38 (DSB co-primary) must stay top, got %q", out[0].ArticleLabel)
|
||||
}
|
||||
if out[1].RegulationShort != "DSGVO" {
|
||||
t.Fatalf("Art. 37 DSGVO must stay co-primary second, got %q", out[1].ArticleLabel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nothing is dropped and topic amplifies", func(t *testing.T) {
|
||||
in := []LegalSearchResult{
|
||||
guidanceRes("ENISA", "ENISA", 0.72),
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package ucca
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// graphCallerRel resolves a path relative to THIS source file (build-time location), so the
|
||||
// graph data is findable under `go test` (cwd = package dir) regardless of working directory.
|
||||
// In a built container the source is gone, so cwd-relative candidates carry the load instead.
|
||||
func graphCallerRel(rel string) string {
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(filepath.Dir(file), rel)
|
||||
}
|
||||
|
||||
// firstExisting returns the first candidate path that exists with the requested kind (dir vs
|
||||
// file). Empty candidates (e.g. unset env overrides) are skipped.
|
||||
func firstExisting(candidates []string, wantDir bool) string {
|
||||
for _, p := range candidates {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(p)
|
||||
if err != nil || info.IsDir() != wantDir {
|
||||
continue
|
||||
}
|
||||
return p
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// LoadComplianceGraph loads the file-backed Compliance Execution Graph: the Registry join-key
|
||||
// contract (obligations/obligation_join_keys.json — owned by the Obligation session) + our
|
||||
// curated, accepted control mappings + evidence requirements. Locations are resolved across
|
||||
// three layouts: dev (cwd = ai-compliance-sdk/, canonical contract at ../obligations), container
|
||||
// (WORKDIR /app, data/ copied in incl. a synced data/obligations/ copy) and `go test`
|
||||
// (cwd = package dir, via graphCallerRel). Fail-closed: a missing/invalid source returns an
|
||||
// error so the handler serves 503 — never a half-built graph.
|
||||
//
|
||||
// NOTE: data/obligations/obligation_join_keys.json is a SYNCED COPY of the repo-root contract
|
||||
// (the canonical owner is the Obligation session). Re-sync it when the Registry grows; dev/test
|
||||
// prefer the canonical repo-root path, only the container falls back to the copy.
|
||||
func LoadComplianceGraph() (*ObligationJoinKeys, *ControlMappingSet, *EvidenceRequirementSet, error) {
|
||||
joinPath := firstExisting([]string{
|
||||
os.Getenv("BP_OBLIGATION_JOIN_KEYS"),
|
||||
"../obligations/obligation_join_keys.json",
|
||||
graphCallerRel("../../../obligations/obligation_join_keys.json"),
|
||||
"data/obligations/obligation_join_keys.json",
|
||||
graphCallerRel("../../data/obligations/obligation_join_keys.json"),
|
||||
}, false)
|
||||
if joinPath == "" {
|
||||
return nil, nil, nil, fmt.Errorf("obligation_join_keys.json not found in any candidate path")
|
||||
}
|
||||
mapDir := firstExisting([]string{
|
||||
os.Getenv("BP_CONTROL_MAPPINGS_DIR"),
|
||||
"data/control_mappings",
|
||||
graphCallerRel("../../data/control_mappings"),
|
||||
}, true)
|
||||
if mapDir == "" {
|
||||
return nil, nil, nil, fmt.Errorf("control_mappings dir not found in any candidate path")
|
||||
}
|
||||
evDir := firstExisting([]string{
|
||||
os.Getenv("BP_EVIDENCE_DIR"),
|
||||
"data/evidence_requirements",
|
||||
graphCallerRel("../../data/evidence_requirements"),
|
||||
}, true)
|
||||
if evDir == "" {
|
||||
return nil, nil, nil, fmt.Errorf("evidence_requirements dir not found in any candidate path")
|
||||
}
|
||||
|
||||
joins, err := LoadObligationJoinKeys(joinPath)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("load join keys (%s): %w", joinPath, err)
|
||||
}
|
||||
mappings, err := LoadControlMappings(mapDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("load control mappings (%s): %w", mapDir, err)
|
||||
}
|
||||
evidence, err := LoadEvidenceRequirements(evDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("load evidence (%s): %w", evDir, err)
|
||||
}
|
||||
return joins, mappings, evidence, nil
|
||||
}
|
||||
@@ -23,7 +23,7 @@ type ControlMapping struct {
|
||||
SourceRole string `json:"source_role"` // source_role of the norm (operational_requirement, ...)
|
||||
TargetFramework string `json:"target_framework"` // e.g. "OWASP ASVS"
|
||||
TargetControl string `json:"target_control"` // e.g. "V6.3.1"
|
||||
MappingType string `json:"mapping_type"` // supports | partially_supports | implements | related | contradicts
|
||||
MappingType string `json:"mapping_type"` // primary_implementation | implements | supports | partially_supports | related | contradicts
|
||||
MappingStatus string `json:"mapping_status"` // candidate | accepted | rejected | superseded
|
||||
Provenance string `json:"provenance"` // retriever_candidate | human_curated | rule_based
|
||||
ObligationID string `json:"obligation_id,omitempty"` // stable cross-session join key (Obligation Registry); empty until adopted, citation_unit is the interim bridge
|
||||
@@ -36,7 +36,7 @@ type ControlMapping struct {
|
||||
|
||||
// Allowed enum values — the deterministic "rule" layer that keeps the curated store clean.
|
||||
var (
|
||||
mappingTypeValues = map[string]bool{"supports": true, "partially_supports": true, "implements": true, "related": true, "contradicts": true}
|
||||
mappingTypeValues = map[string]bool{"primary_implementation": true, "implements": true, "supports": true, "partially_supports": true, "related": true, "contradicts": true}
|
||||
mappingStatusValues = map[string]bool{"candidate": true, "accepted": true, "rejected": true, "superseded": true}
|
||||
provenanceValues = map[string]bool{"retriever_candidate": true, "human_curated": true, "rule_based": true}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# Capability Model v1 — Objektarten & Beziehungstypen (Schema-Papier, NICHT materialisiert)
|
||||
|
||||
Status: **OFFEN / Entscheidung erforderlich (2026-06-26).** Dies ist Schritt **#5a** (Papier).
|
||||
Schritt **#5b** (Materialisierung: `capabilities.json`, Migration, Obligation→Capability-Links,
|
||||
Guidance-Mapping, Runtime) ist **GEGATED** auf die Annahme dieses Papiers. **Es wurde noch keine
|
||||
Zeile Daten verschoben.**
|
||||
|
||||
Baut auf [legal_obligation_layer_v1.md](legal_obligation_layer_v1.md),
|
||||
[obligation_registry_v1.md](obligation_registry_v1.md) und dem Cross-Domain-Review
|
||||
(`obligations/cross_domain_relationships.json`, Commit `ed31fdc0`).
|
||||
|
||||
---
|
||||
|
||||
## 0. Warum ein Papier statt `capabilities.json`
|
||||
|
||||
Die Plattform hat drei empirische Architektur-Sprünge gemacht:
|
||||
1. **Control ≠ Wissensobjekt** → Legal Obligation (sofort implementiert, datenbestätigt).
|
||||
2. **Procedure ist eigenständig** (implementiert: `cra_procedures.json`).
|
||||
3. **Capabilities tauchen domänenübergreifend wieder auf** (Cross-Domain-Review).
|
||||
|
||||
(1) und (2) waren breit datenbelegt → sofort umgesetzt. Bei (3) ist die **Objektart selbst noch
|
||||
nicht definiert.** Wir wissen NICHT genau, was eine Capability ist. Materialisieren wir jetzt,
|
||||
riskieren wir, in drei Wochen festzustellen: „Attack Surface war gar keine Capability" → Umbau.
|
||||
|
||||
---
|
||||
|
||||
## 1. Der Auslöser: die 8 „Capabilities" sind NICHT eine Objektart
|
||||
|
||||
Der Cross-Domain-Review fand 16 `SHARED_CAPABILITY`-Paare → 8 Cluster. Bei Inspektion zerfallen
|
||||
sie in **zwei verschiedene Objektarten**:
|
||||
|
||||
| Cluster (Opus-Benennung) | Art | Begründung |
|
||||
|---|---|---|
|
||||
| `mfa` | **Capability** | implementierbar als Funktion |
|
||||
| `session_management` | **Capability** | implementierbar |
|
||||
| `transport_encryption` (tls/mutual_tls/cert) | **Capability** | implementierbar (vom Klassifikator fein gesplittet → 1 Capability) |
|
||||
| `code_signing` | **Capability** | implementierbar |
|
||||
| `anomaly_detection` | **Capability** | implementierbar |
|
||||
| `access_control` | **Ziel** (schwach) | abstraktes Ziel, kein Baustein — eher OVERLAP (siehe Konsolidierung) |
|
||||
|
||||
Dazu die **zwei Gap-„Obligations" aus Handoff #4** (NIST SI-7/CM-7 waren breiter als jeder
|
||||
einzelne Treffer):
|
||||
|
||||
| Kandidat | Art | Begründung |
|
||||
|---|---|---|
|
||||
| `software_integrity_protection` (SI-7) | **Sicherheitsziel** | wird NICHT direkt gebaut; erreicht durch code_signing + hash_verification + secure_boot |
|
||||
| `attack_surface_minimization` (CM-7) | **Sicherheitsziel** | erreicht durch least_functionality + Port-Deaktivierung + Interface-Reduktion |
|
||||
|
||||
**Kernbeobachtung (User):** Es gibt **Typ 1 — technische Fähigkeiten** (implementierbar) und
|
||||
**Typ 2 — Sicherheitsziele** (nicht direkt implementierbar, durch mehrere Capabilities erreicht).
|
||||
Sie in eine `capabilities.json` zu werfen wäre der Fehler.
|
||||
|
||||
```
|
||||
Integrity Protection (Ziel) Access Protection (Ziel)
|
||||
↑ erreicht durch ↑ erreicht durch
|
||||
code_signing · hash_verification · mfa · session_management ·
|
||||
secure_boot (Capabilities) credential_storage (Capabilities)
|
||||
```
|
||||
|
||||
Das erklärt rückwirkend auch das **systematische Synth-Über-Tiering** (Auth 14→6, Remote 14→5):
|
||||
das LLM mischte ziel-nahe Obligations mit fähigkeits-nahen Mechanismen, weil die Modellsprache
|
||||
die Ebenen nicht trennte.
|
||||
|
||||
---
|
||||
|
||||
## 2. Kandidat-Objektarten
|
||||
|
||||
| Objektart | Definition | Diskriminator-Test |
|
||||
|---|---|---|
|
||||
| **Regulation** | Rechtsakt (CRA, NIS2, DSGVO, MaschVO) | „Ist es ein Gesetz/VO?" |
|
||||
| **Legal Obligation** | rechtlich verankerte Pflicht. **CORE** (abstrakt, oft = Sicherheitsziel) ⊇ **DOMAIN** (spezialisiert) — die CORE/DOMAIN-Achse existiert bereits (portability). | „Steht das so (sinngemäß) im Recht? Kann ein Prüfer FEHLT/ERFÜLLT sagen?" |
|
||||
| **Capability** *(NEU)* | implementierbare, **regulierungs-agnostische** technische Funktion, als Einheit baubar & testbar | „Kann ein Hersteller GENAU DAS bauen/konfigurieren?" → ja |
|
||||
| **Procedure** | wiederholbarer operativer Prozess, der eine Capability ausbringt/erhält (bereits modelliert) | „Ist es eine Tätigkeit/ein Ablauf?" |
|
||||
| **Control** | testbare Prüfanweisung | „Kann man es prüfen (pass/fail)?" |
|
||||
| **Evidence** | Nachweis-Artefakt (Audit-Log, SBOM, Release Notes) | „Ist es ein Beleg-Dokument/Datum?" |
|
||||
| **Guidance** *(quer)* | externe Empfehlung WIE (NIST/OWASP/ENISA/BSI). **Nicht-bindend.** | „Beschreibt es eine empfohlene Umsetzung, kein Primärrecht?" |
|
||||
|
||||
---
|
||||
|
||||
## 3. DER ZENTRALE KNACKPUNKT: Ist „Security Objective" eine eigene Klasse?
|
||||
|
||||
### Modell A — flach (Objektive = Obligations)
|
||||
```
|
||||
Regulation → Legal Obligation → Capability → Procedure → Control → Evidence
|
||||
```
|
||||
Sicherheitsziele sind einfach **CORE Legal Obligations**; domänen-scoped Pflichten sind DOMAIN-
|
||||
Obligations, die per `specializes` an die CORE hängen.
|
||||
|
||||
### Modell B — mit eigener Security-Objective-Klasse
|
||||
```
|
||||
Regulation → Legal Obligation → Security Objective → Capability → Procedure → Control → Evidence
|
||||
```
|
||||
|
||||
### Modell C — hybrid (Capability als einzige neue Klasse) ← **EMPFEHLUNG**
|
||||
```
|
||||
Regulation → Legal Obligation (CORE ⊇ DOMAIN) --realized_by--> Capability → Procedure → Control → Evidence
|
||||
▲ ▲
|
||||
└── specializes (DOMAIN→CORE) └── described_by ── Guidance (NIST/OWASP/…)
|
||||
```
|
||||
|
||||
**Empfehlung: Modell C.** Begründung aus den Daten:
|
||||
- Die „Sicherheitsziele" (`software_integrity_protection`, `attack_surface_minimization`, CIA,
|
||||
access-protection) **SIND im CRA bindende Pflichten** (Annex I (2)(a–m) ist Primärrecht). Ein
|
||||
Sicherheitsziel ist also eine **CORE Legal Obligation**, kein neuer Objekttyp.
|
||||
- Die **CORE/DOMAIN-Achse existiert schon** (portability_core ⊇ health/data_act). `attack_surface_
|
||||
minimization` (CORE) ⊇ `remote_access_attack_surface_min` (DOMAIN) ist exakt dasselbe Muster.
|
||||
→ keine neue Klasse, nur konsequente Nutzung des Vorhandenen.
|
||||
- **Genau EINE** wirklich neue Klasse (**Capability**) ist sparsam und niedrig-risiko.
|
||||
- Modell B verdoppelt die normative Ebene (Obligation vs Objective), die im CRA 1:1 zusammenfällt
|
||||
→ Klasse, die niemand sauber befüllt.
|
||||
|
||||
**Konsequenz für die #4-Gap:** `software_integrity_protection` + `attack_surface_minimization`
|
||||
werden als **CORE Legal Obligations** angelegt (nicht als Capabilities), und die domänen-scoped
|
||||
Treffer (`signed_update_integrity`, `remote_access_attack_surface_min`) `specializes` → CORE.
|
||||
NIST SI-7/CM-7 mappen dann `primary_implementation` auf die CORE.
|
||||
|
||||
**Offen für den User:** Modell C akzeptieren? Oder ist die regulierungs-AGNOSTISCHE Vereinheitlichung
|
||||
(eine „confidentiality" über CRA+NIS2+ISO) so wertvoll, dass „Security Objective" doch eine eigene
|
||||
Klasse verdient (Modell B)? Das ist die einzige wirklich offene Architekturentscheidung.
|
||||
|
||||
---
|
||||
|
||||
## 4. Beziehungstypen — das Modell ist ein GRAPH, keine flache Ebene
|
||||
|
||||
Der Review fand **vier distinkte Cross-Domain-Strukturrelationen** (nicht eine):
|
||||
`SUPPORTED_BY` 23 · `SHARED_CAPABILITY` 16 · `SHARED_EVIDENCE` 7 · `SHARED_PROCEDURE` 5 (+ 1 Merge).
|
||||
Das ist kein Baum. Vorgeschlagenes gerichtetes Kanten-Vokabular:
|
||||
|
||||
| Kante | von → nach | aus Review-Relation |
|
||||
|---|---|---|
|
||||
| `specializes` | DOMAIN-Obligation → CORE-Obligation | (SUPPORTED_BY, Spezialfall) |
|
||||
| `contributes_to` | Obligation → Obligation | (SUPPORTED_BY, Beitrag) |
|
||||
| `realized_by` | Obligation → Capability | (SHARED_CAPABILITY ⇒ 2 Obl. teilen 1 Capability) |
|
||||
| `deployed_via` | Capability → Procedure | (SHARED_PROCEDURE) |
|
||||
| `verified_by` | Procedure/Capability → Control | — |
|
||||
| `produces` | Procedure → Evidence | (SHARED_EVIDENCE ⇒ 2 Obl. teilen 1 Nachweis) |
|
||||
| `described_by` | Capability → Guidance | (guidance_basis) |
|
||||
| `same_as` | Obligation ↔ Obligation | (SAME_OBLIGATION, Merge) |
|
||||
|
||||
`SHARED_CAPABILITY`/`SHARED_EVIDENCE`/`SHARED_PROCEDURE` sind also **keine Obligation-Obligation-
|
||||
Kanten**, sondern Belege, dass zwei Obligations **denselben Knoten einer tieferen Ebene** teilen
|
||||
(Capability / Evidence / Procedure). Genau das ist der Mehrwert gegenüber „sieht ähnlich aus".
|
||||
|
||||
---
|
||||
|
||||
## 5. Die 8 offenen Fragen (Antwort + Tradeoff)
|
||||
|
||||
1. **Was ist eine Capability?** Eine implementierbare, regulierungs-agnostische technische Funktion,
|
||||
als Einheit baubar/konfigurierbar/testbar (MFA, TLS, Code Signing, Session-Mgmt, Anomaly-Detection).
|
||||
2. **Unterschied zur Obligation?** Obligation = rechtliche Pflicht (WAS das Recht verlangt, regulierungs-
|
||||
verankert, normativ). Capability = technisches Mittel (WIE man sie erfüllt, agnostisch). n:m.
|
||||
3. **Unterschied zum Security Objective?** Ziel = erwünschter Sicherheitszustand (CIA, attack-surface-min);
|
||||
Capability = Mittel dorthin. **Empfehlung (Modell C):** das Ziel ist eine CORE Obligation, kein
|
||||
eigener Typ → Unterschied reduziert sich auf Obligation(abstrakt) vs Capability(Mittel).
|
||||
4. **Wann Guidance?** Wenn es eine **nicht-bindende externe Empfehlung zur Umsetzung** ist (NIST AC-12,
|
||||
OWASP ASVS V6). Hängt an der **Capability** (meist) bzw. Procedure — NIE als `legal_basis` einer
|
||||
LEGAL_MINIMUM-Obligation (Primärrecht-Regel bleibt).
|
||||
5. **Wann Procedure?** Wenn es ein **wiederholbarer operativer Ablauf** ist, der eine Capability
|
||||
ausbringt/erhält (MFA konfigurieren, Schlüssel rotieren, Patch-Zyklus fahren).
|
||||
6. **Capability → mehrere Obligations?** **JA, belegt:** `mfa` erfüllt 6 Obligations (auth+remote),
|
||||
`code_signing` 2 (auth+updates). n:m.
|
||||
7. **Obligation → mehrere Capabilities?** **JA, belegt:** access-protection ← mfa + session_management
|
||||
+ credential_storage. n:m.
|
||||
8. **Wo hängen NIST/OWASP/ENISA/BSI?** Primär an der **Capability** (sie beschreiben deren Umsetzung),
|
||||
teils an Procedure. **Das erklärt, warum die über-getierten BP-Obligations `guidance_basis` trugen:
|
||||
sie waren in Wahrheit Capabilities.** Sauberer Sitz von `guidance_basis` = Capability.
|
||||
|
||||
---
|
||||
|
||||
## 6. Worked Examples (4 Domänen, echte IDs)
|
||||
|
||||
**Authentication** — `user_authentication_required` (Obl, CORE: access-protection)
|
||||
`--realized_by-->` { `mfa`, `session_management`, `credential_storage` } (Capabilities)
|
||||
`--described_by-->` NIST IA-2 / OWASP ASVS V6 (Guidance).
|
||||
|
||||
**Updates** — `provide_security_updates` (Obl, LEGAL_MINIMUM) `--realized_by-->`
|
||||
{ `code_signing` (= signed_update_integrity-Capability), `automatic_update_delivery`, `rollback` }
|
||||
— exakt die `capability_candidate`-Marker aus `cra_updates.json`.
|
||||
|
||||
**Remote Access** — CORE `attack_surface_minimization` (NEU, = CM-7-Ziel) `⊇ specializes ⊇`
|
||||
`remote_access_attack_surface_min` (DOMAIN) `--realized_by-->` { `least_functionality`, `port_disabling` }.
|
||||
|
||||
**SBOM** — Sonderfall: die SBOM-Familie ist im Cross-Review der **Evidence-/Procedure-Input** für
|
||||
`vuln_identification_inventory` (5× SUPPORTED_BY-Hub), weniger Capability. → bestätigt, dass nicht
|
||||
jede Domäne primär Capabilities beisteuert; manche liefern **Evidence**. Stützt den Graph-Charakter.
|
||||
|
||||
---
|
||||
|
||||
## 7. Entscheidung, die ich vom User brauche (vor #5b)
|
||||
|
||||
1. **Modell C** (Capability = einzige neue Klasse; Sicherheitsziele = CORE-Obligations) — akzeptiert?
|
||||
Oder Modell B (Security Objective als eigene Klasse für regulierungs-agnostische Vereinheitlichung)?
|
||||
2. **Kanten-Vokabular** aus §4 — so einfrieren?
|
||||
3. **`guidance_basis` wandert konzeptionell an die Capability** — einverstanden? (Bricht nichts sofort;
|
||||
die Obligations behalten den Verweis bis #5b.)
|
||||
4. Erst danach **#5b**: `capabilities.json` (capability_id, fulfills_obligations[] via `realized_by`,
|
||||
guidance_basis hochgezogen), die 2 CORE-Gap-Obligations, der Merge (`vuln_remediation_patching` ≈
|
||||
`provide_security_updates`), und die 2 Remote-Grenzfälle final tiern.
|
||||
|
||||
## 8. Bewusst NICHT in #5a (gegated)
|
||||
|
||||
Keine `capabilities.json`, keine Migration, kein Obligation-Rewrite, kein Guidance-Move, kein Runtime.
|
||||
Erst Modell-Annahme, dann Daten. „Erst das Schema, dann verschieben."
|
||||
@@ -0,0 +1,108 @@
|
||||
# Compliance Operating System — Meta Model v1.0 (FROZEN)
|
||||
|
||||
> **STATUS: EINGEFROREN (2026-06-26). ARCHITEKTUR-FREEZE IN KRAFT.**
|
||||
> Ab v1.0 dürfen neue Regulierungen das Modell **nicht mehr verändern** — sie müssen sich
|
||||
> **einfügen**. Das Modell wird nur wieder geöffnet, wenn eine Regulierung **nachweislich
|
||||
> scheitert** (eine Anforderung lässt sich ohne neue Objektklasse nicht abbilden).
|
||||
> Validiert gegen 5 Regulierungsarten: DSGVO · CRA · MaschVO · Data Act · NIS2.
|
||||
|
||||
Konsolidiert + friert ein: [legal_obligation_layer_v1.md](legal_obligation_layer_v1.md),
|
||||
[capability_model_v1.md](capability_model_v1.md) (Modell C), [meta_model_validation_v1.md](meta_model_validation_v1.md).
|
||||
Was hier eingefroren wird, ist **ausschließlich die Meta-Semantik** — NICHT die Registry, NICHT die
|
||||
Capabilities-Liste, NICHT die Procedures (diese wachsen als Daten weiter).
|
||||
|
||||
## 1. Objektklassen (6 + Guidance) — eingefroren
|
||||
|
||||
| Klasse | Was | Regulierungs-Bindung |
|
||||
|---|---|---|
|
||||
| **Regulation** | Rechtsakt | — |
|
||||
| **Legal Obligation** | rechtlich verankerte Pflicht; **CORE ⊇ DOMAIN** | regulierungs-anchored |
|
||||
| **Capability** | implementierbare technische Faehigkeit (OPTIONAL für eine Obligation) | **agnostisch** (n:m über Regulierungen) |
|
||||
| **Procedure** | wiederholbarer operativer Prozess | agnostisch |
|
||||
| **Control** | testbare Prüfanweisung | agnostisch |
|
||||
| **Evidence** | Nachweis-Artefakt | agnostisch |
|
||||
| **Guidance** *(quer)* | externe nicht-bindende Empfehlung (NIST/OWASP/ISO/BSI) — hängt an der **Capability** | agnostisch |
|
||||
|
||||
## 2. Die Kette + kanonisches Kanten-Vokabular — eingefroren
|
||||
|
||||
```
|
||||
Regulation
|
||||
↓ definiert
|
||||
Legal Obligation (CORE ⊇ DOMAIN)
|
||||
↓ realized_by (OPTIONAL — rein prozessuale/dokumentarische Obligations überspringen Capability)
|
||||
Capability
|
||||
↓ deployed_via (alias: operationalized_by)
|
||||
Procedure
|
||||
↓ verified_by
|
||||
Control
|
||||
↓ produces (alias: produces_evidence_for)
|
||||
Evidence
|
||||
→ Produktstatus
|
||||
```
|
||||
|
||||
Kanten (gerichtet, eingefroren):
|
||||
`specializes` (DOMAIN→CORE) · `realized_by` (Obligation→Capability) · `deployed_via` (Capability→Procedure) ·
|
||||
`verified_by` (Procedure/Capability→Control) · `produces` (Procedure→Evidence) · `described_by` (Capability→Guidance) ·
|
||||
`supports` / `depends_on` / `contributes_to` (Obligation↔Obligation) · `same_as` (Merge/Alias).
|
||||
**Das Modell ist ein GRAPH, kein Baum** (n:m an realized_by, supports, produces).
|
||||
|
||||
## 3. Attribute (KEINE Klassen) — eingefroren
|
||||
|
||||
`applicability` · `tier` (LEGAL_MINIMUM/BEST_PRACTICE) · `legal_basis` (Primärrecht) ·
|
||||
`guidance_basis` (NIST/OWASP/…, kanonisch an der Capability) · `objective_tags`
|
||||
(integrity/confidentiality/attack_surface/… — Vorwärts-Kompat zu einer späteren Security-Objective-
|
||||
Klasse) · `risk_level` · `deadline` · **`hazard` (Attribut, KEINE Klasse)**.
|
||||
|
||||
**Watch-Point (bewusste Nicht-Klasse):** `Hazard/Threat` bleibt ein Risiko-Treiber-Attribut. Es wird
|
||||
*erst dann* eine eigene Klasse, wenn quantitatives Risiko (FMEA: Hazard→Risiko→Maßnahme) als
|
||||
First-Class-Graph-Knoten modelliert werden soll — das ist die einzige bekannte künftige Öffnungs-Ursache.
|
||||
|
||||
## 4. Architektur-Freeze-Policy
|
||||
|
||||
1. **Neue Regulierung = Daten, nicht Architektur.** Sie läuft durch `Parser → Discovery-Pipeline →
|
||||
Review → Registry` und fügt Obligations/Capabilities/Procedures/Evidence hinzu.
|
||||
2. **Eine neue Objektklasse ist eine Architektur-Änderung** und erfordert explizite Wieder-Öffnung +
|
||||
Begründung (nachgewiesenes Scheitern der Abbildung). Default-Erwartung: **0 neue Klassen.**
|
||||
3. Verfeinerungen an Attributen (neues `*_tag`, neues risk-Attribut) sind erlaubt, solange keine
|
||||
neue Klasse entsteht.
|
||||
|
||||
## 5. Reuse-Metrik (KPI je neuer Regulierung) — der Wissens-Akkumulations-Beweis
|
||||
|
||||
Für jede neue Regulierung gemessen (Baseline = der jeweils vorhandene Bestand):
|
||||
|
||||
| Kennzahl | Soll/Bedeutung |
|
||||
|---|---|
|
||||
| **Neue Objektklassen** | **= 0** (Invariante; sonst Freeze gebrochen) |
|
||||
| Neue Capabilities | additiv (z.B. +8) |
|
||||
| **Wiederverwendete Capabilities %** | Kern-KPI (z.B. NIS2 ~70–80 % erwartet) |
|
||||
| Wiederverwendete Procedures % | (z.B. 58 %) |
|
||||
| Wiederverwendete Evidence % | (z.B. 81 %) |
|
||||
| Neue Obligations | additiv (z.B. +42) |
|
||||
|
||||
Zielaussage: *„Beim AI Act: 0 neue Objektklassen, 12 neue Capabilities, 41 neue Obligations,
|
||||
78 % der vorhandenen Capabilities wiederverwendet."* → belegt, dass das System **Wissen akkumuliert**,
|
||||
statt je Regulierung neu gebaut zu werden. (Tool zur Berechnung folgt mit dem ersten Live-Durchlauf.)
|
||||
|
||||
## 6. Der Burggraben (warum das mehr ist als ein Advisor / RAG)
|
||||
|
||||
Der Kunde denkt nicht in Artikeln, sondern: *„Wir haben Remote-Updates / signierte Firmware / einen
|
||||
Vuln-Prozess."* Über die Capability-Schicht bildet das System diese Aussagen auf **alle betroffenen
|
||||
Obligations mehrerer Regulierungen** ab und beantwortet die eigentliche Frage aus dem Kundengespräch:
|
||||
> **„Habe ich das Gesetz richtig verstanden, und reicht das, was wir umgesetzt haben?"**
|
||||
|
||||
Das ist regel-/wissensgestütztes Reasoning über ein gemeinsames Modell — keine RAG-Aufgabe.
|
||||
(Die Reasoning-Session hält dabei die Welt-Grenze: `ClaimCoverage` „potenziell relevant" ⊥
|
||||
`ComplianceStatus` „erfüllt aus Nachweisen".)
|
||||
|
||||
## 7. Was NICHT eingefroren ist (wächst weiter als Daten)
|
||||
|
||||
Registry-Inhalte (Obligations je Regulierung), die Capabilities-Liste, Procedures, Evidence-Typen,
|
||||
Applicability-Prädikate, Citation-Spans. Diese Schicht ist **Wissensaufbau** — explizit erwünschtes
|
||||
Wachstum gegen das eingefrorene Modell.
|
||||
|
||||
## 8. Erster Live-Durchlauf (User-Priorität nach Informationswert)
|
||||
|
||||
1. **MaschVO** ⭐⭐⭐⭐⭐ — beweist „Compliance-OS ≠ Cybersecurity" (physische Safety, CE, Restgefahren).
|
||||
2. **NIS2** ⭐⭐⭐⭐ — misst maximalen Capability-Reuse (erwartet 70–80 %).
|
||||
3. **AI Act** ⭐⭐⭐⭐ — Risikoklassifizierung/Governance, vermutlich 0 neue Klassen.
|
||||
4. **Data Act** ⭐⭐⭐ — bestätigt „Capability optional".
|
||||
@@ -0,0 +1,159 @@
|
||||
# Meta-Model Validation v1 — Ist das Modell regulierungsunabhängig?
|
||||
|
||||
Status: **Phase 6 — Meta-Validierung (2026-06-26). KEIN neues Coding, KEINE Regulierung ingestiert.**
|
||||
Dieses Dokument ist der Stresstest VOR der nächsten Regulierung. Baut auf
|
||||
[capability_model_v1.md](capability_model_v1.md) (Modell C, #5b materialisiert) +
|
||||
[legal_obligation_layer_v1.md](legal_obligation_layer_v1.md).
|
||||
|
||||
## Die eigentliche Frage
|
||||
|
||||
Nicht „welche Regulierung kommt als nächstes?", sondern:
|
||||
|
||||
> **Kann eine völlig neue Regulierung in dieses Modell eingeordnet werden, OHNE eine neue
|
||||
> Objektklasse einzuführen?**
|
||||
|
||||
Wenn ja für MaschVO + Data Act + AI Act + NIS2 → das ist kein CRA-Graph mehr, sondern ein
|
||||
**Compliance Meta Model**. Ab dann bringt jede Regulierung primär *Daten*, nicht *Architektur*.
|
||||
|
||||
## Das zu testende Modell (6 Klassen + Attribute, KEINE weitere Klasse erlaubt)
|
||||
|
||||
```
|
||||
Regulation
|
||||
↓ definiert
|
||||
Legal Obligation (CORE ⊇ DOMAIN; tier=LEGAL_MINIMUM/BEST_PRACTICE; objective_tags[]; applicability)
|
||||
↓ realized_by (OPTIONAL)
|
||||
Capability (regulierungs-agnostische technische Faehigkeit; guidance_basis hier)
|
||||
↓ deployed_via
|
||||
Procedure
|
||||
↓ verified_by
|
||||
Control
|
||||
↓ produces
|
||||
Evidence
|
||||
```
|
||||
Quer: **Guidance** (NIST/OWASP/ISO/BSI) hängt an der Capability. **Attribute** (keine Klassen):
|
||||
`tier`, `objective_tags`, `applicability`, später `deadline`/`risk_level`/`severity`.
|
||||
Kanten: realized_by · specializes · contributes_to · deployed_via · verified_by · produces · described_by · same_as.
|
||||
|
||||
---
|
||||
|
||||
## Test 1 — Maschinenverordnung (EU) 2023/1230
|
||||
|
||||
| Modell-Klasse | MaschVO-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation (CORE) | `hazard_minimization` (Sicherheits-Analogon zu attack_surface_minimization), `safe_control_systems`, `machine_risk_assessment`, `ce_conformity`, `instructions_for_use` — exakt die `machine_*`-Obligations, die die Reasoning-Session bereits unabhängig geprägt hat. |
|
||||
| Capability | **physische Sicherheitsfunktionen**: `emergency_stop`, `safety_interlock`, `two_hand_control`, `guarding`, `safe_torque_off`. → die **Capability-Klasse generalisiert von Cyber auf physische Safety** (gleiche Klasse, andere Domäne). |
|
||||
| Procedure | Risikobeurteilung (ISO 12100), CE-Konformitätsbewertung. |
|
||||
| Evidence | Technische Unterlagen, Risikobeurteilungsbericht, Konformitätserklärung. |
|
||||
|
||||
**Stress-Punkt:** „**Hazard**" (mechanisch/elektrisch/thermisch) = Schadensquelle — weder Obligation
|
||||
noch Capability. Kandidat für eine neue Klasse? → **Nein, für die Repräsentation:** ein Hazard ist
|
||||
ein *Risiko-Treiber* (Attribut/Applicability der Risikobeurteilungs-Procedure); eine Capability
|
||||
*mitigiert* einen Hazard, genau wie eine Cyber-Capability eine (implizite) Bedrohung kontert. `PL/SIL`
|
||||
= Attribut (wie `tier`). **Hazard wird erst dann eine Klasse, wenn ihr quantitatives FMEA-Risiko als
|
||||
First-Class-Graph-Knoten wollt** (vgl. [[project-fmea-safety-direction]]) — nicht für Compliance-Abbildung.
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** (Stärkstes Ergebnis: die Capability-Klasse trägt von Cyber zu Safety.)
|
||||
|
||||
---
|
||||
|
||||
## Test 2 — Data Act (EU) 2023/2854
|
||||
|
||||
| Modell-Klasse | Data-Act-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation | `data_act_data_access_by_design`, `data_act_user_data_access`, `data_portability_switching`, `fair_contract_terms` (FRAND), `interoperability` — deckt sich mit den `data_act_*`-Obligations der Reasoning-Session. |
|
||||
| Capability | `data_export_api`, `interoperability_interface`, `access_control` (**Reuse**). ABER: `fair_contract_terms` hat **KEINE technische Capability**. |
|
||||
| Procedure | FRAND-Klauseln entwerfen; Switching-Prozess. |
|
||||
| Evidence | Vertrag/Klauselwerk, API-Doku. |
|
||||
|
||||
**Stress-Punkt:** **vertraglich-rechtliche Pflichten** (FRAND, Verbot unfairer Klauseln) haben kein
|
||||
technisches Mittel. → Beleg, dass **`realized_by Capability` OPTIONAL ist**: manche Obligations werden
|
||||
rein über **Procedure (Entwurf) + Evidence (Vertrag)** erfüllt. Das ist KEINE neue Klasse — wir haben
|
||||
es schon gesehen (SBOM-Familie war Evidence-/Procedure-lastig, kaum Capability).
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** Verfeinerung: Capability ist optional (Obligation → Procedure → Evidence
|
||||
ohne Capability ist gültig).
|
||||
|
||||
---
|
||||
|
||||
## Test 3 — AI Act (EU) 2024/1689
|
||||
|
||||
| Modell-Klasse | AI-Act-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation | `ai_risk_management_system`, `ai_data_governance`, `ai_technical_documentation`, `ai_transparency_disclosure`, `human_oversight`, `accuracy_robustness`, `fundamental_rights_assessment`. |
|
||||
| Capability | `event_logging` (**Reuse**!), `bias_detection`, `accuracy_testing`, `human_oversight_mechanism`, `ai_transparency_notice`. |
|
||||
| Procedure | Risikomanagement-Prozess; FRIA-Durchführung; Human-Oversight-Prozess. |
|
||||
| Evidence | Technische Dokumentation, FRIA-Bericht, Logs. |
|
||||
|
||||
**Stress-Punkt:** **Risiko-Klassifikation** (unacceptable/high/limited/minimal) bestimmt, WELCHE
|
||||
Obligations gelten. → das ist **Applicability** (existiert bereits; analog zu CRA-Produktklasse).
|
||||
`human_oversight` = Procedure + Capability (Oversight-UI). `transparency` = Disclosure (Capability/Evidence,
|
||||
wie Cookie/DSE-Offenlegung). `FRIA` = Procedure + Evidence.
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** Verfeinerung: Risiko-Tier = Applicability-Attribut (vorhanden).
|
||||
|
||||
---
|
||||
|
||||
## Test 4 — NIS2 (EU) 2022/2555
|
||||
|
||||
| Modell-Klasse | NIS2-Inhalt |
|
||||
|---|---|
|
||||
| Legal Obligation | `nis2_risk_management_measures`, `nis2_incident_reporting`, `supply_chain_security`, `governance_accountability`, `business_continuity`. |
|
||||
| Capability | **MFA, transport_encryption, security_monitoring_alerting, patch/update, backup** — **dieselben Capabilities wie CRA**. Das ist die Auszahlung: NIS2-Obligations `realized_by` die bereits gebaute Capability-Schicht. |
|
||||
| Procedure | Incident-Response-Prozess; Lieferketten-Audit; Governance-Prozess. |
|
||||
| Evidence | Incident-Reports, Audit-Logs, Vorstandsprotokolle. |
|
||||
|
||||
**Stress-Punkt:** **Meldefristen** (24h/72h/1 Monat) = zeitgebundene Procedure → `deadline` = Attribut.
|
||||
`governance_accountability` (Management-Haftung) = organisatorische Obligation → Procedure + Evidence.
|
||||
|
||||
**Verdikt: KEINE neue Klasse.** Stärkster Reuse-Fall (teilt die CRA-Capability-Schicht vollständig).
|
||||
|
||||
---
|
||||
|
||||
## Ergebnis: 4 × NEIN → das Metamodell steht
|
||||
|
||||
Alle vier Regulierungen passen in die 6 Klassen **ohne neue Objektklasse** — unter zwei
|
||||
Verfeinerungen, die der Test selbst aufdeckt (beide sind KEINE neuen Klassen):
|
||||
|
||||
1. **`realized_by Capability` ist OPTIONAL.** Vertraglich/dokumentarisch/prozessuale Obligations
|
||||
(Data-Act-FRAND, NIS2-Governance, AI-Act-FRIA) werden rein über Procedure + Evidence erfüllt.
|
||||
2. **Risiko-Niveau / Frist / Hazard-Schwere / Risiko-Tier sind ATTRIBUTE**, keine Klassen
|
||||
(`tier`-Muster: `deadline`, `risk_level`, `severity`, `risk_tier`).
|
||||
|
||||
**Der einzige Watch-Point:** **Hazard / Threat.** Heute implizit (Obligations existieren, um sie zu
|
||||
kontern). Eine eigene Klasse wird *erst* nötig, wenn ihr **quantitatives Risiko first-class** modelliert
|
||||
(FMEA: Hazard→Risiko→Maßnahme als Graph-Knoten). Für die reine Compliance-Abbildung: nicht nötig.
|
||||
→ Das ist die präzise Antwort auf „wo wäre erstmals eine neue Klasse nötig?".
|
||||
|
||||
## Empirische Stütze (nicht nur Theorie)
|
||||
|
||||
Die 3. Session (Reasoning Engine) hat **unabhängig** `proposed=True`-Obligations für MaschVO
|
||||
(`machine_*`) und Data Act (`data_act_*`) geprägt — und brauchte dafür **keine neue Objektklasse**,
|
||||
nur Obligation-IDs. Zwei Sessions kommen unabhängig zum selben Schluss.
|
||||
|
||||
## Konsequenz für die Reasoning-Schicht (Produktvision)
|
||||
|
||||
Heute: `Product → Applicable Regulations → Applicable Obligations`.
|
||||
Mit der Capability-Schicht wird daraus:
|
||||
```
|
||||
Applicable Capabilities → Required Procedures → Expected Evidence
|
||||
```
|
||||
Antwort auf die Kundenaussage „Ich habe X umgesetzt" ist dann nicht „CRA Artikel …", sondern:
|
||||
```
|
||||
✓ Capability A ✓ Capability B ✗ Capability C
|
||||
↓
|
||||
erfüllt CRA, MaschVO, NIS2 (teilweise)
|
||||
```
|
||||
Eine Capability erfüllt Obligations über *mehrere Regulierungen* (n:m) → eine Umsetzung wird gegen
|
||||
das gesamte Regelwerk bewertet. Das ist qualitativ ein anderes Produkt als RAG.
|
||||
|
||||
## Entscheidung / nächster Schritt
|
||||
|
||||
Wenn dieses Dokument akzeptiert ist („keine weitere Klasse nötig"), verschiebt sich die Arbeit von
|
||||
**Architektur** zu **Wissensaufbau**: jede neue Regulierung läuft durch
|
||||
`Parser → Discovery-Pipeline → Review → Registry` (vorhandene Tooling), statt das Modell zu ändern.
|
||||
Offen für den User: (a) Metamodell als stabil einfrieren? (b) den Hazard/Threat-Watch-Point als
|
||||
bewusste Nicht-Klasse dokumentieren (bis FMEA-Quantifizierung)? (c) dann erste Regulierung als DATEN.
|
||||
|
||||
## Bewusst NICHT in diesem Schritt
|
||||
|
||||
Kein Code, keine Regulierung ingestiert, keine neue Klasse angelegt. Reiner Modell-Stresstest.
|
||||
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"schema_version": "capability_layer_v1",
|
||||
"model": "Modell C (docs-src/development/capability_model_v1.md)",
|
||||
"note": "Capability = technische Faehigkeit (regulierungs-agnostisch). realized_by = Obligations, die sie erfuellt (n:m). guidance_basis hier KANONISCH hochgezogen aus den realisierten Obligations (die Obligation-Kopien bleiben vorerst als Legacy; Strip = Folge-Cleanup). Sicherheitsziele sind KEINE Capabilities -> cra_core.json.",
|
||||
"dropped": {
|
||||
"access_control": "OVERLAP (credential_confidentiality <-> sbom_confidentiality), nicht materialisiert"
|
||||
},
|
||||
"candidate_capabilities_followup": [
|
||||
"automatic_update_delivery",
|
||||
"update_rollback",
|
||||
"trusted_update_source",
|
||||
"hash_verification",
|
||||
"secure_boot",
|
||||
"least_functionality",
|
||||
"credential_storage"
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"capability_id": "multi_factor_authentication",
|
||||
"name": "Multi-Factor Authentication",
|
||||
"description": "Mehrfaktor-Authentisierung als technische Faehigkeit (Besitz/Wissen/Inhaerenz).",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"mfa_required",
|
||||
"privileged_op_reauth",
|
||||
"remote_access_authentication",
|
||||
"remote_access_mfa",
|
||||
"remote_access_user_validation_ot",
|
||||
"supplier_access_auth"
|
||||
],
|
||||
"realizes_count": 6,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-63B",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "Out-of-Band-Authentifizierung",
|
||||
"anchor": "",
|
||||
"role": "implementation_guidance",
|
||||
"merged_from": "out_of_band_authentication"
|
||||
},
|
||||
{
|
||||
"source": "Hardware-basierte Authentifizierung (AAL3)",
|
||||
"anchor": "",
|
||||
"role": "implementation_guidance",
|
||||
"merged_from": "hardware_authenticators"
|
||||
},
|
||||
{
|
||||
"source": "E-Mail-Authentifizierungsmechanismen (SPF/DKIM/DMARC)",
|
||||
"anchor": "",
|
||||
"role": "implementation_guidance",
|
||||
"merged_from": "email_authentication"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-02",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-02(1)",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "AC-17",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-53 IA-2",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "BSI",
|
||||
"anchor": "ICS Security Kompendium",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "ISO",
|
||||
"anchor": "ISO 27001 A.5.19",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "session_management",
|
||||
"name": "Session Management",
|
||||
"description": "Sichere Sitzungsverwaltung: Timeouts, Bindung, Re-Auth, Beendigung.",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"reauth_after_inactivity",
|
||||
"remote_session_management",
|
||||
"session_binding_management",
|
||||
"temporary_remote_access_mgmt"
|
||||
],
|
||||
"realizes_count": 4,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-63B 4.3",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-53 AC-12",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "OWASP",
|
||||
"anchor": "ASVS V3",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "AC-2(5)",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "transport_encryption",
|
||||
"name": "Transport Encryption",
|
||||
"description": "Verschluesselter Transport (TLS, mutual-TLS, Zertifikats-Auth, VPN/Tunnel).",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"encrypted_auth_channel",
|
||||
"mutual_authentication",
|
||||
"reject_insecure_remote_protocols",
|
||||
"remote_access_confidentiality_integrity",
|
||||
"remote_access_encryption",
|
||||
"service_to_service_auth",
|
||||
"tls_certificate_auth"
|
||||
],
|
||||
"realizes_count": 7,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "BSI",
|
||||
"anchor": "TR-02102-2",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-03",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SC-8",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "BSI",
|
||||
"anchor": "IT-Grundschutz NET.3.3",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "OWASP",
|
||||
"anchor": "API Security Top 10",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "IA-05(2)",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "code_signing",
|
||||
"name": "Code & Update Signing",
|
||||
"description": "Digitale Signatur + Integritaets-/Authentizitaetspruefung von Firmware/Software/Updates.",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"firmware_software_authentication",
|
||||
"signed_update_integrity"
|
||||
],
|
||||
"realizes_count": 2,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SI-07",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-147 BIOS Protection",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"authentication",
|
||||
"updates"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability_id": "security_monitoring_alerting",
|
||||
"name": "Security Monitoring & Alerting",
|
||||
"description": "Anomalie-/Bedrohungserkennung und Alarmierung aus Logs/Telemetrie.",
|
||||
"type": "technical_capability",
|
||||
"realized_by": [
|
||||
"log_monitoring_alerting",
|
||||
"remote_access_threat_detection"
|
||||
],
|
||||
"realizes_count": 2,
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "AU-6/SI-4",
|
||||
"role": "best_practice"
|
||||
},
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SP 800-94",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"domains": [
|
||||
"logging",
|
||||
"remote_access"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "cross_domain_relationships.json SHARED_CAPABILITY"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"schema_version": "controls_for_obligation_mapping_v1",
|
||||
"purpose": "Accepted CRA->OWASP controls (Compliance Execution Graph) for the Obligation Registry to propose the SEMANTIC control->obligation_id, replacing the coarse citation_unit interim join. Fill proposed_obligation_id per control, then we adopt it into control_mapping.obligation_id.",
|
||||
"source": "ai-compliance-sdk control_mappings, mapping_status=accepted, reviewed_by=benjamin 2026-06-25",
|
||||
"filled_by": "obligation-registry-session 2026-06-25 (alle 7/7: 4 auth/crypto + 3 logging via cra_logging.json)",
|
||||
"purpose": "Accepted CRA->Framework controls (Compliance Execution Graph) for the Obligation Registry to propose the SEMANTIC control->obligation_id, replacing the coarse citation_unit interim join. Fill proposed_obligation_id per control, then we adopt it into control_mapping.obligation_id.",
|
||||
"source": "ai-compliance-sdk control_mappings, mapping_status=accepted, reviewed_by=benjamin 2026-06-25. OWASP ASVS (7, gefuellt) + NIST SP 800-53 (3, pending).",
|
||||
"filled_by": "obligation-registry-session 2026-06-25. OWASP 7/7 (4 auth/crypto + 3 logging). NIST 3/3 GEFUELLT (Obligation-Session): SI-2->provide_security_updates (stark, (2)(c)/Art.13) · SI-7->signed_update_integrity (update-scoped; SI-7 breiter) · CM-7->remote_access_attack_surface_min (remote-scoped; CM-7 breiter). GAP-BEFUND (Cross-Domain-Review): generische Parent-Obligations software_integrity_protection + attack_surface_minimization FEHLEN — SI-7/CM-7 sind breiter als die domaenen-scoped Treffer. Kandidaten fuer neue generische Obligations (User-Entscheidung). Damit 10/10 proposed_obligation_id gefuellt.",
|
||||
"join_principle": "SEMANTISCH via obligation_id, NICHT via citation_unit/legal_basis-Anker. Die CRA-Anker sind im Registry teils approximativ (siehe anchor_quality_note) — daher ist obligation_id der stabile Primaerschluessel, nicht der Anker.",
|
||||
"anchor_quality_note": "Registry-legal_basis-Anker sind teils CRA-Part-I-fehlzugeordnet (Opus-Synthese): user_authentication_required steht auf (2)(d) statt (2)(c); Crypto-Obligations auf (2)(e) statt (2)(d). CRA Annex I Part I: (2)(c)=Zugriffsschutz, (2)(d)=Vertraulichkeit, (2)(e)=Integritaet. Korrektur kommt mit dem zitierfaehigen Re-Ingest (span-genau). Deshalb: NICHT auf Anker joinen. ABER: der Logging-Cut (V16.*) ist korrekt auf (2)(k) verankert (echte Logging-Subsektion, kein Fehl-Anker).",
|
||||
"count": 7,
|
||||
"mapping_type_note": "NEU: mapping_type=primary_implementation = die kanonische Primaer-Control einer Anforderung (genau eine), staerker als implements/supports. related-Controls (SC-3(3), RA-5, AC-6, SI-16, SA-10, ...) folgen separat als supports. Eine Obligation kann mehrere Controls haben, aber genau einen primary_implementation-Einstieg.",
|
||||
"count": 10,
|
||||
"controls": [
|
||||
{
|
||||
"framework": "OWASP ASVS", "control": "V6.3.1",
|
||||
@@ -62,6 +63,30 @@
|
||||
"proposed_obligation_id": "event_logging_security_events",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "V16.1 = allgemeine Logging-Anforderung -> Umbrella-LM event_logging_security_events. Hohe Konfidenz."
|
||||
},
|
||||
{
|
||||
"framework": "NIST SP 800-53", "control": "SI-7",
|
||||
"source_norm": "CRA Annex I Part I (2)(e) — Integritaet",
|
||||
"citation_unit": "Annex I (2)(e)", "family": "integrity", "mapping_type": "primary_implementation",
|
||||
"proposed_obligation_id": "software_integrity_protection",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "NIST SI-7 = Software/Firmware/Information Integrity (gesamte Produkt-Integritaet). #6 ADOPTIERT (2026-06-26) auf CORE software_integrity_protection (Annex I (2)(f)) — die in #5b materialisierte generische Integritaets-Obligation. Die domaenen-scoped signed_update_integrity (Update-Signatur, (1)(3)(f)) bleibt gueltig als DOMAIN, specializes->CORE. NICHT log_integrity_immutability (Audit-Log-Schutz, andere Ebene)."
|
||||
},
|
||||
{
|
||||
"framework": "NIST SP 800-53", "control": "SI-2",
|
||||
"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates",
|
||||
"citation_unit": "Annex I (2)(l)", "family": "update", "mapping_type": "primary_implementation",
|
||||
"proposed_obligation_id": "provide_security_updates",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "NIST SI-2 = Flaw Remediation. STARKER Treffer in eurer NEUEN updates-Familie (93-Stand): provide_security_updates (LEGAL_MINIMUM, Annex I (2)(c) + Art. 13) = DAS sichere-Update-LM. -> SI-2 primary_implementation = provide_security_updates. Verwandt (supports): vuln_remediation_patching (Part II Remediations-PROZESS), support_period_maintenance, update_testing_validation, update_rollback. Mein source_norm-Anker (2)(l) ist approximativ -> bitte (2)(c)/Art.13 via provide_security_updates nutzen."
|
||||
},
|
||||
{
|
||||
"framework": "NIST SP 800-53", "control": "CM-7",
|
||||
"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren",
|
||||
"citation_unit": "Annex I (2)(i)", "family": "attack_surface", "mapping_type": "primary_implementation",
|
||||
"proposed_obligation_id": "attack_surface_minimization",
|
||||
"mapping_method": "semantic",
|
||||
"mapping_note": "NIST CM-7 = Least Functionality (deaktivierte Ports/Dienste/Funktionen, GESAMTE Angriffsflaeche). #6 ADOPTIERT (2026-06-26) auf CORE attack_surface_minimization (Annex I (2)(j)) — die in #5b materialisierte generische Obligation. Die domaenen-scoped remote_access_attack_surface_min (nur Remote-Access-Flaeche) bleibt gueltig als DOMAIN, specializes->CORE. related (supports): SC-3(3)/AC-6/SI-16."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -966,7 +966,10 @@
|
||||
"relationships": [],
|
||||
"citation_anchor_ids": [],
|
||||
"citation_status": "pending_span_anchor",
|
||||
"review_status": "draft"
|
||||
"review_status": "draft",
|
||||
"merged_into": "provide_security_updates",
|
||||
"status": "deprecated_alias",
|
||||
"merge_note": "SAME_OBLIGATION (Cross-Domain-Review). Kanonisch: provide_security_updates ((2)(c)/Art.13). ID bleibt als Alias aufloesbar; downstream provide_security_updates nutzen."
|
||||
},
|
||||
{
|
||||
"id": "vuln_handling_process",
|
||||
|
||||
@@ -10281,7 +10281,11 @@
|
||||
"cluster_size": 4,
|
||||
"llm_model": "claude-opus-4-8",
|
||||
"synthesis_version": "v1"
|
||||
}
|
||||
},
|
||||
"specializes": "software_integrity_protection",
|
||||
"objective_tags": [
|
||||
"integrity"
|
||||
]
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"schema_version": "obligation_registry_v1",
|
||||
"regulation": "CRA",
|
||||
"regulation_code": "CRA",
|
||||
"family": "core",
|
||||
"theme": "CORE Security Objectives (CRA Annex I als regulierungs-agnostische Sicherheitsziele)",
|
||||
"generated_by": "materialize_capabilities.py (#5b, Modell C)",
|
||||
"note": "CORE Legal Obligations = Sicherheitsziele (Modell C: KEINE eigene SecurityObjective-Klasse). DOMAIN-Obligations specializes-en hierauf. objective_tags = Vorwaerts-Kompat zu Modell B.",
|
||||
"citation_status": "pending_span_anchor",
|
||||
"obligations": [
|
||||
{
|
||||
"id": "attack_surface_minimization",
|
||||
"name": "Minimierung der Angriffsflaeche",
|
||||
"family": "core",
|
||||
"description": "Das Produkt minimiert seine Angriffsflaeche: unnoetige Funktionen/Ports/Dienste/Schnittstellen sind deaktiviert (Least Functionality).",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"source_role": "LEGAL_BASIS",
|
||||
"applicability": "universal",
|
||||
"objective_tags": [
|
||||
"attack_surface"
|
||||
],
|
||||
"legal_basis": [
|
||||
{
|
||||
"source": "CRA",
|
||||
"anchor": "Annex I Part I (2)(j)",
|
||||
"citation": "limit attack surfaces, including external interfaces"
|
||||
}
|
||||
],
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "CM-7 Least Functionality",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"specialized_by": [
|
||||
"remote_access_attack_surface_min",
|
||||
"component_remote_interface_security"
|
||||
],
|
||||
"primary_implementation": "NIST CM-7",
|
||||
"citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"
|
||||
},
|
||||
{
|
||||
"id": "software_integrity_protection",
|
||||
"name": "Schutz der Software-/Firmware-Integritaet",
|
||||
"family": "core",
|
||||
"description": "Das Produkt schuetzt Integritaet und Authentizitaet von Software/Firmware (Manipulationserkennung, Secure Boot, Signaturpruefung, Runtime-Integritaet).",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"source_role": "LEGAL_BASIS",
|
||||
"applicability": "universal",
|
||||
"objective_tags": [
|
||||
"integrity"
|
||||
],
|
||||
"legal_basis": [
|
||||
{
|
||||
"source": "CRA",
|
||||
"anchor": "Annex I Part I (2)(f)",
|
||||
"citation": "protect the integrity of stored, transmitted or processed data, software and configuration"
|
||||
}
|
||||
],
|
||||
"guidance_basis": [
|
||||
{
|
||||
"source": "NIST",
|
||||
"anchor": "SI-7 Software, Firmware, and Information Integrity",
|
||||
"role": "best_practice"
|
||||
}
|
||||
],
|
||||
"specialized_by": [
|
||||
"signed_update_integrity",
|
||||
"firmware_software_authentication"
|
||||
],
|
||||
"realized_by_capabilities": [
|
||||
"code_signing"
|
||||
],
|
||||
"primary_implementation": "NIST SI-7",
|
||||
"citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"
|
||||
}
|
||||
],
|
||||
"relationships": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "obligation_join_keys_v1",
|
||||
"contract": "obligation_id ist der stabile Join-Key. Legal Knowledge Graph haengt citation_spans an obligation_id; Compliance Execution Graph mappt control_mapping.source_norm -> obligation_id. Interim-Bruecke = citation_units. obligation_id NIE neu vergeben (re-link).",
|
||||
"count": 66,
|
||||
"count": 95,
|
||||
"obligation_ids": [
|
||||
{
|
||||
"obligation_id": "sbom_creation",
|
||||
@@ -175,6 +175,26 @@
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "attack_surface_minimization",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(j)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "software_integrity_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "core",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I Part I (2)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "user_authentication_required",
|
||||
"regulation": "CRA",
|
||||
@@ -582,6 +602,245 @@
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_control_least_privilege",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_confidentiality_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(b)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_session_management",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_mfa",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_encryption",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "reject_insecure_remote_protocols",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_logging_audit",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(g)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_user_validation_ot",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_training",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_architecture_design",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_attack_surface_min",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)(a)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_vuln_patch_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(1)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_threat_detection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_maintenance_governance",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "temporary_remote_access_mgmt",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_data_export_protection",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "component_remote_interface_security",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "remote_access_fallback_concept",
|
||||
"regulation": "CRA",
|
||||
"family": "remote_access",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "provide_security_updates",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)",
|
||||
"Art. 13"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "support_period_maintenance",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Art. 13(8)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "signed_update_integrity",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(f)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "trusted_update_source",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(3)(d)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_testing_validation",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_rollback",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "GUIDANCE"
|
||||
},
|
||||
{
|
||||
"obligation_id": "automatic_updates_optout",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (2)(c)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "update_risk_assessment",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "LEGAL_MINIMUM",
|
||||
"citation_units": [
|
||||
"Annex I (1)(2)"
|
||||
],
|
||||
"source_role": "LEGAL_BASIS"
|
||||
},
|
||||
{
|
||||
"obligation_id": "secure_modification_control",
|
||||
"regulation": "CRA",
|
||||
"family": "updates",
|
||||
"tier": "BEST_PRACTICE",
|
||||
"citation_units": [],
|
||||
"source_role": "IMPLEMENTATION"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Cross-Domain Relationship Discovery — Stufe 2: Opus klassifiziert jede Kandidaten-Beziehung
|
||||
in GENAU EINE Kategorie. Liefert das Rohmaterial der Compliance-Ontologie (insb. SHARED_CAPABILITY
|
||||
= Capability-Schicht). ANTHROPIC_API_KEY aus ENV (nie hartcodiert). Streaming.
|
||||
|
||||
ANTHROPIC_API_KEY=… python3 classify_relationships.py --pairs /tmp/cd_pairs.json \
|
||||
--only-cross-family --out /tmp/cd_classified.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
SYS = """Du bist Compliance-Ontologe. Gegeben Paare von Legal Obligations (CRA), bestimme fuer
|
||||
JEDES Paar GENAU EINE Beziehung. Ziel ist NICHT Aehnlichkeit, sondern die STRUKTURELLE Beziehung.
|
||||
|
||||
Kategorien (genau EINE; bei Mehrdeutigkeit gilt diese Prioritaet):
|
||||
1 SAME_OBLIGATION — dieselbe rechtliche Pflicht, nur pro Domaene anders formuliert -> MERGE-Kandidat.
|
||||
2 SUPPORTED_BY — A ist domaenenspezifische Auspraegung/Teilfall von B ODER A traegt zur Erfuellung von B bei. RICHTUNG angeben.
|
||||
3 SHARED_CAPABILITY — beide werden durch DIESELBE technische Faehigkeit erfuellt (z.B. MFA, TLS-Verschluesselung, digitale Signatur, Session-Management, Patch-Management, Logging-Pipeline). capability_name (snake_case) angeben.
|
||||
4 SHARED_PROCEDURE — beide ueber denselben operativen Prozess erfuellt, ohne gemeinsames technisches Artefakt.
|
||||
5 SHARED_EVIDENCE — beide erzeugen/nutzen denselben Nachweis (Audit-Log, SBOM, Release Notes). evidence_name angeben.
|
||||
6 SHARED_GUIDANCE — beide berufen sich auf denselben externen Standard (NIST/OWASP/ISO), sonst distinkt.
|
||||
7 OVERLAP_ONLY — nur oberflaechliche Wort-/Themenueberlappung, keine echte strukturelle Beziehung.
|
||||
8 UNRELATED — Falsch-Positiv der Embedding-Naehe.
|
||||
|
||||
Gib AUSSCHLIESSLICH JSON aus:
|
||||
{"results":[{"i":0,"relation":"SHARED_CAPABILITY","direction":"a->b|b->a|none","capability_name":"","evidence_name":"","reason":"max 18 Woerter"}]}
|
||||
Regeln: relation = genau eine der 8 Strings. direction nur bei SUPPORTED_BY, sonst "none".
|
||||
capability_name NUR bei SHARED_CAPABILITY (sonst ""), evidence_name NUR bei SHARED_EVIDENCE (sonst "").
|
||||
Sei streng: SHARED_GUIDANCE/OVERLAP_ONLY/UNRELATED grosszuegig nutzen; SAME_OBLIGATION nur bei echter Deckungsgleichheit.
|
||||
Gib fuer JEDES Paar (per Index i) genau ein Ergebnis."""
|
||||
|
||||
|
||||
def build_user(pairs: list[dict]) -> str:
|
||||
lines = []
|
||||
for i, p in enumerate(pairs):
|
||||
lines.append(f'[{i}] A={p["a"]} ({p["fa"]}/{p["ta"]}): {p["da"]}\n'
|
||||
f' B={p["b"]} ({p["fb"]}/{p["tb"]}): {p["db"]} [sim={p["sim"]}]')
|
||||
return "Paare:\n" + "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--pairs", required=True)
|
||||
ap.add_argument("--only-cross-family", action="store_true")
|
||||
ap.add_argument("--min-sim", type=float, default=0.0)
|
||||
ap.add_argument("--model", default="claude-opus-4-8")
|
||||
ap.add_argument("--out", required=True)
|
||||
a = ap.parse_args()
|
||||
d = json.load(open(a.pairs, encoding="utf-8"))
|
||||
pairs = [p for p in d["pairs"]
|
||||
if (not a.only_cross_family or p["cross_family"]) and p["sim"] >= a.min_sim]
|
||||
|
||||
import anthropic
|
||||
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
|
||||
with client.messages.stream(model=a.model, max_tokens=24000, system=SYS,
|
||||
messages=[{"role": "user", "content": build_user(pairs)}]) as st:
|
||||
msg = st.get_final_message()
|
||||
txt = msg.content[0].text
|
||||
m = re.search(r"\{.*\}", txt, re.DOTALL)
|
||||
data = json.loads(m.group(0) if m else txt)
|
||||
|
||||
res = []
|
||||
for r in data.get("results", []):
|
||||
i = r.get("i")
|
||||
if not isinstance(i, int) or i < 0 or i >= len(pairs):
|
||||
continue
|
||||
p = pairs[i]
|
||||
res.append({"a": p["a"], "fa": p["fa"], "b": p["b"], "fb": p["fb"], "sim": p["sim"],
|
||||
"relation": r.get("relation", "?"), "direction": r.get("direction", "none"),
|
||||
"capability_name": r.get("capability_name", ""),
|
||||
"evidence_name": r.get("evidence_name", ""), "reason": r.get("reason", "")})
|
||||
dist = Counter(r["relation"] for r in res)
|
||||
out = {"n_pairs": len(pairs), "n_classified": len(res), "distribution": dict(dist),
|
||||
"model": a.model, "results": res}
|
||||
json.dump(out, open(a.out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(f"classified {len(res)}/{len(pairs)} | {dict(dist)}")
|
||||
print("written:", a.out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Cross-Domain Relationship Discovery — Stufe 1 (key-frei, im bp-compliance-backend-Container).
|
||||
Alle Obligations mehrerer Registries -> BGE-M3-Embedding -> je Obligation Top-K Nachbarn ->
|
||||
Kandidaten-Paare (cross- UND same-family) >= min-sim. KEIN Urteil hier — nur Kandidaten.
|
||||
Stufe 2 (classify_relationships.py) klassifiziert die Beziehung per Opus.
|
||||
|
||||
python3 cross_domain_pairs.py /tmp/reg/cra.json /tmp/reg/cra_authentication.json ... \
|
||||
--top-k 8 --min-sim 0.60 --out /tmp/cd_pairs.json
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from _core import cosine
|
||||
|
||||
|
||||
async def run(paths: list[str], top_k: int, min_sim: float, out: str) -> None:
|
||||
from compliance.services.mc_embedding_matcher import _embed_texts
|
||||
|
||||
obls: list[dict] = []
|
||||
for p in paths:
|
||||
reg = json.load(open(p, encoding="utf-8"))
|
||||
fam = reg.get("family", "")
|
||||
for o in reg.get("obligations", []):
|
||||
obls.append({"id": o["id"], "family": o.get("family", "") or fam,
|
||||
"tier": o.get("tier", ""), "name": o.get("name", ""),
|
||||
"desc": o.get("description", "")})
|
||||
vecs = await _embed_texts([f'{o["name"]}. {o["desc"]}' for o in obls])
|
||||
n = len(obls)
|
||||
print(f"obligations={n}")
|
||||
|
||||
best: dict[tuple[int, int], float] = {}
|
||||
for i in range(n):
|
||||
nbrs = sorted(((cosine(vecs[i], vecs[j]), j) for j in range(n) if j != i), reverse=True)[:top_k]
|
||||
for s, j in nbrs:
|
||||
if s < min_sim:
|
||||
continue
|
||||
a, b = sorted((i, j))
|
||||
if (a, b) not in best or s > best[(a, b)]:
|
||||
best[(a, b)] = s
|
||||
|
||||
pairs = []
|
||||
for (a, b), s in sorted(best.items(), key=lambda x: -x[1]):
|
||||
pairs.append({
|
||||
"a": obls[a]["id"], "fa": obls[a]["family"], "ta": obls[a]["tier"], "da": obls[a]["desc"][:220],
|
||||
"b": obls[b]["id"], "fb": obls[b]["family"], "tb": obls[b]["tier"], "db": obls[b]["desc"][:220],
|
||||
"sim": round(s, 3), "cross_family": obls[a]["family"] != obls[b]["family"]})
|
||||
cf = sum(1 for p in pairs if p["cross_family"])
|
||||
json.dump({"n_obligations": n, "n_pairs": len(pairs), "cross_family": cf, "pairs": pairs},
|
||||
open(out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
print(f"pairs={len(pairs)} (cross-family={cf}, same-family={len(pairs) - cf}) written: {out}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("registries", nargs="+")
|
||||
ap.add_argument("--top-k", type=int, default=8)
|
||||
ap.add_argument("--min-sim", type=float, default=0.60)
|
||||
ap.add_argument("--out", default="/tmp/cd_pairs.json")
|
||||
a = ap.parse_args()
|
||||
asyncio.run(run(a.registries, a.top_k, a.min_sim, a.out))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,170 @@
|
||||
"""#5b — Materialisierung der Capability-Schicht (Modell C, User-Entscheidung 2026-06-26).
|
||||
|
||||
Aus `cross_domain_relationships.json` (SHARED_CAPABILITY) + den 6 CRA-P1-Registries:
|
||||
- `obligations/capabilities.json` — Capability-Knoten: realized_by (n:m) + guidance_basis hochgezogen.
|
||||
- `obligations/cra_core.json` — 2 CORE-Obligations (Sicherheitsziele): attack_surface_minimization,
|
||||
software_integrity_protection (Modell C: KEINE eigene SecurityObjective-Klasse; das Ziel IST eine
|
||||
abstrakte CORE-Pflicht).
|
||||
- patcht DOMAIN-Obligations in ihren Registries: `specializes` (→CORE) + `objective_tags` (Vorwärts-
|
||||
Kompat zu Modell B: Tags, keine Klasse).
|
||||
- markiert `vuln_remediation_patching` als deprecated_alias von `provide_security_updates` (Merge).
|
||||
- `remote_access_data_export_protection` bleibt BEST_PRACTICE (Notiz: pending Data-Act-Scope).
|
||||
|
||||
Deterministisch. Lokal lauffähig (nur json). Danach export_join_keys neu (inkl. cra_core).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
REG_FILES = ["obligations/cra.json", "obligations/cra_authentication.json",
|
||||
"obligations/cra_logging.json", "obligations/cra_remote_access.json",
|
||||
"obligations/cra_updates.json"]
|
||||
|
||||
# Cluster-capability_name -> kanonische capability_id (access_control absichtlich gedroppt: zu schwach)
|
||||
CONSOLIDATE = {"mfa": "multi_factor_authentication", "session_management": "session_management",
|
||||
"tls_encryption": "transport_encryption", "mutual_tls": "transport_encryption",
|
||||
"tls_certificate_auth": "transport_encryption", "code_signing": "code_signing",
|
||||
"anomaly_detection": "security_monitoring_alerting"}
|
||||
CAP_META = {
|
||||
"multi_factor_authentication": ("Multi-Factor Authentication",
|
||||
"Mehrfaktor-Authentisierung als technische Faehigkeit (Besitz/Wissen/Inhaerenz)."),
|
||||
"session_management": ("Session Management",
|
||||
"Sichere Sitzungsverwaltung: Timeouts, Bindung, Re-Auth, Beendigung."),
|
||||
"transport_encryption": ("Transport Encryption",
|
||||
"Verschluesselter Transport (TLS, mutual-TLS, Zertifikats-Auth, VPN/Tunnel)."),
|
||||
"code_signing": ("Code & Update Signing",
|
||||
"Digitale Signatur + Integritaets-/Authentizitaetspruefung von Firmware/Software/Updates."),
|
||||
"security_monitoring_alerting": ("Security Monitoring & Alerting",
|
||||
"Anomalie-/Bedrohungserkennung und Alarmierung aus Logs/Telemetrie."),
|
||||
}
|
||||
CAP_ORDER = ["multi_factor_authentication", "session_management", "transport_encryption",
|
||||
"code_signing", "security_monitoring_alerting"]
|
||||
|
||||
# DOMAIN-Obligation -> (CORE-Ziel, objective_tags)
|
||||
SPECIALIZES = {
|
||||
"remote_access_attack_surface_min": ("attack_surface_minimization", ["attack_surface"]),
|
||||
"component_remote_interface_security": ("attack_surface_minimization", ["attack_surface"]),
|
||||
"signed_update_integrity": ("software_integrity_protection", ["integrity"]),
|
||||
"firmware_software_authentication": ("software_integrity_protection", ["integrity"]),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
regs = {f: json.load(open(f, encoding="utf-8")) for f in REG_FILES}
|
||||
idx = {}
|
||||
for f, r in regs.items():
|
||||
for o in r.get("obligations", []):
|
||||
idx[o["id"]] = (f, o)
|
||||
|
||||
# realized_by aus dem Artefakt
|
||||
art = json.load(open("obligations/cross_domain_relationships.json", encoding="utf-8"))
|
||||
realized = {cid: set() for cid in CONSOLIDATE.values()}
|
||||
for rr in art["raw_results"]:
|
||||
if rr["relation"] == "SHARED_CAPABILITY" and rr.get("capability_name") in CONSOLIDATE:
|
||||
cid = CONSOLIDATE[rr["capability_name"]]
|
||||
realized[cid].update([rr["a"], rr["b"]])
|
||||
|
||||
def guidance_for(ids):
|
||||
seen, out = set(), []
|
||||
for oid in ids:
|
||||
if oid in idx:
|
||||
for g in idx[oid][1].get("guidance_basis", []):
|
||||
k = (g.get("source", ""), g.get("anchor", ""))
|
||||
if k not in seen:
|
||||
seen.add(k)
|
||||
out.append(g)
|
||||
return out
|
||||
|
||||
caps = []
|
||||
for cid in CAP_ORDER:
|
||||
obls = sorted(realized[cid])
|
||||
name, desc = CAP_META[cid]
|
||||
caps.append({"capability_id": cid, "name": name, "description": desc,
|
||||
"type": "technical_capability", "realized_by": obls, "realizes_count": len(obls),
|
||||
"guidance_basis": guidance_for(obls),
|
||||
"domains": sorted({idx[o][1].get("family", "") for o in obls if o in idx}),
|
||||
"provenance": {"source": "cross_domain_relationships.json SHARED_CAPABILITY"}})
|
||||
|
||||
capabilities = {
|
||||
"schema_version": "capability_layer_v1", "model": "Modell C (docs-src/development/capability_model_v1.md)",
|
||||
"note": "Capability = technische Faehigkeit (regulierungs-agnostisch). realized_by = Obligations, "
|
||||
"die sie erfuellt (n:m). guidance_basis hier KANONISCH hochgezogen aus den realisierten "
|
||||
"Obligations (die Obligation-Kopien bleiben vorerst als Legacy; Strip = Folge-Cleanup). "
|
||||
"Sicherheitsziele sind KEINE Capabilities -> cra_core.json.",
|
||||
"dropped": {"access_control": "OVERLAP (credential_confidentiality <-> sbom_confidentiality), nicht materialisiert"},
|
||||
"candidate_capabilities_followup": ["automatic_update_delivery", "update_rollback",
|
||||
"trusted_update_source", "hash_verification", "secure_boot", "least_functionality",
|
||||
"credential_storage"],
|
||||
"capabilities": caps}
|
||||
json.dump(capabilities, open("obligations/capabilities.json", "w", encoding="utf-8"),
|
||||
ensure_ascii=False, indent=1)
|
||||
|
||||
core = [
|
||||
{"id": "attack_surface_minimization", "name": "Minimierung der Angriffsflaeche", "family": "core",
|
||||
"description": "Das Produkt minimiert seine Angriffsflaeche: unnoetige Funktionen/Ports/Dienste/"
|
||||
"Schnittstellen sind deaktiviert (Least Functionality).",
|
||||
"tier": "LEGAL_MINIMUM", "source_role": "LEGAL_BASIS", "applicability": "universal",
|
||||
"objective_tags": ["attack_surface"],
|
||||
"legal_basis": [{"source": "CRA", "anchor": "Annex I Part I (2)(j)",
|
||||
"citation": "limit attack surfaces, including external interfaces"}],
|
||||
"guidance_basis": [{"source": "NIST", "anchor": "CM-7 Least Functionality", "role": "best_practice"}],
|
||||
"specialized_by": ["remote_access_attack_surface_min", "component_remote_interface_security"],
|
||||
"primary_implementation": "NIST CM-7", "citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"},
|
||||
{"id": "software_integrity_protection", "name": "Schutz der Software-/Firmware-Integritaet", "family": "core",
|
||||
"description": "Das Produkt schuetzt Integritaet und Authentizitaet von Software/Firmware "
|
||||
"(Manipulationserkennung, Secure Boot, Signaturpruefung, Runtime-Integritaet).",
|
||||
"tier": "LEGAL_MINIMUM", "source_role": "LEGAL_BASIS", "applicability": "universal",
|
||||
"objective_tags": ["integrity"],
|
||||
"legal_basis": [{"source": "CRA", "anchor": "Annex I Part I (2)(f)",
|
||||
"citation": "protect the integrity of stored, transmitted or processed data, software and configuration"}],
|
||||
"guidance_basis": [{"source": "NIST", "anchor": "SI-7 Software, Firmware, and Information Integrity", "role": "best_practice"}],
|
||||
"specialized_by": ["signed_update_integrity", "firmware_software_authentication"],
|
||||
"realized_by_capabilities": ["code_signing"],
|
||||
"primary_implementation": "NIST SI-7", "citation_status": "pending_span_anchor",
|
||||
"review_status": "core_from_5b"},
|
||||
]
|
||||
json.dump({"schema_version": "obligation_registry_v1", "regulation": "CRA", "regulation_code": "CRA",
|
||||
"family": "core",
|
||||
"theme": "CORE Security Objectives (CRA Annex I als regulierungs-agnostische Sicherheitsziele)",
|
||||
"generated_by": "materialize_capabilities.py (#5b, Modell C)",
|
||||
"note": "CORE Legal Obligations = Sicherheitsziele (Modell C: KEINE eigene SecurityObjective-Klasse). "
|
||||
"DOMAIN-Obligations specializes-en hierauf. objective_tags = Vorwaerts-Kompat zu Modell B.",
|
||||
"citation_status": "pending_span_anchor", "obligations": core, "relationships": []},
|
||||
open("obligations/cra_core.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
|
||||
dirty = set()
|
||||
patched = []
|
||||
for oid, (coreid, tags) in SPECIALIZES.items():
|
||||
if oid in idx:
|
||||
f, o = idx[oid]
|
||||
o["specializes"] = coreid
|
||||
o["objective_tags"] = tags
|
||||
dirty.add(f)
|
||||
patched.append(oid)
|
||||
if "vuln_remediation_patching" in idx:
|
||||
f, o = idx["vuln_remediation_patching"]
|
||||
o["merged_into"] = "provide_security_updates"
|
||||
o["status"] = "deprecated_alias"
|
||||
o["merge_note"] = ("SAME_OBLIGATION (Cross-Domain-Review). Kanonisch: provide_security_updates "
|
||||
"((2)(c)/Art.13). ID bleibt als Alias aufloesbar; downstream provide_security_updates nutzen.")
|
||||
dirty.add(f)
|
||||
if "remote_access_data_export_protection" in idx:
|
||||
f, o = idx["remote_access_data_export_protection"]
|
||||
o["tier_note"] = ("Bleibt BEST_PRACTICE (NICHT LM) bis Data-Act/Export-Scope sauber ist (User #5b.6). "
|
||||
"Evtl. Capability-or-Procedure statt Obligation.")
|
||||
dirty.add(f)
|
||||
for f in dirty:
|
||||
json.dump(regs[f], open(f, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
|
||||
|
||||
print("capabilities.json:", len(caps), "Capabilities")
|
||||
for c in caps:
|
||||
print(f" {c['capability_id']:30s} realizes {c['realizes_count']:2d} | guidance {len(c['guidance_basis'])} | {c['domains']}")
|
||||
print("cra_core.json: 2 CORE (attack_surface_minimization, software_integrity_protection)")
|
||||
print("specializes gepatcht:", patched)
|
||||
print("alias: vuln_remediation_patching -> provide_security_updates")
|
||||
print("dirty registries:", sorted(dirty))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -22,6 +22,17 @@ SCOPES = {
|
||||
"logging": ["%logging%", "%protokollierung%", "%audit-log%", "%audit-trail%",
|
||||
"%ereignisprotokoll%", "%sicherheitsprotokoll%", "%audit-protokoll%",
|
||||
"%log-management%", "%sicherheitsereignis%protokoll%", "%audit-trail%"],
|
||||
"remote_access": ["%fernwartung%", "%fernzugriff%", "%fernzugang%", "%fernwartungs%",
|
||||
"%remote access%", "%remote maintenance%", "%remote management%",
|
||||
"%remote-wartung%", "%remote-zugriff%", "%remote-zugang%",
|
||||
"%sichere fernwartung%", "%fernsteuerung%"],
|
||||
"updates": ["%sicherheitsupdate%", "%security update%", "%sicherheits-update%",
|
||||
"%security patch%", "%sicherheitspatch%", "%patch-management%",
|
||||
"%patchmanagement%", "%patch management%", "%firmware-update%",
|
||||
"%firmware update%", "%software-update%", "%software update%",
|
||||
"%automatische aktualisierung%", "%update-mechanismus%",
|
||||
"%update-bereitstellung%", "%bereitstellung von updates%",
|
||||
"%sichere aktualisierung%", "%signierte update%", "%update-paket%"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user