Release Metadata Runbook
Release Metadata Runbook
Section titled “Release Metadata Runbook”Use this runbook to reproduce a ZEVM release/build boundary and audit light-mode baked defaults against published release metadata artifacts.
Canonical release metadata location: ZEVM release metadata.
Canonical reproducibility boundary for this runbook: both release artifacts must be published for the selected RELEASE_IDENTIFIER (release-tuple.json and light-default-checkpoints.json).
Publication-time validation gate for canonical claims:
- before any canonical reproducibility claim, publication validation must pass for the selected
RELEASE_IDENTIFIER - publication validation requires exactly one published asset named
release-tuple.jsonand exactly one published asset namedlight-default-checkpoints.jsonfor that identifier - zero matches or more than one match for either required filename fails the gate and marks the identifier metadata-invalid for canonical claims
Prerequisites
Section titled “Prerequisites”- one artifact fetch tool:
gh(recommended) orcurl jqfor schema checks and field extractionshasumforsha256provenance hashingdatefor UTC manifest timestamps
1. Select A Release Identifier
Section titled “1. Select A Release Identifier”Select one GitHub release identifier and use it consistently for all metadata and checkouts.
# List recent identifiers first.gh release list --repo evmts/zevm --limit 20
# Set this to one real published identifier from the list.RELEASE_IDENTIFIER='<published-release-identifier>'Before fetch/validation, preflight required assets for that identifier:
ASSET_NAMES_JSON="$(gh api "repos/evmts/zevm/releases/tags/$RELEASE_IDENTIFIER" --jq '[.assets[].name]')"
RELEASE_TUPLE_COUNT="$(printf '%s' "$ASSET_NAMES_JSON" | jq '[.[] | select(. == "release-tuple.json")] | length')"LIGHT_DEFAULTS_COUNT="$(printf '%s' "$ASSET_NAMES_JSON" | jq '[.[] | select(. == "light-default-checkpoints.json")] | length')"
test "$RELEASE_TUPLE_COUNT" -eq 1test "$LIGHT_DEFAULTS_COUNT" -eq 1If either test fails, do not use that identifier for canonical claims.
If you are using the curl-only path, choose RELEASE_IDENTIFIER from the GitHub releases page and run the curl duplicate-asset gate in step 2.
Release identifier contract:
- tag-based identifiers must match
^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ - commit-based identifiers must match
^commit-[0-9a-f]{40}$ - for commit-based identifiers, the
40-hex suffix must equalzevmGitRevisionin the same release’srelease-tuple.json
RELEASE_IDENTIFIER must exactly match the GitHub release entry that publishes both files.
Inside those files, .releaseIdentifier must exactly equal RELEASE_IDENTIFIER.
Do not mix tuple/checkpoint files from different release identifiers.
Run the checkout steps below from the parent workspace that contains the zevm/ checkout. Voltaire and Guillotine Mini are materialized from build.zig.zon package URL pins.
Deterministic Publication And Supersession Policy
Section titled “Deterministic Publication And Supersession Policy”Treat published release metadata as append-only by releaseIdentifier:
- for one published
releaseIdentifier, there is exactly one canonical artifact pair (release-tuple.jsonandlight-default-checkpoints.json) for operator audit - if a correction is required, publish a new
releaseIdentifierwith a complete replacement artifact pair (release-tuple.jsonandlight-default-checkpoints.json) - do not mutate or replace artifact files in place for an existing
releaseIdentifier - the correcting release entry must include one supersession note in the release notes body under
## ZEVM Supersession Noteusing exactly:
schemaVersion: zevm-supersession-note.v1supersedesReleaseIdentifier: v0.6.1correctedArtifacts: bothreason: Corrected release-tuple and light-default-checkpoints metadata defects.Supersession-note field constraints:
schemaVersionmust be the literalzevm-supersession-note.v1supersedesReleaseIdentifiermust name one valid previously publishedreleaseIdentifiercorrectedArtifactsmust be exactly one ofrelease-tuple.json,light-default-checkpoints.json, orbothreasonmust be non-empty single-line UTF-8 text describing the corrected defect
2. Fetch release-tuple.json And light-default-checkpoints.json
Section titled “2. Fetch release-tuple.json And light-default-checkpoints.json”Use one operator workspace for fetched artifacts:
ARTIFACT_DIR=.ops/release-metadata/"$RELEASE_IDENTIFIER"mkdir -p "$ARTIFACT_DIR"Fetch both required files from the selected release:
gh release download "$RELEASE_IDENTIFIER" \ --repo evmts/zevm \ --pattern 'release-tuple.json' \ --pattern 'light-default-checkpoints.json' \ --dir "$ARTIFACT_DIR"Before asserting canonical publication validity, validate required release-asset uniqueness (duplicate-asset gate):
ASSET_NAMES_JSON="$(gh api "repos/evmts/zevm/releases/tags/$RELEASE_IDENTIFIER" --jq '[.assets[].name]')"
RELEASE_TUPLE_COUNT="$(printf '%s' "$ASSET_NAMES_JSON" | jq '[.[] | select(. == "release-tuple.json")] | length')"LIGHT_DEFAULTS_COUNT="$(printf '%s' "$ASSET_NAMES_JSON" | jq '[.[] | select(. == "light-default-checkpoints.json")] | length')"
test "$RELEASE_TUPLE_COUNT" -eq 1test "$LIGHT_DEFAULTS_COUNT" -eq 1test failure means publication validation did not pass for canonical claims (0 assets = missing; >1 assets = duplicate filename publication).
If gh is unavailable, use direct release-asset URLs:
curl -fsSL -o "$ARTIFACT_DIR/release-tuple.json" \ "https://github.com/evmts/zevm/releases/download/$RELEASE_IDENTIFIER/release-tuple.json"
curl -fsSL -o "$ARTIFACT_DIR/light-default-checkpoints.json" \ "https://github.com/evmts/zevm/releases/download/$RELEASE_IDENTIFIER/light-default-checkpoints.json"curl-only publication-validation equivalent for duplicate-asset detection:
ASSET_NAMES_JSON="$(curl -fsSL "https://api.github.com/repos/evmts/zevm/releases/tags/$RELEASE_IDENTIFIER" | jq '[.assets[].name]')"
RELEASE_TUPLE_COUNT="$(printf '%s' "$ASSET_NAMES_JSON" | jq '[.[] | select(. == "release-tuple.json")] | length')"LIGHT_DEFAULTS_COUNT="$(printf '%s' "$ASSET_NAMES_JSON" | jq '[.[] | select(. == "light-default-checkpoints.json")] | length')"
test "$RELEASE_TUPLE_COUNT" -eq 1test "$LIGHT_DEFAULTS_COUNT" -eq 1If either required artifact is unavailable for the selected identifier, stop this canonical runbook flow:
- either select a different
RELEASE_IDENTIFIERthat publishes both artifacts - or continue as an unpublished source build with internal provenance only (no canonical release-metadata reproducibility claim for that build boundary)
Publication-Validation Checklist For Canonical Claims
Section titled “Publication-Validation Checklist For Canonical Claims”Before asserting canonical release-metadata reproducibility for a selected RELEASE_IDENTIFIER, confirm:
- publication-time validation gate passed: release asset listing shows exactly one
release-tuple.jsonand onelight-default-checkpoints.jsonfor that identifier (no missing or duplicate required filenames) - both required artifacts are published and fetched from that exact identifier:
release-tuple.jsonandlight-default-checkpoints.json - both artifacts pass schema/version checks and
.releaseIdentifierequality checks for that same identifier - local checkouts/toolchain match tuple pins exactly (
zevmGitRevision,voltaireGitRevision,guillotineMiniGitRevision,zigVersion) - operator manifest records artifact paths and
sha256hashes for both files - runtime baked-default audit (when used) confirms
checkpointSource = "default"andlastCheckpoint == .defaults[NETWORK]from the selected identifier’slight-default-checkpoints.json
If any item fails, do not make a canonical reproducibility claim for that build boundary.
Metadata-invalid handling for canonical claims:
- if required artifacts for a
RELEASE_IDENTIFIERare missing, duplicate, unreadable, malformed, schema-mismatched, or value-mismatched, treat that identifier as metadata-invalid - a metadata-invalid identifier cannot be used as canonical reproducibility evidence
- correction must be published as a new
releaseIdentifierwith a complete replacement artifact pair; do not repair artifacts in place for the invalid identifier
3. Validate Metadata Files
Section titled “3. Validate Metadata Files”Validate release-tuple.json contract fields and values:
jq -e --arg rid "$RELEASE_IDENTIFIER" ' (type == "object") and ((keys | sort) == [ "guillotineMiniGitRevision", "releaseIdentifier", "schemaVersion", "voltaireGitRevision", "zevmGitRevision", "zigVersion" ]) and (.schemaVersion == "zevm-release-tuple.v1") and (.releaseIdentifier == $rid) and (.zevmGitRevision | test("^[0-9a-fA-F]{40}$")) and (.voltaireGitRevision | test("^[0-9a-fA-F]{40}$")) and (.guillotineMiniGitRevision | test("^[0-9a-fA-F]{40}$")) and (.zigVersion | type == "string" and length > 0)' "$ARTIFACT_DIR/release-tuple.json" > /dev/nullValidate light-default-checkpoints.json contract fields and values:
EXPECTED_MAINNET="0x$(sed -n 's/^pub const mainnet_hex = "\(.*\)";$/\1/p' src/light_default_checkpoints.zig)"EXPECTED_SEPOLIA="0x$(sed -n 's/^pub const sepolia_hex = "\(.*\)";$/\1/p' src/light_default_checkpoints.zig)"EXPECTED_HOLESKY="0x$(sed -n 's/^pub const holesky_hex = "\(.*\)";$/\1/p' src/light_default_checkpoints.zig)"
jq -e \ --arg rid "$RELEASE_IDENTIFIER" \ --arg expected_mainnet "$EXPECTED_MAINNET" \ --arg expected_sepolia "$EXPECTED_SEPOLIA" \ --arg expected_holesky "$EXPECTED_HOLESKY" ' (type == "object") and ((keys | sort) == ["defaults", "releaseIdentifier", "schemaVersion"]) and (.schemaVersion == "zevm-light-default-checkpoints.v1") and (.releaseIdentifier == $rid) and (.defaults | type == "object") and ((.defaults | keys | sort) == ["holesky", "mainnet", "sepolia"]) and (.defaults.mainnet | test("^0x[0-9a-fA-F]{64}$")) and (.defaults.sepolia | test("^0x[0-9a-fA-F]{64}$")) and (.defaults.holesky | test("^0x[0-9a-fA-F]{64}$")) and (.defaults.mainnet == $expected_mainnet) and (.defaults.sepolia == $expected_sepolia) and (.defaults.holesky == $expected_holesky)' "$ARTIFACT_DIR/light-default-checkpoints.json" > /dev/nullQuick schema reference for light-default-checkpoints.json:
schemaVersion(string): must bezevm-light-default-checkpoints.v1releaseIdentifier(string): must equal the selectedRELEASE_IDENTIFIERdefaults(object): required network map with exactlymainnet,sepolia,holesky; each value is a0x-prefixed 32-byte hex checkpoint (^0x[0-9a-fA-F]{64}$)
Sample payload:
{ "schemaVersion": "zevm-light-default-checkpoints.v1", "releaseIdentifier": "vX.Y.Z", "defaults": { "mainnet": "0x9b41a80f58c52068a00e8535b8d6704769c7577a5fd506af5e0c018687991d55", "sepolia": "0x4065c2509eaa15dbe60e1f80cff5205a532aa95aaa1d73c1c286f7f8535555d4", "holesky": "0xe1f575f0b691404fe82cce68a09c2c98af197816de14ce53c0fe9f9bd02d2399" }}The checkpoint hashes above are the baked defaults from src/light_default_checkpoints.zig; update this sample in the same change as runtime default checkpoint changes.
Operator interpretation of defaults entries:
defaults.mainnet: release-specific baked default checkpoint formainnet, used when checkpoint precedence falls todefaultdefaults.sepolia: release-specific baked default checkpoint forsepolia, used when checkpoint precedence falls todefaultdefaults.holesky: release-specific baked default checkpoint forholesky, used when checkpoint precedence falls todefault
4. Check Out Pinned Revisions And Zig Version
Section titled “4. Check Out Pinned Revisions And Zig Version”Extract the pinned tuple:
ZEVM_REVISION="$(jq -r '.zevmGitRevision' "$ARTIFACT_DIR/release-tuple.json")"VOLTAIRE_REVISION="$(jq -r '.voltaireGitRevision' "$ARTIFACT_DIR/release-tuple.json")"GUILLOTINE_MINI_REVISION="$(jq -r '.guillotineMiniGitRevision' "$ARTIFACT_DIR/release-tuple.json")"PINNED_ZIG_VERSION="$(jq -r '.zigVersion' "$ARTIFACT_DIR/release-tuple.json")"Materialize the ZEVM checkout and verify package pins:
git -C zevm checkout "$ZEVM_REVISION"Verify local state matches tuple pins:
test "$(git -C zevm rev-parse HEAD)" = "$ZEVM_REVISION"test "$(zig version)" = "$PINNED_ZIG_VERSION"cd zevmzig build --fetchzig build dependency-preflight -- \ --voltaire-revision "$VOLTAIRE_REVISION" \ --guillotine-mini-revision "$GUILLOTINE_MINI_REVISION" \ --zig-version "$PINNED_ZIG_VERSION"Then run the release reproducibility gate from the materialized ZEVM checkout:
zig build verifyzig build qualification-check -- --require-coveredbun tools/hive_rpc_compat_smoke.tsIf checkout, zig version, dependency preflight, build, qualification command, or Hive smoke fails, the tuple is not valid reproducibility evidence for canonical release claims.
5. Record Provenance In An Operator Manifest
Section titled “5. Record Provenance In An Operator Manifest”Phase-1 reproducibility depends on preserved build provenance. Record selected identifier, tuple pins, and metadata file hashes in an operator manifest:
MANIFEST_PATH=.ops/manifests/zevm-release-manifest.jsonmkdir -p "$(dirname "$MANIFEST_PATH")"
RELEASE_TUPLE_SHA256="$(shasum -a 256 "$ARTIFACT_DIR/release-tuple.json" | awk '{print $1}')"LIGHT_DEFAULTS_SHA256="$(shasum -a 256 "$ARTIFACT_DIR/light-default-checkpoints.json" | awk '{print $1}')"
jq -n \ --arg generatedAtUtc "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ --arg releaseIdentifier "$RELEASE_IDENTIFIER" \ --arg zevmGitRevision "$ZEVM_REVISION" \ --arg voltaireGitRevision "$VOLTAIRE_REVISION" \ --arg guillotineMiniGitRevision "$GUILLOTINE_MINI_REVISION" \ --arg zigVersion "$PINNED_ZIG_VERSION" \ --arg releaseTuplePath "$ARTIFACT_DIR/release-tuple.json" \ --arg releaseTupleSha256 "$RELEASE_TUPLE_SHA256" \ --arg lightDefaultsPath "$ARTIFACT_DIR/light-default-checkpoints.json" \ --arg lightDefaultsSha256 "$LIGHT_DEFAULTS_SHA256" \ '{ schemaVersion: "zevm-operator-manifest.v1", generatedAtUtc: $generatedAtUtc, releaseIdentifier: $releaseIdentifier, buildTuple: { zevmGitRevision: $zevmGitRevision, voltaireGitRevision: $voltaireGitRevision, guillotineMiniGitRevision: $guillotineMiniGitRevision, zigVersion: $zigVersion }, metadataArtifacts: { releaseTuple: { path: $releaseTuplePath, sha256: $releaseTupleSha256 }, lightDefaultCheckpoints: { path: $lightDefaultsPath, sha256: $lightDefaultsSha256 } } }' > "$MANIFEST_PATH"6. Audit Baked Defaults Via zevm_lightSyncStatus
Section titled “6. Audit Baked Defaults Via zevm_lightSyncStatus”Use runtime probing only as a verification step.
For selected network NETWORK, start light mode with no explicit or persisted checkpoint input:
- omit
--checkpoint - keep
mode.light.checkpointunset - ensure
checkpointDir/checkpointis absent
Then verify checkpointSource = "default" and compare lastCheckpoint with release metadata:
NETWORK="${NETWORK:-mainnet}"case "$NETWORK" in mainnet|sepolia|holesky) ;; *) echo "NETWORK must be one of: mainnet|sepolia|holesky" >&2 exit 1 ;;esac
STATUS_JSON="$(curl -s -X POST http://127.0.0.1:8545 \ -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"zevm_lightSyncStatus","params":[]}')"
CHECKPOINT_SOURCE="$(printf '%s' "$STATUS_JSON" | jq -r '.result.checkpointSource')"ACTUAL_CHECKPOINT="$(printf '%s' "$STATUS_JSON" | jq -r '.result.lastCheckpoint')"EXPECTED_CHECKPOINT="$(jq -r --arg network "$NETWORK" '.defaults[$network] // empty' "$ARTIFACT_DIR/light-default-checkpoints.json")"
test -n "$EXPECTED_CHECKPOINT"test "$CHECKPOINT_SOURCE" = "default"test "$ACTUAL_CHECKPOINT" = "$EXPECTED_CHECKPOINT"7. Audit Restart/Resume Path With checkpointSource = "persisted"
Section titled “7. Audit Restart/Resume Path With checkpointSource = "persisted"”This audit verifies restart/resume semantics when startup source is operator-managed persisted input.
Requirements:
- do not pass
--checkpoint - keep
mode.light.checkpointunset ornull - provide
checkpointDir/checkpointwith 64 hex chars (no0x)
Prepare persisted checkpoint input from release defaults:
NETWORK="${NETWORK:-sepolia}"CHECKPOINT_DIR=".zevm/checkpoints/$NETWORK"PERSISTED_CHECKPOINT_HEX="$(jq -r --arg network "$NETWORK" '.defaults[$network]' "$ARTIFACT_DIR/light-default-checkpoints.json" | sed 's/^0x//')"
mkdir -p "$CHECKPOINT_DIR"printf '%s\n' "$PERSISTED_CHECKPOINT_HEX" > "$CHECKPOINT_DIR/checkpoint"printf '%s\n' "$PERSISTED_CHECKPOINT_HEX" | grep -Eq '^[0-9a-fA-F]{64}$'Start ZEVM in light mode with that checkpointDir, then verify:
STATUS_JSON="$(curl -s -X POST http://127.0.0.1:8545 \ -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"zevm_lightSyncStatus","params":[]}')"
test "$(printf '%s' "$STATUS_JSON" | jq -r '.result.checkpointSource')" = "persisted"printf '%s' "$(printf '%s' "$STATUS_JSON" | jq -r '.result.lastCheckpoint')" | grep -Eq '^0x[0-9a-fA-F]{64}$'Restart ZEVM with the same configuration and run the same status check again.
For the restart/resume audit to pass, each process start must report checkpointSource = "persisted" and a valid Hash32 lastCheckpoint.
8. Phase-1 Limitation: Release Identifier Is Not Runtime-Queryable
Section titled “8. Phase-1 Limitation: Release Identifier Is Not Runtime-Queryable”Phase 1 has no runtime JSON-RPC or CLI contract to directly report the ZEVM releaseIdentifier from a running process.
Operators must rely on build provenance records (operator manifest, pinned revisions, and archived release metadata artifacts) to identify the release/build boundary.
If canonical release metadata artifacts were not published for the build, rely on internal provenance records only and treat that build boundary as non-canonical in this release-metadata workflow.