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 }