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>
This commit is contained in:
Benjamin Admin
2026-06-25 09:32:15 +02:00
parent a3053c3c86
commit d987e4fde6
3 changed files with 222 additions and 0 deletions
@@ -0,0 +1,78 @@
package ucca
import (
"os"
"path/filepath"
"testing"
)
func TestControlMapping_Validate(t *testing.T) {
valid := ControlMapping{SourceNorm: "CRA Annex I", TargetFramework: "OWASP ASVS", TargetControl: "V6.2.4", MappingType: "supports", Confidence: "high", Provenance: "human_curated"}
if err := valid.Validate(); err != nil {
t.Fatalf("valid mapping rejected: %v", err)
}
bad := []struct {
name string
m ControlMapping
}{
{"no source_norm", ControlMapping{TargetFramework: "X", TargetControl: "Y", MappingType: "supports", Confidence: "high", Provenance: "human_curated"}},
{"no target_control", ControlMapping{SourceNorm: "A", TargetFramework: "X", MappingType: "supports", Confidence: "high", Provenance: "human_curated"}},
{"bad mapping_type", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "nope", Confidence: "high", Provenance: "human_curated"}},
{"bad confidence", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", Confidence: "huge", Provenance: "human_curated"}},
{"bad provenance", ControlMapping{SourceNorm: "A", TargetFramework: "X", TargetControl: "Y", MappingType: "supports", Confidence: "high", Provenance: "guessed"}},
}
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":"V11.1.1","mapping_type":"supports","confidence":"high","provenance":"human_curated","rationale":"r","version":"2026-06-25"}
{"source_norm":"CRA Annex I","source_role":"operational_requirement","target_framework":"OWASP ASVS","target_control":"V6.2.4","mapping_type":"related","confidence":"low","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(curatedOnly): want 1 (only human_curated), got %d", len(got))
}
if got := set.ObligationsFor("OWASP ASVS", "V11.1.1", false); len(got) != 1 {
t.Errorf("ObligationsFor reverse lookup: want 1, got %d", len(got))
}
}
func TestLoadControlMappings_RejectsInvalid(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "bad.jsonl"), []byte(`{"source_norm":"A","target_framework":"X","target_control":"Y","mapping_type":"BOGUS","confidence":"high","provenance":"human_curated"}`), 0o644); err != nil {
t.Fatal(err)
}
if _, err := LoadControlMappings(dir); err == nil {
t.Error("invalid mapping_type must fail the load (fail-closed audit store)")
}
}
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")
}
}