rfgen.supply_chain_evidence

rfgen.supply_chain_evidence qualifies a release from caller-supplied, already-parsed evidence. It is a deterministic, offline policy boundary: it does not read artifact files, invoke Syft, Trivy, or Cosign, contact a registry, or create a scan, signature, provenance statement, or report.

The caller owns evidence collection and parsing. This module owns strict model validation, hash and identity comparisons, vulnerability-exception policy, and the resulting PASS or FAIL decision.

All record models are source-backed, strict, frozen Pydantic models: extra="forbid" rejects unknown fields, strict validation does not coerce input values, and accepted instances cannot be mutated. They describe facts obtained from retained release evidence; they are not a way to manufacture those facts. SHA-256 values are exactly 64 lowercase hexadecimal characters; source_revision is exactly 40 lowercase hexadecimal characters.

Closed vocabularies

SupplyChainStatus

PASS or FAIL, the status stored in a report and returned by verification.

ArtifactKind

The complete release artifact set: OCI_AMD64, OCI_ARM64, SDIST, and WHEEL. A passing report contains each kind exactly once.

VulnerabilitySeverity

LOW, MEDIUM, HIGH, or CRITICAL. Only HIGH and CRITICAL findings participate in the exception decision.

Artifact and parsed-evidence models

SupplyChainArtifactV1

class SupplyChainArtifactV1(BaseModel):
    id: str
    kind: ArtifactKind
    sha256: Sha256
    sbom_sha256: Sha256
    scan_sha256: Sha256
    signature_bundle_sha256: Sha256
    provenance_sha256: Sha256

The release report’s hash commitments for one distributable artifact. The verifier compares every field with the corresponding parsed evidence; it does not calculate these hashes from files.

VulnerabilityFindingV1

class VulnerabilityFindingV1(BaseModel):
    cve: str
    severity: VulnerabilitySeverity

One caller-parsed scan finding. cve must match CVE-<year>-<number> and the model never invokes a scanner.

ParsedArtifactEvidenceV1

class ParsedArtifactEvidenceV1(BaseModel):
    artifact_sha256: Sha256
    sbom_sha256: Sha256 | None
    scan_sha256: Sha256 | None
    signature_bundle_sha256: Sha256 | None
    provenance_sha256: Sha256 | None
    signature_verified: bool
    provenance_verified: bool
    identity: str | None
    issuer: str | None
    syft_version: str
    trivy_version: str
    cosign_version: str
    cyclonedx_schema_version: Literal["1.6"]
    slsa_schema_version: Literal["1.0"]
    findings: tuple[VulnerabilityFindingV1, ...] = ()

The adapter supplies facts parsed from real, externally produced evidence. The version policy is exact at the compatibility boundary: Syft must be a 1.x release; Trivy must be 0.58.0 or newer; Cosign must be a 2.x release; CycloneDX must be schema 1.6; and SLSA provenance must be schema 1.0. Version strings outside those policies are rejected rather than treated as approximately compatible. signature_verified and provenance_verified are inputs from that caller-owned verification process; setting either flag does not perform verification here.

Absent evidence hashes, failed flags, mismatched policy identity or issuer, or unexcepted high-severity findings make verification fail. A caller must not fabricate evidence, a successful scan result, a signature result, or a provenance result to satisfy this record contract.

SupplyChainEvidenceAdapter

class SupplyChainEvidenceAdapter(Protocol):
    def evidence_for(
        self, artifact: SupplyChainArtifactV1
    ) -> ParsedArtifactEvidenceV1 | None: ...

The narrow injection seam for parsed evidence. It returns evidence for the requested artifact, or None when it is unavailable. It is not a scanner or verification plugin interface: its implementation must preserve the boundary above and return only caller-parsed facts.

Policy, exception, and report models

SupplyChainPolicyV1

class SupplyChainPolicyV1(BaseModel):
    identity: str
    issuer: str

The non-empty signing identity and issuer required for every signed artifact and the report itself.

VulnerabilityExceptionV1

class VulnerabilityExceptionV1(BaseModel):
    cve: str
    severity: VulnerabilitySeverity
    owner: str
    expires_at: date
    mitigation: str
    evidence_sha256: Sha256

One owned, time-limited exception. It must expire no earlier than today and no later than 30 days from today at construction. During verification an exception binds to exactly one finding: its CVE and severity must exactly match that finding, it must not be expired at as_of, and its evidence_sha256 must equal the parsed scan hash that produced the finding. An exception cannot waive a different CVE, a different severity, a different scan, or a missing scan hash.

SupplyChainReportV1

class SupplyChainReportV1(BaseModel):
    schema_version: Literal[1]
    source_revision: GitSha
    artifacts: tuple[SupplyChainArtifactV1, ...]
    exceptions: tuple[VulnerabilityExceptionV1, ...]
    identity: str
    issuer: str
    status: SupplyChainStatus
    causes: tuple[str, ...]
    report_sha256: Sha256

The canonical release-qualification record. Artifacts are unique and sorted by (kind, id); exceptions are unique and sorted by CVE. report_sha256 must be the SHA-256 of the RFC 8785 canonical JSON representation after removing only report_sha256 itself.

A PASS report has all four artifact kinds exactly once and no causes. A FAIL report has at least one non-empty cause. Constructing a valid report only validates its internal record shape and self-hash; it does not prove the referenced evidence exists or is authentic.

SupplyChainVerificationResultV1

class SupplyChainVerificationResultV1(BaseModel):
    status: SupplyChainStatus
    causes: tuple[str, ...]

The immutable result of a verification attempt. Every discovered problem is retained in causes so release tooling can report all evidence gaps together.

Offline verifier

ParsedEvidenceSupplyChainVerifier

class ParsedEvidenceSupplyChainVerifier:
    def __init__(
        self, *, adapter: SupplyChainEvidenceAdapter, policy: SupplyChainPolicyV1
    ) -> None: ...

    def verify(
        self, report: SupplyChainReportV1, *, as_of: date | None = None
    ) -> SupplyChainVerificationResultV1: ...

Checks only the self-authenticated report and parsed evidence supplied by the adapter. For every report artifact it requires evidence, all five matching hashes, verified signature and provenance flags, and matching identity and issuer. It also rejects unexcepted HIGH or CRITICAL findings and invalid, expired, or scan-hash-mismatched exceptions.

as_of makes exception expiry deterministic for offline runs. If omitted, the verifier uses the local current date. The result is PASS only when the submitted report says PASS and no verification causes were found; otherwise it is FAIL. The verifier never changes a report and never runs or simulates external security tools.

See Also

  • Build and CI: required release evidence and the external-tool boundary.

  • Attestation: the separate Sigstore DSSE trust-root boundary.