Evidence bundles for compliance
When an auditor asks for evidence, you shouldn't spend a day grepping CI logs and screenshotting dashboards. Here's an OrchStep workflow that gathers the artifacts, hashes each one, and seals them into a single bundle per release.
blog/evidence-bundlesThe audit request is always the same: "For release 2026.5.0, show us the test results, the SBOM, and the approval records." And the scramble is always the same — someone opens three browser tabs, screenshots a green CI run, exports a dependency list, digs the approval out of a ticket, and pastes it all into a folder named evidence_final_v2. It takes most of a day, and the result is a pile of screenshots nobody can verify.
The evidence already exists at release time. The test report, the SBOM, the approvals — they're all produced during the release. The expensive part is re-collecting them weeks later from systems that have since rotated their logs. So collect them once, when the release happens, hash them, and seal them into a bundle you can hand over without a scramble.
Gather, hash, seal
Pull every required artifact in parallel, hash them together, render a manifest, and assemble the bundle:
name: evidence-bundles
defaults:
release: "2026.5.0"
tasks:
# `orchstep run bundle --var release=2026.6.0`
bundle:
steps:
- name: collect
desc: "Pull every required artifact at once"
parallel:
- name: tests
func: shell
do: echo "TEST_REPORT=passed-510-510"
outputs:
ref: '{{ result.output | regexFind "TEST_REPORT=(.+)" }}'
- name: sbom
func: shell
do: echo "SBOM=312-packages"
outputs:
ref: '{{ result.output | regexFind "SBOM=(.+)" }}'
- name: approvals
func: shell
do: echo "APPROVALS=2-of-2"
outputs:
ref: '{{ result.output | regexFind "APPROVALS=(.+)" }}'
- name: hash
func: shell
do: echo "sha256 — tests={{ steps.tests.ref }} sbom={{ steps.sbom.ref }} approvals={{ steps.approvals.ref }}"
outputs:
digest: "{{ result.output }}"
- name: manifest
func: render
args:
template: |
release: {{ vars.release }}
tests: {{ steps.tests.ref }}
sbom: {{ steps.sbom.ref }}
approvals: {{ steps.approvals.ref }}
outputs:
doc: "{{ result.output }}"
- name: assemble
func: shell
do: echo "evidence-{{ vars.release }}.tar.gz sealed from 3 hashed artifacts + manifest"The parallel: block gathers all three artifacts at once and names each one's reference. The hash step reads those references and produces a digest; manifest renders a human-readable index from the same outputs; assemble seals the bundle. Every artifact in the bundle is traceable back to the run that produced it.
Collect at release time, not audit time
The whole trick is when this runs. Bolt it onto the tail of your release workflow and the bundle is a byproduct of shipping, not a project you start when the auditor emails:
- name: collect
parallel:
- name: tests
func: shell
do: echo "TEST_REPORT=passed-510-510"
- name: sbom
func: shell
do: echo "SBOM=312-packages"
- name: approvals
func: shell
do: echo "APPROVALS=2-of-2"Collecting in parallel keeps it cheap enough that there's no reason not to do it on every release. Six weeks later, "show me evidence for 2026.5.0" is a file you already have, not a day you have to spend. Parallel details: Composition.
A manifest, not a pile of screenshots
The render step turns the collected references into a structured manifest:
- name: manifest
func: render
args:
template: |
release: {{ vars.release }}
tests: {{ steps.tests.ref }}
sbom: {{ steps.sbom.ref }}
approvals: {{ steps.approvals.ref }}That's the index an auditor reads first — what's in the bundle, for which release, with the hashed reference to each item. It beats a folder of PNGs because it's generated from the run's outputs, so it can't claim an artifact the bundle doesn't contain. See the render function.
What you actually gained
| Concern | Audit-time scramble | Evidence bundle |
|---|---|---|
| When evidence is gathered | weeks later, by hand | at release, automatically |
| Collection cost | a day of grepping | one parallel step |
| Verifiability | screenshots | hashed artifacts |
| Index | a folder name | a rendered manifest |
| Coverage | "did we get everything?" | the workflow is the checklist |
This isn't a substitute for a compliance platform that stores and retains evidence under policy — if you're regulated, you still need somewhere durable to put the bundle. But it removes the worst part of an audit: re-assembling proof of things you already did, from systems that have moved on.
Where to go next
- The render function — generate manifests and indexes
- Composition — gather artifacts in parallel
- Variables & Outputs — chain outputs into the bundle
Bolt this onto your release: one parallel collect step turns your next audit from a day of grepping into handing over a file.
curl -fsSL https://orchstep.dev/install.sh | sh