Evidence Ingest (GP2)
Evidence Ingest (GP2)
The evidence-ingest pipeline turns a published, signed recipe-evidence
bundle into the source-keyed tree that the corroboration dashboard
generator consumes. It is the bridge between the per-run attestation
bundles produced by aicr validate --emit-attestation --push (see
ADR-007) and the aggregated,
consensus view. The tree it writes is rendered and served by
dashboard-publish (GP5).
The GPn labels name stages of the evidence-corroboration pipeline (epic #1400):
GP1 signer-allowlist management (recipes/evidence/allowlist.yaml),
GP2 ingest/verify (this doc), GP3 dashboard infrastructure
(infra/evidence-dashboard), GP4 consensus/corroboration
(pkg/corroborate), and GP5 dashboard publish.
Its defining property is verify-before-count: a bundle’s signature, issuer, identity, and source registry are all checked in a step that holds no bucket-write credentials, before any of its results are recorded. Unverified evidence never reaches the tree, and contributors never hold credentials to the publish bucket.
Pipeline
The Go pieces:
pkg/evidence/project— the synthesis library. Pure and offline: given an already-verified bundle plus its verified signer claims, it derives the coordinate and signer id-hash and writes the run directory. It performs no verification and no network I/O of its own.tools/evidence-project— the CLI that wires it together: resolve the OCI ref, enforce the trusted-registry allowlist, materialize once, verify on the unpacked directory with non-empty pins (pkg/evidence/verifier), classify, then synthesize..github/workflows/evidence-ingest.yaml+.github/scripts/evidence-ingest.sh— the CI driver with two triggers (below) and a fork-safe split between the credential-free verify job and the credentialed publish job.
Triggers
- Push to
recipes/evidence/**— community and partner pointers added or refreshed in-tree. Each changed pointer is verified with the signature pinned to the signer the pointer claims; the verifier also cross-checks the certificate against that claim, so a pointer cannot lie about who signed. workflow_call/workflow_dispatchwithbundle_ref— a first-party UAT run ingests its bundle directly, by ref, with no repo commit. The signature is pinned to the NVIDIA/aicr Actions identity.uat-aws.yamlanduat-gcp.yamlcall this workflow as a dependentingest-evidencejob on a successful run, passing the digest-pinned ref read from the conformance step’sevidence/pointer.yaml.
meta.json
Each run directory carries one meta.json (schema
aicr-corroboration-meta/v1). It is the authoritative coordinate
source — the directory layout is organizational only. Every field traces
to the verified predicate or certificate; there is no clock and no
randomness, so output is byte-identical across runs from identical input.
ctrf/<phase>.json reports are copied for each phase the run produced
(deployment, performance, conformance); a phase the run did not
produce is simply absent — never stubbed.
idHash (producer ↔ consumer contract)
idHash is the stable per-signer dedup key the consensus model counts
distinct values of. It is
defined once in project.SignerIDHash. The same verified signer hashes
to the same value across every recipe and run; two different signers do
not collide. This is the GP2-producer/GP4-consumer contract — do not
change the algorithm without migrating both the bucket tree and the
consumer.
Classification
project.Allowlist.Classify derives a signer’s trust tier from the
verified (issuer, identity), never a raw pointer string:
- With an allowlist loaded, the shared loader (
pkg/evidence/allowlist, the same one the GP4 consumerpkg/corroborateuses) decides: a slug or pattern match wins,allowlisted=true. - When no allowlist file is loaded (no
--allowlistflag), a built-in heuristic admits AICR’s own UAT identity (GitHub Actions OIDC +NVIDIA/aicr) asfirst-partyso it is not mislabeled. - Otherwise
community,allowlisted=false— the fail-closed default. Reported, but never counted toward consensus.
The canonical recipes/evidence/allowlist.yaml keys first-party CI signers
by an anchored identityPattern and external contributors/partners by a
one-way source slug — it deliberately does not store a cleartext
identity field (the loader rejects one, to keep personal emails out of the
repo):
identityPattern is a ^…$-anchored regex (full-string match); the loader
rejects an over-broad pattern (any wildcard left of the OIDC subject’s @ —
only the ref may vary) and overlapping entries, so the allowlist cannot
itself be used to manufacture consensus.
Both the GP2 ingest loader (pkg/evidence/project) and the GP4 consumer
(pkg/corroborate) delegate to the shared canonical loader in
pkg/evidence/allowlist (#1505), so producer and consumer parse the
identical identityPattern/source schema and classify a verified signer
identically. The shared loader rejects a cleartext identity: field, so
personal emails cannot be reintroduced by accident.
Forward limitations
- Today’s
pkg/evidence/verifierpopulates a verified signer only from an in-artifactattestation.intoto.jsonl(DSSE + Fulcio cert). The “statement-only bundle whose signer is carried in the pointer and verified against Rekor by digest” shape is not yet implemented, so such community bundles cannot be ingested — the producer fails closed rather than record an unverified signer. - Discovery walks the per-source nested layout
recipes/evidence/<recipe>/<src>/<digest>.yaml(the<recipe>/<src>/*.yamlglob inpkg/evidence/verifier/discover.go); a flatrecipes/evidence/<recipe>.yamlis rejected as an unexpected root file. - The publish job writes to
gs://aicr-testgrid-staging/resultsusing the shared eidosx WIF service account fromuat-gcp.yaml. GP3 will replace it with a dedicatedobjectCreator-only identity scoped to that prefix.