Compare commits

...

8 Commits

Author SHA1 Message Date
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 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
10 changed files with 5325 additions and 57 deletions
@@ -2,13 +2,13 @@
// 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"}
{"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"}
{"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"}
{"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"}
{"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"}
{"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"}
{"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"}
{"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"}
@@ -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)
}
}
@@ -26,6 +26,7 @@ type ControlMapping struct {
MappingType string `json:"mapping_type"` // supports | partially_supports | implements | 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
@@ -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)
}
}
}
@@ -2,70 +2,66 @@
"schema_version": "controls_for_obligation_mapping_v1",
"purpose": "Accepted CRA->OWASP controls (Compliance Execution Graph) for the Obligation Registry to propose the SEMANTIC control->obligation_id, replacing the coarse citation_unit interim join. Fill proposed_obligation_id per control, then we adopt it into control_mapping.obligation_id.",
"source": "ai-compliance-sdk control_mappings, mapping_status=accepted, reviewed_by=benjamin 2026-06-25",
"filled_by": "obligation-registry-session 2026-06-25 (alle 7/7: 4 auth/crypto + 3 logging via cra_logging.json)",
"join_principle": "SEMANTISCH via obligation_id, NICHT via citation_unit/legal_basis-Anker. Die CRA-Anker sind im Registry teils approximativ (siehe anchor_quality_note) — daher ist obligation_id der stabile Primaerschluessel, nicht der Anker.",
"anchor_quality_note": "Registry-legal_basis-Anker sind teils CRA-Part-I-fehlzugeordnet (Opus-Synthese): user_authentication_required steht auf (2)(d) statt (2)(c); Crypto-Obligations auf (2)(e) statt (2)(d). CRA Annex I Part I: (2)(c)=Zugriffsschutz, (2)(d)=Vertraulichkeit, (2)(e)=Integritaet. Korrektur kommt mit dem zitierfaehigen Re-Ingest (span-genau). Deshalb: NICHT auf Anker joinen. ABER: der Logging-Cut (V16.*) ist korrekt auf (2)(k) verankert (echte Logging-Subsektion, kein Fehl-Anker).",
"count": 7,
"controls": [
{
"framework": "OWASP ASVS",
"control": "V6.3.1",
"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": ""
"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",
"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": ""
"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",
"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": ""
"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",
"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": ""
"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",
"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": ""
"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",
"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": ""
"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",
"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": ""
"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."
}
]
}
File diff suppressed because it is too large Load Diff
+165 -1
View File
@@ -1,7 +1,7 @@
{
"schema_version": "obligation_join_keys_v1",
"contract": "obligation_id ist der stabile Join-Key. Legal Knowledge Graph haengt citation_spans an obligation_id; Compliance Execution Graph mappt control_mapping.source_norm -> obligation_id. Interim-Bruecke = citation_units. obligation_id NIE neu vergeben (re-link).",
"count": 47,
"count": 66,
"obligation_ids": [
{
"obligation_id": "sbom_creation",
@@ -418,6 +418,170 @@
"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"
}
]
}
@@ -19,6 +19,9 @@ 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%"],
}