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:
@@ -0,0 +1,139 @@
|
||||
package ucca
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ControlMapping is one persisted, versioned link from a legal obligation/requirement
|
||||
// to a concrete framework control. The retriever only PROPOSES candidates
|
||||
// (provenance=retriever_candidate); the curated mapping (human_curated/rule_based) is the
|
||||
// audited truth the Advisor uses at runtime — never re-invented per query.
|
||||
type ControlMapping struct {
|
||||
SourceNorm string `json:"source_norm"` // e.g. "CRA Annex I Part II"
|
||||
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.2.4"
|
||||
MappingType string `json:"mapping_type"` // supports | partially_supports | evidence_for | related
|
||||
Confidence string `json:"confidence"` // high | medium | low
|
||||
Provenance string `json:"provenance"` // retriever_candidate | human_curated | rule_based
|
||||
Rationale string `json:"rationale"`
|
||||
Version string `json:"version"` // YYYY-MM-DD
|
||||
}
|
||||
|
||||
// Allowed enum values — the deterministic "rule" layer that keeps the curated store clean.
|
||||
var (
|
||||
mappingTypeValues = map[string]bool{"supports": true, "partially_supports": true, "evidence_for": true, "related": true}
|
||||
confidenceValues = map[string]bool{"high": true, "medium": true, "low": true}
|
||||
provenanceValues = map[string]bool{"retriever_candidate": true, "human_curated": true, "rule_based": true}
|
||||
)
|
||||
|
||||
// Validate checks required fields + enum membership, so the persisted audit store never
|
||||
// holds garbage (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 !confidenceValues[m.Confidence]:
|
||||
return fmt.Errorf("control mapping: invalid confidence %q", m.Confidence)
|
||||
case !provenanceValues[m.Provenance]:
|
||||
return fmt.Errorf("control mapping: invalid provenance %q", m.Provenance)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsCurated reports whether this mapping is part of the audited truth (not a raw candidate).
|
||||
func (m ControlMapping) IsCurated() bool {
|
||||
return m.Provenance == "human_curated" || m.Provenance == "rule_based"
|
||||
}
|
||||
|
||||
// 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. curatedOnly restricts to the
|
||||
// audited truth (what the Advisor may treat as fact).
|
||||
func (s *ControlMappingSet) ControlsFor(sourceNorm string, curatedOnly bool) []ControlMapping {
|
||||
return filterProvenance(s.bySourceNorm[sourceNorm], curatedOnly)
|
||||
}
|
||||
|
||||
// ObligationsFor returns the norms mapped to a framework control (reverse lookup).
|
||||
func (s *ControlMappingSet) ObligationsFor(framework, control string, curatedOnly bool) []ControlMapping {
|
||||
return filterProvenance(s.byControl[controlKey(framework, control)], curatedOnly)
|
||||
}
|
||||
|
||||
func filterProvenance(in []ControlMapping, curatedOnly bool) []ControlMapping {
|
||||
if !curatedOnly {
|
||||
return in
|
||||
}
|
||||
out := make([]ControlMapping, 0, len(in))
|
||||
for _, m := range in {
|
||||
if m.IsCurated() {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user