Build and review local release evidence

Use this runbook to inspect Vexcalibur’s schema-1 self-evidence for one exact commit. The commands do not mutate GitHub, PyPI, or another external service: they do not create a tag, GitHub Release, attestation, or package upload.

They do change the local checkout and tool caches. Expect .venv/, dist/, src/vexcalibur/_version.py, tests/integration/csaf-validator/node_modules/, the requested bundle under build/, and uv, Go, and npm cache entries. Temporary evidence workspaces are removed on exit. make clean removes the repository-generated build output and validator installation; remove .venv/ or prune tool caches separately only when you no longer need them.

For the credentialed schema-2 publication path, use Publish Vexcalibur to PyPI.

Prepare a clean release checkout

Use Linux and the tool versions in .tool-versions. You also need GNU Make, Git, sha256sum, the Go version selected by tests/integration/openvex-go/go.mod, and the locked Node dependencies for the CSAF validator.

From the repository root:

git status --short
test -z "$(git status --porcelain)"
uv sync --frozen
make csaf-validator-install

Stop if Git reports any path. A dirty checkout is not a release-evidence input.

Dependency setup may download exact packages. VEX generation uses the reviewed local-findings provider, but this process is not a general network sandbox.

Everything under release-evidence/ is intended to become public. Do not place embargoed advisories, private SBOM data, tokens, customer identifiers, or internal URLs there.

Check the human-reviewed snapshot

Display the two digests that the review binds:

sha256sum uv.lock release-evidence/findings.json
uv run --frozen python scripts/release_evidence.py validate-review \
  --review release-evidence/review.json \
  --findings release-evidence/findings.json \
  --lock uv.lock

The initial zero-finding review prints:

production	0

A digest mismatch is a review failure. Do not update review.json merely to make the command pass; review the complete changed lock or findings file first.

Build once and run the conformance gate

Build the exact clean commit, select the only wheel, and exercise both evidence fixtures:

uv build --clear --no-create-gitignore --no-sources
mapfile -t wheels < <(find dist -maxdepth 1 -type f -name "*.whl" | sort)
test "${#wheels[@]}" -eq 1
export VEXCALIBUR_WHEEL="${wheels[0]}"
make release-evidence-check

The gate generates the production and synthetic bundles twice in distinct temporary directories and compares every byte. It also:

  • verifies clean, full-commit wheel SCM metadata;

  • installs with exact hash-locked dependencies and a SHA-256-bound wheel URI;

  • validates CycloneDX output;

  • runs the pinned official OpenVEX parser;

  • runs the pinned CSAF schema and mandatory-test suite; and

  • compares normalized assertions across all generated VEX formats.

Success ends with:

release-evidence production and synthetic fixture checks passed

The synthetic fixture is conformance data, not a vulnerability claim. Its reserved .test URL and review_kind: synthetic_fixture prevent accidental publication as production evidence.

Create an inspectable schema-1 bundle

Make the commit-derived timestamp explicit and generate into a fresh directory:

export RELEASE_SHA="$(git rev-parse --verify HEAD)"
export SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)"
RELEASE_EVIDENCE_OUTPUT=build/release-evidence make release-evidence

For a zero-finding review, the file list is:

SHA256SUMS
findings.json
manifest.json
review.json
runtime-constraints.txt
sbom.cdx.json
vex.cdx.json

Verify the bundle and its checksum inventory:

uv run --frozen python scripts/release_evidence.py verify-bundle \
  --bundle-dir build/release-evidence
(
  cd build/release-evidence
  sha256sum --check SHA256SUMS
)

Inspect manifest.json. For the empty production snapshot, require:

python - <<'PY'
import json
from pathlib import Path

manifest = json.loads(Path("build/release-evidence/manifest.json").read_text())
assert manifest["schema_version"] == 1
assert manifest["review"]["assertion_count"] == 0
assert manifest["formats"]["openvex"]["status"] == "omitted"
assert manifest["formats"]["csaf"]["status"] == "omitted"
assert manifest["source_tree_clean"] is True
PY

Do not upload this local bundle to an existing release. The release workflow creates the broader schema-2 asset set from isolated jobs and refuses manual replacement.

Update public findings

Use this sequence only after a maintainer has reviewed a public finding:

  1. Add the finding to release-evidence/findings.json. Select exactly one component, explicitly set analysis_state to in_triage, and provide a public source and honest analysis text.

  2. Review the entire new file and calculate its SHA-256.

  3. Increment analysis_revision; update reviewed_at, reviewed_by, the findings digest, and conclusion in review.json.

  4. If uv.lock changed, review the entire lock and update its digest in the same new revision.

  5. Run the complete conformance gate and inspectable-bundle checks again.

A nonempty production bundle must contain vex.cdx.json, vex.openvex.json, and vexcalibur-vex.json. Do not promote a finding to resolved, exploitable, false_positive, or not_affected until a separate evidence and approval policy exists for that stronger claim.

Recover from local failures

Failure

Safe response

Dirty-tree rejection

Commit the intended change or remove unrelated local state, then rebuild

Timestamp mismatch

Derive the epoch from the exact commit again

Review digest mismatch

Review and revise the bound input, or restore the reviewed bytes

Wheel SCM mismatch

Rebuild once from the exact clean commit; never relabel another wheel

Constraint grammar failure

Regenerate from the committed lock; never remove hash or binary-only enforcement

Missing OpenVEX or CSAF validator

Install the pinned prerequisites and rerun

Cross-format mismatch

Keep all output unpublished, fix the renderer or comparator, and add a regression test

Output directory already exists

Verify or remove only that generated directory, then rerun; the generator never overwrites it

Generated files have no external effect. Roll back reviewed-input changes with a normal Git revert and rebuild from the resulting exact commit.