Git, CI/CD & GitOpsLXIV · AuditabilityReceipt
The deployment receipt — what the audit-grade record contains
What you'll learn
- Define a deployment receipt as the durable, queryable record of a deployment event
- Identify the seven fields an audit-grade receipt must contain
- Apply the receipt format to a Kubernetes deployment annotation set
- Distinguish a receipt (what was applied) from a log (what happened) and from a manifest (what should be)
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
A deployment receipt is the durable, queryable record
of a deployment event. The word durable matters: a
receipt must survive in the cluster for the life of
the resource, must survive the deploy pipeline being
edited, and must survive the team that wrote it
leaving. The word queryable matters: a receipt must
be readable by kubectl, by an auditor’s script, and
by a SIEM without a human in the loop. Anything that
is not durable and queryable is a label, not a
receipt.
Receipt versus record versus log
Three artefacts are commonly conflated:
flowchart LR
M["Manifest: what should run"] -->|applied by| R["Receipt: what was applied"]
R -->|emitted to| L["Log: what happened"]
R -->|"stamped on"| D["Resource: in-cluster evidence"]
L -->|"queried by"| A["Auditor"]
D -->|"queried by"| A
- Manifest. The desired state. A Kubernetes manifest, a Terraform configuration, an Ansible playbook. Says “this is what should be running”.
- Receipt. The recorded state. The set of annotations, status fields, and metadata the apply produced. Says “this is what was applied, and here is the evidence”.
- Log. The narrative. The chronological record of events that produced the receipt: the commit, the pipeline run, the approver, the apply.
The manifest lives in Git. The receipt lives on the resource. The log lives in the CI system, the Git repository, the controller’s reconcile log, and the cloud provider’s audit log. An audit is the act of joining them.
The seven fields of an audit-grade receipt
A receipt that supports an audit must contain seven fields. The fields are a superset of the five facts in the deployment claim, plus two required for the chain to be traversable:
flowchart TB
subgraph Receipt["Audit-grade receipt (7 fields)"]
S["source: repo URL"]
R["revision: commit SHA"]
B["build: pipeline run URL"]
D["digest: artefact identity"]
T["target: cluster / namespace"]
TR["trigger: who applied"]
DC["decision: who approved"]
end
S --> Chain["Provenance chain"]
R --> Chain
B --> Chain
D --> Chain
T --> Chain
TR --> Chain
DC --> Chain
- source. The repository URL the artefact was built from.
- revision. The commit SHA at the time of build.
- build. The CI pipeline run URL.
- digest. The content-addressed identity of the artefact.
- target. The cluster, namespace, account, region.
- trigger. The system or person who executed the apply.
- decision. The human or process that authorised the change.
A receipt with all seven is audit-grade. With five is partial. With three is a label.
The receipt format
The receipt is stored as Kubernetes annotations on the deployed resource. The annotation keys follow a naming convention that makes the receipt queryable without ambiguity:
NS=payments
APP=checkout
kubectl get deployment "$APP" -n "$NS" \
-o jsonpath='{.items[*].metadata.annotations}'
# expected keys (illustrative):
# deploy.time/source: https://github.com/acme/checkout
# deploy.time/revision: 8a3f9d2c5e7b1f4a9d2c6e8b1f4a9d2c6e8b1f4a
# deploy.time/build: https://github.com/acme/checkout/actions/runs/1234567890
# deploy.time/digest: sha256:1a2b3c4d5e6f...
# deploy.time/target: cluster=payments-prod namespace=payments
# deploy.time/trigger: github-actions:acme-deploy
# deploy.time/decision: approvers=alice,bob pr=987
The convention uses a deploy.time/ prefix to
distinguish the receipt annotations from others.
Querying the receipt
A receipt is auditable only if it can be queried without the original pipeline. Three queries any auditor should be able to run from a fresh terminal:
NS=payments
APP=checkout
# Q1: which commit is running?
kubectl get deployment "$APP" -n "$NS" \
-o jsonpath='{.items[*].metadata.annotations.deploy\.time/revision}'
# Q2: which artefact is running?
kubectl get deployment "$APP" -n "$NS" \
-o jsonpath='{.items[*].spec.template.spec.containers[*].image}'
# Q3: which pipeline produced it?
kubectl get deployment "$APP" -n "$NS" \
-o jsonpath='{.items[*].metadata.annotations.deploy\.time/build}'
A team whose deployment cannot answer these three queries without leaving the cluster has a broken receipt.
Production discipline
- Stamp at apply time, not after. The receipt must be in the pipeline’s context when the resource is created or updated.
- Use the seven-field format, not a subset. A receipt with seven is auditable on its own. A receipt with fewer requires external systems.
- Make the receipt queryable from kubectl. The annotation-based format is the lowest common denominator.
Cross-course references
- Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers the analogous Ansible run receipt: the playbook revision, the inventory hash, the variables file digest, the target hosts.
- Terraform for Production Sysadmins - Part IX (State) covers the analogous Terraform state record: the configuration version, the provider versions, the resource addresses.
Quiz
Knowledge check · 4 questions
Q1. A receipt is stamped on a Kubernetes Deployment at apply time with six of the seven fields. The missing field is `decision` (the approver). What is the impact on auditability?
Q2. A deployment receipt that is reconstructed by an engineer six months after the deployment, by reading the CI system and filling in a wiki page, is equivalent to a receipt stamped at apply time.
Q3. Name the seven fields of an audit-grade receipt, and identify the one most often missing in a team that uses branch protection with required reviews but does not propagate the approver list to the deployment.
Q4. Audit a deployment using the seven-field receipt, and identify which fields are present and which are missing.
A team has been running in production for two years. The team adopted the receipt convention six months ago. An auditor queries a random production Deployment: `kubectl get deployment checkout -n payments -o jsonpath='{.items[*].metadata.annotations}'`. The output contains: `deploy.time/source`, `deploy.time/revision`, `deploy.time/build`, `deploy.time/digest`, `deploy.time/target`, `deploy.time/trigger`. The `decision` field is missing.
Passing score: 75%. Answers are checked in this browser.