Skip to content

Release / prepare-release-pr / main #79

Release / prepare-release-pr / main

Release / prepare-release-pr / main #79

Workflow file for this run

name: Release
run-name: Release / ${{ inputs.operation }} / ${{ github.ref_name }}
on:
workflow_dispatch:
inputs:
operation:
description: Prepare, dry-run, publish with trusted publishers, or bootstrap first registry versions
required: true
type: choice
default: prepare-release-pr
options:
- prepare-release-pr
- publish-dry-run
- publish
- publish-bootstrap
release_commit:
description: Optional full commit SHA assertion; when set it must equal the workflow commit
required: false
type: string
default: ""
continuation_pointer:
description: Internal exact-parent continuation pointer; leave empty for a maintainer dispatch
required: false
type: string
default: ""
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CANONICAL_RELEASE_REPOSITORY: f0rr0/oliphaunt
NODE_VERSION: 22.22.3
NPM_VERSION: 11.18.0
PNPM_VERSION: 11.5.0
DENO_VERSION: v2.8.1
BUN_VERSION: 1.3.14
PUBLICATION_LOCK_PATH: target/release/publication-lock.json
BOOTSTRAP_LEDGER_PATH: target/release/bootstrap-ledger
# Bootstrap retains its independent six-hour recovery envelope. Normal
# publication is split into three sequential jobs, each with a fresh hard
# deadline and an explicit runner/action cleanup reserve.
RELEASE_JOB_HARD_WINDOW_SECONDS: 21180
REGISTRY_MUTATION_WINDOW_SECONDS: 19800
GITHUB_STAGE_JOB_HARD_WINDOW_SECONDS: 21000
REGISTRY_JOB_HARD_WINDOW_SECONDS: 21000
FINALIZE_JOB_HARD_WINDOW_SECONDS: 6840
NORMAL_REGISTRY_MUTATION_WINDOW_SECONDS: 11400
REGISTRY_EVIDENCE_HANDOFF_RESERVE_SECONDS: 900
# Finalization has its own two-hour job. Its entry gate requires 48 intact
# minutes: every finalization step's 45-minute hard bound plus a separate
# three-minute runner/action transition margin.
RELEASE_FINALIZATION_RESERVE_SECONDS: 3000
RELEASE_MINIMUM_FINALIZATION_SECONDS: 2880
RELEASE_FINALIZATION_CLEANUP_MARGIN_SECONDS: 180
# Public consumer lanes run concurrently under one 13-minute internal
# deadline and stop with ten minutes preserved for evidence, lock proof, and
# draft promotion. The enclosing workflow step has a 15-minute hard bound.
PUBLIC_CONSUMER_SMOKE_TIMEOUT_SECONDS: 780
PUBLIC_CONSUMER_FINALIZATION_RESERVE_SECONDS: 600
MAVEN_CENTRAL_NAMESPACE: dev.oliphaunt
concurrency:
# Every registry-writing or release-branch-writing operation shares one
# non-cancelling lock. GitHub permits one pending run while the active run
# completes, so maintainers must not stack mutation dispatches. Dry-runs are
# read-only and may run once per exact SHA.
group: release-${{ inputs.operation == 'publish-dry-run' && github.sha || 'mutation' }}
cancel-in-progress: false
defaults:
run:
shell: bash
jobs:
validate-inputs:
name: Validate release inputs
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Require canonical release repository
run: |
if [[ "${GITHUB_REPOSITORY}" != "${CANONICAL_RELEASE_REPOSITORY}" ]]; then
echo "Release workflow is pinned to ${CANONICAL_RELEASE_REPOSITORY}; got ${GITHUB_REPOSITORY}" >&2
exit 1
fi
- name: Checkout exact workflow commit
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.sha }}
fetch-depth: 1
persist-credentials: false
- name: Validate release workflow inputs
id: validate_release_inputs
env:
RELEASE_OPERATION: ${{ inputs.operation }}
RELEASE_COMMIT: ${{ inputs.release_commit }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
run: bash .github/scripts/validate-release-workflow-inputs.sh
prepare-release-pr:
name: Prepare release PR
needs: validate-inputs
runs-on: ubuntu-24.04
timeout-minutes: 20
if: ${{ inputs.operation == 'prepare-release-pr' }}
environment: release-pr
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Require main
run: |
if [[ "${GITHUB_REF}" != "refs/heads/main" ]]; then
echo "Releases must be run from main; got ${GITHUB_REF}" >&2
exit 1
fi
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: Require current main
id: require_current_main
timeout-minutes: 1
run: bash .github/scripts/require-current-main.sh "$GITHUB_SHA"
- name: Set up Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "true"
- name: Set up Rust
uses: ./.github/actions/setup-rust
- name: Validate release metadata
run: |
tools/dev/bun.sh tools/release/release-check.mjs
- name: Require release PR token
env:
RELEASE_PR_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }}
run: |
if [[ -z "${RELEASE_PR_TOKEN}" ]]; then
echo "RELEASE_PR_TOKEN is required so generated release PRs trigger normal PR CI." >&2
echo "Configure a GitHub App or maintainer bot token in the release-pr environment." >&2
exit 1
fi
- name: Create or update release-please PR
id: release_please
uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071
with:
token: ${{ secrets.RELEASE_PR_TOKEN }}
target-branch: main
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
skip-github-release: true
- name: Sync derived release PR files
id: sync_release_pr
env:
GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }}
RELEASE_PLEASE_PR: ${{ steps.release_please.outputs.pr }}
RELEASE_PLEASE_PRS: ${{ steps.release_please.outputs.prs }}
RELEASE_PLEASE_PRS_CREATED: ${{ steps.release_please.outputs.prs_created }}
run: |
set -euo pipefail
release_pr_number="$(bun .github/scripts/resolve-release-please-pr.mjs)"
if [[ -z "${release_pr_number}" ]]; then
release_pr_number="$(
gh pr list \
--base main \
--head release-please--branches--main \
--state open \
--json number \
--jq '.[0].number // empty'
)"
fi
if [[ -z "${release_pr_number}" ]]; then
if [[ "${RELEASE_PLEASE_PRS_CREATED}" == "true" ]]; then
echo "release-please reported PR changes, but no release PR number could be resolved." >&2
exit 1
fi
echo "No open release-please PR found; derived release file sync skipped."
exit 0
fi
IFS=$'\t' read -r \
release_pr_observed_number \
release_pr_base \
release_pr_head \
release_pr_old_sha \
release_pr_head_repository \
release_pr_is_cross_repository \
release_pr_state \
release_pr_title \
<<<"$(
gh pr view "${release_pr_number}" \
--json number,baseRefName,headRefName,headRefOid,headRepository,isCrossRepository,state,title \
--jq '[.number, .baseRefName, .headRefName, .headRefOid, .headRepository.nameWithOwner, (.isCrossRepository | tostring), .state, .title] | @tsv'
)"
gh auth setup-git
bash .github/scripts/require-current-main.sh "$GITHUB_SHA"
release_pr_identity_args=(
--pr-number "${release_pr_number}"
--observed-pr-number "${release_pr_observed_number}"
--base "${release_pr_base}"
--head "${release_pr_head}"
--head-sha "${release_pr_old_sha}"
--head-repository "${release_pr_head_repository}"
--cross-repository "${release_pr_is_cross_repository}"
--state "${release_pr_state}"
--title "${release_pr_title}"
--main-sha "$GITHUB_SHA"
)
bun .github/scripts/normalize-release-please-pr.mjs \
normalize \
"${release_pr_identity_args[@]}"
tools/dev/bun.sh tools/release/sync-release-pr.mjs
tools/dev/bun.sh tools/release/sync-release-pr.mjs --check
if [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
echo "Derived release synchronization created untracked files; refusing an incomplete amend." >&2
git status --short >&2
exit 1
fi
git config user.name "oliphaunt-release-bot"
git config user.email "oliphaunt-release-bot@users.noreply.github.com"
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then
git add -u
git commit --amend --no-edit
else
echo "Derived release files already match the normalized Release Please tree."
fi
release_products_json="$(
tools/dev/bun.sh tools/release/verify-release-commit.mjs \
--derive-products \
--head-ref HEAD
)"
tools/dev/bun.sh tools/release/verify-release-commit.mjs \
--products-json "${release_products_json}" \
--head-ref HEAD
tools/dev/bun.sh tools/release/release-metadata-check.mjs
bun .github/scripts/normalize-release-please-pr.mjs \
push \
"${release_pr_identity_args[@]}"
- name: Report release-please PR result
env:
RELEASE_PLEASE_PRS_CREATED: ${{ steps.release_please.outputs.prs_created }}
run: |
if [[ "${RELEASE_PLEASE_PRS_CREATED}" == "true" ]]; then
echo "release-please created or updated a release PR."
else
echo "release-please found no releasable changes."
fi
publish-dry-run:
name: Prepare release dry run
needs:
- validate-inputs
runs-on: macos-26
timeout-minutes: 360
if: ${{ inputs.operation == 'publish-dry-run' }}
environment: release-dry-run
permissions:
actions: read
contents: read
steps: &release_candidate_steps
- name: Record bounded GitHub staging job deadline
id: github_stage_job_deadline
if: ${{ inputs.operation == 'publish' }}
run: |
if [[ ! "$GITHUB_STAGE_JOB_HARD_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'GITHUB_STAGE_JOB_HARD_WINDOW_SECONDS must be a positive integer' >&2
exit 1
fi
hard_deadline=$(( $(date +%s) + GITHUB_STAGE_JOB_HARD_WINDOW_SECONDS ))
{
echo "GITHUB_STAGE_JOB_HARD_DEADLINE_EPOCH=$hard_deadline"
echo "OLIPHAUNT_GITHUB_CONTENT_WRITE_COLD_START_EPOCH=$(date +%s)"
echo "OLIPHAUNT_GITHUB_CONTENT_WRITE_PACER_PATH=$RUNNER_TEMP/oliphaunt-github-content-write-pacer.json"
echo "OLIPHAUNT_GITHUB_CORE_REQUEST_JOURNAL_PATH=$RUNNER_TEMP/oliphaunt-github-core-request-journal.json"
echo "OLIPHAUNT_REQUIRE_GITHUB_CORE_REQUEST_JOURNAL=true"
echo "OLIPHAUNT_RELEASE_ROOT_RUN_ID=$GITHUB_RUN_ID"
echo "OLIPHAUNT_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots"
} >> "$GITHUB_ENV"
echo "The GitHub staging job must seal its immutable handoff before Unix time $hard_deadline."
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: Resolve release commit
id: release_head
timeout-minutes: 1
env:
INPUT_RELEASE_COMMIT: ${{ inputs.release_commit }}
run: .github/scripts/resolve-release-head.sh
- name: Set up pinned Node.js
id: setup_github_stage_node
timeout-minutes: 3
uses: ./.github/actions/setup-node-runtime
with:
node-version: ${{ env.NODE_VERSION }}
- name: Set up Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "true"
- name: Set up Rust
uses: ./.github/actions/setup-rust
- name: Configure macOS release toolchains
id: configure_release_candidate_toolchains
run: bash .github/scripts/configure-macos-release-toolchains.sh --android
- name: Validate release metadata
run: |
tools/dev/bun.sh tools/release/release-check.mjs
- name: Plan product releases
id: release_plan
run: |
release_plan_args=(
--from-product-tags
--include-current-tags
--head-ref "$RELEASE_HEAD_SHA"
--format github-output
)
tools/dev/bun.sh tools/release/release_plan.mjs "${release_plan_args[@]}" >> "$GITHUB_OUTPUT"
- name: No package release planned
if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }}
run: echo "No release-affecting product changes were found since the last product tag."
- name: Resolve selected registry authentication needs
id: registry_needs
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: bun .github/scripts/selected-registry-needs.mjs
- name: Verify direct-workflow OIDC identity
id: verify_oidc_identity
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
RELEASE_OPERATION: ${{ inputs.operation }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
run: bun .github/scripts/verify-github-oidc-identity.mjs
- name: Prove workflow HEAD is the release-bump commit
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/verify-release-commit.mjs \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Preflight selected product tag and release collisions
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
gh auth setup-git
tools/dev/bun.sh tools/release/verify_product_tags.mjs \
--products-json "$PRODUCTS_JSON" \
--target "$RELEASE_HEAD_SHA" \
--allow-missing
bun .github/scripts/manage-release-drafts.mjs preflight \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Check publish environment
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: tools/release/check_publish_environment.mjs --products-json "${PRODUCTS_JSON}"
- name: Verify external registry ownership and trust links
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
run: bun .github/scripts/verify-external-publish-readiness.mjs
- name: Import, sign, and verify Maven credentials before mutation
id: verify_maven_signing
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.registry_needs.outputs.needs_maven == 'true' }}
timeout-minutes: 2
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: tools/dev/bun.sh tools/release/verify-maven-signing-readiness.mjs
- name: Require qualified release-commit CI run
id: ci_qualification
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: ${{ inputs.operation }}
REQUIRES_WASIX_EVIDENCE: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }}
run: |
qualification_timeout=7200
if [[ "$RELEASE_OPERATION" != publish-dry-run ]]; then
qualification_timeout=0
fi
qualification_args=(
CI
"$RELEASE_HEAD_SHA"
"$qualification_timeout"
--event push
--event workflow_dispatch
--job Builds
--job Required
--job Qualified
--artifact artifact-build-plan
--artifact oliphaunt-release-candidate
)
if [[ "$REQUIRES_WASIX_EVIDENCE" == true ]]; then
qualification_args+=(--artifact wasix-release-regression-evidence)
fi
if [[ "${{ steps.release_plan.outputs.has_extension_products }}" == true ]]; then
qualification_args+=(--artifact oliphaunt-extension-package-artifacts)
fi
bash .github/scripts/require-workflow-success.sh "${qualification_args[@]}"
- name: Download exact-SHA qualification record
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/release-candidate \
--run-id "$CI_RUN_ID" \
--job Qualified \
--artifact oliphaunt-release-candidate
- name: Download exact-SHA affected plan
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/release-candidate/affected-plan \
--run-id "$CI_RUN_ID" \
--job Plan \
--artifact artifact-build-plan
- name: Download required exact-SHA WASIX evidence
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.requires_wasix_release_regression_evidence == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/release-candidate/wasix-evidence \
--run-id "$CI_RUN_ID" \
--job "E2E / WASIX release regression" \
--artifact wasix-release-regression-evidence
- name: Verify exact-SHA qualification record
id: verify_qualification
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
WASIX_EVIDENCE_REQUIRED: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }}
run: |
node .github/scripts/verify-release-candidate.mjs \
target/release-candidate/oliphaunt-release-candidate.json \
--plan target/release-candidate/affected-plan/ci-plan.json \
--wasix-evidence-required "$WASIX_EVIDENCE_REQUIRED" \
--wasix-evidence-root target/release-candidate/wasix-evidence
- name: Require one approved dry-run lock and capsule
id: approved_publication_lock
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
bash .github/scripts/require-workflow-success.sh \
Release \
"$RELEASE_HEAD_SHA" \
300 \
--event workflow_dispatch \
--artifact oliphaunt-publication-lock \
--artifact oliphaunt-bootstrap-capsule
- name: Download prior approved publication lock
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
RELEASE_LOCK_RUN_ID: ${{ steps.approved_publication_lock.outputs.run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.approved_publication_lock.outputs.artifact_metadata_json }}
run: |
node .github/scripts/download-build-artifacts.mjs \
Release \
"$RELEASE_HEAD_SHA" \
target/approved-publication-lock \
--run-id "$RELEASE_LOCK_RUN_ID" \
--artifact-metadata-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--artifact oliphaunt-publication-lock
- name: Validate product versions and registry state
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: tools/dev/bun.sh tools/release/release-check-registries.mjs --products-json "${PRODUCTS_JSON}" --head-ref "$RELEASE_HEAD_SHA"
- name: Download WASIX runtime build artifacts
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_liboliphaunt_wasix == 'true' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: bun .github/scripts/download-wasix-runtime-build-artifacts.mjs
- name: Download WASIX release assets
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_liboliphaunt_wasix == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/oliphaunt-wasix/release-assets \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact liboliphaunt-wasix-release-assets
- name: Download exact-extension package artifacts
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.has_extension_products == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/extension-artifacts \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact oliphaunt-extension-package-artifacts
- name: Download SDK package artifacts
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
download_sdk_artifact() {
local product="$1"
local artifact_args=()
while IFS= read -r artifact; do
artifact_args+=(--artifact "$artifact")
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs ci-artifact-names --product "$product" --family sdk-package --format lines)
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
"target/sdk-artifacts/$product" \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifact_args[@]}"
}
while IFS= read -r product; do
download_sdk_artifact "$product"
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs ci-products --family sdk-package --products-json "$PRODUCTS_JSON" --format lines)
- name: Download liboliphaunt release assets
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_liboliphaunt_native == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/liboliphaunt/release-assets \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact liboliphaunt-native-release-assets
- name: Set up Deno for TypeScript JSR consumer checks
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_oliphaunt_js == 'true' }}
uses: ./.github/actions/setup-deno
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Set up Bun for TypeScript npm consumer checks
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_oliphaunt_js == 'true' }}
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install TypeScript release tooling
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_oliphaunt_js == 'true' }}
run: pnpm install --frozen-lockfile
- name: Download native helper release assets
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && (steps.release_plan.outputs.product_oliphaunt_broker == 'true' || steps.release_plan.outputs.product_oliphaunt_node_direct == 'true') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCT_OLIPHAUNT_BROKER: ${{ steps.release_plan.outputs.product_oliphaunt_broker }}
PRODUCT_OLIPHAUNT_NODE_DIRECT: ${{ steps.release_plan.outputs.product_oliphaunt_node_direct }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
download_helper_artifacts() {
local product="$1"
local kind="$2"
local destination="$3"
local artifact_args=()
while IFS= read -r artifact; do
artifact_args+=(--artifact "$artifact")
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs ci-artifact-names --product "$product" --kind "$kind" --family release-assets --format lines)
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
"$destination" \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifact_args[@]}"
}
if [ "$PRODUCT_OLIPHAUNT_BROKER" = "true" ]; then
download_helper_artifacts \
oliphaunt-broker \
broker-helper \
target/oliphaunt-broker/release-assets
fi
if [ "$PRODUCT_OLIPHAUNT_NODE_DIRECT" = "true" ]; then
download_helper_artifacts \
oliphaunt-node-direct \
node-direct-addon \
target/oliphaunt-node-direct/release-assets
fi
- name: Download Node direct optional npm packages
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.release_plan.outputs.product_oliphaunt_node_direct == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
artifact_args=()
while IFS= read -r artifact; do
artifact_args+=(--artifact "$artifact")
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs ci-artifact-names --product oliphaunt-node-direct --kind node-direct-addon --family npm-package --format lines)
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
target/oliphaunt-node-direct/npm-packages \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifact_args[@]}"
- name: Freeze canonical Apple extension carrier input
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && (steps.release_plan.outputs.product_oliphaunt_swift == 'true' || steps.release_plan.outputs.product_oliphaunt_react_native == 'true') }}
env:
HAS_EXTENSION_PRODUCTS: ${{ steps.release_plan.outputs.has_extension_products }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
includes_swift="$(bun -e 'console.log(JSON.parse(process.argv[1]).includes("oliphaunt-swift"))' "$PRODUCTS_JSON")"
includes_react_native="$(bun -e 'console.log(JSON.parse(process.argv[1]).includes("oliphaunt-react-native"))' "$PRODUCTS_JSON")"
swift_source_carrier=target/sdk-artifacts/oliphaunt-swift/release-tree/src/sdks/swift/Carriers/oliphaunt-react-native-ios-carriers.json
react_native_source_carrier=target/sdk-artifacts/oliphaunt-react-native/ios-carriers/oliphaunt-react-native-ios-carriers.json
extension_manifest_args=()
extension_carrier_args=()
while IFS= read -r product; do
product_root="target/extension-artifacts/$product"
manifest="$product_root/extension-artifacts.json"
if [[ ! -f "$manifest" ]]; then
echo "Selected extension product $product is missing $manifest" >&2
exit 1
fi
extension_manifest_args+=(--extension-manifest "$manifest")
product_carriers=()
while IFS= read -r carrier; do
product_carriers+=("$carrier")
done < <(find "$product_root/release-assets" -maxdepth 1 -type f -name '*-swift-extension-carrier.json' | LC_ALL=C sort)
if (( ${#product_carriers[@]} != 1 )); then
echo "Selected extension product $product must provide exactly one independent Swift carrier; found ${#product_carriers[@]}" >&2
exit 1
fi
extension_carrier_args+=(--extension-carrier "${product_carriers[0]}")
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs \
ci-products \
--family extension-artifacts \
--products-json "$PRODUCTS_JSON" \
--format lines)
if [[ "$HAS_EXTENSION_PRODUCTS" == true && ${#extension_manifest_args[@]} == 0 ]]; then
echo 'The release plan selected extension products but resolved no selected extension artifacts.' >&2
exit 1
fi
rm -rf target/release/ios-carriers target/release-work/ios-carriers
if [[ "$includes_swift" == true && "$includes_react_native" == true ]] && ! cmp -s "$swift_source_carrier" "$react_native_source_carrier"; then
echo 'Swift and React Native source carriers disagree for the same release plan.' >&2
diff --unified "$swift_source_carrier" "$react_native_source_carrier" || true
exit 1
fi
if [[ "$includes_react_native" == true ]]; then
mkdir -p target/release/ios-carriers
public_args=(
--base-carrier "$react_native_source_carrier"
"${extension_manifest_args[@]}"
--output target/release/ios-carriers/oliphaunt-react-native-ios-carriers.json
)
tools/dev/bun.sh tools/release/ios-carrier-manifest.mjs "${public_args[@]}"
fi
if [[ "$includes_swift" == true && "$HAS_EXTENSION_PRODUCTS" == true ]]; then
mkdir -p target/release-work/ios-carriers
local_aggregate_carrier=target/release-work/ios-carriers/oliphaunt-react-native-ios-carriers.json
local_args=(
--base-carrier "$swift_source_carrier"
"${extension_manifest_args[@]}"
--output "$local_aggregate_carrier"
--local-urls
)
tools/dev/bun.sh tools/release/ios-carrier-manifest.mjs "${local_args[@]}"
extensions_csv="$(bun -e '
const manifest = JSON.parse(await Bun.file(process.argv[1]).text());
console.log(manifest.extensions.map((row) => row.sqlName).sort().join(","));
' "$local_aggregate_carrier")"
if [[ -z "$extensions_csv" || ${#extension_carrier_args[@]} == 0 ]]; then
echo 'Swift extension validation requires selected independent extension carrier assets.' >&2
exit 1
fi
swift_version="$(tools/dev/bun.sh tools/release/product-version.mjs version oliphaunt-swift)"
cache=target/release-work/swiftpm-extension-cache
node src/sdks/swift/tools/render-extension-products.mjs \
--carrier "$local_aggregate_carrier" \
--extensions "$extensions_csv" \
--cache-dir "$cache" \
--allow-file-urls \
--base-package-version "$swift_version" \
--output-dir target/release-work/swiftpm-extension-cache-warm
node src/sdks/swift/tools/render-extension-products.mjs \
--carrier "$swift_source_carrier" \
"${extension_carrier_args[@]}" \
--extensions "$extensions_csv" \
--cache-dir "$cache" \
--offline \
--base-package-version "$swift_version" \
--output-dir target/release/swiftpm-extension-consumer-fixture
fi
- name: Set up pinned npm publisher for product dry-runs
id: setup_github_stage_npm
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }}
timeout-minutes: 3
uses: ./.github/actions/setup-npm-publisher
with:
npm-version: ${{ env.NPM_VERSION }}
- name: Validate selected release product dry-runs
id: validate_product_dry_runs
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
OLIPHAUNT_BROKER_RELEASE_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-broker/release-assets
OLIPHAUNT_NODE_ADDON_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-node-direct/release-assets
OLIPHAUNT_VERIFIED_NODE_EXECUTABLE: ${{ steps.setup_github_stage_npm.outputs.node-executable }}
OLIPHAUNT_VERIFIED_NPM_CLI: ${{ steps.setup_github_stage_npm.outputs.npm-cli }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
WASIX_EVIDENCE_REQUIRED: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }}
run: tools/dev/bun.sh tools/release/release-publish.mjs publish-dry-run --qualified-ci --products-json "${PRODUCTS_JSON}" --head-ref "$RELEASE_HEAD_SHA"
- name: Assemble exact candidate Cargo registry
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_cargo == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
candidate_registry_args=(
publish
--surface cargo
--strict
--exact-artifacts
--products-json "$PRODUCTS_JSON"
--registry-root target/release-work/candidate-registries
)
for artifact_root in \
target/release \
target/sdk-artifacts \
target/liboliphaunt/release-assets \
target/liboliphaunt/cargo-artifacts \
target/oliphaunt-wasix/release-assets \
target/oliphaunt-wasix/cargo-artifacts \
target/oliphaunt-broker/release-assets \
target/oliphaunt-broker/cargo-artifacts \
target/oliphaunt-node-direct/release-assets \
target/oliphaunt-node-direct/npm-packages; do
if [[ -e "$artifact_root" ]]; then
candidate_registry_args+=(--artifact-root "$artifact_root")
fi
done
while IFS= read -r product; do
artifact_root="target/extension-artifacts/$product"
if [[ ! -d "$artifact_root" ]]; then
echo "Selected extension product $product is missing $artifact_root" >&2
exit 1
fi
candidate_registry_args+=(--artifact-root "$artifact_root")
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs \
ci-products \
--family extension-artifacts \
--products-json "$PRODUCTS_JSON" \
--format lines)
tools/dev/bun.sh tools/release/local-registry-publish.mjs "${candidate_registry_args[@]}"
- name: Resolve Cargo examples against exact candidates
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.registry_needs.outputs.needs_cargo == 'true' }}
run: |
tools/dev/bun.sh tools/release/validate-example-cargo-candidates.mjs \
--index target/release-work/candidate-registries/cargo/index \
--output-root target/release-work/example-cargo-candidates
tools/dev/bun.sh tools/release/validate-example-cargo-candidates.mjs \
--index target/release-work/candidate-registries/cargo/index \
--output-root target/release-work/example-cargo-candidate-build \
--policy wasix-electron-sidecar \
--build
- name: Freeze exhaustive publication lock
id: freeze_publication_lock
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
lock_args=(
create
--products-json "$PRODUCTS_JSON"
--head-ref "$RELEASE_HEAD_SHA"
--output target/release/publication-lock.json
)
for artifact_root in \
target/release \
target/sdk-artifacts \
target/liboliphaunt/release-assets \
target/liboliphaunt/cargo-artifacts \
target/oliphaunt-wasix/release-assets \
target/oliphaunt-broker/release-assets \
target/oliphaunt-broker/cargo-artifacts \
target/oliphaunt-wasix/cargo-artifacts \
target/oliphaunt-node-direct/release-assets \
target/oliphaunt-node-direct/npm-packages; do
if [[ -e "$artifact_root" ]]; then
lock_args+=(--artifact-root "$artifact_root")
fi
done
while IFS= read -r product; do
artifact_root="target/extension-artifacts/$product"
if [[ ! -d "$artifact_root" ]]; then
echo "Selected extension product $product is missing $artifact_root" >&2
exit 1
fi
lock_args+=(--artifact-root "$artifact_root")
done < <(tools/dev/bun.sh tools/release/release_graph_query.mjs \
ci-products \
--family extension-artifacts \
--products-json "$PRODUCTS_JSON" \
--format lines)
tools/dev/bun.sh tools/release/publication-lock.mjs "${lock_args[@]}"
tools/dev/bun.sh tools/release/publication-lock.mjs \
verify \
--lock target/release/publication-lock.json \
--head-ref "$RELEASE_HEAD_SHA"
- name: Match prior approved publication lock
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
run: |
approved_lock=target/approved-publication-lock/publication-lock.json
if ! cmp -s "$approved_lock" "$PUBLICATION_LOCK_PATH"; then
echo 'publication lock differs from the prior approved dry-run lock' >&2
diff --unified "$approved_lock" "$PUBLICATION_LOCK_PATH" || true
exit 1
fi
echo 'publication lock exactly matches the prior approved dry-run lock'
- name: Assemble and sign the exact Maven Central bundle before release mutation
id: preflight_maven_bundle
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.registry_needs.outputs.needs_maven == 'true' }}
timeout-minutes: 15
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: |
tools/dev/bun.sh tools/release/preflight-maven-central-bundle.mjs \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--release-commit "$RELEASE_HEAD_SHA"
- name: Prove the exact SwiftPM source tag is remotely collision-free
id: preflight_swift_source_tag
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_oliphaunt_swift == 'true' }}
timeout-minutes: 2
run: |
tools/dev/bun.sh tools/release/preflight-swiftpm-source-tag.mjs \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--release-commit "$RELEASE_HEAD_SHA"
- name: Admit the complete paced GitHub release request envelope
id: github_request_budget
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/github-release-request-budget.mjs \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--github-output "$GITHUB_OUTPUT"
- name: Preflight normal all-registry publication capacity
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
SIMULATED_REGISTRY_DEADLINE_EPOCH: ${{ steps.github_request_budget.outputs.simulated_registry_deadline_epoch }}
run: |
REGISTRY_MUTATION_DEADLINE_EPOCH="$SIMULATED_REGISTRY_DEADLINE_EPOCH" \
bun .github/scripts/check-crates-io-publish-capacity.mjs
- name: Prove exact-lock normal registry topology
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/normal-publication-plan.mjs \
--lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
> target/release/normal-publication-plan.json
- name: Audit exact cross-job registry input topology
id: audit_registry_input_transfer
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
timeout-minutes: 5
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
APPROVED_RUN_ID: ${{ steps.approved_publication_lock.outputs.run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.approved_publication_lock.outputs.artifact_metadata_json }}
run: |
tools/dev/bun.sh tools/release/release-phase-handoff.mjs audit \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--approved-run-id "$APPROVED_RUN_ID" \
--approved-artifacts-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--github-output "$GITHUB_OUTPUT"
- name: Prove bounded GitHub stage phase margin
id: github_stage_phase_budget
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
timeout-minutes: 1
env:
STAGE_OPERATION_SECONDS: ${{ steps.github_request_budget.outputs.pre_registry_operation_seconds }}
run: |
tools/dev/bun.sh tools/release/release-phase-budget.mjs \
--phase github-staged \
--stage-operation-seconds "$STAGE_OPERATION_SECONDS" \
--github-output "$GITHUB_OUTPUT"
- name: Classify pre-tag registry publication state
id: bootstrap_ledger_state
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && (steps.registry_needs.outputs.needs_cargo == 'true' || steps.registry_needs.outputs.needs_npm == 'true') }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: bun .github/scripts/registry-bootstrap-ledger-state.mjs
- name: Download immutable registry bootstrap ledger
if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
node .github/scripts/download-build-artifacts.mjs \
Release \
"$RELEASE_HEAD_SHA" \
"$BOOTSTRAP_LEDGER_PATH" \
--artifact oliphaunt-bootstrap-ledger
- name: Verify immutable bootstrap ledger and registry existence
if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/bootstrap-ledger.mjs verify \
--lock "$PUBLICATION_LOCK_PATH" \
--ledger "$BOOTSTRAP_LEDGER_PATH" \
--products-json "$PRODUCTS_JSON" \
--verify-registries
- name: Freeze bootstrap publication capsule
id: freeze_bootstrap_capsule
if: ${{ inputs.operation == 'publish-dry-run' && steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mjs pack \
--lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--output target/release/oliphaunt-bootstrap-capsule.tar
- name: Upload frozen publication lock
id: preserve_publication_lock
if: ${{ inputs.operation == 'publish-dry-run' && steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: oliphaunt-publication-lock
path: target/release/publication-lock.json
if-no-files-found: error
overwrite: true
retention-days: 90
- name: Upload frozen bootstrap publication capsule
id: preserve_bootstrap_capsule
if: ${{ inputs.operation == 'publish-dry-run' && steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: oliphaunt-bootstrap-capsule
path: target/release/oliphaunt-bootstrap-capsule.tar
if-no-files-found: error
overwrite: true
retention-days: 90
- name: Upload publication lock audit evidence
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: oliphaunt-publication-lock-${{ inputs.operation }}
path: target/release/publication-lock.json
if-no-files-found: error
overwrite: true
retention-days: 90
- name: Re-admit the live GitHub request envelope immediately before mutation
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/github-release-request-budget.mjs \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--github-output "$RUNNER_TEMP/oliphaunt-final-github-admission.out"
- name: Cool down and open the shared GitHub content-write journal
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 65
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve \
--label "release mutation cold-start admission"
- name: Admit or recover exact immutable release transport ref
id: ensure_release_transport_ref
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 3
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: publish
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_TRANSPORT_CONTENT_WRITE_ADMISSION: pre-reserved
run: node .github/scripts/release-transport-ref.mjs ensure "$RELEASE_HEAD_SHA"
- name: Stage exact-SHA product tags and draft releases
id: stage_github_releases
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 31
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
bun .github/scripts/manage-release-drafts.mjs stage \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--state staged
- name: Verify exact product tags
id: verify_product_tags
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: tools/dev/bun.sh tools/release/verify_product_tags.mjs --products-json "${PRODUCTS_JSON}" --target "$RELEASE_HEAD_SHA"
- name: Verify exact-SHA GitHub release staging
id: verify_github_staging
if: ${{ inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: bun .github/scripts/manage-release-drafts.mjs verify --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" --state staged
- name: Publish all selected GitHub release asset sets concurrently
id: publish_github_assets
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
timeout-minutes: 90
env:
GITHUB_RELEASE_ASSET_UPLOAD_REPORT_PATH: ${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: tools/dev/bun.sh tools/release/release-publish.mjs publish --step github-release-assets --products-json "${PRODUCTS_JSON}" --head-ref "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH"
- name: Resolve exact selected extension attestation subjects
id: extension_attestation_subjects
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.has_extension_products == 'true' }}
env:
EXTENSION_PRODUCTS_JSON: ${{ steps.release_plan.outputs.extension_products_json }}
run: |
tools/dev/bun.sh tools/release/locked-attestation-subjects.mjs \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$EXTENSION_PRODUCTS_JSON" \
--github-output "$GITHUB_OUTPUT"
- name: Reserve extension attestation content write (shard 1)
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve --label "extension attestation shard 1"
tools/dev/bun.sh tools/release/github-core-request-journal.mjs reserve --label "extension attestation shard 1 API attempt"
- name: Attest selected extension release assets (shard 1)
id: attest_extensions_1
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_1 == 'true' }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_1 }}
- name: Reserve extension attestation content write (shard 2)
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve --label "extension attestation shard 2"
tools/dev/bun.sh tools/release/github-core-request-journal.mjs reserve --label "extension attestation shard 2 API attempt"
- name: Attest selected extension release assets (shard 2)
id: attest_extensions_2
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.has_extension_products == 'true' && steps.extension_attestation_subjects.outputs.nonempty_2 == 'true' }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: ${{ steps.extension_attestation_subjects.outputs.paths_2 }}
- name: Reserve liboliphaunt attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_liboliphaunt_native == 'true' }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve --label "liboliphaunt native attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mjs reserve --label "liboliphaunt native attestation API attempt"
- name: Attest liboliphaunt release assets
id: attest_liboliphaunt_native
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_liboliphaunt_native == 'true' }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: |
target/liboliphaunt/release-assets/*.tar.gz
target/liboliphaunt/release-assets/*.tar.zst
target/liboliphaunt/release-assets/*.zip
target/liboliphaunt/release-assets/*.tsv
target/liboliphaunt/release-assets/*.sha256
- name: Publish Swift SDK GitHub release and SwiftPM tags
id: publish_swift_source_tag
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_oliphaunt_swift == 'true' }}
timeout-minutes: 6
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: tools/dev/bun.sh tools/release/release-publish.mjs publish --product oliphaunt-swift --step github-release --head-ref "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH"
- name: Reserve broker attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_oliphaunt_broker == 'true' }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve --label "broker attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mjs reserve --label "broker attestation API attempt"
- name: Attest broker release assets
id: attest_broker
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_oliphaunt_broker == 'true' }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: |
target/oliphaunt-broker/release-assets/*.tar.gz
target/oliphaunt-broker/release-assets/*.zip
target/oliphaunt-broker/release-assets/*.sha256
- name: Reserve Node direct attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_oliphaunt_node_direct == 'true' }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve --label "Node direct attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mjs reserve --label "Node direct attestation API attempt"
- name: Attest Node direct release assets
id: attest_node_direct
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_oliphaunt_node_direct == 'true' }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: |
target/oliphaunt-node-direct/release-assets/*.tar.gz
target/oliphaunt-node-direct/release-assets/*.zip
target/oliphaunt-node-direct/release-assets/*.sha256
- name: Reserve WASIX attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_liboliphaunt_wasix == 'true' }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mjs reserve --label "WASIX attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mjs reserve --label "WASIX attestation API attempt"
- name: Attest WASIX release assets
id: attest_wasix
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' && steps.release_plan.outputs.product_liboliphaunt_wasix == 'true' }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: |
target/oliphaunt-wasix/release-assets/*.tar.zst
target/oliphaunt-wasix/release-assets/*.sha256
- name: Freeze exact GitHub release asset and attestation evidence
id: freeze_github_evidence
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
timeout-minutes: 10
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
EXTENSIONS_ATTESTATION_BUNDLE_1: ${{ steps.attest_extensions_1.outputs.bundle-path }}
EXTENSIONS_ATTESTATION_BUNDLE_2: ${{ steps.attest_extensions_2.outputs.bundle-path }}
LIBOLIPHAUNT_NATIVE_ATTESTATION_BUNDLE: ${{ steps.attest_liboliphaunt_native.outputs.bundle-path }}
BROKER_ATTESTATION_BUNDLE: ${{ steps.attest_broker.outputs.bundle-path }}
NODE_DIRECT_ATTESTATION_BUNDLE: ${{ steps.attest_node_direct.outputs.bundle-path }}
WASIX_ATTESTATION_BUNDLE: ${{ steps.attest_wasix.outputs.bundle-path }}
run: |
bundle_args=()
for bundle in \
"$EXTENSIONS_ATTESTATION_BUNDLE_1" \
"$EXTENSIONS_ATTESTATION_BUNDLE_2" \
"$LIBOLIPHAUNT_NATIVE_ATTESTATION_BUNDLE" \
"$BROKER_ATTESTATION_BUNDLE" \
"$NODE_DIRECT_ATTESTATION_BUNDLE" \
"$WASIX_ATTESTATION_BUNDLE"
do
if [[ -n "$bundle" ]]; then
bundle_args+=(--attestation-bundle "$bundle")
fi
done
tools/dev/bun.sh tools/release/verify_github_release_attestations.mjs pre-mutation \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--output target/release/github-release-attestation-receipt.json \
"${bundle_args[@]}"
- name: Seal immutable GitHub-stage handoff
id: seal_github_stage_handoff
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
timeout-minutes: 5
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
APPROVED_RUN_ID: ${{ steps.approved_publication_lock.outputs.run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.approved_publication_lock.outputs.artifact_metadata_json }}
run: |
tools/dev/bun.sh tools/release/release-phase-handoff.mjs seal \
--phase github-staged \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--runner-root "$RUNNER_TEMP" \
--approved-run-id "$APPROVED_RUN_ID" \
--approved-artifacts-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--output "$RUNNER_TEMP/github-stage-handoff"
- name: Preserve immutable GitHub-stage handoff
id: preserve_github_stage_handoff
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.operation == 'publish' }}
timeout-minutes: 25
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: github-stage-handoff-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/github-stage-handoff
if-no-files-found: error
include-hidden-files: true
compression-level: 0
retention-days: 90
- name: Preserve failed GitHub staging evidence
id: preserve_failed_github_stage_evidence
if: ${{ failure() && inputs.operation == 'publish' && steps.release_plan.outputs.has_release_changes == 'true' }}
continue-on-error: true
timeout-minutes: 5
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: github-staging-recovery-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
path: |
target/release/publication-lock.json
target/release/normal-publication-plan.json
target/release/github-release-attestation-receipt.json
target/release/bootstrap-ledger
${{ runner.temp }}/github-stage-handoff
${{ runner.temp }}/oliphaunt-github-content-write-pacer.json
${{ runner.temp }}/oliphaunt-github-core-request-journal.json
${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json
if-no-files-found: warn
include-hidden-files: true
retention-days: 90
publish:
name: Prepare and stage release
needs:
- validate-inputs
runs-on: macos-26
timeout-minutes: 360
if: ${{ inputs.operation == 'publish' && inputs.continuation_pointer == '' }}
environment: release-publish
permissions:
actions: read
attestations: write
contents: write
id-token: write
outputs:
has_release_changes: ${{ steps.release_plan.outputs.has_release_changes }}
products_json: ${{ steps.release_plan.outputs.products_json }}
release_head_sha: ${{ steps.release_head.outputs.sha }}
approved_run_id: ${{ steps.approved_publication_lock.outputs.run_id }}
approved_artifact_metadata_json: ${{ steps.approved_publication_lock.outputs.artifact_metadata_json }}
stage_handoff_artifact_id: ${{ steps.preserve_github_stage_handoff.outputs.artifact-id }}
stage_handoff_artifact_digest: ${{ steps.preserve_github_stage_handoff.outputs.artifact-digest }}
steps: *release_candidate_steps
publish-registry:
name: Publish exact registry topology
needs:
- validate-inputs
- publish
runs-on: macos-26
timeout-minutes: 360
if: ${{ always() && inputs.operation == 'publish' && ((inputs.continuation_pointer == '' && needs.publish.result == 'success' && needs.publish.outputs.has_release_changes == 'true') || (inputs.continuation_pointer != '' && needs.validate-inputs.result == 'success')) }}
environment: release-publish
permissions:
actions: read
contents: read
id-token: write
outputs:
publication_complete: ${{ steps.require_registry_execution_decision.outputs.complete }}
release_head_sha: ${{ steps.registry_release_head.outputs.sha }}
products_json: ${{ steps.registry_inputs.outputs.products_json }}
approved_run_id: ${{ steps.registry_inputs.outputs.approved_run_id }}
approved_artifact_metadata_json: ${{ steps.registry_inputs.outputs.approved_artifact_metadata_json }}
root_run_id: ${{ steps.registry_inputs.outputs.root_run_id }}
registry_handoff_artifact_id: ${{ steps.preserve_publication_receipts.outputs.artifact-id }}
registry_handoff_artifact_digest: ${{ steps.preserve_publication_receipts.outputs.artifact-digest }}
continuation_required: ${{ steps.require_registry_execution_decision.outputs.deferred }}
continuation_artifact_id: ${{ steps.preserve_deferred_registry_recovery.outputs.artifact-id }}
continuation_artifact_digest: ${{ steps.preserve_deferred_registry_recovery.outputs.artifact-digest }}
continuation_contract_digest: ${{ steps.prepare_registry_continuation.outputs.contract_digest }}
steps:
- name: Record bounded registry job deadline
id: registry_job_deadline
run: |
if [[ ! "$REGISTRY_JOB_HARD_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'REGISTRY_JOB_HARD_WINDOW_SECONDS must be a positive integer' >&2
exit 1
fi
hard_deadline=$(( $(date +%s) + REGISTRY_JOB_HARD_WINDOW_SECONDS ))
{
echo "REGISTRY_JOB_HARD_DEADLINE_EPOCH=$hard_deadline"
echo "OLIPHAUNT_GITHUB_CORE_REQUEST_JOURNAL_PATH=$RUNNER_TEMP/registry-input-download-core-journal.json"
echo "OLIPHAUNT_REQUIRE_GITHUB_CORE_REQUEST_JOURNAL=true"
echo "OLIPHAUNT_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots"
} >> "$GITHUB_ENV"
echo "Registry publication and immutable handoff must complete before Unix time $hard_deadline."
- name: Checkout exact release commit
timeout-minutes: 5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ inputs.release_commit || needs.publish.outputs.release_head_sha }}
fetch-depth: 0
persist-credentials: false
- name: Resolve exact release commit
id: registry_release_head
timeout-minutes: 1
env:
INPUT_RELEASE_COMMIT: ${{ inputs.release_commit || needs.publish.outputs.release_head_sha }}
run: .github/scripts/resolve-release-head.sh
- name: Set up pinned Node.js
id: setup_registry_node
timeout-minutes: 3
uses: ./.github/actions/setup-node-pnpm
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Set up pinned Bun
timeout-minutes: 3
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Recompute exact continued release plan
id: continued_release_plan
if: ${{ inputs.continuation_pointer != '' }}
timeout-minutes: 2
run: |
tools/dev/bun.sh tools/release/release_plan.mjs \
--from-product-tags \
--include-current-tags \
--head-ref "$RELEASE_HEAD_SHA" \
--format github-output \
>> "$GITHUB_OUTPUT"
- name: Inspect exact parent continuation
id: inspect_registry_continuation
if: ${{ inputs.continuation_pointer != '' }}
# Covers the 53-minute sequential GitHub-read envelope plus local verification margin.
timeout-minutes: 55
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCTS_JSON: ${{ steps.continued_release_plan.outputs.products_json }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_CONTINUATION_ARCHIVE: ${{ runner.temp }}/release-continuation.zip
RELEASE_CONTINUATION_GITHUB_PACER_PATH: ${{ runner.temp }}/continued-oliphaunt-github-content-write-pacer.json
RELEASE_CONTINUATION_GITHUB_CORE_JOURNAL_PATH: ${{ runner.temp }}/continued-oliphaunt-github-core-request-journal.json
RELEASE_OPERATION: publish
run: node .github/scripts/inspect-release-continuation.mjs
- name: Resolve exact registry continuation inputs
id: registry_inputs
timeout-minutes: 1
env:
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
ROOT_PRODUCTS_JSON: ${{ needs.publish.outputs.products_json }}
ROOT_APPROVED_RUN_ID: ${{ needs.publish.outputs.approved_run_id }}
ROOT_APPROVED_ARTIFACT_METADATA_JSON: ${{ needs.publish.outputs.approved_artifact_metadata_json }}
ROOT_STAGE_HANDOFF_ARTIFACT_ID: ${{ needs.publish.outputs.stage_handoff_artifact_id }}
ROOT_STAGE_HANDOFF_ARTIFACT_DIGEST: ${{ needs.publish.outputs.stage_handoff_artifact_digest }}
CONTINUED_PRODUCTS_JSON: ${{ steps.inspect_registry_continuation.outputs.products_json }}
CONTINUED_APPROVED_RUN_ID: ${{ steps.inspect_registry_continuation.outputs.approved_run_id }}
CONTINUED_APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.inspect_registry_continuation.outputs.approved_artifact_metadata_json }}
CONTINUED_STAGE_HANDOFF_RUN_ID: ${{ steps.inspect_registry_continuation.outputs.stage_handoff_run_id }}
CONTINUED_STAGE_HANDOFF_ARTIFACT_ID: ${{ steps.inspect_registry_continuation.outputs.stage_handoff_artifact_id }}
CONTINUED_STAGE_HANDOFF_ARTIFACT_NAME: ${{ steps.inspect_registry_continuation.outputs.stage_handoff_artifact_name }}
CONTINUED_STAGE_HANDOFF_ARTIFACT_DIGEST: ${{ steps.inspect_registry_continuation.outputs.stage_handoff_artifact_digest }}
CONTINUED_STAGE_HANDOFF_ARTIFACT_SIZE: ${{ steps.inspect_registry_continuation.outputs.stage_handoff_artifact_size }}
CONTINUED_ROOT_RUN_ID: ${{ steps.inspect_registry_continuation.outputs.root_run_id }}
run: |
if [[ -n "$RELEASE_CONTINUATION_POINTER" ]]; then
products_json="$CONTINUED_PRODUCTS_JSON"
approved_run_id="$CONTINUED_APPROVED_RUN_ID"
approved_artifacts="$CONTINUED_APPROVED_ARTIFACT_METADATA_JSON"
stage_run_id="$CONTINUED_STAGE_HANDOFF_RUN_ID"
stage_artifact_id="$CONTINUED_STAGE_HANDOFF_ARTIFACT_ID"
stage_artifact_name="$CONTINUED_STAGE_HANDOFF_ARTIFACT_NAME"
stage_artifact_digest="$CONTINUED_STAGE_HANDOFF_ARTIFACT_DIGEST"
stage_artifact_size="$CONTINUED_STAGE_HANDOFF_ARTIFACT_SIZE"
root_run_id="$CONTINUED_ROOT_RUN_ID"
else
products_json="$ROOT_PRODUCTS_JSON"
approved_run_id="$ROOT_APPROVED_RUN_ID"
approved_artifacts="$ROOT_APPROVED_ARTIFACT_METADATA_JSON"
stage_run_id="$GITHUB_RUN_ID"
stage_artifact_id="$ROOT_STAGE_HANDOFF_ARTIFACT_ID"
stage_artifact_name="github-stage-handoff-${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
stage_artifact_digest="$ROOT_STAGE_HANDOFF_ARTIFACT_DIGEST"
stage_artifact_size=''
root_run_id="$GITHUB_RUN_ID"
fi
for name in products_json approved_run_id approved_artifacts stage_run_id stage_artifact_id stage_artifact_name stage_artifact_digest root_run_id; do
if [[ -z "${!name}" ]]; then
echo "resolved registry continuation input $name is empty" >&2
exit 1
fi
done
{
printf 'products_json=%s\n' "$products_json"
printf 'approved_run_id=%s\n' "$approved_run_id"
printf 'approved_artifact_metadata_json=%s\n' "$approved_artifacts"
printf 'stage_handoff_run_id=%s\n' "$stage_run_id"
printf 'stage_handoff_artifact_id=%s\n' "$stage_artifact_id"
printf 'stage_handoff_artifact_name=%s\n' "$stage_artifact_name"
printf 'stage_handoff_artifact_digest=%s\n' "$stage_artifact_digest"
printf 'stage_handoff_artifact_size=%s\n' "$stage_artifact_size"
printf 'root_run_id=%s\n' "$root_run_id"
} >> "$GITHUB_OUTPUT"
- name: Prove bounded registry phase margin
id: registry_phase_budget
timeout-minutes: 1
run: |
node tools/release/release-phase-budget.mjs \
--phase registry-published \
--registry-transfer-seconds 4200
- name: Download exact approved lock and capsule
id: download_approved_publication_inputs
timeout-minutes: 50
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
APPROVED_RUN_ID: ${{ steps.registry_inputs.outputs.approved_run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.registry_inputs.outputs.approved_artifact_metadata_json }}
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
run: |
node .github/scripts/download-build-artifacts.mjs \
Release \
"$RELEASE_HEAD_SHA" \
"$RUNNER_TEMP/approved-dry-run" \
--run-id "$APPROVED_RUN_ID" \
--artifact-metadata-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--artifact oliphaunt-publication-lock \
--artifact oliphaunt-bootstrap-capsule
- name: Download exact GitHub-stage handoff
id: download_github_stage_handoff
if: ${{ inputs.continuation_pointer == '' }}
timeout-minutes: 20
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
artifact-ids: ${{ needs.publish.outputs.stage_handoff_artifact_id }}
path: ${{ runner.temp }}/github-stage-handoff
- name: Download original exact GitHub-stage handoff for continuation
id: download_continued_github_stage_handoff
if: ${{ inputs.continuation_pointer != '' }}
timeout-minutes: 20
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
STAGE_HANDOFF_RUN_ID: ${{ steps.registry_inputs.outputs.stage_handoff_run_id }}
STAGE_HANDOFF_ARTIFACT_NAME: ${{ steps.registry_inputs.outputs.stage_handoff_artifact_name }}
STAGE_HANDOFF_ARTIFACT_METADATA_JSON: ${{ steps.inspect_registry_continuation.outputs.stage_handoff_artifact_metadata_json }}
run: |
node .github/scripts/download-build-artifacts.mjs \
Release \
"$RELEASE_HEAD_SHA" \
"$RUNNER_TEMP/github-stage-handoff" \
--run-id "$STAGE_HANDOFF_RUN_ID" \
--artifact-metadata-json "$STAGE_HANDOFF_ARTIFACT_METADATA_JSON" \
--artifact "$STAGE_HANDOFF_ARTIFACT_NAME"
- name: Verify and install exact registry inputs
id: install_github_stage_handoff
timeout-minutes: 8
env:
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
APPROVED_RUN_ID: ${{ steps.registry_inputs.outputs.approved_run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.registry_inputs.outputs.approved_artifact_metadata_json }}
ORIGINAL_GITHUB_PACER_PATH: ${{ runner.temp }}/oliphaunt-github-content-write-pacer.json
ORIGINAL_GITHUB_CORE_JOURNAL_PATH: ${{ runner.temp }}/oliphaunt-github-core-request-journal.json
PREINSTALL_GITHUB_CORE_JOURNAL_PATH: ${{ runner.temp }}/registry-input-download-core-journal.json
OLIPHAUNT_GITHUB_CONTENT_WRITE_PACER_PATH: ${{ runner.temp }}/oliphaunt-github-content-write-pacer.json
OLIPHAUNT_GITHUB_CORE_REQUEST_JOURNAL_PATH: ${{ runner.temp }}/oliphaunt-github-core-request-journal.json
OLIPHAUNT_RELEASE_ROOT_RUN_ID: ${{ steps.registry_inputs.outputs.root_run_id }}
RELEASE_CONTINUATION_GITHUB_PACER_PATH: ${{ steps.inspect_registry_continuation.outputs.continued_github_pacer_path }}
RELEASE_CONTINUATION_GITHUB_CORE_JOURNAL_PATH: ${{ steps.inspect_registry_continuation.outputs.continued_github_core_journal_path }}
RELEASE_CONTINUATION_GITHUB_STATE_JSON: ${{ steps.inspect_registry_continuation.outputs.continued_github_state_json }}
run: |
bun tools/release/bootstrap-publication-capsule.mjs verify-extract \
--transport "$RUNNER_TEMP/approved-dry-run/oliphaunt-bootstrap-capsule.tar" \
--approved-lock "$RUNNER_TEMP/approved-dry-run/publication-lock.json" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--workspace-root "$GITHUB_WORKSPACE"
tools/dev/bun.sh tools/release/release-phase-handoff.mjs install \
--phase github-staged \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--runner-root "$RUNNER_TEMP" \
--approved-run-id "$APPROVED_RUN_ID" \
--approved-artifacts-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--merge-existing-target true \
--input "$RUNNER_TEMP/github-stage-handoff"
cmp "$RUNNER_TEMP/approved-dry-run/publication-lock.json" "$PUBLICATION_LOCK_PATH"
node .github/scripts/install-release-continuation-github-state.mjs
{
echo "OLIPHAUNT_GITHUB_CONTENT_WRITE_PACER_PATH=$RUNNER_TEMP/oliphaunt-github-content-write-pacer.json"
echo "OLIPHAUNT_GITHUB_CORE_REQUEST_JOURNAL_PATH=$RUNNER_TEMP/oliphaunt-github-core-request-journal.json"
echo "OLIPHAUNT_REQUIRE_GITHUB_CORE_REQUEST_JOURNAL=true"
echo "OLIPHAUNT_RELEASE_ROOT_RUN_ID=${{ steps.registry_inputs.outputs.root_run_id }}"
} >> "$GITHUB_ENV"
- name: Set up Rust
timeout-minutes: 10
uses: ./.github/actions/setup-rust
- name: Configure macOS registry toolchains
id: configure_registry_toolchains
timeout-minutes: 1
run: bash .github/scripts/configure-macos-release-toolchains.sh
- name: Set up pinned Deno
timeout-minutes: 3
uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Resolve selected registry authentication needs
id: registry_publish_needs
timeout-minutes: 1
env:
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
run: bun .github/scripts/selected-registry-needs.mjs
- name: Install exact JSR publisher dependencies
id: install_registry_jsr_tooling
if: ${{ steps.registry_publish_needs.outputs.needs_jsr == 'true' }}
timeout-minutes: 2
run: pnpm install --frozen-lockfile --ignore-scripts --filter @oliphaunt/ts
- name: Set up pinned npm publisher
id: setup_registry_npm
if: ${{ steps.registry_publish_needs.outputs.needs_npm == 'true' }}
timeout-minutes: 3
uses: ./.github/actions/setup-npm-publisher
with:
npm-version: ${{ env.NPM_VERSION }}
- name: Verify direct-workflow OIDC identity
id: verify_registry_oidc_identity
timeout-minutes: 2
env:
RELEASE_OPERATION: publish
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
run: bun .github/scripts/verify-github-oidc-identity.mjs
- name: Recheck registry credentials and trust links
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: |
tools/release/check_publish_environment.mjs --products-json "$PRODUCTS_JSON"
bun .github/scripts/verify-external-publish-readiness.mjs
- name: Reverify Maven signing credentials before registry mutation
id: verify_registry_maven_signing
if: ${{ steps.registry_publish_needs.outputs.needs_maven == 'true' }}
timeout-minutes: 2
env:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: tools/dev/bun.sh tools/release/verify-maven-signing-readiness.mjs
- name: Verify exact GitHub staging before registry mutation
id: verify_registry_github_staging
timeout-minutes: 5
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/verify_product_tags.mjs \
--products-json "$PRODUCTS_JSON" \
--target "$RELEASE_HEAD_SHA"
bun .github/scripts/manage-release-drafts.mjs verify \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--state staged
- name: Restore exact-SHA normal-publication checkpoint
id: restore_normal_publication_checkpoint
timeout-minutes: 7
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PUBLICATION_LOCK_PATH: target/release/publication-lock.json
NORMAL_PUBLICATION_CHECKPOINT_PATH: target/release/normal-publication-checkpoint.json
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_CONTINUATION_ARCHIVE: ${{ runner.temp }}/release-continuation.zip
run: tools/dev/bun.sh .github/scripts/download-normal-publication-checkpoint.mjs
- name: Establish bounded capacity-inspection deadline
id: registry_capacity_deadline
timeout-minutes: 1
run: |
for name in NORMAL_REGISTRY_MUTATION_WINDOW_SECONDS REGISTRY_JOB_HARD_DEADLINE_EPOCH REGISTRY_EVIDENCE_HANDOFF_RESERVE_SECONDS; do
value="${!name:-}"
if [[ ! "$value" =~ ^[1-9][0-9]*$ ]]; then
echo "$name must be a positive integer" >&2
exit 1
fi
done
now=$(date +%s)
handoff_deadline=$(( REGISTRY_JOB_HARD_DEADLINE_EPOCH - REGISTRY_EVIDENCE_HANDOFF_RESERVE_SECONDS ))
available=$(( handoff_deadline - now ))
if (( available <= 0 )); then
echo 'No positive capacity-inspection window remains before protected recovery handoff' >&2
exit 1
fi
echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$handoff_deadline" >> "$GITHUB_ENV"
echo "Capacity inspection and any admitted mutation must stop before Unix time $handoff_deadline."
- name: Reprove all-registry publication capacity immediately before registry mutation
id: reprove_registry_capacity
timeout-minutes: 3
env:
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
run: bun .github/scripts/check-crates-io-publish-capacity.mjs
- name: Start authoritative bounded normal registry mutation window
id: registry_mutation_deadline
if: ${{ steps.reprove_registry_capacity.outputs.admission == 'execute' }}
timeout-minutes: 1
env:
REQUIRED_WINDOW_SECONDS: ${{ steps.reprove_registry_capacity.outputs.required_window_seconds }}
run: node tools/release/normal-registry-mutation-start.mjs
- name: Seal typed zero-mutation capacity or deadline deferral
id: record_registry_capacity_deferral
if: ${{ steps.reprove_registry_capacity.outputs.admission == 'defer' || steps.registry_mutation_deadline.outputs.admission == 'defer' }}
timeout-minutes: 10
env:
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
CAPACITY_NAMES_SATISFIED: ${{ steps.reprove_registry_capacity.outputs.names_satisfied }}
CAPACITY_NOT_BEFORE_EPOCH: ${{ steps.registry_mutation_deadline.outputs.not_before_epoch || steps.reprove_registry_capacity.outputs.not_before_epoch }}
CAPACITY_REQUIRED_WINDOW_SECONDS: ${{ steps.reprove_registry_capacity.outputs.required_window_seconds }}
PRE_MUTATION_DEFERRAL_MODE: ${{ steps.registry_mutation_deadline.outputs.admission == 'defer' && 'pre-mutation-deadline' || '' }}
run: tools/dev/bun.sh tools/release/record-normal-publication-capacity-deferral.mjs
- name: Publish exact-lock registry topology
id: exact_registry_publish
if: ${{ steps.reprove_registry_capacity.outputs.admission == 'execute' && steps.registry_mutation_deadline.outputs.admission == 'execute' }}
timeout-minutes: 190
env:
ADMISSION_FILE: ${{ steps.reprove_registry_capacity.outputs.admission_file }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NORMAL_PUBLICATION_ADMISSION_DIGEST: ${{ steps.reprove_registry_capacity.outputs.admission_digest }}
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.MAVEN_GPG_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: |
tools/dev/bun.sh tools/release/release-publish.mjs publish \
--registry-plan \
--registry-admission "$ADMISSION_FILE" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--publication-lock "$PUBLICATION_LOCK_PATH"
- name: Require a typed registry execution decision
id: require_registry_execution_decision
if: ${{ steps.exact_registry_publish.outcome == 'success' || steps.record_registry_capacity_deferral.outcome == 'success' }}
timeout-minutes: 1
env:
ADMISSION: ${{ steps.reprove_registry_capacity.outputs.admission }}
COMPLETE: ${{ steps.exact_registry_publish.outputs.complete || steps.record_registry_capacity_deferral.outputs.complete }}
CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
DEFERRED: ${{ steps.exact_registry_publish.outputs.deferred || steps.record_registry_capacity_deferral.outputs.deferred }}
DEFERRAL_MODE: ${{ steps.exact_registry_publish.outputs.deferral_mode || steps.record_registry_capacity_deferral.outputs.deferral_mode }}
PROGRESS_COUNT: ${{ steps.exact_registry_publish.outputs.progress_count || steps.record_registry_capacity_deferral.outputs.progress_count }}
REMAINING_COUNT: ${{ steps.exact_registry_publish.outputs.remaining_count || steps.record_registry_capacity_deferral.outputs.remaining_count }}
NOT_BEFORE_EPOCH: ${{ steps.exact_registry_publish.outputs.not_before_epoch || steps.record_registry_capacity_deferral.outputs.not_before_epoch }}
run: |
if [[ "$COMPLETE" == true && "$DEFERRED" == false ]]; then
if [[ "$ADMISSION" != execute || -n "$DEFERRAL_MODE" || "$REMAINING_COUNT" != 0 ]]; then
echo 'complete registry result disagrees with its execute admission or retains remaining operations' >&2
exit 1
fi
elif [[ "$COMPLETE" == false && "$DEFERRED" == true ]]; then
if [[ ! "$REMAINING_COUNT" =~ ^[1-9][0-9]*$ || ! "$NOT_BEFORE_EPOCH" =~ ^[1-9][0-9]*$ ]]; then
echo 'deferred registry result requires remaining work and a positive not-before time' >&2
exit 1
fi
if [[ "$DEFERRAL_MODE" == progress ]]; then
if [[ "$ADMISSION" != execute || ! "$PROGRESS_COUNT" =~ ^[1-9][0-9]*$ ]]; then
echo 'progress deferral requires execute admission and nonzero durable progress' >&2
exit 1
fi
elif [[ "$DEFERRAL_MODE" == rate-limit ]]; then
if [[ "$ADMISSION" != execute || "$PROGRESS_COUNT" != 0 ]]; then
echo 'rate-limit deferral requires execute admission and zero claimed progress' >&2
exit 1
fi
elif [[ "$DEFERRAL_MODE" == pre-mutation-deadline ]]; then
if [[ "$PROGRESS_COUNT" != 0 ]] || ! { [[ "$ADMISSION" == execute ]] || [[ "$ADMISSION" == defer && -n "$CONTINUATION_POINTER" ]]; }; then
echo 'pre-mutation deadline deferral requires zero progress and either execute admission or a continued capacity admission' >&2
exit 1
fi
elif [[ "$DEFERRAL_MODE" == pre-mutation-capacity ]]; then
if [[ "$ADMISSION" != defer || "$PROGRESS_COUNT" != 0 || -n "$CONTINUATION_POINTER" ]]; then
echo 'zero-mutation capacity deferral is permitted only on the root deferred admission' >&2
exit 1
fi
else
echo 'deferred registry result has an unsupported deferral mode' >&2
exit 1
fi
else
echo 'registry publisher must emit exactly one of complete or deferred' >&2
exit 1
fi
{
echo "complete=$COMPLETE"
echo "deferred=$DEFERRED"
echo "deferral_mode=$DEFERRAL_MODE"
echo "progress_count=$PROGRESS_COUNT"
echo "remaining_count=$REMAINING_COUNT"
echo "not_before_epoch=$NOT_BEFORE_EPOCH"
} >> "$GITHUB_OUTPUT"
- name: Seal exact normal-publication continuation contract
id: prepare_registry_continuation
if: ${{ steps.require_registry_execution_decision.outputs.deferred == 'true' }}
timeout-minutes: 3
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
APPROVED_RUN_ID: ${{ steps.registry_inputs.outputs.approved_run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.registry_inputs.outputs.approved_artifact_metadata_json }}
RELEASE_OPERATION: publish
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_EXECUTION_RESULT_PATH: target/release/normal-publication-execution-result.json
RELEASE_CONTINUATION_STATE_PATH: target/release/normal-publication-checkpoint.json
RELEASE_CONTINUATION_CONTRACT_PATH: target/release/release-continuation-contract.json
STAGE_HANDOFF_RUN_ID: ${{ steps.registry_inputs.outputs.stage_handoff_run_id }}
STAGE_HANDOFF_ARTIFACT_ID: ${{ steps.registry_inputs.outputs.stage_handoff_artifact_id }}
STAGE_HANDOFF_ARTIFACT_NAME: ${{ steps.registry_inputs.outputs.stage_handoff_artifact_name }}
STAGE_HANDOFF_ARTIFACT_DIGEST: ${{ steps.registry_inputs.outputs.stage_handoff_artifact_digest }}
run: bun .github/scripts/prepare-release-continuation.mjs
- name: Preserve immutable deferred normal-publication continuation
id: preserve_deferred_registry_recovery
if: ${{ steps.require_registry_execution_decision.outputs.deferred == 'true' }}
timeout-minutes: 10
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: normal-publication-continuation-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-${{ steps.prepare_registry_continuation.outputs.next_generation }}
path: |
target/release/normal-publication-checkpoint.json
target/release/normal-publication-execution-result.json
target/release/release-continuation-contract.json
target/release/oliphaunt-github-content-write-pacer.json
target/release/oliphaunt-github-core-request-journal.json
if-no-files-found: error
include-hidden-files: true
compression-level: 0
retention-days: 90
- name: Preserve failed normal-publication recovery checkpoint
id: preserve_failed_registry_recovery
if: ${{ always() && ((steps.exact_registry_publish.outcome != 'skipped' && steps.exact_registry_publish.outcome != 'success') || (steps.record_registry_capacity_deferral.outcome != 'skipped' && steps.record_registry_capacity_deferral.outcome != 'success') || ((steps.exact_registry_publish.outcome == 'success' || steps.record_registry_capacity_deferral.outcome == 'success') && steps.require_registry_execution_decision.outcome != 'success') || (steps.require_registry_execution_decision.outputs.deferred == 'true' && (steps.prepare_registry_continuation.outcome != 'success' || steps.preserve_deferred_registry_recovery.outcome != 'success'))) }}
continue-on-error: true
timeout-minutes: 3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: normal-publication-recovery-${{ github.sha }}
path: |
target/release/normal-publication-plan.json
target/release/normal-publication-checkpoint.json
target/release/registry-integrity-receipts.json
if-no-files-found: warn
include-hidden-files: true
overwrite: true
retention-days: 90
- name: Preserve complete normal-publication recovery checkpoint
id: preserve_complete_registry_recovery
if: ${{ steps.require_registry_execution_decision.outputs.complete == 'true' }}
timeout-minutes: 3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: normal-publication-recovery-${{ github.sha }}
path: |
target/release/normal-publication-plan.json
target/release/normal-publication-checkpoint.json
target/release/registry-integrity-receipts.json
target/release/github-release-attestation-receipt.json
if-no-files-found: error
include-hidden-files: true
overwrite: true
retention-days: 90
- name: Seal immutable registry-published handoff
id: seal_registry_handoff
if: ${{ steps.require_registry_execution_decision.outputs.complete == 'true' }}
timeout-minutes: 2
env:
PRODUCTS_JSON: ${{ steps.registry_inputs.outputs.products_json }}
APPROVED_RUN_ID: ${{ steps.registry_inputs.outputs.approved_run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.registry_inputs.outputs.approved_artifact_metadata_json }}
run: |
tools/dev/bun.sh tools/release/release-phase-handoff.mjs seal \
--phase registry-published \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--runner-root "$RUNNER_TEMP" \
--approved-run-id "$APPROVED_RUN_ID" \
--approved-artifacts-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--output "$RUNNER_TEMP/registry-published-handoff"
- name: Preserve immutable publication receipts
id: preserve_publication_receipts
if: ${{ steps.require_registry_execution_decision.outputs.complete == 'true' }}
timeout-minutes: 10
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: registry-published-handoff-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/registry-published-handoff
if-no-files-found: error
include-hidden-files: true
compression-level: 0
retention-days: 90
publish-finalize:
name: Verify consumers and publish GitHub releases
needs:
- validate-inputs
- publish-registry
runs-on: macos-26
timeout-minutes: 120
if: ${{ always() && inputs.operation == 'publish' && needs.validate-inputs.result == 'success' && needs.publish-registry.result == 'success' && needs.publish-registry.outputs.publication_complete == 'true' }}
environment: release-publish
permissions:
actions: read
contents: write
steps:
- name: Record bounded finalization job deadline
id: finalize_job_deadline
run: |
if [[ ! "$FINALIZE_JOB_HARD_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'FINALIZE_JOB_HARD_WINDOW_SECONDS must be a positive integer' >&2
exit 1
fi
hard_deadline=$(( $(date +%s) + FINALIZE_JOB_HARD_WINDOW_SECONDS ))
{
echo "FINALIZE_JOB_HARD_DEADLINE_EPOCH=$hard_deadline"
echo "REGISTRY_JOB_HARD_DEADLINE_EPOCH=$hard_deadline"
} >> "$GITHUB_ENV"
echo "Final verification and draft promotion must complete before Unix time $hard_deadline."
- name: Checkout exact release commit
timeout-minutes: 5
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ needs.publish-registry.outputs.release_head_sha }}
fetch-depth: 0
persist-credentials: false
- name: Resolve exact release commit
id: finalize_release_head
timeout-minutes: 1
env:
INPUT_RELEASE_COMMIT: ${{ needs.publish-registry.outputs.release_head_sha }}
run: .github/scripts/resolve-release-head.sh
- name: Set up pinned Node.js
id: setup_finalize_node
timeout-minutes: 5
uses: ./.github/actions/setup-node-runtime
with:
node-version: ${{ env.NODE_VERSION }}
- name: Prove bounded finalization phase margin
id: finalize_phase_budget
timeout-minutes: 1
run: node tools/release/release-phase-budget.mjs --phase github-finalized
- name: Download exact registry-published handoff
id: download_registry_handoff
timeout-minutes: 5
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
artifact-ids: ${{ needs.publish-registry.outputs.registry_handoff_artifact_id }}
path: ${{ runner.temp }}/registry-published-handoff
- name: Set up pinned Bun
timeout-minutes: 5
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Install and verify exact registry-published handoff
id: install_registry_handoff
timeout-minutes: 5
env:
PRODUCTS_JSON: ${{ needs.publish-registry.outputs.products_json }}
APPROVED_RUN_ID: ${{ needs.publish-registry.outputs.approved_run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ needs.publish-registry.outputs.approved_artifact_metadata_json }}
RELEASE_ROOT_RUN_ID: ${{ needs.publish-registry.outputs.root_run_id }}
run: |
tools/dev/bun.sh tools/release/release-phase-handoff.mjs install \
--phase registry-published \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--runner-root "$RUNNER_TEMP" \
--approved-run-id "$APPROVED_RUN_ID" \
--approved-artifacts-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--input "$RUNNER_TEMP/registry-published-handoff"
{
echo "OLIPHAUNT_GITHUB_CONTENT_WRITE_PACER_PATH=$RUNNER_TEMP/oliphaunt-github-content-write-pacer.json"
echo "OLIPHAUNT_GITHUB_CORE_REQUEST_JOURNAL_PATH=$RUNNER_TEMP/oliphaunt-github-core-request-journal.json"
echo "OLIPHAUNT_REQUIRE_GITHUB_CORE_REQUEST_JOURNAL=true"
echo "OLIPHAUNT_RELEASE_ROOT_RUN_ID=$RELEASE_ROOT_RUN_ID"
} >> "$GITHUB_ENV"
- name: Set up Rust
timeout-minutes: 15
uses: ./.github/actions/setup-rust
- name: Set up pinned npm
id: setup_finalize_npm
timeout-minutes: 5
uses: ./.github/actions/setup-npm-publisher
with:
npm-version: ${{ env.NPM_VERSION }}
- name: Set up pinned Deno
timeout-minutes: 5
uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb
with:
deno-version: ${{ env.DENO_VERSION }}
- name: Configure macOS finalization toolchains
id: configure_finalize_toolchains
timeout-minutes: 1
run: bash .github/scripts/configure-macos-release-toolchains.sh
- name: Verify final GitHub staging
id: verify_final_github_staging
timeout-minutes: 6
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ needs.publish-registry.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/verify_product_tags.mjs \
--products-json "$PRODUCTS_JSON" \
--target "$RELEASE_HEAD_SHA"
bun .github/scripts/manage-release-drafts.mjs verify \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--state staged
- name: Enter bounded release finalization
id: enter_finalization
timeout-minutes: 1
run: |
if [[ ! "$RELEASE_MINIMUM_FINALIZATION_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'RELEASE_MINIMUM_FINALIZATION_SECONDS must be a positive integer' >&2
exit 1
fi
now=$(date +%s)
remaining=$(( FINALIZE_JOB_HARD_DEADLINE_EPOCH - now ))
if (( remaining < RELEASE_MINIMUM_FINALIZATION_SECONDS )); then
echo "Only ${remaining}s remain before the finalization hard deadline; at least ${RELEASE_MINIMUM_FINALIZATION_SECONDS}s are required" >&2
exit 1
fi
echo "Mandatory finalization started with ${remaining}s before its fresh hard deadline."
- name: Verify published release
id: verify_published_release
timeout-minutes: 8
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ needs.publish-registry.outputs.products_json }}
run: |
gh auth setup-git
git fetch --force --tags origin
tools/dev/bun.sh tools/release/release-verify.mjs \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--registry-receipts target/release/registry-integrity-receipts.json \
--github-release-receipt target/release/github-release-attestation-receipt.json
- name: Resolve and install exact public consumer surfaces
id: public_consumer_smoke
timeout-minutes: 15
env:
PRODUCTS_JSON: ${{ needs.publish-registry.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/public-consumer-smoke.mjs \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--registry-receipts target/release/registry-integrity-receipts.json \
--github-release-receipt target/release/github-release-attestation-receipt.json \
--output target/release/public-consumer-smoke.json
- name: Preserve immutable public consumer evidence
id: preserve_consumer_evidence
timeout-minutes: 2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: public-consumer-evidence-${{ github.sha }}
path: target/release/public-consumer-smoke.json
if-no-files-found: error
overwrite: true
retention-days: 90
- name: Reverify exact publication lock before promotion
id: reverify_publication_lock
timeout-minutes: 2
run: |
tools/dev/bun.sh tools/release/publication-lock.mjs \
verify \
--lock "$PUBLICATION_LOCK_PATH" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Preserve pre-promotion GitHub mutation evidence
id: preserve_pre_promotion_evidence
if: ${{ always() }}
timeout-minutes: 2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: github-pre-promotion-mutation-evidence-${{ github.sha }}
path: |
target/release/publication-lock.json
target/release/normal-publication-checkpoint.json
target/release/registry-integrity-receipts.json
target/release/public-consumer-smoke.json
${{ runner.temp }}/oliphaunt-github-content-write-pacer.json
${{ runner.temp }}/oliphaunt-github-core-request-journal.json
${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json
if-no-files-found: error
include-hidden-files: true
overwrite: true
retention-days: 90
- name: Promote verified GitHub release drafts
id: promote_github_releases
timeout-minutes: 12
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ needs.publish-registry.outputs.products_json }}
run: bun .github/scripts/manage-release-drafts.mjs promote --products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA"
publish-bootstrap:
name: Bootstrap registry identities
needs:
- validate-inputs
runs-on: ubuntu-24.04
timeout-minutes: 360
if: ${{ inputs.operation == 'publish-bootstrap' }}
environment: release-bootstrap
permissions:
actions: read
contents: write
id-token: write
outputs:
continuation_required: ${{ steps.require_bootstrap_execution_decision.outputs.deferred }}
continuation_artifact_id: ${{ steps.preserve_deferred_bootstrap_ledger.outputs.artifact-id }}
continuation_artifact_digest: ${{ steps.preserve_deferred_bootstrap_ledger.outputs.artifact-digest }}
continuation_contract_digest: ${{ steps.prepare_bootstrap_continuation.outputs.contract_digest }}
steps:
- name: Record bounded bootstrap job deadline
id: bootstrap_job_deadline
run: |
if [[ ! "$RELEASE_JOB_HARD_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'RELEASE_JOB_HARD_WINDOW_SECONDS must be a positive integer' >&2
exit 1
fi
hard_deadline=$(( $(date +%s) + RELEASE_JOB_HARD_WINDOW_SECONDS ))
{
echo "REGISTRY_JOB_HARD_DEADLINE_EPOCH=$hard_deadline"
echo "OLIPHAUNT_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots"
} >> "$GITHUB_ENV"
echo "The bootstrap job must stop registry work before Unix time $hard_deadline."
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: Resolve exact release commit
id: release_head
timeout-minutes: 1
env:
INPUT_RELEASE_COMMIT: ${{ inputs.release_commit }}
run: .github/scripts/resolve-release-head.sh
- name: Set up pinned Node.js
id: setup_bootstrap_node
timeout-minutes: 3
uses: ./.github/actions/setup-node-runtime
with:
node-version: ${{ env.NODE_VERSION }}
- name: Set up pinned Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Plan bootstrap releases
id: release_plan
run: |
tools/dev/bun.sh tools/release/release_plan.mjs \
--from-product-tags \
--include-current-tags \
--head-ref "$RELEASE_HEAD_SHA" \
--format github-output \
>> "$GITHUB_OUTPUT"
- name: No package release planned
if: ${{ steps.release_plan.outputs.has_release_changes != 'true' }}
run: echo "No release-affecting product changes were found since the last product tag."
- name: Resolve selected bootstrap authentication needs
id: registry_needs
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: bun .github/scripts/selected-registry-needs.mjs
- name: Resolve registry identity bootstrap scope
id: bootstrap_scope
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
NEEDS_CARGO: ${{ steps.registry_needs.outputs.needs_cargo }}
NEEDS_NPM: ${{ steps.registry_needs.outputs.needs_npm }}
run: |
required=false
if [[ "$NEEDS_CARGO" == true || "$NEEDS_NPM" == true ]]; then
required=true
fi
echo "required=$required" >> "$GITHUB_OUTPUT"
- name: No registry identities require bootstrap
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && steps.bootstrap_scope.outputs.required != 'true' }}
run: echo 'The selected release has no Cargo or npm identities; bootstrap is a no-op.'
- name: Set up pinned npm publisher
id: setup_bootstrap_npm
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }}
timeout-minutes: 3
uses: ./.github/actions/setup-npm-publisher
with:
npm-version: ${{ env.NPM_VERSION }}
- name: Verify direct-workflow OIDC identity
id: verify_bootstrap_oidc_identity
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
RELEASE_OPERATION: publish-bootstrap
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
run: bun .github/scripts/verify-github-oidc-identity.mjs
- name: Prove workflow HEAD is the release-bump commit
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/verify-release-commit.mjs \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Preflight selected product tag and release collisions
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/verify_product_tags.mjs \
--products-json "$PRODUCTS_JSON" \
--target "$RELEASE_HEAD_SHA" \
--allow-missing
bun .github/scripts/manage-release-drafts.mjs preflight \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Require existing qualified release-commit CI run
id: ci_qualification
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
REQUIRES_WASIX_EVIDENCE: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }}
run: |
qualification_args=(
CI
"$RELEASE_HEAD_SHA"
0
--event push
--event workflow_dispatch
--job Builds
--job Required
--job Qualified
--artifact artifact-build-plan
--artifact oliphaunt-release-candidate
)
if [[ "$REQUIRES_WASIX_EVIDENCE" == true ]]; then
qualification_args+=(--artifact wasix-release-regression-evidence)
fi
bash .github/scripts/require-workflow-success.sh "${qualification_args[@]}"
- name: Download exact-SHA qualification record
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
"$RUNNER_TEMP/release-candidate" \
--run-id "$CI_RUN_ID" \
--job Qualified \
--artifact oliphaunt-release-candidate
- name: Download exact-SHA affected plan
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
"$RUNNER_TEMP/release-candidate/affected-plan" \
--run-id "$CI_RUN_ID" \
--job Plan \
--artifact artifact-build-plan
- name: Download required exact-SHA WASIX evidence
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.release_plan.outputs.requires_wasix_release_regression_evidence == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
node .github/scripts/download-build-artifacts.mjs \
CI \
"$RELEASE_HEAD_SHA" \
"$RUNNER_TEMP/release-candidate/wasix-evidence" \
--run-id "$CI_RUN_ID" \
--job "E2E / WASIX release regression" \
--artifact wasix-release-regression-evidence
- name: Verify exact-SHA qualification record
id: verify_bootstrap_qualification
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
WASIX_EVIDENCE_REQUIRED: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }}
run: |
node .github/scripts/verify-release-candidate.mjs \
"$RUNNER_TEMP/release-candidate/oliphaunt-release-candidate.json" \
--plan "$RUNNER_TEMP/release-candidate/affected-plan/ci-plan.json" \
--wasix-evidence-required "$WASIX_EVIDENCE_REQUIRED" \
--wasix-evidence-root "$RUNNER_TEMP/release-candidate/wasix-evidence"
- name: Inspect exact parent bootstrap continuation
id: inspect_bootstrap_continuation
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && inputs.continuation_pointer != '' }}
# Covers the 53-minute sequential GitHub-read envelope plus local verification margin.
timeout-minutes: 55
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_CONTINUATION_ARCHIVE: ${{ runner.temp }}/release-continuation.zip
RELEASE_OPERATION: publish-bootstrap
run: node .github/scripts/inspect-release-continuation.mjs
- name: Select one approved dry-run capsule
id: approved_bootstrap_capsule
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && inputs.continuation_pointer == '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
bash .github/scripts/require-workflow-success.sh \
Release \
"$RELEASE_HEAD_SHA" \
0 \
--event workflow_dispatch \
--artifact oliphaunt-publication-lock \
--artifact oliphaunt-bootstrap-capsule
- name: Download approved lock and capsule from one dry-run
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
DRY_RUN_ID: ${{ steps.inspect_bootstrap_continuation.outputs.approved_run_id || steps.approved_bootstrap_capsule.outputs.run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.inspect_bootstrap_continuation.outputs.approved_artifact_metadata_json || steps.approved_bootstrap_capsule.outputs.artifact_metadata_json }}
run: |
node .github/scripts/download-build-artifacts.mjs \
Release \
"$RELEASE_HEAD_SHA" \
"$RUNNER_TEMP/approved-bootstrap" \
--run-id "$DRY_RUN_ID" \
--artifact-metadata-json "$APPROVED_ARTIFACT_METADATA_JSON" \
--artifact oliphaunt-publication-lock \
--artifact oliphaunt-bootstrap-capsule
- name: Verify and install approved bootstrap capsule
id: verify_bootstrap_capsule
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
if [[ -e "$GITHUB_WORKSPACE/target" ]]; then
echo 'bootstrap capsule installation requires an absent workspace target directory' >&2
exit 1
fi
tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mjs verify-extract \
--transport "$RUNNER_TEMP/approved-bootstrap/oliphaunt-bootstrap-capsule.tar" \
--approved-lock "$RUNNER_TEMP/approved-bootstrap/publication-lock.json" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--workspace-root "$GITHUB_WORKSPACE"
- name: Verify external lock equals installed capsule lock
id: verify_bootstrap_lock
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
run: |
if ! cmp -s "$RUNNER_TEMP/approved-bootstrap/publication-lock.json" "$PUBLICATION_LOCK_PATH"; then
echo 'installed capsule lock differs from the separately downloaded approved publication lock' >&2
exit 1
fi
tools/dev/bun.sh tools/release/publication-lock.mjs verify \
--lock "$PUBLICATION_LOCK_PATH" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Restore prior bootstrap checkpoint chain
id: restore_bootstrap_checkpoint
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_CONTINUATION_ARCHIVE: ${{ runner.temp }}/release-continuation.zip
run: node .github/scripts/download-bootstrap-ledger.mjs
- name: Require bootstrap credentials before mutation
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
CRATES_IO_BOOTSTRAP_TOKEN: ${{ secrets.CRATES_IO_BOOTSTRAP_TOKEN }}
NPM_BOOTSTRAP_TOKEN: ${{ secrets.NPM_BOOTSTRAP_TOKEN }}
NEEDS_CARGO: ${{ steps.registry_needs.outputs.needs_cargo }}
NEEDS_NPM: ${{ steps.registry_needs.outputs.needs_npm }}
run: |
if [[ "$NEEDS_CARGO" == true && -z "$CRATES_IO_BOOTSTRAP_TOKEN" ]]; then
echo 'selected products require CRATES_IO_BOOTSTRAP_TOKEN in the release-bootstrap environment' >&2
exit 1
fi
if [[ "$NEEDS_NPM" == true && -z "$NPM_BOOTSTRAP_TOKEN" ]]; then
echo 'selected products require NPM_BOOTSTRAP_TOKEN in the release-bootstrap environment' >&2
exit 1
fi
- name: Admit or recover exact immutable release transport ref
id: ensure_bootstrap_transport_ref
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && inputs.continuation_pointer == '' }}
timeout-minutes: 3
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: publish-bootstrap
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_TRANSPORT_CONTENT_WRITE_ADMISSION: isolated-bootstrap
run: node .github/scripts/release-transport-ref.mjs ensure "$RELEASE_HEAD_SHA"
- name: Start bounded bootstrap mutation window
id: bootstrap_mutation_deadline
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
run: |
if [[ ! "$REGISTRY_MUTATION_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'REGISTRY_MUTATION_WINDOW_SECONDS must be a positive integer' >&2
exit 1
fi
if [[ ! "$REGISTRY_JOB_HARD_DEADLINE_EPOCH" =~ ^[1-9][0-9]*$ ]]; then
echo 'REGISTRY_JOB_HARD_DEADLINE_EPOCH must be a positive Unix timestamp' >&2
exit 1
fi
now=$(date +%s)
window_deadline=$(( now + REGISTRY_MUTATION_WINDOW_SECONDS ))
deadline=$window_deadline
if (( REGISTRY_JOB_HARD_DEADLINE_EPOCH < deadline )); then
deadline=$REGISTRY_JOB_HARD_DEADLINE_EPOCH
fi
echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$deadline" >> "$GITHUB_ENV"
echo "Bootstrap registry mutation must stop before Unix time $deadline."
- name: Configure npm identity-bootstrap authentication
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.registry_needs.outputs.needs_npm == 'true' }}
env:
NPM_BOOTSTRAP_TOKEN: ${{ secrets.NPM_BOOTSTRAP_TOKEN }}
run: |
umask 077
npmrc="$RUNNER_TEMP/oliphaunt-bootstrap.npmrc"
printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_BOOTSTRAP_TOKEN" > "$npmrc"
- name: Bootstrap missing Cargo and npm identities
id: bootstrap_registry_identities
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_BOOTSTRAP_TOKEN }}
NPM_CONFIG_USERCONFIG: ${{ runner.temp }}/oliphaunt-bootstrap.npmrc
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
REGISTRY_BOOTSTRAP_CARGO_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_CARGO_SECONDS_PER_CARRIER || '30' }}
REGISTRY_BOOTSTRAP_NPM_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_NPM_SECONDS_PER_CARRIER || '30' }}
REGISTRY_BOOTSTRAP_RECONCILIATION_SECONDS_PER_CARRIER: ${{ vars.REGISTRY_BOOTSTRAP_RECONCILIATION_SECONDS_PER_CARRIER || '6' }}
REGISTRY_BOOTSTRAP_RESERVE_SECONDS: ${{ vars.REGISTRY_BOOTSTRAP_RESERVE_SECONDS || '600' }}
run: bun .github/scripts/bootstrap-registry-identities.mjs
- name: Require a typed bootstrap execution decision
id: require_bootstrap_execution_decision
if: ${{ steps.bootstrap_registry_identities.outcome == 'success' }}
timeout-minutes: 1
env:
COMPLETE: ${{ steps.bootstrap_registry_identities.outputs.complete }}
DEFERRED: ${{ steps.bootstrap_registry_identities.outputs.deferred }}
DEFERRAL_MODE: ${{ steps.bootstrap_registry_identities.outputs.deferral_mode }}
PROGRESS_COUNT: ${{ steps.bootstrap_registry_identities.outputs.progress_count }}
REMAINING_COUNT: ${{ steps.bootstrap_registry_identities.outputs.remaining_count }}
NOT_BEFORE_EPOCH: ${{ steps.bootstrap_registry_identities.outputs.not_before_epoch }}
run: |
if [[ "$COMPLETE" == true && "$DEFERRED" == false ]]; then
if [[ -n "$DEFERRAL_MODE" || "$REMAINING_COUNT" != 0 ]]; then
echo 'complete bootstrap result retains a deferral mode or remaining carriers' >&2
exit 1
fi
elif [[ "$COMPLETE" == false && "$DEFERRED" == true ]]; then
if [[ ! "$REMAINING_COUNT" =~ ^[1-9][0-9]*$ || ! "$NOT_BEFORE_EPOCH" =~ ^[1-9][0-9]*$ ]]; then
echo 'deferred bootstrap result requires remaining work and a positive not-before time' >&2
exit 1
fi
if [[ "$DEFERRAL_MODE" == progress ]]; then
if [[ ! "$PROGRESS_COUNT" =~ ^[1-9][0-9]*$ ]]; then
echo 'bootstrap progress deferral requires nonzero durable progress' >&2
exit 1
fi
elif [[ "$DEFERRAL_MODE" == rate-limit ]]; then
if [[ "$PROGRESS_COUNT" != 0 ]]; then
echo 'bootstrap rate-limit deferral cannot claim durable progress' >&2
exit 1
fi
elif [[ "$DEFERRAL_MODE" == pre-mutation-deadline ]]; then
if [[ "$PROGRESS_COUNT" != 0 ]]; then
echo 'bootstrap pre-mutation deadline deferral cannot claim durable progress' >&2
exit 1
fi
else
echo 'deferred bootstrap result has an unsupported deferral mode' >&2
exit 1
fi
else
echo 'bootstrap publisher must emit exactly one of complete or deferred' >&2
exit 1
fi
{
echo "complete=$COMPLETE"
echo "deferred=$DEFERRED"
echo "deferral_mode=$DEFERRAL_MODE"
echo "progress_count=$PROGRESS_COUNT"
echo "remaining_count=$REMAINING_COUNT"
echo "not_before_epoch=$NOT_BEFORE_EPOCH"
} >> "$GITHUB_OUTPUT"
- name: Seal exact bootstrap continuation contract
id: prepare_bootstrap_continuation
if: ${{ steps.require_bootstrap_execution_decision.outputs.deferred == 'true' }}
timeout-minutes: 3
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
APPROVED_RUN_ID: ${{ steps.inspect_bootstrap_continuation.outputs.approved_run_id || steps.approved_bootstrap_capsule.outputs.run_id }}
APPROVED_ARTIFACT_METADATA_JSON: ${{ steps.inspect_bootstrap_continuation.outputs.approved_artifact_metadata_json || steps.approved_bootstrap_capsule.outputs.artifact_metadata_json }}
RELEASE_OPERATION: publish-bootstrap
RELEASE_CONTINUATION_POINTER: ${{ inputs.continuation_pointer }}
RELEASE_EXECUTION_RESULT_PATH: target/release/bootstrap-execution-result.json
RELEASE_CONTINUATION_STATE_PATH: target/release/bootstrap-ledger
RELEASE_CONTINUATION_CONTRACT_PATH: target/release/release-continuation-contract.json
run: bun .github/scripts/prepare-release-continuation.mjs
- name: Record bootstrap identity result
if: ${{ steps.require_bootstrap_execution_decision.outputs.complete == 'true' }}
run: |
lock_sha256="$(sha256sum "$PUBLICATION_LOCK_PATH" | awk '{print $1}')"
{
echo '## Registry identity bootstrap complete'
echo
echo "- Release commit: \`$RELEASE_HEAD_SHA\`"
echo "- Publication lock SHA-256: \`$lock_sha256\`"
echo '- Scope: selected Cargo and npm identities only'
echo '- Next: configure trusted publishers, revoke bootstrap tokens, then run the normal publish operation'
} >> "$GITHUB_STEP_SUMMARY"
- name: Remove bootstrap npm credentials
id: remove_bootstrap_credentials
if: ${{ always() }}
run: rm -f "$RUNNER_TEMP/oliphaunt-bootstrap.npmrc"
- name: Upload immutable deferred bootstrap continuation
id: preserve_deferred_bootstrap_ledger
if: ${{ steps.require_bootstrap_execution_decision.outputs.deferred == 'true' }}
timeout-minutes: 10
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: bootstrap-continuation-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}-${{ steps.prepare_bootstrap_continuation.outputs.next_generation }}
path: |
target/release/bootstrap-ledger
target/release/bootstrap-execution-result.json
target/release/release-continuation-contract.json
if-no-files-found: error
include-hidden-files: true
compression-level: 0
retention-days: 90
- name: Upload bootstrap identity ledger
id: preserve_bootstrap_ledger
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: oliphaunt-bootstrap-ledger
path: target/release/bootstrap-ledger
if-no-files-found: warn
overwrite: true
retention-days: 90
dispatch-bootstrap-continuation:
name: Dispatch verified bootstrap continuation
needs: publish-bootstrap
if: ${{ needs.publish-bootstrap.outputs.continuation_required == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
actions: write
contents: read
steps:
- name: Checkout exact release transport
timeout-minutes: 3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: Set up pinned Node.js
id: setup_dispatch_node
timeout-minutes: 3
uses: ./.github/actions/setup-node-runtime
with:
node-version: ${{ env.NODE_VERSION }}
- name: Dispatch exact verified child run
id: dispatch_continuation
timeout-minutes: 40
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: publish-bootstrap
RELEASE_HEAD_SHA: ${{ github.sha }}
CONTINUATION_ARTIFACT_ID: ${{ needs.publish-bootstrap.outputs.continuation_artifact_id }}
CONTINUATION_ARTIFACT_DIGEST: ${{ needs.publish-bootstrap.outputs.continuation_artifact_digest }}
CONTINUATION_CONTRACT_DIGEST: ${{ needs.publish-bootstrap.outputs.continuation_contract_digest }}
CONTINUATION_AUTHORIZATION_PATH: ${{ runner.temp }}/release-continuation-authorization.json
run: node .github/scripts/dispatch-release-continuation.mjs
- name: Publish exact dispatched-child authorization
id: preserve_continuation_authorization
timeout-minutes: 5
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: ${{ steps.dispatch_continuation.outputs.authorization_artifact_name }}
path: ${{ runner.temp }}/release-continuation-authorization.json
if-no-files-found: error
compression-level: 0
retention-days: 90
dispatch-publish-continuation:
name: Dispatch verified registry continuation
needs: publish-registry
if: ${{ needs.publish-registry.outputs.continuation_required == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
actions: write
contents: read
steps:
- name: Checkout exact release transport
timeout-minutes: 3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: Set up pinned Node.js
id: setup_dispatch_node
timeout-minutes: 3
uses: ./.github/actions/setup-node-runtime
with:
node-version: ${{ env.NODE_VERSION }}
- name: Dispatch exact verified child run
id: dispatch_continuation
timeout-minutes: 40
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: publish
RELEASE_HEAD_SHA: ${{ github.sha }}
CONTINUATION_ARTIFACT_ID: ${{ needs.publish-registry.outputs.continuation_artifact_id }}
CONTINUATION_ARTIFACT_DIGEST: ${{ needs.publish-registry.outputs.continuation_artifact_digest }}
CONTINUATION_CONTRACT_DIGEST: ${{ needs.publish-registry.outputs.continuation_contract_digest }}
CONTINUATION_AUTHORIZATION_PATH: ${{ runner.temp }}/release-continuation-authorization.json
run: node .github/scripts/dispatch-release-continuation.mjs
- name: Publish exact dispatched-child authorization
id: preserve_continuation_authorization
timeout-minutes: 5
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: ${{ steps.dispatch_continuation.outputs.authorization_artifact_name }}
path: ${{ runner.temp }}/release-continuation-authorization.json
if-no-files-found: error
compression-level: 0
retention-days: 90