Use pull requests and deployments as change evidence instead of tickets
For teams that already ship through Git and CI/CD, this guide shows how to satisfy change-management evidence requirements without forcing every change through a separate ticket. You’ll see what evidence to capture from pull requests and deployments, how to link it end to end, and where this approach breaks down.
TL;DR — If your code already flows through Git, pull requests, CI checks, and deployment logs, those systems can usually provide stronger change-management evidence than a manually updated ticket. The key is not “no tickets”; it is having an auditable chain from proposed change → review/approval → tested artifact → deployment event, with immutable IDs and retained logs. Reading time: ~7 min
What it is and where it sits
In compliance terms, the question is usually not “did you create a ticket?” It is “can you prove who changed what, who approved it, what was tested, when it reached production, and whether emergency paths were controlled?” For software teams using Git-based delivery, the best evidence often already exists in pull requests and deployment records.
This approach replaces tickets as the primary evidence object for standard software changes. A ticket can still exist for planning, incident response, or business approval, but it stops being the mandatory source of truth for every code change.
Architecturally, this sits across your delivery path:
- source control: branch, commits, PR metadata, review approvals
- CI: test results, status checks, artifact build provenance
- artifact store or image registry: immutable artifact digest
- CD or deployment system: who deployed what, to which environment, when
- runtime/platform logs: rollout status, health checks, rollback events
What talks to it:
- developers push commits and open PRs
- CI posts commit statuses and produces artifacts
- deployment automation reads the merged commit or tagged release and deploys an artifact
- auditors, security, or engineering managers query PR history and deployment logs
What it replaces:
- “change ticket required before merge” for routine application changes
- manually copied evidence like screenshots pasted into a service desk item
- free-text “implemented and tested” comments with no machine-verifiable backing
Where it lives in a typical flow: not in one tool, but in the links between records. The important design choice is the join key. In practice, use commit SHA and artifact digest, not branch names or release titles.
View as text
[Developer] ---- 1. push commits ----> [Git Repo] [Git Repo] ---- 2. open/review PR ----> [PR Record] [PR Record] ---- 3. trigger checks ----> [CI] [CI] ---- 4. build artifact@sha ----> [Registry] [PR Record] ---- 5. merge commit SHA ----> [Main Branch] [CD] ---- 6. deploy artifact digest ----> [Production] [Production] ---- 7. emit deploy event ----> [Audit Evidence] [PR Record] ---- 8. link approvals ----> [Audit Evidence]
If you can answer these five questions from system records, you have the core of change evidence:
- What changed? PR diff and merged commit SHA.
- Who approved it? Recorded reviewer approval, ideally with branch protection requiring it.
- What validated it? Required CI checks attached to the merge commit.
- What exactly was deployed? Immutable artifact digest or release SHA.
- When and by whom was it deployed? Deployment event log from CD/platform.
How it actually works
Walk one realistic example: a developer changes a payment-service API timeout and ships it to production.
Step 1: propose the change in a PR
The developer creates a branch, commits, pushes, and opens a PR.
git checkout -b feat/payment-timeout
sed -i 's/TIMEOUT_MS=2000/TIMEOUT_MS=3500/' config/prod.env
git add config/prod.env
git commit -m "Increase payment API timeout to 3500ms"
git push origin feat/payment-timeout
Evidence created here:
- author identity from Git hosting account
- commit SHA, timestamp, diff
- PR number, title, description
What matters for compliance is not the branch name; it is that the PR record persists and points to the exact commits.
Step 2: enforce review and checks before merge
A reviewer approves the PR. CI runs unit tests, integration tests, linting, maybe SAST. Branch protection blocks merge until required checks pass.
Typical CLI/API shape when inspecting a PR in a Git-hosting API:
curl -s -H "Authorization: Bearer $TOKEN" \
https://git.example.internal/api/pulls/482 | jq '{number,title,state,merge_commit_sha,approved_by:.reviews[]?|select(.state=="APPROVED")|.user.login}'
Example output shape:
{
"number": 482,
"title": "Increase payment API timeout to 3500ms",
"state": "merged",
"merge_commit_sha": "9f3a8a7c5d1b2e4f6a0c9d8e7f1234567890abcd",
"approved_by": "sara"
}
And for CI statuses on the merge commit:
curl -s -H "Authorization: Bearer $TOKEN" \
https://git.example.internal/api/commits/9f3a8a7c5d1b2e4f6a0c9d8e7f1234567890abcd/status | jq '.statuses[] | {context,state,target_url}'
Output shape:
{
"context": "ci/unit-tests",
"state": "success",
"target_url": "https://ci.example.internal/builds/18421"
}
{
"context": "ci/integration-tests",
"state": "success",
"target_url": "https://ci.example.internal/builds/18422"
}
{
"context": "ci/container-scan",
"state": "success",
"target_url": "https://ci.example.internal/builds/18423"
}
This is stronger than a ticket checkbox because it is machine-generated and tied to the exact merge commit.
Step 3: build an immutable artifact from the merged commit
CI builds a container image from the merge commit and pushes it to a registry tagged with the commit SHA. The digest, not the mutable tag, is the evidence anchor.
docker build -t registry.example.internal/payment-service:9f3a8a7 .
docker push registry.example.internal/payment-service:9f3a8a7
docker inspect --format='{{index .RepoDigests 0}}' registry.example.internal/payment-service:9f3a8a7
Output shape:
registry.example.internal/payment-service@sha256:2e9d7b4c1b6f8a0e5d3c2b1a9f887766554433221100ffeeddccbbaa99887766
Now you can prove that production got a specific artifact built from a specific reviewed commit.
Step 4: deploy and record the event
CD deploys that image digest to production. The deployment system should record at least:
- environment: production
- artifact digest or release SHA
- actor: automation account or human approver if manual promotion exists
- start/end timestamps
- result: succeeded, failed, rolled back
Example Kubernetes-style deployment update:
kubectl -n payments set image deployment/payment-service \
payment-service=registry.example.internal/payment-service@sha256:2e9d7b4c1b6f8a0e5d3c2b1a9f887766554433221100ffeeddccbbaa99887766
kubectl -n payments rollout status deployment/payment-service --timeout=120s
Output shape:
deployment.apps/payment-service image updated
Waiting for deployment "payment-service" rollout to finish: 2 out of 6 new replicas have been updated...
Waiting for deployment "payment-service" rollout to finish: 4 out of 6 new replicas have been updated...
deployment "payment-service" successfully rolled out
If rollout fails, that failure is also evidence:
error: deployment "payment-service" exceeded its progress deadline
That message matters. It shows the deployment control worked and did not silently declare success.
Step 5: assemble the evidence chain
At audit time, you do not want screenshots. You want a query or report that joins:
- PR #482
- merge commit
9f3a8a7... - CI runs
18421,18422,18423 - image digest
sha256:2e9d...7766 - production deployment event at
2026-10-30T14:22:11Z
That chain is the change record. If your process requires business context, add a field in the PR template like Change reason or Risk/rollback plan; do not force a separate ticket just to duplicate machine evidence.
When to use it (and when not to)
Use this when your delivery path is already mostly automated and your controls can be enforced in tooling instead of policy docs.
| Scenario | Recommendation |
|---|---|
| App code changes merged through protected PRs, CI required, deployments logged | Use PR + deployment evidence as primary change record |
| Infrastructure-as-code changes go through the same PR/CI/CD path | Use the same model; it works well here too |
| Manual production changes over SSH or ad hoc SQL are still common | Do not rely on this alone; keep explicit change records until you remove manual paths |
| Highly regulated approval process requires business sign-off separate from engineering review | Keep a ticket or approval system for that sign-off, but use PR/deploy logs for technical evidence |
| Emergency fixes bypass normal review | Allow it only with a documented break-glass path and mandatory post-hoc review |
| Small team deploying a hobby app with no auditors and no formal approvals | You probably don’t need a formal evidence model at all |
You probably don’t need this if every deployment is still manual and your source control, CI, and production logs are not retained. In that case, “PRs as evidence” is mostly theater because the deployment half of the chain is missing.
Trade-offs
Every benefit here has a cost.
- Better evidence quality than tickets
- Cost: you must wire systems together by commit SHA/digest and keep logs long enough for audits.
- Less developer busywork
- Cost: more upfront process design in branch protection, CI requirements, and deployment metadata.
- Harder to fake than manual ticket updates
- Cost: exceptions become more visible, so you need a real emergency-change process instead of informal bypasses.
- Faster audits because evidence is queryable
- Cost: someone must own report generation and retention; “the data exists somewhere” is not enough.
- Closer to actual engineering work
- Cost: non-code changes still need another control path, such as vendor config changes, firewall changes, or direct database edits.
- Reduced ticket-system lock-in
- Cost: increased dependence on your Git/CI/CD platform retention and API stability.
The biggest edge case: one PR does not always equal one change. A PR may contain multiple unrelated edits, or one deployment may batch many PRs. Auditors care less about your preferred granularity than about whether the chain is consistent and approvals match the risk. If you batch 40 PRs into one Friday release, your evidence exists, but your blast radius and review traceability are worse.
In practice
Example 1: PR template that captures the non-machine context
<!-- .github/pull_request_template.md or equivalent -->
## Change summary
- What changed:
- Why:
## Risk
- User impact if this fails:
- Rollback command or procedure:
## Validation
- [ ] Unit tests added/updated
- [ ] Integration test passed
- [ ] Backward compatibility checked
## Change management
- Change type: standard / normal / emergency
- Production rollout plan:
- Post-deploy check:
This adds the human context auditors ask for without duplicating machine-generated facts. Gotcha: keep it short; if the template becomes a compliance novella, developers will paste junk and your evidence quality drops.
Example 2: CI script that emits a deployment evidence record
#!/usr/bin/env bash
set -euo pipefail
APP="payment-service"
ENV="production"
COMMIT_SHA="${GIT_COMMIT_SHA:?missing GIT_COMMIT_SHA}"
IMAGE_DIGEST="${IMAGE_DIGEST:?missing IMAGE_DIGEST}"
ACTOR="${DEPLOY_ACTOR:-cd-bot}"
STARTED_AT="$(date -u +%FT%TZ)"
kubectl -n payments set image deployment/${APP} \
${APP}=registry.example.internal/${APP}@${IMAGE_DIGEST}
kubectl -n payments rollout status deployment/${APP} --timeout=180s
ENDED_AT="$(date -u +%FT%TZ)"
jq -n \
--arg app "$APP" \
--arg env "$ENV" \
--arg commit "$COMMIT_SHA" \
--arg digest "$IMAGE_DIGEST" \
--arg actor "$ACTOR" \
--arg started "$STARTED_AT" \
--arg ended "$ENDED_AT" \
'{app:$app,environment:$env,commit_sha:$commit,image_digest:$digest,actor:$actor,started_at:$started,ended_at:$ended,status:"succeeded"}' \
| tee deployment-evidence.json
This creates a machine-readable deployment record you can ship to object storage, log indexing, or an audit database. Gotcha: set -e means the JSON file is never written on rollout failure; add a trap if you also want failure evidence persisted.
Example 3: branch protection policy as code where your platform supports it
{
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true
},
"required_status_checks": {
"strict": true,
"contexts": [
"ci/unit-tests",
"ci/integration-tests",
"ci/container-scan"
]
},
"enforce_admins": true,
"restrictions": null
}
This is the control that makes PR approvals meaningful instead of optional ceremony. Gotcha: if admins can bypass checks silently, your audit story becomes “controls existed except when they didn’t.” Log and review every bypass.
⚠️ If you shorten log retention, delete old CI runs, garbage-collect artifacts aggressively, or allow force-push on protected branches, you can destroy your evidence chain. Change retention settings before you tell auditors tickets are no longer needed.
Further reading
- NIST SP 800-53 Rev. 5 — CM-3, CM-5, AU family
- SOC 2 CC8 and CC7 criteria guidance
- The "Deployments and environments" section of your Git hosting platform docs
- The "Artifact Registry" or "Container image digests" section of your registry docs
- Accelerate by Nicole Forsgren, Jez Humble, and Gene Kim
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI