Compare commits

..

39 Commits

Author SHA1 Message Date
Benjamin Admin f85fff4398 chore(ucca): re-sync data/obligations join-keys copy (93 -> 95)
CI / detect-changes (push) Successful in 4s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 4s
CI / validate-canonical-controls (push) Successful in 4s
CI / loc-budget (push) Successful in 17s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Successful in 59s
CI / iace-gt-coverage (push) Successful in 17s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
Registry grew to 95 (Capability materialization #5b added CORE obligations).
Keep the ai-sdk build-context copy current so obligation-status reflects the
live registry contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 01:02:02 +02:00
Benjamin Admin 3bcffaf52c Merge remote-tracking branch 'origin/main' into feat/advisor-status 2026-06-26 01:01:16 +02:00
Benjamin Admin 3a19affb67 ci(compliance): re-trigger scoped ai-sdk build + doc synced join-keys copy
Prior gitea push's build-ai-sdk failed on a transient registry push (arm64 built
clean on macmini; amd64 cross-compile is green) and last-build/main got poisoned
to that SHA, so a plain re-run scopes to nothing. A real touch in ai-compliance-sdk/
re-scopes the build. Also documents the synced-copy contract for
data/obligations/obligation_join_keys.json.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 01:00:53 +02:00
Benjamin Admin 2b985ad526 Merge origin/main (9aef5ecf) in capability-materialization 2026-06-26 00:54:43 +02:00
Benjamin Admin 4e761c1363 feat: #5b materialize capability layer (Modell C) — capabilities.json + cra_core.json
User-Entscheidung Modell C + objective_tags-Safeguard (Tags, keine Klasse). Deterministisch
via materialize_capabilities.py:
- obligations/capabilities.json: 5 Capabilities (multi_factor_authentication/session_management/
  transport_encryption/code_signing/security_monitoring_alerting), realized_by (n:m) +
  guidance_basis KANONISCH hochgezogen. access_control gedroppt (OVERLAP).
- obligations/cra_core.json: 2 CORE-Sicherheitsziele (attack_surface_minimization (2)(j)/CM-7 +
  software_integrity_protection (2)(f)/SI-7) -> fuellt den #4-NIST-Gap.
- DOMAIN specializes->CORE (remote_access_attack_surface_min, component_remote_interface_security,
  signed_update_integrity, firmware_software_authentication) + objective_tags.
