diff --git a/.github/workflows/rainix-tag-release.yaml b/.github/workflows/rainix-tag-release.yaml index d078cfb..7142f96 100644 --- a/.github/workflows/rainix-tag-release.yaml +++ b/.github/workflows/rainix-tag-release.yaml @@ -1,6 +1,7 @@ name: rainix-tag-release -# Tag-triggered release for DEPLOY repos (deploy + publish + snapshot together), -# the counterpart to rainix-autopublish's merge-driven publish for LIBRARY repos. +# Tag-triggered release for DEPLOY repos (verify + publish + snapshot; the deploy +# itself stays manual — see below). The counterpart to rainix-autopublish's +# merge-driven publish for LIBRARY repos. # # The two lifecycles are mutually exclusive and a repo is strictly one or the # other: @@ -31,16 +32,15 @@ name: rainix-tag-release # Soldeer, and commits the new (append-only) snapshot back to main so the daily # drift sweep always has the current release's pins to check. # -# A release is deploy + publish + snapshot as one act. The `deploy` job broadcasts -# every suite in dependency order (one forge run per suite, the Zoltu -# nonce-isolation the manual dispatch already enforces) by fanning -# rainix-manual-sol-artifacts over `deploy-suites` at max-parallel 1 — composing -# the existing deploy reusable rather than re-implementing its broadcast. The -# `release` job then `needs:` it, so publish/snapshot only run once the chain -# actually carries the code the pins name. Deploy is independent of the snapshot -# regeneration (both derive from the same deterministic bytecode), so it needs no -# shared filesystem with the release job — the regen recomputes the pins and the -# verify gate confirms they now resolve on-chain. +# The on-chain DEPLOY is deliberately NOT part of this workflow. A deploy is +# flaky by nature — per-network, funds-dependent, and at the mercy of RPC +# archive/fork behaviour — and a flaky, retry-prone operation must not gate a +# one-shot tag publish, where a single transient failure would block the release +# entirely. So the deploy stays the repo's own human-driven +# rainix-manual-sol-artifacts dispatch, run BEFORE tagging. This workflow does not +# broadcast; it ATTESTS that the broadcast already landed: the `verify` step (the +# repo's fork suite) checks prod exists at the freshly regenerated pins, and a +# release whose pins don't resolve on-chain fails there and publishes nothing. on: workflow_call: inputs: @@ -70,46 +70,11 @@ on: required: false type: string default: main - deploy-suites: - description: >- - JSON array of deploy suites IN DEPENDENCY ORDER, e.g. `["stox-receipt", "stox-wrapped-token-vault"]`. Each is broadcast in a separate forge run (Zoltu nonce isolation) sequentially. A later suite that references an earlier one fails loud on the on-chain dep-codehash check if run out of order, so ordering is enforced by the deploy, not just by this list. - required: true - type: string - deploy-script: - description: >- - Fully qualified forge deploy script (`path:Contract`), passed to rainix-manual-sol-artifacts. Defaults to the conventional `script/Deploy.sol:Deploy`. - required: false - type: string - default: script/Deploy.sol:Deploy - deploy-verify: - description: >- - Whether the deploy passes `--verify` to forge. Set false when broadcasting pinned historical creation code that no longer matches current source (Etherscan would reject it). Default true. - required: false - type: boolean - default: true secrets: PUBLISH_PRIVATE_KEY: # A deploy key whose push events (unlike GITHUB_TOKEN pushes) trigger the # downstream git-clean / rainix-sol workflows on the commit-back to main. required: false - PRIVATE_KEY: - # The on-chain DEPLOYMENT key (distinct from PUBLISH_PRIVATE_KEY, the git - # deploy key). Broadcasts the suites. - required: false - EXPLORER_VERIFICATION_KEY: - required: false - CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY: - required: false - CI_DEPLOY_BASE_ETHERSCAN_API_KEY: - required: false - CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY: - required: false - CI_DEPLOY_ETHEREUM_ETHERSCAN_API_KEY: - required: false - CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY: - required: false - CI_DEPLOY_FLARE_ETHERSCAN_API_KEY: - required: false CI_GIT_EMAIL: required: false CI_GIT_USER: @@ -138,9 +103,8 @@ jobs: # The release tag must point at a commit already merged to the release branch. # `on: push: tags` fires for ANY tag, including one cut from an unmerged branch; # without this the later rebase would replay that branch's unreviewed commits - # onto main and push them, and the deploy would broadcast unreviewed bytecode. - # Gate both `deploy` and `release` on it. All refs come from built-in env vars, - # never interpolated into the shell, to avoid template injection. + # onto main and push them. Gate `release` on it. All refs come from built-in env + # vars, never interpolated into the shell, to avoid template injection. guard: runs-on: ubuntu-latest steps: @@ -164,40 +128,8 @@ jobs: echo "::error::tag $GITHUB_REF_NAME ($GITHUB_SHA) is not on origin/$MAIN — refusing to release an unmerged commit" >&2 exit 1 fi - # Broadcast every suite in dependency order, one forge run each (Zoltu nonce - # isolation), by fanning the existing deploy reusable over `deploy-suites` at - # max-parallel 1. Runs before `release`, so publish/snapshot only happen once - # the chain carries the code the pins name. `verify:false`-capable via the - # `deploy-verify` input for pinned historical bytecode. - deploy: - needs: guard - strategy: - max-parallel: 1 - matrix: - suite: ${{ fromJSON(inputs.deploy-suites) }} - uses: rainlanguage/rainix/.github/workflows/rainix-manual-sol-artifacts.yaml@main - with: - suite: ${{ matrix.suite }} - script: ${{ inputs.deploy-script }} - verify: ${{ inputs.deploy-verify }} - secrets: - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} - EXPLORER_VERIFICATION_KEY: ${{ secrets.EXPLORER_VERIFICATION_KEY }} - RPC_URL_ARBITRUM_FORK: ${{ secrets.RPC_URL_ARBITRUM_FORK }} - RPC_URL_BASE_FORK: ${{ secrets.RPC_URL_BASE_FORK }} - RPC_URL_BASE_SEPOLIA_FORK: ${{ secrets.RPC_URL_BASE_SEPOLIA_FORK }} - RPC_URL_ETHEREUM_FORK: ${{ secrets.RPC_URL_ETHEREUM_FORK }} - RPC_URL_FLARE_FORK: ${{ secrets.RPC_URL_FLARE_FORK }} - RPC_URL_POLYGON_FORK: ${{ secrets.RPC_URL_POLYGON_FORK }} - CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_ARBITRUM_ETHERSCAN_API_KEY }} - CI_DEPLOY_BASE_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_BASE_ETHERSCAN_API_KEY }} - CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_BASE_SEPOLIA_ETHERSCAN_API_KEY }} - CI_DEPLOY_ETHEREUM_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_ETHEREUM_ETHERSCAN_API_KEY }} - CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_POLYGON_ETHERSCAN_API_KEY }} - CI_DEPLOY_FLARE_ETHERSCAN_API_KEY: ${{ secrets.CI_DEPLOY_FLARE_ETHERSCAN_API_KEY }} release: - needs: deploy + needs: guard runs-on: ubuntu-latest # contents: write for the commit-back to main + the gh-release composite. No # id-token: nothing here uses OIDC (Soldeer uses SOLDEER_API_TOKEN, the release @@ -290,9 +222,11 @@ jobs: # an already-released tag must never change (consumers pin its constants). uses: rainlanguage/rainix/.github/actions/frozen-snapshots-append-only@main - name: Verify live chain matches the fresh pins - # The `deploy` job (needs:) has broadcast every suite; this gate confirms - # it actually landed, so a snapshot of addresses the - # chain does not carry never gets published. + # The manual deploy was run before tagging; this is the attestation that it + # landed — the fork suite checks prod exists at the freshly regenerated + # pins, so a snapshot of addresses the chain does not carry never gets + # published. A transient fork/RPC failure here just fails the release + # (retry the tag); it publishes nothing and is not a broadcast. env: RPC_URL_ARBITRUM_FORK: ${{ secrets.RPC_URL_ARBITRUM_FORK }} RPC_URL_BASE_FORK: ${{ secrets.RPC_URL_BASE_FORK }}