refactor(ci): run pinned-input archiving as a validate step

The archive-inputs reusable-workflow job existed only to serialize one
command behind validate. Run scripts.ci.archive_inputs directly inside
validate with the job's own gate shape (skipped for disposable and
termux-only runs), so the archiving never gates or skips the native
build legs and the whole dispatch loses one job-level hop.
This commit is contained in:
ethernet
2026-09-16 11:48:34 -04:00
parent 4fbec9c442
commit b1aa63b918
4 changed files with 96 additions and 35 deletions

View File

@@ -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 == ''

View File

@@ -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 }}'

View File

@@ -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')

View File

@@ -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"