- Merge: vuln_remediation_patching -> deprecated_alias von provide_security_updates.
- remote_access_data_export_protection bleibt BEST_PRACTICE (pending Data-Act-Scope).
- join_keys 93->95 (core 2). Bidirektional validiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 00:54:23 +02:00
Benjamin Admin 9aef5ecf6c Merge remote-tracking branch 'origin/main' into feat/advisor-status
CI / detect-changes (push) Successful in 6s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / validate-canonical-controls (push) Successful in 5s
CI / loc-budget (push) Successful in 18s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go (push) Successful in 58s
CI / iace-gt-coverage (push) Successful in 18s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
2026-06-26 00:26:56 +02:00
Benjamin Admin f6c5f4e0a9 fix(ucca): SI-2 evidence = config_export + test_report
Aligns provide_security_updates -> SI-2 evidence to the curated acceptance set:
config_export (secure-update mechanism config) + test_report (patch verification).
For "provide updates" the patch-verification test is more on-point than a vuln
scan; repo_scan stays on CM-7 for attack-surface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 00:26:29 +02:00
Benjamin Admin c72fd3eb5a Merge origin/main (Compliance endpoint+graph-loader 2341bda6) in capability-model 2026-06-26 00:24:37 +02:00
Benjamin Admin b0435f9885 docs: capability_model_v1.md (#5a) — Objektarten + Beziehungstypen, NICHT materialisiert
Schema-Papier statt capabilities.json (User-Entscheidung). Befund: die 8 SHARED_CAPABILITY-
Cluster zerfallen in Typ-1 (technische Capabilities: mfa/tls/code_signing/session/anomaly)
und Typ-2 (Sicherheitsziele: attack_surface_min/software_integrity = die #4-Gaps). Empfehlung
Modell C: Capability = EINZIGE neue Klasse; Sicherheitsziele = CORE Legal Obligations
(CORE/DOMAIN existiert bereits). Kanten-Graph (realized_by/specializes/...). guidance_basis
gehört konzeptionell an die Capability. 4 Entscheidungen offen (User). #5b Materialisierung
GEGATED auf Modell-Annahme — keine Daten verschoben.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 00:24:09 +02:00
Benjamin Admin 2341bda621 feat(ucca): adopt NIST obligation_ids (Registry Handoff #4, 10/10)
Registry filled proposed_obligation_id for the 3 NIST primary_implementation
controls: SI-7->signed_update_integrity, SI-2->provide_security_updates,
CM-7->remote_access_attack_surface_min. Adopted onto cra_nist.jsonl so the join
is now EXACT (obligation_id) instead of the coarse citation_unit fallback.
obligation-status now surfaces SI-2 under provide_security_updates; test extended.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 19:37:14 +02:00
Benjamin Admin 4634cc09d0 Merge remote-tracking branch 'origin/main' into feat/advisor-status 2026-06-25 19:31:20 +02:00
Benjamin Admin d4df1e01df feat(compliance): GET /sdk/v1/compliance/obligation-status (file-backed graph)
Vertical slice over the Compliance Execution Graph: obligation_id -> accepted
controls -> required evidence -> status. NEVER auto-asserts fulfillment - with
no evidence collection wired (MVP), a mapped obligation is "not_assessed" and
every required evidence is "missing". Fail-closed: no id -> 400; unknown id ->
unknown_obligation; mapped-but-no-control -> unmapped; graph not loaded -> 503.

- ComplianceGraphHandlers (separate from the DB-backed ObligationsHandlers):
  loads Registry join keys + accepted control mappings + evidence once at start.
- LoadComplianceGraph: candidate-path resolution across dev/container/test.
- Data plumbing: Dockerfile now COPYs data/{control_mappings,evidence_requirements,
  obligations}; data/obligations/obligation_join_keys.json is a SYNCED COPY of the
  repo-root Registry contract (re-sync on Registry growth).
- Table-driven handler test (mapped/unmapped/unknown/400 + no-fulfillment-claim).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 19:29:37 +02:00
Benjamin Admin ed31fdc0df fill: NIST primary_implementation -> obligation_id (Handoff #4, jetzt 10/10)
SI-2 -> provide_security_updates (stark, (2)(c)/Art.13) · SI-7 -> signed_update_integrity
(update-scoped) · CM-7 -> remote_access_attack_surface_min (remote-scoped). Validiert gegen
Registries (join_keys 93). 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 Obligations (User-Entscheidung).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 19:15:49 +02:00
Benjamin Admin 5412bf0ba3 Merge origin/main (NIST-Export e46e74dd) in cross-domain-discovery 2026-06-25 19:13:21 +02:00
Benjamin Admin 8a9d5e7c4d Merge remote-tracking branch 'origin/main' into feat/advisor-status 2026-06-25 19:12:41 +02:00
Benjamin Admin 01956ee690 feat: cross-domain relationship discovery — Capability-Schicht-Entwurf (CRA P1)
Stufe 1+2 der Ontologie-Entdeckung (User-Schaerfung #54): nicht Aehnlichkeit sondern
STRUKTURELLE Beziehung. 93 Obligations -> BGE-M3 -> 101 cross-family Paare -> Opus
klassifiziert in 8 Kategorien (genau eine je Paar).
- scripts/obligation_discovery/cross_domain_pairs.py (Stufe 1, key-frei)
- scripts/obligation_discovery/classify_relationships.py (Stufe 2, Opus)
- obligations/cross_domain_relationships.json: 16 SHARED_CAPABILITY -> 8 Capabilities
  (mfa/session/transport-tls/code_signing/anomaly_detection), 23 SUPPORTED_BY
  (Hubs: vuln_identification_inventory<-SBOM-Familie 5x, vuln_remediation_patching 5x),
  1 SAME_OBLIGATION (vuln_remediation_patching == provide_security_updates, MERGE-Kandidat),
  42 OVERLAP_ONLY sauber verworfen. Erstentwurf der Capability-Schicht (Phase 4).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 19:12:17 +02:00
Benjamin Admin e46e74ddbb feat(bridge): export 3 CRA->NIST controls (primary_implementation) for obligation_id
Adds SI-7/SI-2/CM-7 to controls_for_obligation_mapping.json (7 OWASP -> 10),
mapping_type=primary_implementation (the single canonical control per obligation).
proposed_obligation_id left empty for the Registry to assign. Notes aligned to the
updates family (join_keys 93): SI-2 -> provide_security_updates (strong),
SI-7 -> signed_update_integrity (partial; SI-7 broader), CM-7 ->
remote_access_attack_surface_min (partial; CM-7 broader).

Origin-only (data/tooling; backend does not load obligations/* at runtime) -> no Orca.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 18:57:17 +02:00
Benjamin Admin 63d65af41b feat(ucca): persist 3 CRA->NIST mappings (primary_implementation) + evidence
CRA Annex I Part I (2)(e)/(2)(l)/(2)(i) had no clean OWASP target (rejected:
"Mapping ueber NIST/BSI erforderlich"). Their NIST home, curated + accepted:
  (2)(e) Integritaet     -> SI-7 (Software/Firmware/Information Integrity)
  (2)(l) Sichere Updates -> SI-2 (Flaw Remediation)
  (2)(i) Angriffsflaeche -> CM-7 (Least Functionality)

New mapping_type=primary_implementation = the single canonical control per
obligation (stronger than implements/supports); related controls (SC-3(3),
RA-5, AC-6, SI-16, ...) follow later as supports.

Evidence is framework-AGNOSTIC: SI-7/SI-2/CM-7 reuse the shared evidence_type
catalog (config_export/test_report/repo_scan) - same types carry CRA, NIST,
ISO 27001, IEC 62443, BSI. (framework,control) is only the link, not the type.

obligation_id left empty: the Obligation Registry assigns it (exported via
controls_for_obligation_mapping.json), then we adopt. go test ./internal/ucca green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 18:52:35 +02:00
Benjamin Admin 8937f105ea feat(bridge): security-updates obligation cut (CRA Annex I (2)(c)/Art 13) — 9 obligations
- obligations/cra_updates.json: 9 (6 LEGAL_MINIMUM + 3 BEST_PRACTICE), Beziehungen.
  Pipeline 670->318 micro->15 review-units -> Opus-Synthese. Synthese gut kalibriert ->
  light review (KEINE Hart-Re-Tier, vs Auth/Remote-Access). out_of_scope M4/M7.
  5 capability_candidate-Marker (signed/trusted/automatic/rollback/testing) fuer
  Phase-4-Capability-Pruefung. Anker approximativ (curation.anchor_quality).
- obligation_join_keys.json: 84 -> 93 (updates 9). Alle 6 CRA-P1-Domaenen abgedeckt.
- precluster.py: updates-Scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 18:51:09 +02:00
Benjamin Admin 1584b8fb2f feat(bridge): remote-access obligation cut (CRA Annex I) — 18 obligations
- obligations/cra_remote_access.json: 18 (5 LEGAL_MINIMUM outcomes + 13 BEST_PRACTICE),
  15 Beziehungen. Two-stage clustering 445->209 micro->27 review-units -> Opus-Synthese.
  Synthese vergab 14 LM -> key-free re-tier nach Auth-Regel (Mechanismen MFA/Session/VPN/
  insecure-protocol/OT/Wartungs-Governance/temp/data-export/component -> BEST_PRACTICE +
  supports-Kante zur Eltern-LM). out_of_scope M5/M11 = physische Maschinen-Fernsteuerung
  (MaschinenVO 2023/1230). Anker approximativ (siehe curation.anchor_quality).
- obligation_join_keys.json: 66 -> 84 (remote_access 18).
- precluster.py: remote_access-Scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 18:37:10 +02:00
Benjamin Admin 2301fb2122 feat(ucca): adopt obligation_id + harden join to semantic (step 3 core)
The Obligation Registry filled proposed_obligation_id (7/7) + cut the logging
family (obligations 47->66). Adopted obligation_id onto our 7 accepted CRA->OWASP
mappings; the join now prefers the EXACT obligation_id over the coarse
citation_unit (which stays as fallback for not-yet-adopted rows).

Effect: semantic coverage 2->4 (user_authentication_required,
credential_confidentiality_protection, auth_key_management,
event_logging_security_events). Befund 1 resolved: V11.2.1 crypto now sits under
credential_confidentiality_protection, not user_authentication_required.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 12:18:34 +02:00
Benjamin Admin 4aa6aa9812 Merge remote-tracking branch 'origin/main' into feat/advisor-status 2026-06-25 12:04:47 +02:00
Benjamin Admin a53d67a35a feat(bridge): logging/audit obligation cut (CRA Annex I (2)(k)) + 7/7 control mapping
- obligations/cra_logging.json: 19 obligations (6 LEGAL_MINIMUM auf (2)(k) korrekt
  verankert, 13 BEST_PRACTICE), 13 Beziehungen; out_of_scope M8/M5/M81 (AI-Act/FRT/PIN).
  Two-stage clustering (2601->1361 micro->100 review-units) -> Opus-Synthese -> Kuration.
- controls_for_obligation_mapping.json: V16.1.1/V16.3.3/V16.3.4 -> event_logging_security_events
  (Umbrella-LM; spezifische Alternativen via ASVS-Control-Text). Jetzt 7/7 gefuellt.
- obligation_join_keys.json: 47->66 obligation_ids (logging family).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 11:57:16 +02:00
Benjamin Admin 3259984d1c Fill semantic control->obligation_id (4/7; V16 pending logging cut)
V6.x->user_authentication_required, V11.2.1->credential_confidentiality_protection,
V11.7.1->auth_key_management; semantisch (NICHT CRA-Anker, die sind approximativ).
V16.x pending bis Logging-Cut. anchor_quality_note dokumentiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 11:44:56 +02:00
Benjamin Admin 5e3ed4071b Merge remote-tracking branch 'origin/main' into feat/obligation-aggregation 2026-06-25 11:41:00 +02:00
Benjamin Admin c090617afd Add logging scope to precluster (logging cut)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 11:40:59 +02:00
Benjamin Admin c5ecfa8f6c feat(bridge): export 7 accepted CRA->OWASP controls for obligation_id proposal
CI / detect-changes (push) Successful in 5s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 9s
CI / validate-canonical-controls (push) Successful in 5s
CI / loc-budget (push) Successful in 23s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
obligations/controls_for_obligation_mapping.json — the Compliance Execution
Graph's accepted controls (V6 auth / V11 crypto / V16 logging) handed to the
Obligation Registry to propose the SEMANTIC control->obligation_id, replacing
the coarse citation_unit interim join (Befund 1). Registry fills
proposed_obligation_id; we then adopt it into control_mapping.obligation_id.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 11:36:57 +02:00
Benjamin Admin 417bcda68c feat(ucca): Advisor obligation-status Durchstich (step 3 complete)
AssessObligationStatus traverses obligation_id -> (citation_unit) -> accepted
controls -> required evidence -> status (erfuellt|offen|unklar). Evidence
presence is a callback; MVP passes nil (nothing collected yet) -> offen.
citation_spans = "pending" until the Legal-Knowledge-Graph session attaches
them. This is the vertical slice that makes the graph a product feature:
"CRA obligation fulfilled because evidence X/Y/Z is present", not "a doc exists".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 11:15:57 +02:00
Benjamin Admin 86d1473a6a feat(ucca): obligation-join loader + citation_unit bridge + coverage report
Consumes the cross-session contract obligations/obligation_join_keys.json (47
obligation_ids). Interim bridge = citation_unit (our source_norm <-> registry
citation_units), to be hardened to the stable obligation_id (field now optional
on ControlMapping).

ComputeObligationCoverage joins the 47 registry obligations to our accepted
control mappings: covered=2 (user_authentication_required, firmware_software_
authentication), mapped_rejected=3 ((2)(e) -> our OWASP mappings rejected,
route via NIST/BSI), uncovered=42. This coverage signal is the feedback to the
Obligation session for what to cut/refine next.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 11:10:53 +02:00
Benjamin Admin 9e0a9ccef4 Add obligation_id join-key contract (cross-session bridge)
CI / detect-changes (push) Successful in 5s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 8s
CI / validate-canonical-controls (push) Successful in 7s
CI / loc-budget (push) Successful in 19s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
Macht meine Seite des Cross-Session-Vertrags konkret: obligation_id ist der stabile Join-Key
zwischen Legal Knowledge Graph (citation_spans -> obligation_id) und Compliance Execution Graph
(control_mapping.source_norm -> obligation_id). Export aller 47 obligation_ids (CRA: 11 sbom +
7 vuln + 29 auth) mit citation_units als Interim-Brücke. Disziplin: obligation_id nie neu
vergeben (re-link, Pendant zu span_id/control_uuid).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 10:29:29 +02:00
Benjamin Admin 7e1c3668bf Merge remote-tracking branch 'origin/main' into feat/obligation-aggregation
CI / detect-changes (push) Successful in 8s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 5s
CI / validate-canonical-controls (push) Successful in 4s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
2026-06-25 10:15:25 +02:00
Benjamin Admin ab3cb86b1c feat(ucca): Evidence-Requirement model (step A)
CI / detect-changes (push) Successful in 5s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / validate-canonical-controls (push) Successful in 5s
CI / loc-budget (push) Successful in 20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Successful in 1m5s
CI / iace-gt-coverage (push) Successful in 17s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
The last edge of the compliance graph: what concrete, fresh evidence proves a
framework control is met (config_export/test_report/sbom/audit_log/pentest/...
from github/ci/scanner/manual_upload, with a freshness requirement).

Seeded for all 7 accepted CRA->OWASP controls (Auth/Crypto/Logging). A graph
test enforces connectivity: every accepted control must carry >=1 required
evidence — no dangling node in Obligation -> Control -> Evidence.

This is what will let the Advisor state "the CRA requirement is fulfilled" from
present evidence, not from the mere existence of a document.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 10:06:09 +02:00
Benjamin Admin 0db0e9a129 feat(ucca): curate CRA Annex I -> OWASP mappings (review B)
7 accepted, 13 rejected (reviewed_by=benjamin, 2026-06-25). The accepted set is
the first audited ground truth of the compliance graph:
  (2c) Zugriff   -> V6.3.1, V6.1.1   (Auth)
  (2d) Crypto    -> V11.2.1, V11.7.1 (corrected from the retriever's wrong V14)
  (2k) Logging   -> V16.3.3, V16.3.4, V16.1.1

Rejected stay as audit trail. (2e) integrity, (2l) updates, (2i) attack surface
rejected with reason "OWASP ASVS not the right target standard, map via NIST/BSI"
— architectural proof for the multi-framework framework_* layer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 10:01:06 +02:00
Benjamin Admin 53ea388ea0 refactor(ucca): control-mapping model per review feedback
- DROP confidence from the persisted mapping: a curated mapping is a
  professional statement, not an AI guess (retriever score -> rationale only).
- ADD mapping_status (candidate|accepted|rejected|superseded) — the review state.
- ADD audit trail (reviewed_by/review_date/review_reason); accepted/rejected
  fail-closed without it.
- EXTEND mapping_type: + implements, + contradicts.
- Advisor truth = mapping_status=accepted (acceptedOnly filter).
- migrate the 18 CRA->OWASP rows to mapping_status=candidate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 09:50:37 +02:00
Benjamin Admin e5cce9caff Extend advisor proof with procedure→evidence chain
Vollständige Begründungskette aus der Registry: Rechtsgrundlage → Obligation → Procedure
→ Controls → Evidence → Antwort. Join cra.json × cra_procedures.json, deterministisch, kein LLM.

SBOM-Beweis: 7 Pflichten je mit CRA-Rechtsgrundlage + Procedure (wie umgesetzt) + Controls
(Prüfung) + aggregierte Required Evidence; 4 Best-Practice (Guidance OWASP/NIST/ENISA);
Beziehung sbom_*→supports→vuln_identification; citation 7/7 pending_span_anchor.

Der Unterschied zu RAG sichtbar: RAG beantwortet — BreakPilot begründet UND operationalisiert.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 09:44:27 +02:00
Benjamin Admin 2f3c98fbe0 feat(ucca): first CRA Annex I -> OWASP retriever candidates (step 3)
18 retriever_candidate mappings generated via the sdk-dev control-intent
retriever. All marked retriever_candidate (NOT curated truth) — the review
step turns the good ones into human_curated.

Empirical validation of the A-decision: the retriever proposes, but produces
wrong candidates (e.g. encryption -> V14 Config instead of V11 Crypto;
V14.2.4 over-appears) that only human review catches. Review notes inline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 09:36:53 +02:00
Benjamin Admin d987e4fde6 feat(ucca): persisted Control-Mapping data model (Obligation -> framework control)
Versioned JSONL store + Go model for Regulation->Control mappings, per the
A-decision: the retriever only PROPOSES candidates; the curated mapping is the
audited truth the Advisor uses at runtime, never re-invented per query.

- ControlMapping struct (source_norm/source_role/target_framework/target_control/
  mapping_type/confidence/provenance/rationale/version)
- enum validation (rule layer), fail-closed loader, forward+reverse index,
  curated-only filter (IsCurated)
- seed: 2 retriever_candidate rows CRA Annex I -> OWASP ASVS (not yet curated)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 09:32:15 +02:00
Benjamin Admin 67dba5f641 Add CRA procedure model (SBOM + Vuln)
Schließt die Lücke Obligation→Procedure→Control→Evidence (Schritt 3, Compliance-OS-Ebene).
Procedure = Umsetzungs-/Nachweisebene EINER Obligation, KEINE neue Pflicht (LEGAL_MINIMUM
bleibt an der Obligation; Procedure beschreibt Umsetzung; Evidence belegt sie).

- 11 Procedures (5 SBOM + 6 Vuln), 2 Worked Examples; source_role=procedural_requirement
  (Konvergenz mit der Legal-Knowledge-Engine der anderen Session)
- fulfills_obligations[] referenziert die cra.json-Obligations (alle gültig, volle Abdeckung)
- steps/controls/evidence je Procedure; KEINE tier/legal_basis-Felder (kein Pflicht-Duplikat)
- citation_spans: [] / pending_span_anchor (Join folgt mit dem zitierfähigen Re-Ingest)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 09:28:40 +02:00
Benjamin Admin db2fd9d8e9 Add obligation advisor proof (P3)
Demonstriert den Produktnutzen der Registry: obligation-basierte Antwort statt RAG-Text.
Frage → Pflicht (LEGAL_MINIMUM + Rechtsgrundlage + Applicability) ⊥ Best Practice
(guidance_basis) ⊥ Nachweise (evidence_facets + member controls) + Beziehungen, deterministisch
aus obligations/cra.json (kein LLM, zitierfähig).

Beleg (SBOM, Maschinenbauer): JA — 7 CRA-Mindestpflichten + 4 Best-Practice (OWASP/NIST/ENISA);
sbom_* supports vuln_identification_inventory.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-25 09:06:34 +02:00
37 changed files with 14001 additions and 3 deletions
+8
View File
@@ -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 ADOPTIERT (2026-06-25, Registry-Handoff #4): SI-7->signed_update_integrity, SI-2->provide_security_updates, CM-7->remote_access_attack_surface_min. Join jetzt exakt (nicht citation_unit). CM-7 ist breiter als remote_access — Registry waehlte die vorhandene Obligation; Verfeinerung via Capability/cross_domain spaeter.
{"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": "signed_update_integrity"}
{"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": "remote_access_attack_surface_min"}
@@ -0,0 +1,24 @@
// Control-Mapping: CRA Annex I -> OWASP ASVS 5.0. Eine Zeile = ein Mapping (Schema: ControlMapping).
// Reviewt 2026-06-25 (benjamin): 7 accepted, 13 rejected. accepted = Audit-Wahrheit (Advisor nutzt acceptedOnly).
// rejected bleiben als Audit-Spur ("warum verworfen"). KEIN confidence — kuratiert = fachliche Feststellung.
// Architekturbeweis: CRA -> OWASP fuer AppSec/Auth/Crypto/Logging; Ops/Update/Attack-Surface/Integritaet -> NIST/BSI.
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.3.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V6 = Authentication.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V6 = Authentication, sauberer Treffer fuer Zugriffsschutz/Authentisierung.", "version": "2026-06-25", "obligation_id": "user_authentication_required"}
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.1.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V6 = Authentication.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V6 = Authentication, sauberer Treffer fuer Zugriffsschutz/Authentisierung.", "version": "2026-06-25", "obligation_id": "user_authentication_required"}
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V11.2.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V11 = Cryptography.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Korrektur von V14: V11 = Cryptography, richtiger Bereich fuer Verschluesselung.", "version": "2026-06-25", "obligation_id": "credential_confidentiality_protection"}
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V11.7.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V11.7 = Key Management.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "Korrektur von V14: V11.7 = Key Management fuer Verschluesselung/Schluesselverwaltung.", "version": "2026-06-25", "obligation_id": "auth_key_management"}
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.3.3", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25", "obligation_id": "event_logging_security_events"}
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.3.4", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25", "obligation_id": "event_logging_security_events"}
{"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V16.1.1", "mapping_type": "supports", "mapping_status": "accepted", "provenance": "human_curated", "rationale": "V16 = Security Logging.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V16 = Logging, sauberer Treffer fuer sicherheitsrelevante Ereignisse.", "version": "2026-06-25", "obligation_id": "event_logging_security_events"}
{"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, kein Auth — verworfen.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, Crypto gehoert zu V11 — verworfen.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.3.2", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, Crypto gehoert zu V11 — verworfen.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.3", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "V14 = Config, Crypto gehoert zu V11 — verworfen.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(e) — Integritaet", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(e) — Integritaet", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V1.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(e) — Integritaet", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.1.1", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V14.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V2.4.1", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(l) — Sichere Updates", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.1.1", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V6.1.1", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V15.3.3", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
{"source_norm": "CRA Annex I Part I (2)(i) — Angriffsflaeche minimieren", "source_role": "operational_requirement", "target_framework": "OWASP ASVS", "target_control": "V8.2.4", "mapping_type": "related", "mapping_status": "rejected", "provenance": "human_curated", "rationale": "Retriever-Kandidat.", "reviewed_by": "benjamin", "review_date": "2026-06-25", "review_reason": "OWASP ASVS ist hier nicht der passende Zielstandard. Mapping ueber NIST/BSI erforderlich.", "version": "2026-06-25"}
@@ -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": "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-7", "evidence_type": "test_report", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "Signatur-/Integritaets-Verifikationstest (CI) belegt funktionierende Manipulationserkennung.", "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": "quarterly", "required": false, "rationale": "Angriffsflaechen-Scan (offene Ports/Dienste) — vertiefend, nicht Pflicht je Release.", "version": "2026-06-25"}
@@ -0,0 +1,16 @@
// Evidence-Requirements je OWASP-ASVS-Control (Schema: EvidenceRequirement). Eine Zeile = eine geforderte Evidenz.
// Autoriert/kuratiert (nicht Retriever). Der Advisor kann eine CRA-Anforderung erst dann als erfuellt melden,
// wenn die required Evidenzen der gemappten, accepted Controls vorliegen + frisch genug sind.
// Stand 2026-06-25, Basis: die 7 accepted CRA->OWASP-Mappings (Auth V6, Crypto V11, Logging V16).
{"framework": "OWASP ASVS", "control": "V6.3.1", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "IAM-/Zugriffskonfiguration als Nachweis der Authentisierungs-Anforderung.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V6.3.1", "evidence_type": "test_report", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "Automatisierter Zugriffstest (CI) belegt funktionierende Zugriffskontrolle.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V6.3.1", "evidence_type": "pentest", "evidence_source": "manual_upload", "freshness_requirement": "annually", "required": false, "rationale": "Jaehrlicher PenTest der Authentisierung — vertieft, aber nicht Pflicht je Release.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V6.1.1", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Rollenmodell/Auth-Architektur als Nachweis.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V11.2.1", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Krypto-Konfiguration (zugelassene Algorithmen) als Nachweis der Verschluesselung.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V11.2.1", "evidence_type": "sbom", "evidence_source": "ci", "freshness_requirement": "per_release", "required": true, "rationale": "SBOM weist die eingesetzten Krypto-Bibliotheken/-Versionen nach.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V11.7.1", "evidence_type": "policy", "evidence_source": "manual_upload", "freshness_requirement": "annually", "required": true, "rationale": "Key-Management-Policy (Rotation, Aufbewahrung) als organisatorischer Nachweis.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V11.7.1", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Konfiguration der Schluesselverwaltung als technischer Nachweis.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V16.3.3", "evidence_type": "audit_log", "evidence_source": "ci", "freshness_requirement": "continuous", "required": true, "rationale": "Security-Audit-Logs belegen, dass sicherheitsrelevante Ereignisse protokolliert werden.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V16.3.3", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Logging-Konfiguration als Nachweis der erfassten Ereignisarten.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V16.3.4", "evidence_type": "audit_log", "evidence_source": "ci", "freshness_requirement": "continuous", "required": true, "rationale": "Security-Audit-Logs.", "version": "2026-06-25"}
{"framework": "OWASP ASVS", "control": "V16.1.1", "evidence_type": "config_export", "evidence_source": "github", "freshness_requirement": "per_release", "required": true, "rationale": "Logging-Architektur-Konfiguration als Nachweis.", "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,95 @@
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},
{"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)
}
}
}
+8 -1
View File
@@ -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
}
+2
View File
@@ -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)
}
}
@@ -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
}
@@ -0,0 +1,71 @@
package ucca
// ObligationStatus is the Advisor's vertical slice over the compliance graph for ONE legal
// obligation: which accepted controls satisfy it, what evidence they require, what's missing,
// and the resulting status. The point is "the required evidence is (not) present", not "a
// document exists". citation_spans is pending until the Legal-Knowledge-Graph session attaches
// them to the obligation (the upper half of the bridge).
type ObligationStatus struct {
ObligationID string `json:"obligation_id"`
LegalBasis []string `json:"legal_basis"` // the obligation's citation_units
Status string `json:"status"` // erfuellt | offen | unklar
Controls []ObligationControlStatus `json:"controls"`
CitationSpans string `json:"citation_spans"` // "pending" until the registry fills them
}
// ObligationControlStatus is one control under an obligation with its evidence picture.
type ObligationControlStatus struct {
Framework string `json:"framework"`
Control string `json:"control"`
MappingType string `json:"mapping_type"`
RequiredEvidence []EvidenceRequirement `json:"required_evidence"`
MissingEvidence []EvidenceRequirement `json:"missing_evidence"`
}
// AssessObligationStatus traverses obligation_id -> (citation_unit) -> accepted Controls ->
// required Evidence -> Status. hasEvidence reports whether a given (framework, control,
// evidence_type) is already collected; pass nil in the MVP (no collection yet) -> everything
// required is missing and the status is "offen". Unknown or unmapped obligation -> "unklar".
func AssessObligationStatus(joins *ObligationJoinKeys, mappings *ControlMappingSet, evidence *EvidenceRequirementSet, obligationID string, hasEvidence func(framework, control, evidenceType string) bool) ObligationStatus {
ob := joins.FindObligation(obligationID)
if ob == nil {
return ObligationStatus{ObligationID: obligationID, Status: "unklar", CitationSpans: "pending"}
}
st := ObligationStatus{
ObligationID: obligationID,
LegalBasis: ob.CitationUnits,
CitationSpans: "pending",
Controls: []ObligationControlStatus{},
}
ctrls := AcceptedControlsForObligation(*ob, mappings)
if len(ctrls) == 0 {
st.Status = "unklar" // no accepted control reaches it — we cannot assess
return st
}
anyMissing := false
for _, m := range ctrls {
req := evidence.RequiredFor(m.TargetFramework, m.TargetControl)
missing := make([]EvidenceRequirement, 0, len(req))
for _, e := range req {
if hasEvidence == nil || !hasEvidence(e.Framework, e.Control, e.EvidenceType) {
missing = append(missing, e)
}
}
if len(missing) > 0 {
anyMissing = true
}
st.Controls = append(st.Controls, ObligationControlStatus{
Framework: m.TargetFramework,
Control: m.TargetControl,
MappingType: m.MappingType,
RequiredEvidence: req,
MissingEvidence: missing,
})
}
if anyMissing {
st.Status = "offen"
} else {
st.Status = "erfuellt"
}
return st
}
@@ -0,0 +1,59 @@
package ucca
import "testing"
func loadGraph(t *testing.T) (*ObligationJoinKeys, *ControlMappingSet, *EvidenceRequirementSet) {
t.Helper()
joins, err := LoadObligationJoinKeys("../../../obligations/obligation_join_keys.json")
if err != nil {
t.Fatalf("join keys: %v", err)
}
maps, err := LoadControlMappings("../../data/control_mappings")
if err != nil {
t.Fatalf("mappings: %v", err)
}
ev, err := LoadEvidenceRequirements("../../data/evidence_requirements")
if err != nil {
t.Fatalf("evidence: %v", err)
}
return joins, maps, ev
}
func TestAssessObligationStatus(t *testing.T) {
joins, maps, ev := loadGraph(t)
// covered obligation, no evidence collected yet (MVP) -> offen
st := AssessObligationStatus(joins, maps, ev, "user_authentication_required", nil)
if st.Status != "offen" {
t.Errorf("want offen, got %q", st.Status)
}
if len(st.Controls) == 0 {
t.Fatal("expected controls for a covered obligation")
}
for _, c := range st.Controls {
if len(c.MissingEvidence) != len(c.RequiredEvidence) {
t.Error("MVP: all required evidence should be missing")
}
}
t.Logf("DURCHSTICH user_authentication_required: status=%s legal_basis=%v citation_spans=%s",
st.Status, st.LegalBasis, st.CitationSpans)
for _, c := range st.Controls {
t.Logf(" %s %s (%s): %d required evidence, %d missing", c.Framework, c.Control, c.MappingType, len(c.RequiredEvidence), len(c.MissingEvidence))
}
// all evidence present -> erfuellt
st2 := AssessObligationStatus(joins, maps, ev, "user_authentication_required", func(f, c, et string) bool { return true })
if st2.Status != "erfuellt" {
t.Errorf("want erfuellt with all evidence present, got %q", st2.Status)
}
// uncovered obligation (no accepted control reaches it) -> unklar
if st3 := AssessObligationStatus(joins, maps, ev, "sbom_creation", nil); st3.Status != "unklar" {
t.Errorf("uncovered sbom_creation: want unklar, got %q", st3.Status)
}
// unknown obligation_id -> unklar
if st4 := AssessObligationStatus(joins, maps, ev, "does_not_exist", nil); st4.Status != "unklar" {
t.Errorf("unknown obligation: want unklar, got %q", st4.Status)
}
}
@@ -0,0 +1,152 @@
package ucca
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// ControlMapping is one persisted, versioned, REVIEWABLE link from a legal
// obligation/requirement to a concrete framework control — a node in the curated
// compliance graph (Regulation -> Obligation -> Control -> Evidence). The retriever only
// PROPOSES candidates (mapping_status=candidate); a human/rule decision turns the good ones
// into mapping_status=accepted, which is the audited truth the Advisor uses at runtime.
//
// There is intentionally NO probabilistic "confidence" field: once curated, a mapping is a
// professional statement, not an AI guess. The retriever's score lives only in the rationale
// of a candidate, never as structured truth.
type ControlMapping struct {
SourceNorm string `json:"source_norm"` // e.g. "CRA Annex I Part I (2)(c)"
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"` // 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
Rationale string `json:"rationale"`
ReviewedBy string `json:"reviewed_by,omitempty"` // who decided (human or rule id)
ReviewDate string `json:"review_date,omitempty"` // YYYY-MM-DD
ReviewReason string `json:"review_reason,omitempty"`
Version string `json:"version"`
}
// Allowed enum values — the deterministic "rule" layer that keeps the curated store clean.
var (
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}
)
// Validate checks required fields + enum membership, and enforces the audit trail: any
// human/rule DECISION (accepted/rejected) must carry who/when/why. Fail-closed at load.
func (m ControlMapping) Validate() error {
switch {
case m.SourceNorm == "":
return fmt.Errorf("control mapping: source_norm required")
case m.TargetFramework == "":
return fmt.Errorf("control mapping: target_framework required")
case m.TargetControl == "":
return fmt.Errorf("control mapping: target_control required")
case !mappingTypeValues[m.MappingType]:
return fmt.Errorf("control mapping: invalid mapping_type %q", m.MappingType)
case !mappingStatusValues[m.MappingStatus]:
return fmt.Errorf("control mapping: invalid mapping_status %q", m.MappingStatus)
case !provenanceValues[m.Provenance]:
return fmt.Errorf("control mapping: invalid provenance %q", m.Provenance)
}
if m.MappingStatus == "accepted" || m.MappingStatus == "rejected" {
if m.ReviewedBy == "" || m.ReviewDate == "" || m.ReviewReason == "" {
return fmt.Errorf("control mapping %s->%s: status %q requires reviewed_by + review_date + review_reason (audit trail)",
m.SourceNorm, m.TargetControl, m.MappingStatus)
}
}
return nil
}
// IsAccepted reports whether this mapping is the active audited truth.
func (m ControlMapping) IsAccepted() bool { return m.MappingStatus == "accepted" }
// ControlMappingSet is the loaded, indexed mapping store (forward + reverse lookup).
type ControlMappingSet struct {
All []ControlMapping
bySourceNorm map[string][]ControlMapping
byControl map[string][]ControlMapping
}
func controlKey(framework, control string) string { return framework + ":" + control }
// ControlsFor returns the controls mapped to a source norm. acceptedOnly restricts to the
// audited truth (what the Advisor may treat as fact).
func (s *ControlMappingSet) ControlsFor(sourceNorm string, acceptedOnly bool) []ControlMapping {
return filterAccepted(s.bySourceNorm[sourceNorm], acceptedOnly)
}
// ObligationsFor returns the norms mapped to a framework control (reverse lookup).
func (s *ControlMappingSet) ObligationsFor(framework, control string, acceptedOnly bool) []ControlMapping {
return filterAccepted(s.byControl[controlKey(framework, control)], acceptedOnly)
}
func filterAccepted(in []ControlMapping, acceptedOnly bool) []ControlMapping {
if !acceptedOnly {
return in
}
out := make([]ControlMapping, 0, len(in))
for _, m := range in {
if m.IsAccepted() {
out = append(out, m)
}
}
return out
}
// LoadControlMappings reads every *.jsonl file under dir (one mapping per line; blank and
// //-prefixed lines ignored), validates each row, and builds the index. An invalid row
// aborts the whole load — fail-closed, because this is the audit truth, not best-effort.
func LoadControlMappings(dir string) (*ControlMappingSet, error) {
files, err := filepath.Glob(filepath.Join(dir, "*.jsonl"))
if err != nil {
return nil, err
}
set := &ControlMappingSet{
bySourceNorm: map[string][]ControlMapping{},
byControl: map[string][]ControlMapping{},
}
for _, f := range files {
fh, err := os.Open(f)
if err != nil {
return nil, err
}
sc := bufio.NewScanner(fh)
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
line := 0
for sc.Scan() {
line++
raw := strings.TrimSpace(sc.Text())
if raw == "" || strings.HasPrefix(raw, "//") {
continue
}
var m ControlMapping
if err := json.Unmarshal([]byte(raw), &m); err != nil {
fh.Close()
return nil, fmt.Errorf("%s:%d: %w", f, line, err)
}
if err := m.Validate(); err != nil {
fh.Close()
return nil, fmt.Errorf("%s:%d: %w", f, line, err)
}
set.All = append(set.All, m)
set.bySourceNorm[m.SourceNorm] = append(set.bySourceNorm[m.SourceNorm], m)
k := controlKey(m.TargetFramework, m.TargetControl)
set.byControl[k] = append(set.byControl[k], m)
}
fh.Close()
if err := sc.Err(); err != nil {
return nil, err
}
}
return set, nil
}
@@ -0,0 +1,85 @@
package ucca
import (
"os"
"path/filepath"
"testing"
)
func TestControlMapping_Validate(t *testing.T) {
candidate := ControlMapping{SourceNorm: "CRA Annex I", TargetFramework: "OWASP ASVS", TargetControl: "V6.3.1", MappingType: "supports", MappingStatus: "candidate", Provenance: "retriever_candidate"}
if err := candidate.Validate(); err != nil {
t.Fatalf("valid candidate rejected: %v", err)
}
accepted := ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "implements", MappingStatus: "accepted", Provenance: "human_curated", ReviewedBy: "benjamin", ReviewDate: "2026-06-25", ReviewReason: "passt"}
if err := accepted.Validate(); err != nil {
t.Fatalf("valid accepted rejected: %v", err)
}
bad := []struct {
name string
m ControlMapping
}{
{"no source_norm", ControlMapping{TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "candidate", Provenance: "retriever_candidate"}},
{"bad mapping_type", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "nope", MappingStatus: "candidate", Provenance: "retriever_candidate"}},
{"bad mapping_status", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "maybe", Provenance: "retriever_candidate"}},
{"bad provenance", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "candidate", Provenance: "guessed"}},
{"accepted without audit trail", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "accepted", Provenance: "human_curated"}},
{"rejected without reason", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", MappingStatus: "rejected", Provenance: "human_curated", ReviewedBy: "b", ReviewDate: "2026-06-25"}},
}
for _, tt := range bad {
if err := tt.m.Validate(); err == nil {
t.Errorf("%s: expected rejection", tt.name)
}
}
}
func TestLoadControlMappings(t *testing.T) {
dir := t.TempDir()
content := `// header comment, ignored
{"source_norm":"CRA Annex I","source_role":"operational_requirement","target_framework":"OWASP ASVS","target_control":"V6.3.1","mapping_type":"supports","mapping_status":"accepted","provenance":"human_curated","reviewed_by":"benjamin","review_date":"2026-06-25","review_reason":"V6=Auth passt","rationale":"r","version":"2026-06-25"}
{"source_norm":"CRA Annex I","source_role":"operational_requirement","target_framework":"OWASP ASVS","target_control":"V14.2.4","mapping_type":"related","mapping_status":"candidate","provenance":"retriever_candidate","rationale":"r","version":"2026-06-25"}
`
if err := os.WriteFile(filepath.Join(dir, "m.jsonl"), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
set, err := LoadControlMappings(dir)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(set.All) != 2 {
t.Fatalf("want 2 mappings, got %d", len(set.All))
}
if got := set.ControlsFor("CRA Annex I", false); len(got) != 2 {
t.Errorf("ControlsFor(all): want 2, got %d", len(got))
}
if got := set.ControlsFor("CRA Annex I", true); len(got) != 1 {
t.Errorf("ControlsFor(acceptedOnly): want 1 (only accepted), got %d", len(got))
}
if got := set.ObligationsFor("OWASP ASVS", "V6.3.1", true); len(got) != 1 {
t.Errorf("ObligationsFor accepted reverse lookup: want 1, got %d", len(got))
}
}
func TestLoadControlMappings_RejectsInvalid(t *testing.T) {
dir := t.TempDir()
// accepted without the who/when/why audit trail must fail-closed.
if err := os.WriteFile(filepath.Join(dir, "bad.jsonl"), []byte(`{"source_norm":"A","target_framework":"X","target_control":"Y","mapping_type":"supports","mapping_status":"accepted","provenance":"human_curated","rationale":"r","version":"v"}`), 0o644); err != nil {
t.Fatal(err)
}
if _, err := LoadControlMappings(dir); err == nil {
t.Error("accepted mapping without audit trail must fail the load (fail-closed)")
}
}
func TestControlMappings_SeedFileValid(t *testing.T) {
// The committed seed store must always load + validate.
set, err := LoadControlMappings("../../data/control_mappings")
if err != nil {
t.Fatalf("seed control_mappings failed to load: %v", err)
}
if len(set.All) == 0 {
t.Fatal("seed control_mappings is empty")
}
}
@@ -0,0 +1,117 @@
package ucca
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
// EvidenceRequirement is the last edge of the compliance graph: it says WHAT concrete
// evidence proves a framework control is met, and how fresh that evidence must be. This is
// what lets the Advisor eventually state "the CRA requirement is fulfilled" — not because a
// document exists, but because the required, current evidence is present. Authored/curated,
// not retriever-generated.
type EvidenceRequirement struct {
Framework string `json:"framework"` // e.g. "OWASP ASVS"
Control string `json:"control"` // e.g. "V6.3.1"
EvidenceType string `json:"evidence_type"` // sbom|test_report|config_export|repo_scan|policy|ticket|audit_log|pentest
EvidenceSource string `json:"evidence_source"` // github|ci|scanner|manual_upload
FreshnessRequirement string `json:"freshness_requirement"` // per_release|quarterly|annually|continuous
Required bool `json:"required"`
Rationale string `json:"rationale"`
Version string `json:"version"`
}
// Allowed enum values — the rule layer that keeps the evidence catalog clean.
var (
evidenceTypeValues = map[string]bool{"sbom": true, "test_report": true, "config_export": true, "repo_scan": true, "policy": true, "ticket": true, "audit_log": true, "pentest": true}
evidenceSourceValues = map[string]bool{"github": true, "ci": true, "scanner": true, "manual_upload": true}
freshnessValues = map[string]bool{"per_release": true, "quarterly": true, "annually": true, "continuous": true}
)
// Validate checks required fields + enum membership. Fail-closed at load.
func (e EvidenceRequirement) Validate() error {
switch {
case e.Framework == "":
return fmt.Errorf("evidence requirement: framework required")
case e.Control == "":
return fmt.Errorf("evidence requirement: control required")
case !evidenceTypeValues[e.EvidenceType]:
return fmt.Errorf("evidence requirement: invalid evidence_type %q", e.EvidenceType)
case !evidenceSourceValues[e.EvidenceSource]:
return fmt.Errorf("evidence requirement: invalid evidence_source %q", e.EvidenceSource)
case !freshnessValues[e.FreshnessRequirement]:
return fmt.Errorf("evidence requirement: invalid freshness_requirement %q", e.FreshnessRequirement)
}
return nil
}
// EvidenceRequirementSet is the loaded, indexed evidence catalog.
type EvidenceRequirementSet struct {
All []EvidenceRequirement
byControl map[string][]EvidenceRequirement
}
// For returns all evidence requirements declared for a framework control.
func (s *EvidenceRequirementSet) For(framework, control string) []EvidenceRequirement {
return s.byControl[controlKey(framework, control)]
}
// RequiredFor returns only the required evidence for a control — the minimum that must be
// present before the control may be treated as met.
func (s *EvidenceRequirementSet) RequiredFor(framework, control string) []EvidenceRequirement {
out := make([]EvidenceRequirement, 0)
for _, e := range s.byControl[controlKey(framework, control)] {
if e.Required {
out = append(out, e)
}
}
return out
}
// LoadEvidenceRequirements reads every *.jsonl file under dir (one requirement per line;
// blank and //-prefixed lines ignored), validates each, and builds the per-control index.
// An invalid row aborts the load — fail-closed.
func LoadEvidenceRequirements(dir string) (*EvidenceRequirementSet, error) {
files, err := filepath.Glob(filepath.Join(dir, "*.jsonl"))
if err != nil {
return nil, err
}
set := &EvidenceRequirementSet{byControl: map[string][]EvidenceRequirement{}}
for _, f := range files {
fh, err := os.Open(f)
if err != nil {
return nil, err
}
sc := bufio.NewScanner(fh)
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
line := 0
for sc.Scan() {
line++
raw := strings.TrimSpace(sc.Text())
if raw == "" || strings.HasPrefix(raw, "//") {
continue
}
var e EvidenceRequirement
if err := json.Unmarshal([]byte(raw), &e); err != nil {
fh.Close()
return nil, fmt.Errorf("%s:%d: %w", f, line, err)
}
if err := e.Validate(); err != nil {
fh.Close()
return nil, fmt.Errorf("%s:%d: %w", f, line, err)
}
set.All = append(set.All, e)
k := controlKey(e.Framework, e.Control)
set.byControl[k] = append(set.byControl[k], e)
}
fh.Close()
if err := sc.Err(); err != nil {
return nil, err
}
}
return set, nil
}
@@ -0,0 +1,84 @@
package ucca
import (
"os"
"path/filepath"
"testing"
)
func TestEvidenceRequirement_Validate(t *testing.T) {
valid := EvidenceRequirement{Framework: "OWASP ASVS", Control: "V6.3.1", EvidenceType: "config_export", EvidenceSource: "github", FreshnessRequirement: "per_release", Required: true}
if err := valid.Validate(); err != nil {
t.Fatalf("valid rejected: %v", err)
}
bad := []struct {
name string
e EvidenceRequirement
}{
{"no control", EvidenceRequirement{Framework: "X", EvidenceType: "sbom", EvidenceSource: "ci", FreshnessRequirement: "per_release"}},
{"bad evidence_type", EvidenceRequirement{Framework: "X", Control: "Y", EvidenceType: "screenshot", EvidenceSource: "ci", FreshnessRequirement: "per_release"}},
{"bad evidence_source", EvidenceRequirement{Framework: "X", Control: "Y", EvidenceType: "sbom", EvidenceSource: "email", FreshnessRequirement: "per_release"}},
{"bad freshness", EvidenceRequirement{Framework: "X", Control: "Y", EvidenceType: "sbom", EvidenceSource: "ci", FreshnessRequirement: "weekly"}},
}
for _, tt := range bad {
if err := tt.e.Validate(); err == nil {
t.Errorf("%s: expected rejection", tt.name)
}
}
}
func TestLoadEvidenceRequirements(t *testing.T) {
dir := t.TempDir()
content := `// header
{"framework":"OWASP ASVS","control":"V6.3.1","evidence_type":"config_export","evidence_source":"github","freshness_requirement":"per_release","required":true,"version":"2026-06-25"}
{"framework":"OWASP ASVS","control":"V6.3.1","evidence_type":"pentest","evidence_source":"manual_upload","freshness_requirement":"annually","required":false,"version":"2026-06-25"}
`
if err := os.WriteFile(filepath.Join(dir, "e.jsonl"), []byte(content), 0o644); err != nil {
t.Fatal(err)
}
set, err := LoadEvidenceRequirements(dir)
if err != nil {
t.Fatalf("load: %v", err)
}
if len(set.All) != 2 {
t.Fatalf("want 2, got %d", len(set.All))
}
if got := set.For("OWASP ASVS", "V6.3.1"); len(got) != 2 {
t.Errorf("For: want 2, got %d", len(got))
}
if got := set.RequiredFor("OWASP ASVS", "V6.3.1"); len(got) != 1 {
t.Errorf("RequiredFor: want 1 (pentest is optional), got %d", len(got))
}
}
func TestEvidenceRequirements_SeedFileValid(t *testing.T) {
set, err := LoadEvidenceRequirements("../../data/evidence_requirements")
if err != nil {
t.Fatalf("seed evidence_requirements failed to load: %v", err)
}
if len(set.All) == 0 {
t.Fatal("seed evidence_requirements is empty")
}
}
// TestGraph_AcceptedControlsHaveEvidence wires the two layers: every control an accepted
// CRA->OWASP mapping points to must have >=1 required evidence — the Obligation -> Control ->
// Evidence chain must be connected, no dangling control nodes.
func TestGraph_AcceptedControlsHaveEvidence(t *testing.T) {
maps, err := LoadControlMappings("../../data/control_mappings")
if err != nil {
t.Fatal(err)
}
ev, err := LoadEvidenceRequirements("../../data/evidence_requirements")
if err != nil {
t.Fatal(err)
}
for _, m := range maps.All {
if !m.IsAccepted() {
continue
}
if len(ev.RequiredFor(m.TargetFramework, m.TargetControl)) == 0 {
t.Errorf("accepted control %s %s has no required evidence (dangling graph node)", m.TargetFramework, m.TargetControl)
}
}
}
@@ -0,0 +1,172 @@
package ucca
import (
"encoding/json"
"os"
"regexp"
"strings"
)
// ObligationKey is one entry of the Obligation Registry's cross-session contract
// (obligations/obligation_join_keys.json). obligation_id is the STABLE join key — assigned
// only by the Registry, never minted here. citation_units are the interim bridge until our
// ControlMapping adopts obligation_id directly.
type ObligationKey struct {
ObligationID string `json:"obligation_id"`
Regulation string `json:"regulation"`
Family string `json:"family"`
Tier string `json:"tier"`
CitationUnits []string `json:"citation_units"`
SourceRole string `json:"source_role"`
}
// ObligationJoinKeys is the loaded contract + a citation-unit index for the interim join.
type ObligationJoinKeys struct {
SchemaVersion string `json:"schema_version"`
Count int `json:"count"`
ObligationIDs []ObligationKey `json:"obligation_ids"`
byCitationKey map[string][]string
}
var citationRefRe = regexp.MustCompile(`\(([0-9a-zA-Z]+)\)`)
// citationUnitKey normalizes a CRA Annex I reference for the INTERIM citation_unit join, so
// our "CRA Annex I Part I (2)(c)" and the Registry's "Annex I (2)(c)" collapse to the same
// key ("i:2.c"). Interim only — superseded by the stable obligation_id once adopted.
func citationUnitKey(cu string) string {
low := strings.ToLower(cu)
part := ""
switch {
case strings.Contains(low, "part ii"):
part = "ii"
case strings.Contains(low, "part i"), strings.Contains(low, "(2)"):
part = "i" // CRA Annex I Part I = the (2)(x) essential requirements
}
var refs []string
for _, m := range citationRefRe.FindAllStringSubmatch(cu, -1) {
refs = append(refs, strings.ToLower(m[1]))
}
return part + ":" + strings.Join(refs, ".")
}
// LoadObligationJoinKeys reads the Registry contract and indexes it by citation-unit key.
func LoadObligationJoinKeys(path string) (*ObligationJoinKeys, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var o ObligationJoinKeys
if err := json.Unmarshal(raw, &o); err != nil {
return nil, err
}
o.byCitationKey = map[string][]string{}
for _, ob := range o.ObligationIDs {
for _, cu := range ob.CitationUnits {
k := citationUnitKey(cu)
o.byCitationKey[k] = append(o.byCitationKey[k], ob.ObligationID)
}
}
return &o, nil
}
// ObligationsForCitation returns the obligation_ids that join (interim) to a citation
// reference such as a control_mapping.source_norm.
func (o *ObligationJoinKeys) ObligationsForCitation(citationRef string) []string {
return o.byCitationKey[citationUnitKey(citationRef)]
}
// FindObligation returns the registry entry for an obligation_id (nil if unknown).
func (o *ObligationJoinKeys) FindObligation(obligationID string) *ObligationKey {
for i := range o.ObligationIDs {
if o.ObligationIDs[i].ObligationID == obligationID {
return &o.ObligationIDs[i]
}
}
return nil
}
// mappingReaches reports whether a control mapping reaches an obligation — EXACT via the
// adopted obligation_id (semantic, preferred), else via the interim citation_unit join (for
// not-yet-adopted rows). Once obligation_id is set, the coarse citation_unit match is ignored:
// that is how the semantic join replaces the structural one (e.g. V11.2.1 crypto no longer
// rides (2)(d) into user_authentication_required — it goes to credential_confidentiality_protection).
func mappingReaches(m ControlMapping, ob ObligationKey, citationKeys map[string]bool) bool {
if m.ObligationID != "" {
return m.ObligationID == ob.ObligationID
}
return citationKeys[citationUnitKey(m.SourceNorm)]
}
// AcceptedControlsForObligation returns our accepted control mappings that reach an obligation
// (deduped by target control), obligation_id-exact where adopted, citation_unit otherwise.
func AcceptedControlsForObligation(ob ObligationKey, mappings *ControlMappingSet) []ControlMapping {
keys := make(map[string]bool, len(ob.CitationUnits))
for _, cu := range ob.CitationUnits {
keys[citationUnitKey(cu)] = true
}
out := []ControlMapping{}
seen := map[string]bool{}
for _, m := range mappings.All {
if !m.IsAccepted() || !mappingReaches(m, ob, keys) {
continue
}
ck := m.TargetFramework + ":" + m.TargetControl
if seen[ck] {
continue
}
seen[ck] = true
out = append(out, m)
}
return out
}
// ObligationCoverage is one row of the cross-session coverage report.
type ObligationCoverage struct {
ObligationID string `json:"obligation_id"`
Family string `json:"family"`
Status string `json:"status"` // covered | mapped_rejected | uncovered
AcceptedControls []string `json:"accepted_controls"`
EvidenceCount int `json:"evidence_count"`
}
// ComputeObligationCoverage joins the Registry obligations to our control mappings — exact via
// obligation_id where adopted, else via the interim citation_unit join — and reports per
// obligation: covered (>=1 accepted control reaches it), mapped_rejected (only rejected
// mappings reach it), or uncovered. The signal back to the Obligation session.
func ComputeObligationCoverage(joins *ObligationJoinKeys, mappings *ControlMappingSet, evidence *EvidenceRequirementSet) []ObligationCoverage {
out := make([]ObligationCoverage, 0, len(joins.ObligationIDs))
for _, ob := range joins.ObligationIDs {
keys := make(map[string]bool, len(ob.CitationUnits))
for _, cu := range ob.CitationUnits {
keys[citationUnitKey(cu)] = true
}
cov := ObligationCoverage{ObligationID: ob.ObligationID, Family: ob.Family}
seen := map[string]bool{}
rejected := false
for _, m := range mappings.All {
if !mappingReaches(m, ob, keys) {
continue
}
if m.IsAccepted() {
ck := m.TargetFramework + ":" + m.TargetControl
if !seen[ck] {
seen[ck] = true
cov.AcceptedControls = append(cov.AcceptedControls, ck)
cov.EvidenceCount += len(evidence.RequiredFor(m.TargetFramework, m.TargetControl))
}
} else if m.MappingStatus == "rejected" {
rejected = true
}
}
switch {
case len(cov.AcceptedControls) > 0:
cov.Status = "covered"
case rejected:
cov.Status = "mapped_rejected"
default:
cov.Status = "uncovered"
}
out = append(out, cov)
}
return out
}
@@ -0,0 +1,61 @@
package ucca
import "testing"
func TestCitationUnitKey_Join(t *testing.T) {
// our source_norm and the registry citation_unit must collapse to the SAME key.
if citationUnitKey("CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff") != citationUnitKey("Annex I (2)(c)") {
t.Errorf("interim join broken: %q vs %q",
citationUnitKey("CRA Annex I Part I (2)(c)"), citationUnitKey("Annex I (2)(c)"))
}
// Part II must NOT collide with Part I.
if citationUnitKey("Annex I Part II (1)") == citationUnitKey("CRA Annex I Part I (2)(c)") {
t.Error("Part II must not join to Part I")
}
}
func TestLoadObligationJoinKeys(t *testing.T) {
o, err := LoadObligationJoinKeys("../../../obligations/obligation_join_keys.json")
if err != nil {
t.Fatalf("load: %v", err)
}
if o.Count != len(o.ObligationIDs) {
t.Errorf("count %d != len %d", o.Count, len(o.ObligationIDs))
}
if len(o.ObligationIDs) == 0 {
t.Fatal("empty contract")
}
if got := o.ObligationsForCitation("CRA Annex I Part I (2)(c)"); len(got) == 0 {
t.Error("expected an obligation joined to (2)(c)")
}
}
func TestObligationCoverage_Report(t *testing.T) {
joins, err := LoadObligationJoinKeys("../../../obligations/obligation_join_keys.json")
if err != nil {
t.Fatalf("join keys: %v", err)
}
maps, err := LoadControlMappings("../../data/control_mappings")
if err != nil {
t.Fatalf("mappings: %v", err)
}
ev, err := LoadEvidenceRequirements("../../data/evidence_requirements")
if err != nil {
t.Fatalf("evidence: %v", err)
}
cov := ComputeObligationCoverage(joins, maps, ev)
if len(cov) == 0 {
t.Fatal("no coverage computed")
}
byStatus := map[string]int{}
for _, c := range cov {
byStatus[c.Status]++
}
t.Logf("COVERAGE: %d Obligations | covered=%d mapped_rejected=%d uncovered=%d",
len(cov), byStatus["covered"], byStatus["mapped_rejected"], byStatus["uncovered"])
for _, c := range cov {
if c.Status != "uncovered" {
t.Logf(" %-15s %-36s controls=%v evidence=%d", c.Status, c.ObligationID, c.AcceptedControls, c.EvidenceCount)
}
}
}
+203
View File
@@ -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)(am) 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."
+253
View File
@@ -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"
}
}
]
}
@@ -0,0 +1,92 @@
{
"schema_version": "controls_for_obligation_mapping_v1",
"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).",
"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",
"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff",
"citation_unit": "Annex I (2)(c)", "family": "auth", "mapping_type": "supports",
"proposed_obligation_id": "user_authentication_required",
"mapping_method": "semantic",
"mapping_note": "Zugriffsschutz/Authentisierung-vor-Zugriff = Nutzer-Auth (NICHT firmware, trotz strukturellem (2)(c)-Join)"
},
{
"framework": "OWASP ASVS", "control": "V6.1.1",
"source_norm": "CRA Annex I Part I (2)(c) — Schutz vor unbefugtem Zugriff",
"citation_unit": "Annex I (2)(c)", "family": "auth", "mapping_type": "supports",
"proposed_obligation_id": "user_authentication_required",
"mapping_method": "semantic",
"mapping_note": "wie V6.3.1"
},
{
"framework": "OWASP ASVS", "control": "V11.2.1",
"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung",
"citation_unit": "Annex I (2)(d)", "family": "crypto", "mapping_type": "supports",
"proposed_obligation_id": "credential_confidentiality_protection",
"mapping_method": "semantic",
"mapping_note": "Vertraulichkeit von Auth-Daten. ALT: encrypted_auth_channel, falls V11.2.1 transit-/kanal-spezifisch ist — bitte aus eurem Control-Text bestaetigen."
},
{
"framework": "OWASP ASVS", "control": "V11.7.1",
"source_norm": "CRA Annex I Part I (2)(d) — Vertraulichkeit / Verschluesselung",
"citation_unit": "Annex I (2)(d)", "family": "crypto", "mapping_type": "supports",
"proposed_obligation_id": "auth_key_management",
"mapping_method": "semantic",
"mapping_note": "Key Management = Schluessel erzeugen/speichern/HSM"
},
{
"framework": "OWASP ASVS", "control": "V16.3.3",
"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging",
"citation_unit": "Annex I (2)(k)", "family": "logging", "mapping_type": "supports",
"proposed_obligation_id": "event_logging_security_events",
"mapping_method": "semantic",
"mapping_note": "Umbrella-LM 'Produkt protokolliert sicherheitsrelevante Ereignisse' (CRA (2)(k)). ALT bei access-decision-spezifischem Control-Text: access_control_event_logging — bitte aus eurem ASVS-V16.3-Text bestaetigen."
},
{
"framework": "OWASP ASVS", "control": "V16.3.4",
"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging",
"citation_unit": "Annex I (2)(k)", "family": "logging", "mapping_type": "supports",
"proposed_obligation_id": "event_logging_security_events",
"mapping_method": "semantic",
"mapping_note": "Umbrella-LM (CRA (2)(k)). ALT bei admin-/privileg-spezifischem Control-Text: audit_trail_admin_actions — bitte aus eurem ASVS-V16.3-Text bestaetigen."
},
{
"framework": "OWASP ASVS", "control": "V16.1.1",
"source_norm": "CRA Annex I Part I (2)(k) — Sicherheitsrelevante Ereignisse / Logging",
"citation_unit": "Annex I (2)(k)", "family": "logging", "mapping_type": "supports",
"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": "signed_update_integrity",
"mapping_method": "semantic",
"mapping_note": "NIST SI-7 = Software/Firmware/Information Integrity (Signaturpruefung, Manipulationserkennung, Secure Boot, Runtime-Integritaet). Naechster vorhandener Treffer (93-Stand): signed_update_integrity (updates-Familie, Annex I (1)(3)(f)) — deckt aber NUR Update-Signatur. SI-7 ist BREITER (gesamte Produkt-Integritaet). Falls keine generische Integritaets-Obligation existiert: neue noetig (Vorschlag software_integrity_protection); sonst SI-7 primary_implementation fuer signed_update_integrity (update-scoped) + supports fuers Breitere. 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": "remote_access_attack_surface_min",
"mapping_method": "semantic",
"mapping_note": "NIST CM-7 = Least Functionality (deaktivierte Ports/Dienste/Funktionen, GESAMTE Angriffsflaeche). Naechster vorhandener Treffer (93-Stand): remote_access_attack_surface_min (remote_access-Familie) — deckt aber NUR Remote-Access-Flaeche. CM-7 ist BREITER. Vermutlich generische Obligation noetig (Vorschlag attack_surface_minimization); sonst CM-7 supports fuer remote_access_attack_surface_min. related (supports): SC-3(3)/AC-6/SI-16."
}
]
}
+4 -1
View File
@@ -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",
+5 -1
View File
@@ -10281,7 +10281,11 @@
"cluster_size": 4,
"llm_model": "claude-opus-4-8",
"synthesis_version": "v1"
}
},
"specializes": "software_integrity_protection",
"objective_tags": [
"integrity"
]
}
],
"relationships": [
+82
View File
@@ -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
+227
View File
@@ -0,0 +1,227 @@
{
"schema_version": "obligation_procedures_v1",
"regulation": "CRA",
"layer": "Regulation -> Legal Obligation -> Procedure -> Control -> Evidence",
"note": "Procedure ist KEINE neue Compliance-Pflicht. LEGAL_MINIMUM liegt an der Obligation; die Procedure beschreibt, WIE sie umgesetzt wird; Evidence belegt die Umsetzung. source_role=procedural_requirement (Konvergenz mit der Legal-Knowledge-Engine der anderen Session).",
"citation_status": "pending_span_anchor",
"scope": "worked examples: SBOM + Vulnerability Handling",
"procedures": [
{
"procedure_id": "sbom_generation_process",
"name": "SBOM-Erstellungsprozess",
"description": "Erzeugen einer vollstaendigen, maschinenlesbaren Software Bill of Materials fuer ein Produkt mit digitalen Elementen.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["sbom_creation", "sbom_dependency_coverage", "sbom_format_standard", "sbom_tooling_automation"],
"steps": [
"Komponenten und (direkte + transitive) Abhaengigkeiten inventarisieren",
"SBOM automatisiert in der Build-/Toolchain generieren",
"Komponenten, Versionen, Lizenzen und Lieferanten erfassen",
"in anerkanntem maschinenlesbarem Format (CycloneDX/SPDX) ausgeben",
"Format- und Schemavalidierung durchfuehren"
],
"controls": [
"SBOM-Datei vorhanden",
"Format ist maschinenlesbar und standardkonform (CycloneDX/SPDX)",
"direkte und transitive Abhaengigkeiten enthalten"
],
"evidence": ["sbom.cyclonedx.json", "Format-Validierungs-Log", "Build-/Toolchain-Konfiguration"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "sbom_update_process",
"name": "SBOM-Aktualisierungsprozess",
"description": "Halten der SBOM aktuell ueber den Produktlebenszyklus bei Komponenten-, Versions- und Patch-Aenderungen.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["sbom_maintenance_update"],
"steps": [
"Komponentenaenderung erkennen (Dependency-/Patch-/Versionsaenderung)",
"SBOM neu generieren",
"Lieferanten-SBOMs aktualisieren",
"neue SBOM-Version speichern",
"SBOM in Release-Artefakte uebernehmen"
],
"controls": [
"CI prueft SBOM vorhanden",
"SBOM-Version passt zum Release",
"Supplier-Komponenten enthalten"
],
"evidence": ["sbom.json", "CI-Log", "Release-Artefakt", "Supplier-SBOM"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "sbom_supplier_integration_process",
"name": "Lieferanten-SBOM-Integration",
"description": "Beschaffen und Einarbeiten von Lieferanten-/Drittkomponenten-SBOMs in die Produkt-SBOM.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["sbom_supply_chain_contracts", "sbom_dependency_coverage"],
"steps": [
"SBOM-Anforderung in Lieferantenvertraege aufnehmen",
"Lieferanten-SBOMs einsammeln",
"in die Produkt-SBOM mergen",
"Drittkomponenten und deren Abhaengigkeiten nachverfolgen"
],
"controls": [
"vertragliche SBOM-Klausel vorhanden",
"Lieferanten-SBOMs eingegangen",
"Drittkomponenten in der SBOM gelistet"
],
"evidence": ["Lieferantenvertrag-Klausel", "eingegangene Supplier-SBOMs", "gemergte SBOM"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "sbom_provision_process",
"name": "SBOM-Bereitstellungsprozess",
"description": "Zugaenglichmachen der SBOM fuer berechtigte Parteien (Nutzer, Behoerde) unter Wahrung der Vertraulichkeit.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["sbom_access_provision", "sbom_authority_provision", "sbom_confidentiality"],
"steps": [
"Zugangskanal definieren (Portal/API/dokumentierter Pfad)",
"Nutzer ueber den Zugangsweg informieren",
"auf begruendetes Verlangen der Marktueberwachungsbehoerde vertraulich bereitstellen",
"Zugriffskontrolle und Vertraulichkeitsmassnahmen anwenden"
],
"controls": [
"Zugangspfad dokumentiert",
"Zugriffskontrolle/Vertraulichkeit umgesetzt",
"Behoerden-Bereitstellungsprozess definiert"
],
"evidence": ["Zugangskanal-Dokumentation", "Behoerden-Anfrage-Log", "Zugriffskontroll-Konfiguration"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "sbom_conformity_documentation_process",
"name": "SBOM in technischer Dokumentation/Konformitaet",
"description": "Aufnehmen der SBOM in die technische Dokumentation und Verifizieren der Vollstaendigkeit fuer die Konformitaetsbewertung.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["sbom_technical_documentation", "sbom_completeness_verification"],
"steps": [
"SBOM in die technische Dokumentation aufnehmen",
"Vollstaendigkeit gegen die real eingesetzte Softwarekomposition pruefen",
"der Konformitaetsbewertung beilegen (ggf. EUCC)"
],
"controls": [
"SBOM Teil der technischen Dokumentation",
"Vollstaendigkeit verifiziert",
"Konformitaetsnachweis vorhanden"
],
"evidence": ["technische Dokumentation", "Vollstaendigkeits-Pruefbericht", "Konformitaetsnachweis"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "vuln_handling_process_setup",
"name": "Schwachstellenbehandlungsprozess einrichten",
"description": "Dokumentierten Prozess und Meldekanal (CVD) fuer die Schwachstellenbehandlung etablieren.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["vuln_handling_process"],
"steps": [
"dokumentierten Schwachstellenbehandlungsprozess definieren",
"Coordinated-Vulnerability-Disclosure-Richtlinie und Meldekanal veroeffentlichen",
"eingehende Meldungen triagieren"
],
"controls": [
"Behandlungsprozess dokumentiert",
"Meldekanal/Kontaktstelle auffindbar (z.B. security.txt)",
"Triage-Verfahren vorhanden"
],
"evidence": ["Prozessdokument", "security.txt / Kontaktstelle", "Triage-Log"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "vuln_identification_process",
"name": "Schwachstellen-Identifikation",
"description": "Bekannte Schwachstellen in eingesetzten Komponenten erkennen und inventarisieren.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["vuln_identification_inventory"],
"steps": [
"Advisories/CVE-Feeds beobachten",
"gegen die SBOM-Komponenten abgleichen",
"Schwachstellen-Inventar pflegen"
],
"controls": [
"Advisory-/CVE-Monitoring aktiv",
"SBOM-zu-CVE-Abgleich durchgefuehrt",
"Schwachstellen-Inventar gepflegt"
],
"evidence": ["CVE-Abgleich-Report", "Schwachstellen-Register"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "vuln_assessment_process",
"name": "Schwachstellen-Bewertung/Priorisierung",
"description": "Identifizierte Schwachstellen nach Schweregrad, Ausnutzbarkeit und Exposition bewerten und priorisieren.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["vuln_assessment_prioritization"],
"steps": [
"Schweregrad bewerten (z.B. CVSS)",
"Ausnutzbarkeit/Exposition einschaetzen",
"risikobasiert priorisieren"
],
"controls": [
"Schweregrad standardisiert bewertet",
"risikobasierte Priorisierung vorhanden"
],
"evidence": ["Bewertungsdatensatz (CVSS)", "Prioritaetenliste"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "vuln_remediation_process",
"name": "Schwachstellen-Behebung",
"description": "Bekannte Schwachstellen fristgerecht durch Patches/Gegenmassnahmen beheben und Sicherheitsupdates bereitstellen.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["vuln_remediation_patching"],
"steps": [
"Fix/Gegenmassnahme entwickeln",
"testen",
"Sicherheitsupdate kostenfrei und zeitnah bereitstellen",
"bis zum Abschluss nachverfolgen"
],
"controls": [
"zeitnahe Behebung",
"Sicherheitsupdate bereitgestellt",
"Follow-up bis Closure"
],
"evidence": ["Patch/Release", "Behebungs-Zeitleiste", "Follow-up-Log"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "vuln_disclosure_process",
"name": "Offenlegung + Nutzerinformation",
"description": "Koordinierte Offenlegung behobener Schwachstellen und Information der Nutzer ueber Schutzmassnahmen.",
"source_role": "procedural_requirement",
"fulfills_obligations": ["coordinated_vulnerability_disclosure", "vuln_info_dissemination_users"],
"steps": [
"Offenlegungszeitpunkt koordinieren",
"Security Advisory / CVE-Eintrag veroeffentlichen",
"Nutzer ueber behobene Schwachstelle und Schutzmassnahmen informieren"
],
"controls": [
"Advisory veroeffentlicht",
"Nutzer informiert"
],
"evidence": ["Security Advisory", "CVE-Eintrag", "Nutzer-Benachrichtigung"],
"citation_spans": [], "citation_status": "pending_span_anchor"
},
{
"procedure_id": "vuln_authority_reporting_process",
"name": "Behoerdenmeldung aktiv ausgenutzter Schwachstellen",
"description": "Aktiv ausgenutzte Schwachstellen fristgerecht an CSIRT/ENISA melden (CRA Art. 14-Kaskade).",
"source_role": "procedural_requirement",
"fulfills_obligations": ["exploited_vuln_reporting_authorities"],
"applicability_note": "bedingt: nur bei aktiv ausgenutzter Schwachstelle",
"steps": [
"aktive Ausnutzung erkennen",
"Fruehwarnung an CSIRT/ENISA (24h)",
"vollstaendige Meldung (72h)",
"Abschlussbericht (14 Tage)"
],
"controls": [
"24h-Fruehwarnung erfolgt",
"72h-Meldung erfolgt",
"14d-Abschlussbericht erfolgt"
],
"evidence": ["CSIRT/ENISA-Meldungsbelege", "Zeitstempel der Kaskade"],
"citation_spans": [], "citation_status": "pending_span_anchor"
}
]
}
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
+846
View File
@@ -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,90 @@
"""P3 — Compliance-Advisor-Proof: obligation-basierte Antwort als vollstaendige
BEGRUENDUNGSKETTE aus der Registry (NICHT RAG-Text, KEIN LLM):
Rechtsgrundlage -> Obligation -> Procedure -> Controls -> Evidence -> Antwort.
Deterministisch + zitierfaehig. Der Unterschied zu RAG: RAG beantwortet BreakPilot
begruendet UND operationalisiert.
python3 scripts/obligation_discovery/advisor_proof.py --registry obligations/cra.json \
--procedures obligations/cra_procedures.json --topic sbom --has-digital-elements
"""
from __future__ import annotations
import argparse
import json
def applies(obl: dict, has_digital: bool) -> tuple[bool, str]:
a = obl.get("applicability", "universal")
if a == "universal":
return True, ""
if a.startswith("domain:products_with_digital_elements"):
return has_digital, "nur fuer Produkte mit digitalen Elementen (CRA Art. 3)"
if a.startswith("domain:"):
return True, a.split(":", 1)[1]
if a.startswith("conditional:"):
return True, f"bedingt: {a.split(':',1)[1]}"
return True, ""
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--registry", required=True)
ap.add_argument("--procedures", required=True)
ap.add_argument("--topic", default="sbom")
ap.add_argument("--has-digital-elements", action="store_true")
ap.add_argument("--question", default="Muss ich als Maschinenbauer eine SBOM bereitstellen?")
a = ap.parse_args()
reg = json.load(open(a.registry, encoding="utf-8"))
procs = json.load(open(a.procedures, encoding="utf-8"))["procedures"]
obls = [o for o in reg["obligations"]
if a.topic in o.get("family", "") or a.topic in o["id"]]
ids = {o["id"] for o in obls}
by_obl: dict[str, list] = {}
for p in procs:
for oid in p.get("fulfills_obligations", []):
by_obl.setdefault(oid, []).append(p)
pflicht = [o for o in obls if o["tier"] == "LEGAL_MINIMUM" and applies(o, a.has_digital_elements)[0]]
best = [o for o in obls if o["tier"] != "LEGAL_MINIMUM"]
print(f"FRAGE: {a.question}")
print(f"\nANTWORT: {'JA' if pflicht and a.has_digital_elements else 'NUR WENN CRA-anwendbar'}"
f"sofern das Produkt unter den CRA faellt (product with digital elements, Art. 3).")
print("\n══ BEGRUENDUNGSKETTE (Recht → Obligation → Procedure → Controls → Evidence) ══")
req_evidence: list[str] = []
for o in pflicht:
lb = "; ".join(f"{b.get('source','')} {b.get('anchor','')}".strip() for b in o.get("legal_basis", []))
print(f"\n● PFLICHT: {o['id']}{o.get('description','')[:80]}")
print(f" Rechtsgrundlage: {lb or ''}")
ps = by_obl.get(o["id"], [])
for p in ps:
print(f" Procedure (wie umgesetzt): {p['procedure_id']} — Schritte: {len(p.get('steps',[]))}")
print(f" Controls (Pruefung): {' · '.join(p.get('controls', []))[:96]}")
print(f" Nachweis: {' · '.join(p.get('evidence', []))}")
req_evidence += p.get("evidence", [])
if not ps:
print(" Procedure: (noch keine modelliert)")
print("\n── REQUIRED EVIDENCE (aggregiert, womit wird es nachgewiesen) ──")
print(" " + " · ".join(dict.fromkeys(req_evidence)) if req_evidence else "")
print("\n── BEST PRACTICE (anerkannte Umsetzung, KEINE CRA-Wortlautpflicht) ──")
for o in best:
gb = "; ".join(b.get("source", "") for b in o.get("guidance_basis", []))
print(f"{o['id']}{o.get('description','')[:64]} | Guidance: {gb or ''}")
print("\n── BEZIEHUNG (warum es zaehlt) ──")
for r in reg.get("relationships", []):
if r.get("from") in ids and r.get("to") not in ids:
print(f"{r['from']} --{r['type']}--> {r['to']}: {r.get('note','')[:64]}")
pend = sum(1 for o in pflicht if o.get("citation_status") == "pending_span_anchor")
print(f"\n── CITATION ──\n {pend}/{len(pflicht)} Pflichten: pending_span_anchor "
f"(Textstellen-Anker folgen mit dem zitierfaehigen Re-Ingest)")
print("\n(RAG beantwortet — BreakPilot begruendet UND operationalisiert.)")
if __name__ == "__main__":
main()
@@ -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,52 @@
"""Exportiert den OBLIGATION_ID-Join-Key-Vertrag aus den Registry-Artefakten.
Die obligation_id ist der stabile Brueckenschluessel zwischen Legal Knowledge Graph
(citation_spans haengen an obligation_id) und Compliance Execution Graph
(control_mapping.source_norm -> obligation_id). citation_units = die legal_basis-Anker,
ueber die beide Seiten heute (vor obligation_id-Adoption) bruecken koennen.
DISZIPLIN: obligation_id wird RE-GELINKT, NIE neu vergeben (Pendant zu span_id/control_uuid).
python3 scripts/obligation_discovery/export_join_keys.py obligations/cra.json obligations/cra_authentication.json
"""
from __future__ import annotations
import argparse
import json
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("registries", nargs="+")
ap.add_argument("--out", default="obligations/obligation_join_keys.json")
a = ap.parse_args()
keys = []
for path in a.registries:
reg = json.load(open(path, encoding="utf-8"))
for o in reg.get("obligations", []):
citation_units = [b.get("anchor", "") for b in o.get("legal_basis", []) if b.get("anchor")]
keys.append({
"obligation_id": o["id"],
"regulation": reg.get("regulation", ""),
"family": o.get("family", ""),
"tier": o.get("tier", ""),
"citation_units": citation_units,
"source_role": o.get("source_role", ""),
})
out = {
"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": len(keys),
"obligation_ids": keys,
}
json.dump(out, open(a.out, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
from collections import Counter
print(f"exportiert: {a.out} ({len(keys)} obligation_ids)")
print("Regulierungen:", dict(Counter(k["regulation"] for k in keys)))
print("Familien:", dict(Counter(k["family"] for k in keys)))
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()
@@ -19,6 +19,20 @@ SCOPES = {
"vuln": ["%schwachstellenbehandl%", "%schwachstellenmanagement%", "%vulnerability handling%",
"%coordinated vulnerab%", "%vulnerability disclosure%", "%cvd-konzept%"],
"auth": ["%authentisierung%", "%authentifizierung%", "%authentication%"],
"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%"],
}