ab3cb86b1c
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>
118 lines
4.4 KiB
Go
118 lines
4.4 KiB
Go
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
|
|
}
|