From 25c574dd4605091054c484728343d20688a53c16 Mon Sep 17 00:00:00 2001 From: JohnBraham Date: Fri, 30 Oct 2026 14:31:15 +0000 Subject: [PATCH] Skip artifact audits when the producer was scoped out --- .github/scripts/test_release_workflow.py | 22 ++++++++++++ .github/workflows/artifact-audit-1.12.yml | 41 +++++++++++++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/.github/scripts/test_release_workflow.py b/.github/scripts/test_release_workflow.py index e5faaa2..24a472c 100644 --- a/.github/scripts/test_release_workflow.py +++ b/.github/scripts/test_release_workflow.py @@ -291,6 +291,28 @@ def test_existing_release_is_not_uploaded_again(self): with self.assertRaises(SystemExit): self.validate('1.10.2', release_exists=True) + def test_112_artifact_audit_skips_only_explicitly_scoped_out_builds(self): + text=WORKFLOW.with_name('artifact-audit-1.12.yml').read_text() + code=text.split('def artifact_required(jobs):',1)[1].split('\n # Fetch',1)[0] + code='def artifact_required(jobs):'+code + import textwrap + code=code.replace('\n ','\n') + namespace={};exec(compile(code,'artifact-scope','exec'),namespace) + check=namespace['artifact_required'] + def job(name,conclusion,status='completed'):return dict(name=name,conclusion=conclusion,status=status) + build=job('Build, test, and audit','success') + self.assertTrue(check([build])) + skipped=[job('Check runtime inputs','success'),job('Build, test, and audit','skipped'),job('Cold Forge bootstrap','skipped')] + self.assertFalse(check(skipped)) + for invalid in [[],[build,build],skipped[1:],skipped[:2], + [dict(build,status='in_progress')], + [dict(build,conclusion='failure')], + [dict(skipped[0],conclusion='failure'),*skipped[1:]], + [*skipped[:2],dict(skipped[2],conclusion='failure')]]: + with self.subTest(invalid=invalid),self.assertRaises(ValueError):check(invalid) + self.assertIn("needs: scope",text) + self.assertIn("if: needs.scope.outputs.required == 'true'",text) + def test_tag_at_another_commit_is_rejected(self): with self.assertRaises(SystemExit): self.validate('1.10.2', wrong_tag=True) diff --git a/.github/workflows/artifact-audit-1.12.yml b/.github/workflows/artifact-audit-1.12.yml index 3f812f9..3654925 100644 --- a/.github/workflows/artifact-audit-1.12.yml +++ b/.github/workflows/artifact-audit-1.12.yml @@ -11,13 +11,48 @@ permissions: actions: read jobs: - audit: - # Pull-request merge trees are audited inside CI. Push artifacts use the - # workflow run's exact head SHA, which is also their declared artifact name. + scope: + name: Check artifact producer scope if: >- github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && (github.event.workflow_run.head_branch == 'master-1.12.2' || github.event.workflow_run.head_branch == 'master-1.12') runs-on: ubuntu-latest + outputs: + required: ${{ steps.scope.outputs.required }} + steps: + - id: scope + env: + GH_TOKEN: ${{ github.token }} + PRODUCER_URL: ${{ github.event.workflow_run.jobs_url }} + REPOSITORY: ${{ github.repository }} + run: | + python3 - <<'PY' + import json,os,urllib.request + def artifact_required(jobs): + def exactly(name): + found=[j for j in jobs if j['name']==name] + if len(found)!=1 or found[0]['status']!='completed':raise ValueError('Missing, duplicate or pending producer job: '+name) + return found[0]['conclusion'] + build=exactly('Build, test, and audit') + if build=='success':return True + if build=='skipped' and exactly('Check runtime inputs')=='success' and exactly('Cold Forge bootstrap')=='skipped':return False + raise ValueError('Artifact producer did not succeed') + # Fetch the exact producer jobs; absent artifacts never imply a skip. + url=os.environ['PRODUCER_URL']+'?per_page=100' + if not url.startswith('https://api.github.com/repos/'+os.environ['REPOSITORY']+'/actions/runs/'):raise ValueError('Unexpected producer API URL') + request=urllib.request.Request(url,headers={'Authorization':'Bearer '+os.environ['GH_TOKEN'],'Accept':'application/vnd.github+json'}) + with urllib.request.urlopen(request,timeout=30) as response:result=json.load(response) + if result['total_count']!=len(result['jobs']):raise ValueError('Incomplete producer job list') + required=artifact_required(result['jobs']) + with open(os.environ['GITHUB_OUTPUT'],'a') as output:output.write('required='+str(required).lower()+'\n') + print('Artifact audit required:',required) + PY + audit: + # Successful builds always require their exact artifact. A dispatcher-only + # change has an explicitly skipped producer and has no release bytes to audit. + needs: scope + if: needs.scope.outputs.required == 'true' + runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7