Skip to content

Release / publish / main #173

Release / publish / main

Release / publish / main #173

Workflow file for this run

name: Release
run-name: Release / ${{ inputs.operation }} / ${{ github.ref_name }}
on:
workflow_dispatch:
inputs:
operation:
description: Prepare the release PR, or freeze and publish the qualified candidate
required: true
type: choice
default: prepare-release-pr
options:
- prepare-release-pr
- publish
release_commit:
description: Optional approved source SHA for recovery after publication-only fixes
required: false
type: string
default: ""
approval_run_id:
description: Optional previous Release run with a successfully prepared frozen candidate
required: false
type: string
default: ""
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
CANONICAL_RELEASE_REPOSITORY: f0rr0/oliphaunt
NPM_VERSION: 11.18.0
PUBLICATION_LOCK_PATH: target/release/publication-lock.json
BOOTSTRAP_LEDGER_PATH: target/release/bootstrap-ledger
RELEASE_JOB_HARD_WINDOW_SECONDS: 21180
BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS: 19800
POST_REGISTRY_RESERVE_SECONDS: 3240
PUBLIC_CONSUMER_SMOKE_TIMEOUT_SECONDS: 780
PUBLIC_CONSUMER_FINALIZATION_RESERVE_SECONDS: 600
MAVEN_CENTRAL_NAMESPACE: dev.oliphaunt
concurrency:
group: release-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
pull-requests: 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_APPROVAL_RUN_ID: ${{ inputs.approval_run_id }}
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: 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: Set up Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "true"
- name: Generate the Release Please candidate locally
id: prepare_candidate
env:
GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }}
run: bash tools/release/prepare-release-pr.sh "$RUNNER_TEMP/release-candidate"
- name: Set up Rust for release manifest synchronization
if: ${{ steps.prepare_candidate.outputs.required == 'true' }}
uses: ./.github/actions/setup-rust
- name: Close and validate the local release candidate
if: ${{ steps.prepare_candidate.outputs.required == 'true' }}
run: bash tools/release/close-release-candidate.sh "$RUNNER_TEMP/release-candidate"
- name: Publish the completed release PR tree
if: ${{ steps.prepare_candidate.outputs.required == 'true' }}
env:
GH_TOKEN: ${{ secrets.RELEASE_PR_TOKEN }}
run: bash .github/scripts/publish-release-pr.sh "$RUNNER_TEMP/release-candidate"
- name: Report no release changes
if: ${{ steps.prepare_candidate.outputs.required != 'true' }}
run: echo 'Release Please found no releasable changes.'
plan-candidate:
name: Plan publication and qualification
needs:
- validate-inputs
runs-on: ubuntu-24.04
timeout-minutes: 20
if: ${{ inputs.operation == 'publish' }}
permissions:
actions: read
contents: read
pull-requests: read
steps:
- 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 Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "true"
- 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
)
bash tools/release/release-plan.sh "${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.mts
- name: Prove pending release identity
id: verify_publication_candidate
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 2
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--github-output "$GITHUB_OUTPUT"
- name: Prove Release Please PR can complete after publication
id: assert_release_please_markable
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \
assert-markable \
--release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \
--base main
- name: Plan qualification reuse or request
id: ci_qualification
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
REQUIRES_WASIX_EVIDENCE: ${{ steps.release_plan.outputs.requires_wasix_release_regression_evidence }}
REQUIRES_NATIVE_EVIDENCE: ${{ steps.release_plan.outputs.requires_native_extension_lifecycle_evidence }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
qualification_args=(
CI
"$RELEASE_HEAD_SHA"
0
--plan-qualification "$PRODUCTS_JSON"
--event push
--event workflow_dispatch
--job Builds
--job Required
--job Qualified
--artifact artifact-build-plan
--artifact oliphaunt-release-candidate
)
if [[ "$REQUIRES_NATIVE_EVIDENCE" == true ]]; then
qualification_args+=(--artifact native-extension-lifecycle-evidence)
fi
if [[ "$REQUIRES_WASIX_EVIDENCE" == true ]]; then
qualification_args+=(--artifact wasix-release-regression-evidence)
fi
if [[ "${{ steps.release_plan.outputs.has_extension_artifacts }}" == true ]]; then
qualification_args+=(--artifact oliphaunt-extension-package-artifacts)
fi
bash .github/scripts/require-workflow-success.sh "${qualification_args[@]}"
outputs:
release_plan: ${{ toJSON(steps.release_plan.outputs) }}
release_head: ${{ toJSON(steps.release_head.outputs) }}
registry_needs: ${{ toJSON(steps.registry_needs.outputs) }}
verify_publication_candidate: ${{ toJSON(steps.verify_publication_candidate.outputs) }}
qualification_request_required: ${{ steps.ci_qualification.outputs.qualification_request_required }}
request-qualification:
name: Request missing CI qualification
needs: plan-candidate
if: ${{ needs.plan-candidate.outputs.qualification_request_required == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
actions: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 1
persist-credentials: false
- name: Set up Bun
uses: ./.github/actions/setup-bun
- name: Request or reuse the candidate CI run
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
RELEASE_HEAD_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
run: bash .github/scripts/require-workflow-success.sh CI "$RELEASE_HEAD_SHA" 120 --dispatch-qualification "$PRODUCTS_JSON"
prepare-candidate:
name: Prepare frozen publication candidate
needs:
- plan-candidate
- request-qualification
runs-on: ubuntu-24.04
timeout-minutes: 360
if: ${{ !cancelled() && needs.plan-candidate.result == 'success' && (needs.request-qualification.result == 'success' || needs.request-qualification.result == 'skipped') && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }}
environment: release-dry-run
permissions:
actions: read
contents: read
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: Restore exact publication source
env:
INPUT_RELEASE_COMMIT: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: .github/scripts/resolve-release-head.sh
- name: Set up Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "true"
- name: Require qualified release-commit CI run
id: ci_qualification
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
REQUIRES_WASIX_EVIDENCE: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence }}
REQUIRES_NATIVE_EVIDENCE: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence }}
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
run: |
qualification_args=(
CI
"$RELEASE_HEAD_SHA"
7200
--qualification-products "$PRODUCTS_JSON"
--event push
--event workflow_dispatch
--job Builds
--job Required
--job Qualified
--artifact artifact-build-plan
--artifact oliphaunt-release-candidate
)
if [[ "$REQUIRES_NATIVE_EVIDENCE" == true ]]; then
qualification_args+=(--artifact native-extension-lifecycle-evidence)
fi
if [[ "$REQUIRES_WASIX_EVIDENCE" == true ]]; then
qualification_args+=(--artifact wasix-release-regression-evidence)
fi
if [[ "${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_artifacts }}" == 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: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
bash .github/scripts/download-build-artifacts.sh \
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: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_HEAD_SHA" \
target/release-candidate/affected-plan \
--run-id "$CI_RUN_ID" \
--job "Planning / Affected Work" \
--artifact artifact-build-plan
- name: Download required exact-SHA WASIX evidence
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).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: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_HEAD_SHA" \
target/release-candidate/wasix-evidence \
--run-id "$CI_RUN_ID" \
--job "E2E / WASIX Extension Lifecycle" \
--artifact wasix-release-regression-evidence
- name: Download required exact-SHA native evidence
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
run: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_HEAD_SHA" \
target/release-candidate/native-evidence \
--run-id "$CI_RUN_ID" \
--job "E2E / Native Extension Lifecycle Evidence" \
--artifact native-extension-lifecycle-evidence
- name: Verify exact-SHA qualification record
id: verify_qualification
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
WASIX_EVIDENCE_REQUIRED: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_wasix_release_regression_evidence }}
NATIVE_EVIDENCE_REQUIRED: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).requires_native_extension_lifecycle_evidence }}
run: |
bash .github/scripts/release-candidate.sh verify \
target/release-candidate/oliphaunt-release-candidate.json \
--plan target/release-candidate/affected-plan/ci-plan.json \
--qualification-mode release \
--products-json "$PRODUCTS_JSON" \
--native-evidence-required "$NATIVE_EVIDENCE_REQUIRED" \
--native-evidence-root target/release-candidate/native-evidence \
--wasix-evidence-required "$WASIX_EVIDENCE_REQUIRED" \
--wasix-evidence-root target/release-candidate/wasix-evidence
- name: Require the explicitly selected prepared candidate
id: approved_publication_lock
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
APPROVAL_RUN_ID: ${{ inputs.approval_run_id }}
run: |
approved_artifacts=(
--artifact oliphaunt-publication-lock
--artifact oliphaunt-publication-candidate
)
gate_output="$RUNNER_TEMP/approved-publication-inputs-gate.out"
GITHUB_OUTPUT="$gate_output" \
bash .github/scripts/require-workflow-success.sh \
Release \
"$RELEASE_HEAD_SHA" \
0 \
--run-id "$APPROVAL_RUN_ID" \
--release-candidate \
--event workflow_dispatch \
"${approved_artifacts[@]}"
cat "$gate_output" >> "$GITHUB_OUTPUT"
- name: Download the approved lock and frozen candidate
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ steps.approved_publication_lock.outputs.run_id }}
artifact-ids: ${{ steps.approved_publication_lock.outputs.artifact_ids }}
merge-multiple: true
path: ${{ runner.temp }}/approved-publication
- name: Verify and install the complete approved candidate
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }}
env:
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
APPROVAL_RUN_ID: ${{ inputs.approval_run_id }}
run: |
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \
--transport "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" \
--approved-lock "$RUNNER_TEMP/approved-publication/publication-lock.json" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--approval-run-id "$APPROVAL_RUN_ID" \
--workspace-root "$GITHUB_WORKSPACE"
- name: Validate product versions and registry state
id: validate_release_registry_state
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
run: |
bash tools/release/release-check-registries.sh \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Download WASIX runtime build artifacts
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: bash .github/scripts/download-wasix-runtime-build-artifacts.sh
- name: Download WASIX release assets
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/oliphaunt-wasix/release-assets \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact liboliphaunt-wasix-release-assets
- name: Download WASIX postmaster release assets
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-wasix-postmaster') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/oliphaunt-wasix-postmaster/release-assets \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact liboliphaunt-wasix-postmaster-release-assets
- name: Download exact-extension package artifacts
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_extension_artifacts == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/extension-artifacts \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact oliphaunt-extension-package-artifacts
- name: Download SDK package artifacts
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
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/query.mts ci-artifact-names --product "$product" --family sdk-package --format lines)
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_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/query.mts ci-products --family sdk-package --products-json "$PRODUCTS_JSON" --format lines)
- name: Download liboliphaunt release assets
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'liboliphaunt-native') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/liboliphaunt/release-assets \
--run-id "$CI_RUN_ID" \
--job Builds \
--artifact liboliphaunt-native-release-assets
- name: Download canonical database resources
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'database-resources') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
artifacts=()
for kind in icu-data native-seeds wasix-seeds; do
while IFS= read -r artifact; do
artifacts+=(--artifact "$artifact")
done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product database-resources --kind "$kind" --family release-assets --format lines)
done
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/database-resources/release-assets \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifacts[@]}"
- name: Download native helper release assets
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-native') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-wasix')) }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PRODUCT_POSTGRES_TOOLS_WASIX: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-wasix') }}
PRODUCT_POSTGRES_TOOLS_NATIVE: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'postgres-tools-native') }}
PRODUCT_OLIPHAUNT_BROKER: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-broker') }}
PRODUCT_OLIPHAUNT_NODE_DIRECT: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }}
PRODUCT_OLIPHAUNT_WASIX_NAPI: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
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/query.mts ci-artifact-names --product "$product" --kind "$kind" --family release-assets --format lines)
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
"$destination" \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifact_args[@]}"
}
if [ "$PRODUCT_POSTGRES_TOOLS_WASIX" = "true" ]; then
download_helper_artifacts postgres-tools-wasix wasix-tools target/postgres-tools/wasix/release-assets
download_helper_artifacts postgres-tools-wasix wasix-tools-aot target/postgres-tools/wasix/release-assets
fi
if [ "$PRODUCT_POSTGRES_TOOLS_NATIVE" = "true" ]; then
download_helper_artifacts postgres-tools-native native-tools target/postgres-tools/native/release-assets
fi
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
if [ "$PRODUCT_OLIPHAUNT_WASIX_NAPI" = "true" ]; then
download_helper_artifacts \
oliphaunt-wasix-napi \
wasix-napi-addon \
target/oliphaunt-wasix-napi/release-assets
fi
- name: Download Node direct optional npm packages
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-node-direct') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
artifact_args=()
while IFS= read -r artifact; do
artifact_args+=(--artifact "$artifact")
done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product oliphaunt-node-direct --kind node-direct-addon --family npm-package --format lines)
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/oliphaunt-node-direct/npm-packages \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifact_args[@]}"
- name: Download WASIX Node-API optional npm packages
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-wasix-napi') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
CI_RUN_ID: ${{ steps.ci_qualification.outputs.run_id }}
RELEASE_ARTIFACT_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: |
artifact_args=()
while IFS= read -r artifact; do
artifact_args+=(--artifact "$artifact")
done < <(tools/dev/bun.sh tools/release/query.mts ci-artifact-names --product oliphaunt-wasix-napi --kind wasix-napi-addon --family npm-package --format lines)
bash .github/scripts/download-build-artifacts.sh \
CI \
"$RELEASE_ARTIFACT_SHA" \
target/oliphaunt-wasix-napi/npm-packages \
--run-id "$CI_RUN_ID" \
--job Builds \
"${artifact_args[@]}"
- name: Freeze canonical Apple extension carrier input
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' && (contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') || contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-react-native')) }}
env:
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
includes_swift: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-swift') }}
includes_react_native: ${{ contains(fromJson(fromJSON(needs.plan-candidate.outputs.release_plan).products_json), 'oliphaunt-react-native') }}
run: |
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=()
product_roots="$(tools/dev/bun.sh tools/release/query.mts \
ci-products \
--family extension-artifacts \
--carrier-family native \
--products-json "$PRODUCTS_JSON" \
--field artifact-root \
--format lines)"
while IFS= read -r product_root; do
[[ -n "$product_root" ]] || continue
product="$(basename "$product_root")"
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 <<< "$product_roots"
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_carrier=target/release/ios-carriers/oliphaunt-react-native-ios-carriers.json
if (( ${#extension_manifest_args[@]} == 0 )); then
cp "$react_native_source_carrier" "$public_carrier"
else
public_args=(
--base-carrier "$react_native_source_carrier"
"${extension_manifest_args[@]}"
--output "$public_carrier"
)
tools/dev/bun.sh src/native/sdks/swift/tools/ios-carrier-manifest.mts "${public_args[@]}"
fi
fi
if [[ "$includes_swift" == true && ${#extension_manifest_args[@]} -gt 0 ]]; 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 src/native/sdks/swift/tools/ios-carrier-manifest.mts "${local_args[@]}"
extensions_csv="$(bun src/native/sdks/swift/tools/ios-carrier-manifest.mts list-extensions "$local_aggregate_carrier")"
if [[ -z "$extensions_csv" || ${#extension_carrier_args[@]} == 0 ]]; then
echo 'Swift extension validation requires selected extension carrier assets.' >&2
exit 1
fi
swift_version="$(tools/dev/bun.sh tools/release/product-version.mts version oliphaunt-swift)"
cache=target/release-work/swiftpm-extension-cache
bun src/native/sdks/swift/tools/render-extension-products.mts \
--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
bun src/native/sdks/swift/tools/render-extension-products.mts \
--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
id: setup_github_stage_npm
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && fromJSON(needs.plan-candidate.outputs.registry_needs).needs_npm == 'true' }}
timeout-minutes: 3
uses: ./.github/actions/setup-npm-publisher
with:
npm-version: ${{ env.NPM_VERSION }}
- name: Verify unchanged candidate source before assembly
id: validate_release
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
CANDIDATE_SHA: ${{ fromJSON(needs.plan-candidate.outputs.release_head).sha }}
run: bash tools/release/qualified-release-replay.sh "$CANDIDATE_SHA" "$CANDIDATE_SHA"
- name: Package public release carriers
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
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_WASIX_NAPI_ASSET_INPUT_DIRS: ${{ github.workspace }}/target/oliphaunt-wasix-napi/release-assets
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
run: bash tools/release/package-release-carriers.sh --products-json "$PRODUCTS_JSON"
- name: Freeze exhaustive publication lock
id: freeze_publication_lock
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id == '' }}
env:
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).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/postgres-tools/native/release-assets \
target/postgres-tools/native/cargo-artifacts \
target/postgres-tools/wasix/release-assets \
target/postgres-tools/wasix/cargo-artifacts \
target/database-resources/release-assets \
target/database-resources/cargo-artifacts \
target/database-resources/seed-carriers \
target/oliphaunt-wasix/release-assets \
target/oliphaunt-wasix-postmaster/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 \
target/oliphaunt-wasix-napi/release-assets \
target/oliphaunt-wasix-napi/npm-packages; do
if [[ -e "$artifact_root" ]]; then
lock_args+=(--artifact-root "$artifact_root")
fi
done
while IFS= read -r artifact_root; do
if [[ ! -d "$artifact_root" ]]; then
echo "Selected extension artifacts are missing $artifact_root" >&2
exit 1
fi
lock_args+=(--artifact-root "$artifact_root")
done < <(tools/dev/bun.sh tools/release/query.mts \
ci-products \
--family extension-artifacts \
--products-json "$PRODUCTS_JSON" \
--field artifact-root \
--format lines)
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts "${lock_args[@]}"
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts \
verify \
--lock target/release/publication-lock.json \
--head-ref "$RELEASE_HEAD_SHA"
- name: Freeze complete publication candidate
id: freeze_publication_candidate
if: ${{ inputs.approval_run_id == '' && fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
run: |
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts pack \
--lock "$PUBLICATION_LOCK_PATH" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--approval-run-id "$GITHUB_RUN_ID" \
--qualification-run-id "${{ steps.ci_qualification.outputs.run_id }}" \
--output target/release/oliphaunt-publication-candidate.tar
- name: Preserve the approved recovery capsule unchanged
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' && inputs.approval_run_id != '' }}
run: cp "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" target/release/oliphaunt-publication-candidate.tar
- name: Upload frozen publication lock
id: preserve_publication_lock
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).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 complete frozen publication candidate
id: preserve_publication_candidate
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: oliphaunt-publication-candidate
path: target/release/oliphaunt-publication-candidate.tar
if-no-files-found: error
overwrite: true
retention-days: 90
- name: Check whether first registry identities need credentials
id: bootstrap_credentials
if: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes == 'true' }}
timeout-minutes: 15
env:
PRODUCTS_JSON: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).products_json }}
run: |-
export REGISTRY_MUTATION_DEADLINE_EPOCH="$(( $(date +%s) + 840 ))"
bun .github/scripts/bootstrap-registry-identities.mts --credential-needs
outputs:
has_release_changes: ${{ fromJSON(needs.plan-candidate.outputs.release_plan).has_release_changes }}
lock_artifact_id: ${{ steps.preserve_publication_lock.outputs.artifact-id }}
candidate_artifact_id: ${{ steps.preserve_publication_candidate.outputs.artifact-id }}
bootstrap_required: ${{ steps.bootstrap_credentials.outputs.needs_cargo_token == 'true' || steps.bootstrap_credentials.outputs.needs_npm_token == 'true' }}
publish-bootstrap:
name: Bootstrap registry identities
needs:
- prepare-candidate
runs-on: ubuntu-24.04
timeout-minutes: 360
if: ${{ needs.prepare-candidate.outputs.bootstrap_required == 'true' }}
environment: release-bootstrap
permissions:
actions: read
contents: write
id-token: write
pull-requests: read
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 Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "false"
- name: Require checked publishing code
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required
- name: Plan bootstrap releases
id: release_plan
run: |
bash tools/release/release-plan.sh \
--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: Prove Release Please PR can complete after bootstrap
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \
--products-json "$PRODUCTS_JSON" --head-ref "$RELEASE_HEAD_SHA" \
--github-output "$RUNNER_TEMP/bootstrap-release-identity"
release_sha="$(sed -n 's/^release_sha=//p' "$RUNNER_TEMP/bootstrap-release-identity")"
tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \
assert-markable --release-sha "$release_sha" --base main
- 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.mts
- 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: 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: |
bash tools/release/verify-product-tags.sh \
--products-json "$PRODUCTS_JSON" \
--target "$RELEASE_HEAD_SHA" \
--allow-missing
bun .github/scripts/manage-release-drafts.mts preflight \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Download the frozen candidate from preparation
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }}
path: ${{ runner.temp }}/approved-bootstrap
merge-multiple: true
- name: Verify and install approved publication candidate
id: verify_bootstrap_candidate
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }}
run: |
if [[ -e "$GITHUB_WORKSPACE/target" ]]; then
echo 'publication candidate installation requires an absent workspace target directory' >&2
exit 1
fi
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \
--transport "$RUNNER_TEMP/approved-bootstrap/oliphaunt-publication-candidate.tar" \
--approved-lock "$RUNNER_TEMP/approved-bootstrap/publication-lock.json" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--approval-run-id "$APPROVAL_RUN_ID" \
--workspace-root "$GITHUB_WORKSPACE"
- name: Verify external lock equals installed candidate 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 candidate lock differs from the separately downloaded approved publication lock' >&2
exit 1
fi
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts 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 }}
run: bun .github/scripts/download-bootstrap-ledger.mts
- name: Classify exact bootstrap credential needs
id: bootstrap_credential_needs
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
REGISTRY_MUTATION_DEADLINE_EPOCH: ${{ env.REGISTRY_JOB_HARD_DEADLINE_EPOCH }}
run: bun .github/scripts/bootstrap-registry-identities.mts --credential-needs
- 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_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_cargo_token }}
NEEDS_NPM_TOKEN: ${{ steps.bootstrap_credential_needs.outputs.needs_npm_token }}
run: |
if [[ "$NEEDS_CARGO_TOKEN" == true && -z "$CRATES_IO_BOOTSTRAP_TOKEN" ]]; then
echo 'approved candidate contains absent Cargo names but CRATES_IO_BOOTSTRAP_TOKEN is unavailable' >&2
exit 1
fi
if [[ "$NEEDS_NPM_TOKEN" == true && -z "$NPM_BOOTSTRAP_TOKEN" ]]; then
echo 'approved candidate contains absent npm names but NPM_BOOTSTRAP_TOKEN is unavailable' >&2
exit 1
fi
- name: Create bootstrap transport tag token
id: bootstrap_tag_token
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with: &release_tag_app
client-id: ${{ secrets.RELEASE_TAG_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_TAG_APP_PRIVATE_KEY }}
owner: f0rr0
repositories: oliphaunt
permission-contents: write
permission-workflows: write
- name: Ensure exact immutable release transport ref
id: ensure_bootstrap_transport_ref
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
timeout-minutes: 3
env:
GH_TOKEN: ${{ steps.bootstrap_tag_token.outputs.token }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: publish-bootstrap
RELEASE_TRANSPORT_CONTENT_WRITE_ADMISSION: isolated-bootstrap
run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA"
- name: Start bounded bootstrap mutation window
id: bootstrap_mutation_deadline
if: ${{ steps.bootstrap_scope.outputs.required == 'true' }}
run: |
if [[ ! "$BOOTSTRAP_REGISTRY_MUTATION_WINDOW_SECONDS" =~ ^[1-9][0-9]*$ ]]; then
echo 'BOOTSTRAP_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 + BOOTSTRAP_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
id: configure_bootstrap_npm_auth
if: ${{ steps.bootstrap_scope.outputs.required == 'true' && steps.bootstrap_credential_needs.outputs.needs_npm_token == '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: ${{ steps.bootstrap_credential_needs.outputs.needs_cargo_token == 'true' && 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: bash .github/scripts/bootstrap-registry-identities.sh
- 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-capacity ]]; then
if [[ "$PROGRESS_COUNT" != 0 ]]; then
echo 'bootstrap pre-mutation capacity 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: 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 '- Publication continues automatically after this job. Configure trusted publishers before the next version, then revoke bootstrap tokens.'
} >> "$GITHUB_STEP_SUMMARY"
- name: Remove bootstrap npm credentials
id: remove_bootstrap_credentials
if: ${{ always() }}
run: rm -f "$RUNNER_TEMP/oliphaunt-bootstrap.npmrc"
- 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
- name: Stop incomplete bootstrap for manual rerun
if: ${{ steps.require_bootstrap_execution_decision.outputs.deferred == 'true' }}
env:
DEFERRAL_MODE: ${{ steps.require_bootstrap_execution_decision.outputs.deferral_mode }}
NOT_BEFORE_EPOCH: ${{ steps.require_bootstrap_execution_decision.outputs.not_before_epoch }}
REMAINING_COUNT: ${{ steps.require_bootstrap_execution_decision.outputs.remaining_count }}
APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }}
run: |
{
echo '## Registry identity bootstrap incomplete—rerun required'
echo
echo "- Release commit: \`$RELEASE_HEAD_SHA\`"
echo "- Approved dry-run: \`$APPROVAL_RUN_ID\`"
echo "- Remaining identities: \`$REMAINING_COUNT\`"
echo "- Reason: \`$DEFERRAL_MODE\`"
echo "- Do not rerun before Unix time: \`$NOT_BEFORE_EPOCH\`"
echo "- Resume this exact SHA and approval: \`gh run rerun $GITHUB_RUN_ID --failed\`"
} >> "$GITHUB_STEP_SUMMARY"
echo "Bootstrap is incomplete; rerun failed jobs for workflow run $GITHUB_RUN_ID after $NOT_BEFORE_EPOCH." >&2
exit 1
outputs:
ledger_artifact_id: ${{ steps.preserve_bootstrap_ledger.outputs.artifact-id }}
publish:
name: Publish release
needs:
- prepare-candidate
- publish-bootstrap
runs-on: macos-26
timeout-minutes: 360
if: ${{ always() && !cancelled() && inputs.operation == 'publish' && needs.prepare-candidate.result == 'success' && needs.prepare-candidate.outputs.has_release_changes == 'true' && (needs.publish-bootstrap.result == 'success' || needs.publish-bootstrap.result == 'skipped') }}
environment: release-publish
outputs:
promoted: ${{ steps.promote_github_releases.outcome == 'success' }}
permissions:
actions: read
artifact-metadata: write
attestations: write
contents: write
id-token: write
pull-requests: write
steps:
- name: Record release deadline
id: release_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 "RELEASE_JOB_HARD_DEADLINE_EPOCH=$hard_deadline"
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_GITHUB_RUN_SNAPSHOT_DIR=$RUNNER_TEMP/oliphaunt-github-run-snapshots"
} >> "$GITHUB_ENV"
echo "The release must finish 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 Moon
uses: ./.github/actions/setup-moon
with:
install-workspace: "true"
- name: Require checked publishing code
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: bash .github/scripts/require-workflow-success.sh CI "$GITHUB_SHA" 0 --job Required
- name: Set up Rust
uses: ./.github/actions/setup-rust
- 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
)
bash tools/release/release-plan.sh "${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.mts
- name: Verify direct-workflow OIDC identity
id: verify_oidc_identity
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
RELEASE_OPERATION: publish
run: bun .github/scripts/verify-github-oidc-identity.mts
- name: Prove pending release identity
id: verify_publication_candidate
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 2
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
bash tools/release/release-please-state.sh "$PWD" HEAD bash tools/release/with-release-history.sh "$PWD" "$RELEASE_HEAD_SHA" tools/dev/bun.sh tools/release/verify-publication-candidate.mts \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--github-output "$GITHUB_OUTPUT"
- name: Prove Release Please PR can complete after publication
id: assert_release_please_markable
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 1
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \
assert-markable \
--release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \
--base main
- name: Preflight selected product tag and release collisions
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
gh auth setup-git
bash tools/release/verify-product-tags.sh \
--products-json "$PRODUCTS_JSON" \
--target "$RELEASE_HEAD_SHA" \
--allow-missing
bun .github/scripts/manage-release-drafts.mts preflight \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Check release tag App permissions
id: check_release_tag_app
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with: *release_tag_app
- name: Check publish environment
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 }}
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.mts --products-json "${PRODUCTS_JSON}"
- name: Verify external registry ownership and trust links
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
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.mts
- name: Import, sign, and verify Maven credentials before mutation
id: verify_maven_signing
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && 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: bash tools/release/verify-maven-signing-readiness.sh
- name: Download the frozen candidate from preparation
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
artifact-ids: ${{ format('{0},{1}', needs.prepare-candidate.outputs.lock_artifact_id, needs.prepare-candidate.outputs.candidate_artifact_id) }}
path: ${{ runner.temp }}/approved-publication
merge-multiple: true
- name: Verify and install the complete approved candidate
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
APPROVAL_RUN_ID: ${{ inputs.approval_run_id || github.run_id }}
run: |
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/bootstrap-publication-capsule.mts verify-extract \
--transport "$RUNNER_TEMP/approved-publication/oliphaunt-publication-candidate.tar" \
--approved-lock "$RUNNER_TEMP/approved-publication/publication-lock.json" \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--approval-run-id "$APPROVAL_RUN_ID" \
--workspace-root "$GITHUB_WORKSPACE"
- name: Validate product versions and registry state
id: validate_release_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: |
bash tools/release/release-check-registries.sh \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Set up pinned npm publisher
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: Assemble and sign the exact Maven Central bundle before release mutation
id: preflight_maven_bundle
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && 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: |
bash tools/release/preflight-maven-central-bundle.sh \
--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' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }}
timeout-minutes: 2
run: |
bash tools/release/publish-swiftpm-source-tag.sh --preflight \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--release-commit "$RELEASE_HEAD_SHA"
- name: Classify pre-tag registry publication state
id: bootstrap_ledger_state
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && (steps.registry_needs.outputs.needs_cargo == 'true' || steps.registry_needs.outputs.needs_npm == 'true') }}
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
RELEASE_HEAD_SHA: ${{ steps.release_head.outputs.sha }}
run: bash .github/scripts/registry-bootstrap-ledger-state.sh
- name: Download the bootstrap ledger from this run
if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' && needs.publish-bootstrap.outputs.ledger_artifact_id != '' }}
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
artifact-ids: ${{ needs.publish-bootstrap.outputs.ledger_artifact_id }}
path: ${{ env.BOOTSTRAP_LEDGER_PATH }}
- name: Restore completed bootstrap evidence for this candidate
if: ${{ steps.bootstrap_ledger_state.outputs.needs_ledger == 'true' && needs.publish-bootstrap.outputs.ledger_artifact_id == '' }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: bash .github/scripts/download-completed-bootstrap.sh
- 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.mts verify \
--lock "$PUBLICATION_LOCK_PATH" \
--ledger "$BOOTSTRAP_LEDGER_PATH" \
--products-json "$PRODUCTS_JSON" \
--require-complete \
--verify-registries
- name: Upload publication lock audit evidence
if: ${{ 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: Create release tag token
id: release_tag_token
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with: *release_tag_app
- name: Reserve release transport content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 3
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve \
--label "release transport tag"
- name: Ensure exact immutable release transport ref
id: ensure_release_transport_ref
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 3
env:
GH_TOKEN: ${{ steps.release_tag_token.outputs.token }}
GH_REPO: ${{ github.repository }}
RELEASE_OPERATION: publish
RELEASE_TRANSPORT_CONTENT_WRITE_ADMISSION: pre-reserved
run: bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bun .github/scripts/release-transport-ref.mts ensure "$RELEASE_HEAD_SHA"
- name: Stage exact-SHA product tags and draft releases
id: stage_github_releases
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 31
env:
GH_TOKEN: ${{ steps.release_tag_token.outputs.token }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
bun .github/scripts/manage-release-drafts.mts stage \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--state staged
- name: Verify exact product tags
id: verify_product_tags
if: ${{ 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: bash tools/release/verify-product-tags.sh --products-json "${PRODUCTS_JSON}" --target "$RELEASE_HEAD_SHA"
- name: Verify exact-SHA GitHub release staging
id: verify_github_staging
if: ${{ 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.mts 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' }}
timeout-minutes: 95
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: bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/release-publish.mts 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' && 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.mts \
--publication-lock "$PUBLICATION_LOCK_PATH" \
--products-json "$EXTENSION_PRODUCTS_JSON" \
--github-output "$GITHUB_OUTPUT"
- name: Reserve extension provenance write (batch 1)
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && 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.mts reserve --label "extension provenance batch 1"
tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 1 API attempt"
- name: Attest extension release assets (batch 1)
id: attest_extensions_1
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && 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 provenance write (batch 2)
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && 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.mts reserve --label "extension provenance batch 2"
tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "extension provenance batch 2 API attempt"
- name: Attest extension release assets (batch 2)
id: attest_extensions_2
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && 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' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-native') }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "liboliphaunt native attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mts 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' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-native') }}
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
target/extension-artifacts/liboliphaunt-native/oliphaunt-extension-contrib-pg18/release-assets/*
- name: Create fresh SwiftPM tag token
id: swift_tag_token
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }}
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
with: *release_tag_app
- name: Publish Swift SDK GitHub release and SwiftPM tags
id: publish_swift_source_tag
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-swift') }}
timeout-minutes: 6
env:
GH_TOKEN: ${{ steps.swift_tag_token.outputs.token }}
run: bash tools/release/publish-swiftpm-source-tag.sh --push --target "$RELEASE_HEAD_SHA" --publication-lock "$PUBLICATION_LOCK_PATH"
- name: Reserve broker attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-broker') }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "broker attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "broker attestation API attempt"
- name: Attest broker release assets
id: attest_broker
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-broker') }}
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' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-node-direct') }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "Node direct attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mts 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' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-node-direct') }}
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 Node-API attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-wasix-napi') }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX Node-API attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX Node-API attestation API attempt"
- name: Attest WASIX Node-API release assets
id: attest_wasix_napi
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'oliphaunt-wasix-napi') }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: |
target/oliphaunt-wasix-napi/release-assets/*.tar.gz
target/oliphaunt-wasix-napi/release-assets/*.zip
target/oliphaunt-wasix-napi/release-assets/*.sha256
- name: Reserve WASIX attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix') }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX attestation API attempt"
- name: Attest WASIX release assets
id: attest_wasix
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix') }}
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
target/extension-artifacts/liboliphaunt-wasix/oliphaunt-extension-contrib-pg18/release-assets/*
- name: Reserve WASIX postmaster attestation content write
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix-postmaster') }}
run: |
tools/dev/bun.sh tools/release/github-content-write-pacer.mts reserve --label "WASIX postmaster attestation"
tools/dev/bun.sh tools/release/github-core-request-journal.mts reserve --label "WASIX postmaster attestation API attempt"
- name: Attest WASIX postmaster release assets
id: attest_wasix_postmaster
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' && contains(fromJson(steps.release_plan.outputs.products_json), 'liboliphaunt-wasix-postmaster') }}
timeout-minutes: 5
uses: actions/attest-build-provenance@43d14bc2b83dec42d39ecae14e916627a18bb661
with:
subject-path: |
target/oliphaunt-wasix-postmaster/release-assets/*.tar.zst
target/oliphaunt-wasix-postmaster/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' }}
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_NAPI_ATTESTATION_BUNDLE: ${{ steps.attest_wasix_napi.outputs.bundle-path }}
WASIX_ATTESTATION_BUNDLE: ${{ steps.attest_wasix.outputs.bundle-path }}
WASIX_POSTMASTER_ATTESTATION_BUNDLE: ${{ steps.attest_wasix_postmaster.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_NAPI_ATTESTATION_BUNDLE" \
"$WASIX_ATTESTATION_BUNDLE" \
"$WASIX_POSTMASTER_ATTESTATION_BUNDLE"
do
if [[ -n "$bundle" ]]; then
bundle_args+=(--attestation-bundle "$bundle")
fi
done
bash tools/release/publication-controller.sh "$RELEASE_HEAD_SHA" "$GITHUB_SHA" bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/release/verify-github-release-attestations.sh 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: Open registry publication window
id: registry_publication_window
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
run: |
for name in RELEASE_JOB_HARD_DEADLINE_EPOCH POST_REGISTRY_RESERVE_SECONDS; do
if [[ ! "${!name:-}" =~ ^[1-9][0-9]*$ ]]; then
echo "$name must be a positive integer" >&2
exit 1
fi
done
mutation_deadline=$(( RELEASE_JOB_HARD_DEADLINE_EPOCH - POST_REGISTRY_RESERVE_SECONDS ))
if (( $(date +%s) >= mutation_deadline )); then
echo 'Not enough time remains for registry publication and final verification; rerun this idempotent release.' >&2
exit 1
fi
echo "REGISTRY_MUTATION_DEADLINE_EPOCH=$mutation_deadline" >> "$GITHUB_ENV"
- name: Publish and reconcile every exact-lock registry carrier
id: publish_registries
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 240
env:
GH_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 }}
run: |
bash tools/release/publish-registries.sh \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA" \
--publication-lock "$PUBLICATION_LOCK_PATH"
- name: Verify published release
id: verify_published_release
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 8
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
gh auth setup-git
git fetch --force --tags origin
bash tools/release/release-verify.sh \
--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
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 15
env:
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
command -v gtimeout >/dev/null || brew install coreutils
bash tools/release/public-consumer-smoke.sh \
--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 public consumer evidence
id: preserve_consumer_evidence
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
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
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 2
run: |
bash tools/release/with-source.sh "$RELEASE_HEAD_SHA" bash tools/dev/bun.sh tools/release/publication-lock.mts \
verify \
--lock "$PUBLICATION_LOCK_PATH" \
--head-ref "$RELEASE_HEAD_SHA"
- name: Preserve release evidence
id: preserve_release_evidence
if: ${{ always() && steps.release_plan.outputs.has_release_changes == 'true' }}
continue-on-error: true
timeout-minutes: 3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: release-evidence-${{ github.sha }}
path: |
target/release/publication-lock.json
target/release/registry-integrity-receipts.json
target/release/github-release-attestation-receipt.json
target/release/public-consumer-smoke.json
${{ runner.temp }}/oliphaunt-github-release-asset-upload-report.json
if-no-files-found: warn
include-hidden-files: true
overwrite: true
retention-days: 90
- name: Promote verified GitHub release drafts
id: promote_github_releases
if: ${{ steps.release_plan.outputs.has_release_changes == 'true' }}
timeout-minutes: 16
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PRODUCTS_JSON: ${{ steps.release_plan.outputs.products_json }}
run: |
tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \
assert-markable \
--release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \
--base main
bun .github/scripts/manage-release-drafts.mts promote \
--products-json "$PRODUCTS_JSON" \
--head-ref "$RELEASE_HEAD_SHA"
tools/dev/bun.sh tools/release/release-please-pr-lifecycle.mts \
mark-tagged \
--release-sha "${{ steps.verify_publication_candidate.outputs.release_sha }}" \
--base main
request-docs-refresh:
name: Refresh published docs
needs: publish
if: ${{ needs.publish.outputs.promoted == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
environment: Production
permissions:
contents: read
steps:
- name: Require main
run: test "$GITHUB_REF" = refs/heads/main
- name: Checkout docs refresh command
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Set up Bun
uses: ./.github/actions/setup-bun
- name: Record completed public releases
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
mkdir -p target/docs
bun src/docs/tools/verify-live.mts snapshot target/docs/expected-products.json
- name: Request main-branch docs rebuild
id: request_docs
env:
VERCEL_DOCS_DEPLOY_HOOK: ${{ secrets.VERCEL_DOCS_DEPLOY_HOOK }}
run: bash src/docs/tools/request-refresh.sh target/docs/deploy-hook.json
- name: Verify live published versions
id: verify_docs
run: bun src/docs/tools/verify-live.mts verify target/docs/expected-products.json
- name: Preserve docs refresh evidence
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: docs-refresh-request-${{ github.run_id }}-${{ github.run_attempt }}
path: |
target/docs/deploy-hook.json
target/docs/expected-products.json
if-no-files-found: ignore
retention-days: 30
- name: Report docs refresh separately from publication
if: ${{ always() }}
env:
REQUEST_OUTCOME: ${{ steps.request_docs.outcome }}
LIVE_OUTCOME: ${{ steps.verify_docs.outcome }}
run: |
{
echo '### Documentation refresh'
echo 'Product publication completed successfully before this job.'
echo "Refresh request: $REQUEST_OUTCOME."
echo "Live published-version check: $LIVE_OUTCOME."
echo 'The live check requires the version page to link the expected completed public releases or newer stable releases; hook acceptance alone is insufficient.'
echo 'Retry this docs job only after checking the Vercel deployment; do not republish products to refresh documentation.'
} >> "$GITHUB_STEP_SUMMARY"