diff --git a/.github/workflows/desktop-bundled-release.yml b/.github/workflows/desktop-bundled-release.yml index 646daf39e8..4d380718b0 100644 --- a/.github/workflows/desktop-bundled-release.yml +++ b/.github/workflows/desktop-bundled-release.yml @@ -162,7 +162,7 @@ env: jobs: validate: - name: Allocate disposable channel and validate the build source + name: Pre-build setup # One dispatch: in disposable mode the allocation steps run first (the # channel pin comes from them), then admission validates the build source. # A failed allocation fails this job and skips everything downstream. @@ -201,6 +201,18 @@ jobs: fetch-depth: 0 fetch-tags: true + - name: Archive every pinned input + # Runner Python is intentional: the toolchain's upstream may be gone. + # Same checkout contract the standalone archive job had: the tag ref + # for tag builds, the dispatch head for commit builds. + if: inputs.disposable_run == '' && inputs.disposable_channel == '' && (inputs.release-phase == '' || inputs.release-phase == 'candidate') + env: + CLOUDFLARE_R2_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_R2_ACCOUNT_ID }} + CLOUDFLARE_R2_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} + CLOUDFLARE_R2_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} + CLOUDFLARE_R2_BUCKET: ${{ vars.CLOUDFLARE_R2_BUCKET }} + run: python3 -m scripts.ci.archive_inputs + - name: Set up native identity reader if: inputs.disposable_channel != '' && inputs.disposable_receivers uses: ./.github/actions/setup-pm @@ -329,21 +341,15 @@ jobs: # ── Windows builders (REAL) ─────────────────────────────────────────────── # Windows assembly and publication depend on these native Windows legs. - archive-inputs: - name: Archive every pinned input - if: inputs.disposable_run == '' && inputs.disposable_channel == '' && (inputs.release-phase == '' || inputs.release-phase == 'candidate') - needs: [validate] - uses: ./.github/workflows/archive-inputs.yml - with: - sha: ${{ needs.validate.outputs.sha }} - secrets: inherit - + # Pinned-input archiving runs as a validate step with the job's own gate + # shape, so a skipped archive must not skip the selected build legs. + # # Cache permissions are literal job-token scopes, not checkout/save-step policy. # Share execution via anchors, and keep the original IDs as downstream gates. build-win32-release: name: bundled ${{ matrix.target.label }} if: inputs.termux_only != true && (inputs.release-phase == '' || inputs.release-phase == 'candidate') && inputs.build_commit == '' && inputs.channel == '' - needs: [validate, archive-inputs] + needs: [validate] runs-on: ${{ matrix.target.runner }} environment: release-signing cache-mode: write @@ -606,10 +612,9 @@ jobs: name: bundled ${{ matrix.target.label }} (commit) if: >- !cancelled() && needs.validate.result == 'success' - && (needs.archive-inputs.result == 'success' || ((inputs.disposable_run != '' || inputs.disposable_channel != '') && needs.archive-inputs.result == 'skipped')) && inputs.termux_only != true && (inputs.release-phase == '' || inputs.release-phase == 'candidate') && (inputs.build_commit != '' || inputs.channel != '') - needs: [validate, archive-inputs] + needs: [validate] runs-on: ${{ matrix.target.runner }} environment: release-signing cache-mode: read @@ -625,8 +630,7 @@ jobs: if: >- always() && inputs.termux_only != true && (inputs.release-phase == '' || inputs.release-phase == 'candidate') && needs.validate.result == 'success' - && (needs.archive-inputs.result == 'success' || ((inputs.disposable_run != '' || inputs.disposable_channel != '') && needs.archive-inputs.result == 'skipped')) - needs: [validate, archive-inputs, build-win32-release, build-win32-commit] + needs: [validate, build-win32] runs-on: ubuntu-24.04 permissions: {} timeout-minutes: 5 @@ -657,7 +661,7 @@ jobs: build-darwin-release: name: bundled ${{ matrix.target.label }} if: inputs.termux_only != true && (inputs.release-phase == '' || inputs.release-phase == 'candidate') && inputs.build_commit == '' && inputs.channel == '' - needs: [validate, archive-inputs] + needs: [validate] runs-on: ${{ matrix.target.runner }} environment: release-signing cache-mode: write @@ -935,10 +939,9 @@ jobs: name: bundled ${{ matrix.target.label }} (commit) if: >- !cancelled() && needs.validate.result == 'success' - && (needs.archive-inputs.result == 'success' || ((inputs.disposable_run != '' || inputs.disposable_channel != '') && needs.archive-inputs.result == 'skipped')) && inputs.termux_only != true && (inputs.release-phase == '' || inputs.release-phase == 'candidate') && (inputs.build_commit != '' || inputs.channel != '') - needs: [validate, archive-inputs] + needs: [validate] runs-on: ${{ matrix.target.runner }} environment: release-signing cache-mode: read @@ -954,8 +957,7 @@ jobs: if: >- always() && inputs.termux_only != true && (inputs.release-phase == '' || inputs.release-phase == 'candidate') && needs.validate.result == 'success' - && (needs.archive-inputs.result == 'success' || ((inputs.disposable_run != '' || inputs.disposable_channel != '') && needs.archive-inputs.result == 'skipped')) - needs: [validate, archive-inputs, build-darwin-release, build-darwin-commit] + needs: [validate, build-darwin] runs-on: ubuntu-24.04 permissions: {} timeout-minutes: 5 @@ -1243,7 +1245,8 @@ jobs: publish-channel: name: Publish the complete native-smoked channel build - needs: [validate, build-darwin, build-win32, assemble-win32-bundle, smoke-darwin, smoke-win32, smoke-win32-universal] + needs: + [validate, build-darwin, build-win32, assemble-win32-bundle, smoke-darwin, smoke-win32, smoke-win32-universal] if: >- !cancelled() && needs.validate.outputs.channel-build != '' && needs.validate.result == 'success' && needs.build-darwin.result == 'success' && needs.build-win32.result == 'success' @@ -1431,7 +1434,7 @@ jobs: termux-deb: name: Build + publish the termux .deb (aarch64) - needs: [validate, archive-inputs] + needs: [validate] if: inputs.channel == '' && inputs.disposable_channel == '' && (inputs.upload_release == true || inputs.release-phase == 'candidate' || inputs.build_commit != '') runs-on: ubuntu-24.04-arm environment: release-signing @@ -1801,7 +1804,20 @@ jobs: builds-table: name: Render the release builds table - needs: [validate, build-win32, build-darwin, build-linux, assemble-win32-bundle, smoke-darwin, smoke-win32, smoke-win32-universal, publish-win32-updater, publish-darwin-updater, termux-deb] + needs: + [ + validate, + build-win32, + build-darwin, + build-linux, + assemble-win32-bundle, + smoke-darwin, + smoke-win32, + smoke-win32-universal, + publish-win32-updater, + publish-darwin-updater, + termux-deb + ] # Failed builds still get a per-tag diagnostic page. The renderer only # advances the channel landing page when every prerequisite succeeded. if: | @@ -1845,7 +1861,18 @@ jobs: commit-builds-summary: name: Upload status page to R2 - needs: [validate, build-win32, build-darwin, build-linux, assemble-win32-bundle, smoke-darwin, smoke-win32, smoke-win32-universal, termux-deb] + needs: + [ + validate, + build-win32, + build-darwin, + build-linux, + assemble-win32-bundle, + smoke-darwin, + smoke-win32, + smoke-win32-universal, + termux-deb + ] if: | always() && inputs.build_commit != '' && inputs.disposable_channel == '' && needs.validate.result == 'success' && needs.validate.outputs.sha != '' @@ -1888,7 +1915,17 @@ jobs: candidate-manifest: name: Stage verified stable candidate artifacts - needs: [validate, build-win32, build-darwin, assemble-win32-bundle, smoke-darwin, smoke-win32, smoke-win32-universal, termux-deb] + needs: + [ + validate, + build-win32, + build-darwin, + assemble-win32-bundle, + smoke-darwin, + smoke-win32, + smoke-win32-universal, + termux-deb + ] if: >- !cancelled() && inputs.release-phase == 'candidate' && needs.validate.result == 'success' && needs.validate.outputs.sha != '' @@ -2062,7 +2099,20 @@ jobs: publish-canary: name: Publish the canary release - needs: [validate, build-win32, build-darwin, build-linux, builds-table, assemble-win32-bundle, smoke-darwin, smoke-win32, smoke-win32-universal, publish-win32-updater, publish-darwin-updater] + needs: + [ + validate, + build-win32, + build-darwin, + build-linux, + builds-table, + assemble-win32-bundle, + smoke-darwin, + smoke-win32, + smoke-win32-universal, + publish-win32-updater, + publish-darwin-updater + ] if: | always() && inputs.build_commit == '' && inputs.channel == '' diff --git a/tests/ci/test_commit_build_staging.py b/tests/ci/test_commit_build_staging.py index 3009dfb2cb..2cf21dad06 100644 --- a/tests/ci/test_commit_build_staging.py +++ b/tests/ci/test_commit_build_staging.py @@ -110,9 +110,11 @@ def test_failed_commit_summary_publishes_downloads_or_run_links(tmp_path, r2_ser assert 'Disabled' in line and '](' not in line assert all(key.startswith(f'releases/commit/{sha}/') for key in r2_server.store) for name in ('build-win32', 'build-darwin'): - execution = f'{name}-commit' - assert execution in jobs[name]['needs'] - assert jobs[execution]['strategy']['fail-fast'] is False + # The commit/release split collapsed into one leg per platform; the + # result job's env still encodes exactly-which-trust-branch-succeeded. + result_job = jobs[name] + assert f"needs.{name}-commit.result" in result_job['env']['SELECTED_BUILD_SUCCEEDED'] + assert jobs[f'{name}-commit']['strategy']['fail-fast'] is False step = next(step for step in jobs['commit-builds-summary']['steps'] if 'run' in step) assert step['env']['RUN_URL'] == '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}' assert step['env']['HERMES_BUNDLE_ENV_JSON'] == '${{ inputs.bundle_env }}' diff --git a/tests/ci/test_desktop_bundle_smoke.py b/tests/ci/test_desktop_bundle_smoke.py index e597a5c6ad..92449bb281 100644 --- a/tests/ci/test_desktop_bundle_smoke.py +++ b/tests/ci/test_desktop_bundle_smoke.py @@ -103,8 +103,8 @@ def test_native_consumers_and_publication_fail_closed_across_trust_skips(tmp_pat for commit, release, commit_result in itertools.product( ('', SHA), outcomes, outcomes): needs = admitted(job['needs']) - needs[f'build-{platform}-release']['result'] = release - needs[f'build-{platform}-commit']['result'] = commit_result + needs[f'build-{platform}-release'] = {'result': release} + needs[f'build-{platform}-commit'] = {'result': commit_result} selected = gate(job['env']['SELECTED_BUILD_SUCCEEDED'], {'build_commit': commit}, needs, job_if=False) expected = (commit == '' and release == 'success' and commit_result == 'skipped') or ( commit != '' and commit_result == 'success' and release == 'skipped') diff --git a/tests/ci/test_termux_input_archive.py b/tests/ci/test_termux_input_archive.py index 5c4ba1eae0..034e86d3e5 100644 --- a/tests/ci/test_termux_input_archive.py +++ b/tests/ci/test_termux_input_archive.py @@ -44,11 +44,20 @@ def test_archive_gate_uses_bootstrap_python_and_trusted_exact_revision(): checkout = job["steps"][0] assert checkout["with"]["ref"] == "${{ inputs.sha || github.sha }}" release = load("desktop-bundled-release.yml")["jobs"] - caller = release["archive-inputs"] - assert caller["needs"] == ["validate"] - assert caller["with"]["sha"] == "${{ needs.validate.outputs.sha }}" + # The archive lives as a validate step with the job's own gate shape — + # a skipped archive (disposable/termux runs) must not skip the builds. + validate = release["validate"] + (archive,) = [s for s in validate["steps"] if s.get("run") == "python3 -m scripts.ci.archive_inputs"] + assert archive["if"] == ( + "inputs.disposable_run == '' && inputs.disposable_channel == ''" + " && (inputs.release-phase == '' || inputs.release-phase == 'candidate')" + ) + assert R2_ENV <= archive["env"].keys() + assert validate["environment"] == "release-signing" + checkout = validate["steps"][0] + assert "actions/checkout" in checkout["uses"] for name in ("build-win32", "build-darwin", "termux-deb"): - assert "archive-inputs" in release[name]["needs"] + assert "archive-inputs" not in release[name].get("needs", []) action = YAML(typ="base").load((ROOT / ".github/actions/setup-pm/action.yml").read_text(encoding="utf-8")) assert action["inputs"]["archive-inputs"]["default"] == "false"