Git, CI/CD & GitOpsCXVII · Compliance and AuditArtefacts
The deployment receipt — the artefact of record
What you'll learn
- Define the deployment receipt as the artefact that joins the commit to the deployment
- Specify the six fields the receipt must contain: commit, artifact digest, runner identity, startedAt, deploy identity, signature
- Emit the receipt from CI and store it as a queryable GitHub Actions artifact
- Query the receipt from a fresh terminal using gh run view to answer WHAT, WHEN, and HOW in one call
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The deployment receipt is the artefact of record. It joins the commit that produced the artefact to the deployment that applied it. Without the receipt, WHAT is queryable (Git holds the commit) and WHY is queryable (PR body holds the rationale), but WHEN and HOW fall back to asking around: who deployed, when, from which runner, to which cluster. With the receipt, all six auditor categories are queryable from a fresh terminal. This lesson defines the receipt, the six fields, and the CI shape.
What the receipt answers
The receipt is the join between the version-control repository (WHO, WHAT, WHY, AUTHORITY) and the deployment controller (WHEN, HOW):
flowchart LR
REPO["Version-control\nrepository"] -->|"commit, PR,\nreviewers"| R["Receipt"]
CI["CI system"] -->|"runner identity,\nstartedAt,\nartifact digest"| R
R --> CTRL["Deployment\ncontroller"]
R --> AUDIT["Audit query:\ngh run view"]
The receipt is not the artifact. The receipt is the attestation that the artefact was produced by this CI run, from this commit, by this runner, and deployed at this time.
The six fields
A receipt that answers every question has six fields:
flowchart TB
R["Deployment receipt"] --> F1["1. commit:\nfull SHA"]
R --> F2["2. artifactDigest:\nOCI digest or\nplan hash"]
R --> F3["3. runnerIdentity:\nOIDC subject of\nthe CI runner"]
R --> F4["4. startedAt:\nRFC3339 timestamp"]
R --> F5["5. deployIdentity:\nOIDC identity\nthat hit the cluster"]
R --> F6["6. signature:\ncosign or\nRekor entry"]
- commit. Full SHA. The SHA is the join key.
- artifactDigest. OCI digest of the image, SHA-256 of the Terraform plan, or equivalent.
- runnerIdentity. OIDC subject claim of the CI runner.
- startedAt. RFC3339 timestamp anchoring WHEN.
- deployIdentity. OIDC identity that hit the cluster.
- signature. Signature over the previous five fields via cosign or Rekor.
Querying the receipt
The receipt is only an audit trail if it is queryable from a fresh terminal:
RUN_ID="$LAST_DEPLOY_RUN_ID"
gh run view "$RUN_ID" --json event,headBranch,conclusion,startedAt
--json returns run metadata as JSON. The receipt
artifact itself is downloaded and verified:
gh run download "$RUN_ID" --name deployment-receipt
cosign verify-blob deployment-receipt.json \
--signature deployment-receipt.sig \
--certificate-identity-regexp '.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
cosign verify-blob is the audit-time check that
the receipt has not been tampered with.
Emitting the receipt from CI
The receipt is emitted by the CI job at the end of the pipeline, after deployment, with all six fields populated from the pipeline’s own state:
flowchart LR
JOB["CI job"] -->|"commit"| J1["JOB_COMMIT"]
JOB -->|"runner OIDC"| J2["RUNNER_IDENTITY"]
JOB -->|"deploy OIDC"| J3["DEPLOY_IDENTITY"]
JOB -->|"now()"| J4["STARTED_AT"]
JOB -->|"artifact digest"| J5["ARTIFACT_DIGEST"]
J1 --> R["receipt.json"]
J2 --> R
J3 --> R
J4 --> R
J5 --> R
R --> SIGN["cosign sign-blob"]
SIGN --> STORE["Upload to artifact store"]
Every field is populated from the pipeline’s own state, not from input parameters. The artefact digest is read from the registry after the push.
Production discipline
- The receipt is emitted by CI. The engineer is not in the loop.
- All six fields are mandatory. One missing field means one auditor question cannot be answered.
- The signature covers all six fields. The join is what is signed.
- Retention matches the audit window. A receipt rotated before the window has elapsed is gone.
Cross-course references
- This course, Part LXIV (Auditability) introduced the deployment receipt and the audit chain.
- This course, Part LXVII (SupplyChain) covers cosign and Rekor, which this receipt depends on.
- Terraform for Production Sysadmins - Part XI covers the equivalent receipt where the artifact digest is the plan hash.
Quiz
Knowledge check · 4 questions
Q1. A CI job emits a deployment receipt as a log line to stdout and stores nothing else. The CI provider rotates logs after 90 days; the audit window is seven years. What is the audit posture?
Q2. A signature that covers only the commit hash is sufficient to prove the deployment receipt was emitted by the CI job that ran.
Q3. Name the six fields a deployment receipt must contain.
Q4. Diagnose the receipt emission and recommend the structural fix.
Team T's CI pipeline emits a deployment summary as a JSON document and uploads it to a shared S3 bucket. The JSON contains the commit hash, the artifact URL, the timestamp, and the runner name. The JSON is not signed. The S3 bucket has a 30-day lifecycle policy that deletes objects older than 30 days. An auditor asks about a change from eight months ago.
Passing score: 75%. Answers are checked in this browser.