From 93d99fa89651a94618118b04a1e4efedbeeafe16 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 05:43:04 -0400 Subject: [PATCH 01/10] ci(recovery): enqueue legacy attested pull requests Signed-off-by: Stephen Lutar --- .../workflows/enqueue-legacy-green-prs.yml | 239 ++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 .github/workflows/enqueue-legacy-green-prs.yml diff --git a/.github/workflows/enqueue-legacy-green-prs.yml b/.github/workflows/enqueue-legacy-green-prs.yml new file mode 100644 index 0000000..697a486 --- /dev/null +++ b/.github/workflows/enqueue-legacy-green-prs.yml @@ -0,0 +1,239 @@ +name: Enqueue Legacy Green PRs — Exact Handoff + +on: + pull_request: + branches: [main] + paths: + - .github/workflows/enqueue-legacy-green-prs.yml + +permissions: + actions: read + checks: read + contents: read + pull-requests: read + statuses: read + +concurrency: + group: enqueue-legacy-green-prs + cancel-in-progress: false + +env: + TARGET_REPOSITORY: szl-holdings/.github + REPORT_DIR: reports/legacy-enqueue + +jobs: + enqueue: + name: Verify signed histories and enqueue exact approved heads + if: >- + github.repository == 'szl-holdings/.github' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'diag/enqueue-legacy-green-prs-v1' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout recovery controller + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 1 + + - name: Mint qillqaq read identity + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.QILLQAQ_CLIENT_ID }} + private-key: ${{ secrets.QILLQAQ_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Verify histories, exact approvals, statuses, and checks + id: verify + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + shell: python + run: | + from __future__ import annotations + + import json + import os + import subprocess + from pathlib import Path + from typing import Any + + targets = { + 341: '81416537a6a6bce01c7482084c1ed13c8a0da4bb', + 342: '942c4318c8717bb44708dd6eab9fae1030746811', + } + repository = os.environ['TARGET_REPOSITORY'] + required_checks = { + 'gate/ground-truth', + 'gate/labels', + 'gate/schema', + 'gate/adversarial', + 'gate/verify-all', + 'gate/provenance', + 'gate/a11y-perf', + 'gate/lean', + 'deploy/staging', + } + + def api(path: str) -> Any: + process = subprocess.run( + ['gh', 'api', '--method', 'GET', path], + check=False, + capture_output=True, + text=True, + env=os.environ, + ) + if process.returncode: + raise RuntimeError(f'{path}: {process.stderr.strip()[:500]}') + return json.loads(process.stdout) + + report: dict[str, Any] = { + 'schema': 'szl.legacy-green-enqueue-preflight/v1', + 'repository': repository, + 'targets': {}, + 'all_ready': True, + 'secret_value_recorded': False, + 'mutation_performed': False, + } + for number, expected_head in targets.items(): + pr = api(f'repos/{repository}/pulls/{number}') + commits = api(f'repos/{repository}/pulls/{number}/commits?per_page=100') + reviews = api(f'repos/{repository}/pulls/{number}/reviews?per_page=100') + statuses = api(f'repos/{repository}/commits/{expected_head}/status') + checks = api(f'repos/{repository}/commits/{expected_head}/check-runs?filter=latest&per_page=100') + + unsigned = [ + { + 'sha': item.get('sha'), + 'reason': ((item.get('commit') or {}).get('verification') or {}).get('reason'), + } + for item in commits + if ((item.get('commit') or {}).get('verification') or {}).get('verified') is not True + ] + approval_count = sum( + 1 + for item in reviews + if (item.get('user') or {}).get('login') in { + 'qillqaq-attestor[bot]', 'qillqaq-attestor' + } + and item.get('state') == 'APPROVED' + and item.get('commit_id') == expected_head + ) + status_count = sum( + 1 + for item in statuses.get('statuses', []) + if item.get('context') == 'attestation/qillqaq' + and item.get('state') == 'success' + ) + successful_checks = { + item.get('name') + for item in checks.get('check_runs', []) + if item.get('conclusion') == 'success' + } + missing_checks = sorted(required_checks - successful_checks) + ready = ( + pr.get('state') == 'open' + and pr.get('draft') is False + and (pr.get('head') or {}).get('sha') == expected_head + and (pr.get('base') or {}).get('ref') == 'main' + and not unsigned + and approval_count >= 1 + and status_count >= 1 + and not missing_checks + ) + report['targets'][str(number)] = { + 'expected_head': expected_head, + 'observed_head': (pr.get('head') or {}).get('sha'), + 'open': pr.get('state') == 'open', + 'draft': pr.get('draft'), + 'commit_count': len(commits), + 'unsigned_commits': unsigned, + 'exact_qillqaq_approvals': approval_count, + 'attestation_statuses': status_count, + 'missing_required_checks': missing_checks, + 'ready': ready, + } + report['all_ready'] = report['all_ready'] and ready + + path = Path(os.environ['REPORT_DIR']) / 'preflight.json' + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(report, indent=2, sort_keys=True) + '\n', encoding='utf-8') + print(json.dumps(report, indent=2, sort_keys=True)) + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output: + output.write(f"all_ready={str(report['all_ready']).lower()}\n") + + - name: Enqueue each exact head through GitHub's native queue + if: steps.verify.outputs.all_ready == 'true' + env: + GH_TOKEN: ${{ secrets.SZL_GITHUB_TOKEN }} + run: | + set -euo pipefail + test -n "${GH_TOKEN:-}" || { echo '::error::governed queue token unavailable'; exit 1; } + mkdir -p "$REPORT_DIR" + jq -n '{schema:"szl.legacy-green-enqueue/v1", targets:{}, direct_merge_attempted:false, bypass_used:false, secret_value_recorded:false}' \ + > "$REPORT_DIR/enqueue-receipt.json" + while read -r number head; do + gh api graphql \ + -f query='query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){id headRefOid state isDraft mergeQueueEntry{id}}}}' \ + -F owner=szl-holdings \ + -F name=.github \ + -F number="$number" \ + > "$REPORT_DIR/pr-$number-before.json" + test "$(jq -r .data.repository.pullRequest.headRefOid "$REPORT_DIR/pr-$number-before.json")" = "$head" + test "$(jq -r .data.repository.pullRequest.state "$REPORT_DIR/pr-$number-before.json")" = 'OPEN' + test "$(jq -r .data.repository.pullRequest.isDraft "$REPORT_DIR/pr-$number-before.json")" = 'false' + pr_id="$(jq -r .data.repository.pullRequest.id "$REPORT_DIR/pr-$number-before.json")" + entry="$(jq -r '.data.repository.pullRequest.mergeQueueEntry.id // empty' "$REPORT_DIR/pr-$number-before.json")" + if [ -z "$entry" ]; then + set +e + gh api graphql \ + -f query='mutation($id:ID!,$head:GitObjectID!){enqueuePullRequest(input:{pullRequestId:$id,expectedHeadOid:$head}){mergeQueueEntry{id}}}' \ + -F id="$pr_id" \ + -F head="$head" \ + > "$REPORT_DIR/pr-$number-enqueue.json" 2> "$REPORT_DIR/pr-$number-enqueue-error.txt" + code=$? + set -e + if [ "$code" -ne 0 ]; then + cat "$REPORT_DIR/pr-$number-enqueue-error.txt" + exit "$code" + fi + fi + gh api graphql \ + -f query='query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){headRefOid mergeQueueEntry{id}}}}' \ + -F owner=szl-holdings \ + -F name=.github \ + -F number="$number" \ + > "$REPORT_DIR/pr-$number-after.json" + entry="$(jq -r '.data.repository.pullRequest.mergeQueueEntry.id // empty' "$REPORT_DIR/pr-$number-after.json")" + test -n "$entry" + jq --arg number "$number" --arg head "$head" --arg entry "$entry" \ + '.targets[$number] = {head_sha:$head, queue_entry_id:$entry, native_enqueue_verified:true}' \ + "$REPORT_DIR/enqueue-receipt.json" > "$REPORT_DIR/enqueue-receipt.tmp" + mv "$REPORT_DIR/enqueue-receipt.tmp" "$REPORT_DIR/enqueue-receipt.json" + done <<'TARGETS' + 341 81416537a6a6bce01c7482084c1ed13c8a0da4bb + 342 942c4318c8717bb44708dd6eab9fae1030746811 + TARGETS + cat "$REPORT_DIR/enqueue-receipt.json" + + - name: Fail closed when a target is not queue-eligible + if: steps.verify.outputs.all_ready != 'true' + run: | + echo '::error::At least one exact legacy PR is not fully signed, attested, and green.' + exit 1 + + - name: Upload immutable handoff evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: legacy-green-enqueue-${{ github.run_id }} + path: ${{ env.REPORT_DIR }} + if-no-files-found: error + retention-days: 90 From ee63199f1e7114b078282b1df240e96adb718264 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 05:47:33 -0400 Subject: [PATCH 02/10] ci(recovery): materialize signed legacy replacements Signed-off-by: Stephen Lutar --- ...materialize-signed-legacy-replacements.yml | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 .github/workflows/materialize-signed-legacy-replacements.yml diff --git a/.github/workflows/materialize-signed-legacy-replacements.yml b/.github/workflows/materialize-signed-legacy-replacements.yml new file mode 100644 index 0000000..a52dadd --- /dev/null +++ b/.github/workflows/materialize-signed-legacy-replacements.yml @@ -0,0 +1,229 @@ +name: Materialize Signed Legacy Replacements + +on: + pull_request: + branches: [main] + paths: + - .github/workflows/materialize-signed-legacy-replacements.yml + +permissions: + contents: write + +concurrency: + group: materialize-signed-legacy-replacements + cancel-in-progress: false + +jobs: + materialize: + name: Publish two signed exact-tree replacement commits + if: >- + github.repository == 'szl-holdings/.github' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'diag/enqueue-legacy-green-prs-v1' + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + EXPECTED_MAIN: 5aa61bb92ba4a1cd1b59c09c4f82b902d1bf7917 + steps: + - name: Checkout controller + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 1 + + - name: Verify clean target branches + run: | + set -euo pipefail + current_main="$(gh api "repos/$REPOSITORY/branches/main" --jq .commit.sha)" + test "$current_main" = "$EXPECTED_MAIN" \ + || { echo "::error::protected main moved from expected bootstrap commit"; exit 1; } + for branch in \ + chore/retire-completed-sda-reconcile-v2-signed \ + fix/governed-secret-health-v3-signed; do + test "$(gh api "repos/$REPOSITORY/branches/$branch" --jq .commit.sha)" = "$EXPECTED_MAIN" \ + || { echo "::error::$branch is not cleanly rooted at expected main"; exit 1; } + done + + - name: Create signed exact-tree replacement commits + id: commits + shell: python + run: | + from __future__ import annotations + + import base64 + import json + import os + import urllib.error + import urllib.parse + import urllib.request + from typing import Any + + repository = os.environ['REPOSITORY'] + token = os.environ['GH_TOKEN'] + expected_main = os.environ['EXPECTED_MAIN'] + + replacements = [ + { + 'source_ref': '81416537a6a6bce01c7482084c1ed13c8a0da4bb', + 'target_branch': 'chore/retire-completed-sda-reconcile-v2-signed', + 'headline': 'chore(sda): retire completed fixed-SHA reconciliation', + 'additions': [ + '.github/scripts/test_sda_reconcile_retired.py', + '.github/workflows/sda-reconcile-retirement.yml', + ], + 'deletions': [ + '.github/data/sda_shared_source_reconcile.json', + '.github/scripts/sda_shared_source_reconcile.py', + '.github/workflows/sda-shared-source-reconcile.yml', + ], + }, + { + 'source_ref': '942c4318c8717bb44708dd6eab9fae1030746811', + 'target_branch': 'fix/governed-secret-health-v3-signed', + 'headline': 'fix(secrets): centralize governed name audit', + 'additions': [ + '.github/data/required_actions_secrets.json', + '.github/scripts/secret_health.py', + '.github/scripts/test_secret_health.py', + '.github/workflows/secret-health.yml', + ], + 'deletions': [], + }, + ] + + def request(url: str, *, data: dict[str, Any] | None = None) -> Any: + req = urllib.request.Request( + url, + data=(json.dumps(data).encode('utf-8') if data is not None else None), + method=('POST' if data is not None else 'GET'), + headers={ + 'Authorization': f'Bearer {token}', + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'szl-signed-legacy-replacement/1', + }, + ) + try: + with urllib.request.urlopen(req, timeout=90) as response: + return json.loads(response.read()) + except urllib.error.HTTPError as exc: + body = exc.read().decode('utf-8', 'replace')[:2000] + raise SystemExit(f'HTTP {exc.code}: {body}') from exc + + def source_bytes(path: str, ref: str) -> bytes: + quoted = urllib.parse.quote(path, safe='/') + payload = request( + f'https://api.github.com/repos/{repository}/contents/{quoted}?ref={ref}' + ) + if payload.get('type') != 'file' or payload.get('encoding') != 'base64': + raise SystemExit(f'unsupported source payload for {path}@{ref}') + return base64.b64decode(payload['content']) + + mutation = ''' + mutation($input: CreateCommitOnBranchInput!) { + createCommitOnBranch(input: $input) { + commit { oid url } + } + } + ''' + results = [] + for item in replacements: + additions = [ + { + 'path': path, + 'contents': base64.b64encode( + source_bytes(path, item['source_ref']) + ).decode('ascii'), + } + for path in item['additions'] + ] + variables = { + 'input': { + 'branch': { + 'repositoryNameWithOwner': repository, + 'branchName': item['target_branch'], + }, + 'message': { + 'headline': item['headline'], + 'body': 'Signed-off-by: Stephen Lutar ', + }, + 'expectedHeadOid': expected_main, + 'fileChanges': { + 'additions': additions, + 'deletions': [{'path': path} for path in item['deletions']], + }, + } + } + payload = request( + 'https://api.github.com/graphql', + data={'query': mutation, 'variables': variables}, + ) + if payload.get('errors'): + raise SystemExit(json.dumps(payload['errors'], indent=2)) + commit = payload['data']['createCommitOnBranch']['commit'] + results.append({ + 'target_branch': item['target_branch'], + 'source_ref': item['source_ref'], + 'commit': commit, + 'additions': item['additions'], + 'deletions': item['deletions'], + }) + + with open('signed-legacy-replacements.json', 'w', encoding='utf-8') as out: + json.dump(results, out, indent=2, sort_keys=True) + out.write('\n') + print(json.dumps(results, indent=2, sort_keys=True)) + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output: + output.write(f"sda_sha={results[0]['commit']['oid']}\n") + output.write(f"secret_sha={results[1]['commit']['oid']}\n") + + - name: Verify signatures and exact affected-path parity + env: + SDA_SHA: ${{ steps.commits.outputs.sda_sha }} + SECRET_SHA: ${{ steps.commits.outputs.secret_sha }} + run: | + set -euo pipefail + for sha in "$SDA_SHA" "$SECRET_SHA"; do + test "$(gh api "repos/$REPOSITORY/commits/$sha" --jq .commit.verification.verified)" = 'true' + test "$(gh api "repos/$REPOSITORY/commits/$sha" --jq .commit.verification.reason)" = 'valid' + done + + compare_file() { + source_ref="$1" + target_ref="$2" + path="$3" + source_sha="$(gh api "repos/$REPOSITORY/contents/$path?ref=$source_ref" --jq .sha)" + target_sha="$(gh api "repos/$REPOSITORY/contents/$path?ref=$target_ref" --jq .sha)" + test "$source_sha" = "$target_sha" + } + + compare_file 81416537a6a6bce01c7482084c1ed13c8a0da4bb "$SDA_SHA" .github/scripts/test_sda_reconcile_retired.py + compare_file 81416537a6a6bce01c7482084c1ed13c8a0da4bb "$SDA_SHA" .github/workflows/sda-reconcile-retirement.yml + for path in \ + .github/data/sda_shared_source_reconcile.json \ + .github/scripts/sda_shared_source_reconcile.py \ + .github/workflows/sda-shared-source-reconcile.yml; do + if gh api "repos/$REPOSITORY/contents/$path?ref=$SDA_SHA" >/dev/null 2>&1; then + echo "::error::retired path remains in signed replacement: $path" + exit 1 + fi + done + + for path in \ + .github/data/required_actions_secrets.json \ + .github/scripts/secret_health.py \ + .github/scripts/test_secret_health.py \ + .github/workflows/secret-health.yml; do + compare_file 942c4318c8717bb44708dd6eab9fae1030746811 "$SECRET_SHA" "$path" + done + + - name: Upload immutable replacement receipt + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signed-legacy-replacements-${{ github.run_id }} + path: signed-legacy-replacements.json + if-no-files-found: error + retention-days: 90 From 78eef9ffb0913e61b8c07045e4f0f5cd58080fee Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 05:58:28 -0400 Subject: [PATCH 03/10] ci(attestor): materialize protected delivery recovery Signed-off-by: Stephen Lutar --- .../materialize-attestor-delivery-repair.yml | 615 ++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100644 .github/workflows/materialize-attestor-delivery-repair.yml diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml new file mode 100644 index 0000000..6318e98 --- /dev/null +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -0,0 +1,615 @@ +name: Materialize Signed Attestor Delivery Repair + +on: + pull_request: + branches: [main] + paths: + - .github/workflows/materialize-attestor-delivery-repair.yml + +permissions: + contents: write + +concurrency: + group: materialize-signed-attestor-delivery-repair + cancel-in-progress: false + +jobs: + materialize: + name: Publish one signed protected-delivery repair + if: >- + github.repository == 'szl-holdings/.github' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'diag/enqueue-legacy-green-prs-v1' + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + GH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + TARGET_BRANCH: fix/attestor-delivery-recovery-v1-signed + EXPECTED_PARENT: 5aa61bb92ba4a1cd1b59c09c4f82b902d1bf7917 + steps: + - name: Checkout controller and protected source + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 1 + + - name: Verify target lineage + run: | + set -euo pipefail + test "$(gh api "repos/$REPOSITORY/branches/main" --jq .commit.sha)" = "$EXPECTED_PARENT" + test "$(gh api "repos/$REPOSITORY/branches/$TARGET_BRANCH" --jq .commit.sha)" = "$EXPECTED_PARENT" + + - name: Build protected default-branch attestor recovery + shell: python + run: | + from __future__ import annotations + + from pathlib import Path + + attestor_path = Path('.github/workflows/attest-and-approve.yml') + verifier_path = Path('.github/scripts/verify_forge9_governance.py') + tests_path = Path('.github/workflows/tests.yml') + contract_path = Path('.github/scripts/test_attestor_delivery.py') + + attestor = attestor_path.read_text(encoding='utf-8') + old_trigger = '''on: + workflow_run: + workflows: + - FORGE-9 gates + types: + - completed + '''.replace(' ', '') + new_trigger = '''on: + workflow_run: + workflows: + - FORGE-9 gates + types: + - completed + workflow_dispatch: + inputs: + pull_request: + description: "Exact open pull request number" + required: true + type: string + head_sha: + description: "Exact 40-character pull request head SHA" + required: true + type: string + gate_run_id: + description: "Successful FORGE-9 gates workflow run ID for the exact head" + required: true + type: string + schedule: + - cron: "*/5 * * * *" + '''.replace(' ', '') + if old_trigger not in attestor: + raise SystemExit('attestor trigger block changed unexpectedly') + attestor = attestor.replace(old_trigger, new_trigger, 1) + attestor = attestor.replace( + 'permissions:\n actions: read\n', + 'permissions:\n actions: write\n', + 1, + ) + attestor = attestor.replace( + ' group: forge9-attest-${{ github.event.workflow_run.head_sha }}', + ' group: forge9-attest-${{ github.event.workflow_run.head_sha || inputs.head_sha || github.run_id }}', + 1, + ) + old_job = ''' attest: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + '''.replace(' ', '') + new_job = ''' attest: + if: >- + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || + github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + SUBJECT_HEAD_SHA: ${{ github.event.workflow_run.head_sha || inputs.head_sha }} + SUBJECT_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch || '' }} + SUBJECT_SOURCE_EVENT: ${{ github.event.workflow_run.event || 'pull_request' }} + SUBJECT_SOURCE_RUN_ID: ${{ github.event.workflow_run.id || inputs.gate_run_id }} + steps: + '''.replace(' ', '') + if old_job not in attestor: + raise SystemExit('attestor job header changed unexpectedly') + attestor = attestor.replace(old_job, new_job, 1) + old_resolve_env = ''' HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + SOURCE_EVENT: ${{ github.event.workflow_run.event }} + REPOSITORY: ${{ github.repository }} + '''.replace(' ', '') + new_resolve_env = ''' HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + HEAD_BRANCH: ${{ env.SUBJECT_HEAD_BRANCH }} + SOURCE_EVENT: ${{ env.SUBJECT_SOURCE_EVENT }} + SOURCE_RUN_ID: ${{ env.SUBJECT_SOURCE_RUN_ID }} + DISPATCH_PR: ${{ inputs.pull_request || '' }} + REPOSITORY: ${{ github.repository }} + '''.replace(' ', '') + if old_resolve_env not in attestor: + raise SystemExit('resolve env block changed unexpectedly') + attestor = attestor.replace(old_resolve_env, new_resolve_env, 1) + old_resolve_start = ''' set -euo pipefail + if [ "$SOURCE_EVENT" = "merge_group" ]; then + '''.replace(' ', '') + new_resolve_start = ''' set -euo pipefail + if [ -n "$DISPATCH_PR" ]; then + [[ "$DISPATCH_PR" =~ ^[0-9]+$ ]] \\ + || { echo "::error::dispatch pull_request is not numeric"; exit 1; } + [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] \\ + || { echo "::error::dispatch head_sha is not canonical"; exit 1; } + [[ "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]] \\ + || { echo "::error::dispatch gate_run_id is not numeric"; exit 1; } + + gh api "repos/$REPOSITORY/actions/runs/$SOURCE_RUN_ID" \\ + > "$RUNNER_TEMP/source-run.json" + [ "$(jq -r .name "$RUNNER_TEMP/source-run.json")" = "FORGE-9 gates" ] \\ + || { echo "::error::dispatch source is not FORGE-9 gates"; exit 1; } + [ "$(jq -r .event "$RUNNER_TEMP/source-run.json")" = "pull_request" ] \\ + || { echo "::error::dispatch source event is not pull_request"; exit 1; } + [ "$(jq -r .conclusion "$RUNNER_TEMP/source-run.json")" = "success" ] \\ + || { echo "::error::dispatch source run is not successful"; exit 1; } + [ "$(jq -r .head_sha "$RUNNER_TEMP/source-run.json")" = "$HEAD_SHA" ] \\ + || { echo "::error::dispatch source head mismatch"; exit 1; } + + gh api "repos/$REPOSITORY/pulls/$DISPATCH_PR" > "$RUNNER_TEMP/pr.json" + [ "$(jq -r .state "$RUNNER_TEMP/pr.json")" = "open" ] \\ + || { echo "::error::dispatch PR is not open"; exit 1; } + [ "$(jq -r .draft "$RUNNER_TEMP/pr.json")" = "false" ] \\ + || { echo "::error::dispatch PR is draft"; exit 1; } + [ "$(jq -r .head.sha "$RUNNER_TEMP/pr.json")" = "$HEAD_SHA" ] \\ + || { echo "::error::dispatch PR head mismatch"; exit 1; } + [ "$(jq -r .head.repo.full_name "$RUNNER_TEMP/pr.json")" = "$REPOSITORY" ] \\ + || { echo "::error::dispatch PR is cross-repository"; exit 1; } + BASE_REF="$(jq -r .base.ref "$RUNNER_TEMP/pr.json")" + { [ "$BASE_REF" = "main" ] || [[ "$BASE_REF" == release/* ]]; } \\ + || { echo "::error::dispatch PR base is not governed"; exit 1; } + echo "kind=pull_request" >> "$GITHUB_OUTPUT" + echo "number=$DISPATCH_PR" >> "$GITHUB_OUTPUT" + echo "node_id=$(jq -r .node_id "$RUNNER_TEMP/pr.json")" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "$SOURCE_EVENT" = "merge_group" ]; then + '''.replace(' ', '') + if old_resolve_start not in attestor: + raise SystemExit('resolve shell changed unexpectedly') + attestor = attestor.replace(old_resolve_start, new_resolve_start, 1) + attestor = attestor.replace( + 'HEAD_SHA: ${{ github.event.workflow_run.head_sha }}', + 'HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }}', + ) + attestor = attestor.replace( + 'name: merge-receipt-${{ github.event.workflow_run.head_sha }}', + 'name: merge-receipt-${{ env.SUBJECT_HEAD_SHA }}', + 1, + ) + attestor = attestor.replace( + ' RUN_ID: ${{ github.run_id }}\n', + ' SOURCE_RUN_ID: ${{ env.SUBJECT_SOURCE_RUN_ID }}\n' + ' DELIVERY_MODE: ${{ github.event_name }}\n' + ' ATTESTOR_RUN_ID: ${{ github.run_id }}\n', + 1, + ) + attestor = attestor.replace( + ' --arg run_id "$RUN_ID" \\\n', + ' --arg source_run_id "$SOURCE_RUN_ID" \\\n' + ' --arg delivery_mode "$DELIVERY_MODE" \\\n' + ' --arg attestor_run_id "$ATTESTOR_RUN_ID" \\\n', + 1, + ) + attestor = attestor.replace( + ' workflow_run_id: $run_id,\n', + ' source_workflow_run_id: $source_run_id,\n' + ' attestor_run_id: $attestor_run_id,\n' + ' delivery_mode: $delivery_mode,\n', + 1, + ) + attestor = attestor.replace( + r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging)\.ya?ml|\.governance/)', + r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)\.ya?ml|\.governance/)', + 1, + ) + old_tail = ''' - name: Request protected merge after attestation + if: steps.subject.outputs.kind == 'pull_request' + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ steps.subject.outputs.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + gh pr merge "$PR" --repo "$REPOSITORY" --auto --squash + echo "Requested the protected merge path for PR $PR" + '''.replace(' ', '') + new_tail = ''' - name: Hand off to protected native enqueue controller + if: steps.subject.outputs.kind == 'pull_request' + env: + PR: ${{ steps.subject.outputs.number }} + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + run: | + set -euo pipefail + echo "Exact-head qillqaq evidence is published for PR $PR at $HEAD_SHA." + echo "Protected Merge Queue Enqueue owns native enqueuePullRequest delivery." + + recover-missed-delivery: + name: Recover missed workflow-run delivery from protected main + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Mint qillqaq read identity + id: recovery-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.QILLQAQ_CLIENT_ID }} + private-key: ${{ secrets.QILLQAQ_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Dispatch exact heads whose workflow-run delivery was missed + env: + READ_TOKEN: ${{ steps.recovery-app-token.outputs.token }} + DISPATCH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/attestor-recovery" + GH_TOKEN="$READ_TOKEN" gh api \\ + "repos/$REPOSITORY/pulls?state=open&per_page=100" \\ + --paginate --slurp > "$RUNNER_TEMP/attestor-recovery/pull-pages.json" + jq -c --arg repo "$REPOSITORY" '[.[][] | select( + .draft == false + and .head.repo.full_name == $repo + and (.base.ref == "main" or (.base.ref | startswith("release/"))) + )] | .[]' "$RUNNER_TEMP/attestor-recovery/pull-pages.json" \\ + > "$RUNNER_TEMP/attestor-recovery/targets.jsonl" + + dispatched=0 + skipped=0 + while IFS= read -r target; do + PR="$(jq -r .number <<<"$target")" + HEAD_SHA="$(jq -r .head.sha <<<"$target")" + GH_TOKEN="$READ_TOKEN" gh api \\ + "repos/$REPOSITORY/pulls/$PR/reviews?per_page=100" \\ + > "$RUNNER_TEMP/attestor-recovery/reviews-$PR.json" + APPROVALS="$(jq --arg head "$HEAD_SHA" '[.[] | select( + (.user.login == "qillqaq-attestor[bot]" or .user.login == "qillqaq-attestor") + and .state == "APPROVED" + and .commit_id == $head + )] | length' "$RUNNER_TEMP/attestor-recovery/reviews-$PR.json")" + GH_TOKEN="$READ_TOKEN" gh api \\ + "repos/$REPOSITORY/commits/$HEAD_SHA/status" \\ + > "$RUNNER_TEMP/attestor-recovery/status-$PR.json" + STATUSES="$(jq '[.statuses[] | select( + .context == "attestation/qillqaq" and .state == "success" + )] | length' "$RUNNER_TEMP/attestor-recovery/status-$PR.json")" + if [ "$APPROVALS" -ge 1 ] && [ "$STATUSES" -ge 1 ]; then + skipped=$((skipped + 1)) + continue + fi + + GH_TOKEN="$READ_TOKEN" gh api \\ + "repos/$REPOSITORY/actions/workflows/gates.yml/runs?event=pull_request&head_sha=$HEAD_SHA&per_page=100" \\ + > "$RUNNER_TEMP/attestor-recovery/gates-$PR.json" + GATE_RUN_ID="$(jq -r --arg head "$HEAD_SHA" --arg repo "$REPOSITORY" '[ + .workflow_runs[] | select( + .head_sha == $head + and .event == "pull_request" + and .conclusion == "success" + and .head_repository.full_name == $repo + ) + ] | sort_by(.created_at) | last | .id // empty' \\ + "$RUNNER_TEMP/attestor-recovery/gates-$PR.json")" + if [ -z "$GATE_RUN_ID" ]; then + skipped=$((skipped + 1)) + continue + fi + GH_TOKEN="$DISPATCH_TOKEN" gh workflow run attest-and-approve.yml \\ + --repo "$REPOSITORY" \\ + --ref main \\ + -f pull_request="$PR" \\ + -f head_sha="$HEAD_SHA" \\ + -f gate_run_id="$GATE_RUN_ID" + dispatched=$((dispatched + 1)) + done < "$RUNNER_TEMP/attestor-recovery/targets.jsonl" + + jq -n \\ + --arg schema 'szl.attestor-delivery-recovery/v1' \\ + --argjson dispatched "$dispatched" \\ + --argjson skipped "$skipped" \\ + '{ + schema: $schema, + dispatched: $dispatched, + skipped: $skipped, + default_branch_code: true, + pull_request_target_used: false, + custom_secret_value_recorded: false + }' > attestor-delivery-recovery.json + cat attestor-delivery-recovery.json + + - name: Upload immutable recovery receipt + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: attestor-delivery-recovery-${{ github.run_id }} + path: attestor-delivery-recovery.json + if-no-files-found: error + retention-days: 90 + '''.replace(' ', '') + if old_tail not in attestor: + raise SystemExit('attestor tail changed unexpectedly') + attestor = attestor.replace(old_tail, new_tail, 1) + attestor_path.write_text(attestor, encoding='utf-8') + + verifier = verifier_path.read_text(encoding='utf-8') + verifier = verifier.replace( + r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging)' + r'\.ya?ml|\.governance/)', + r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)' + r'\.ya?ml|\.governance/)', + 1, + ) + verifier = verifier.replace( + ' ".github/workflows/forge9-staging.yaml",\n' + ' ".governance/gates.json",\n', + ' ".github/workflows/forge9-staging.yaml",\n' + ' ".github/workflows/merge-queue-enqueue.yml",\n' + ' ".github/workflows/merge-queue-enqueue.yaml",\n' + ' ".governance/gates.json",\n', + 1, + ) + old_verifier = ''' if GOVERNED_BASE_FILTER not in attestor_template: + fail("attestor must resolve PRs targeting main and release/*") + if "environment: production" in attestor_template: + fail("the merge attestor must not consume the production deployment gate") + if "GH_TOKEN: ${{ github.token }}" not in attestor_template: + fail("the protected queue request must use the ephemeral workflow token") + for marker in ( + "Publish required App attestation status", + "context=attestation/qillqaq", + "GH_TOKEN: ${{ steps.app-token.outputs.token }}", + "client-id: ${{ vars.QILLQAQ_CLIENT_ID }}", + 'SOURCE_EVENT: ${{ github.event.workflow_run.event }}', + 'if [ "$SOURCE_EVENT" = "merge_group" ]; then', + '[[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]]', + "subject_kind: $subject_kind", + ): + if marker not in attestor_template: + fail(f"attestor status publication is missing {marker!r}") + if "app-id:" in attestor_template: + fail("the attestor must use the supported GitHub App client-id input") + if attestor_template.count( + "if: steps.subject.outputs.kind == 'pull_request'" + ) != 2: + fail("only PR-head attestations may approve or request a queue entry") + if 'gh pr merge "$PR" --repo "$REPOSITORY" --auto --squash' not in ( + attestor_template + ): + fail("the attestor must use GitHub's supported merge-queue CLI path") + '''.replace(' ', '') + new_verifier = ''' if GOVERNED_BASE_FILTER not in attestor_template: + fail("attestor must resolve PRs targeting main and release/*") + if "environment: production" in attestor_template: + fail("the merge attestor must not consume the production deployment gate") + enqueue_template = ( + ROOT / ".github/workflows/merge-queue-enqueue.yml" + ).read_text(encoding="utf-8") + for marker in ( + "Publish required App attestation status", + "context=attestation/qillqaq", + "GH_TOKEN: ${{ steps.app-token.outputs.token }}", + "client-id: ${{ vars.QILLQAQ_CLIENT_ID }}", + "workflow_dispatch:", + "schedule:", + "Recover missed workflow-run delivery from protected main", + "gh workflow run attest-and-approve.yml", + "SUBJECT_HEAD_SHA: ${{ github.event.workflow_run.head_sha || inputs.head_sha }}", + "DISPATCH_PR: ${{ inputs.pull_request || '' }}", + "source_workflow_run_id", + 'if [ "$SOURCE_EVENT" = "merge_group" ]; then', + '[[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]]', + "subject_kind: $subject_kind", + "Hand off to protected native enqueue controller", + ): + if marker not in attestor_template: + fail(f"attestor delivery contract is missing {marker!r}") + if "app-id:" in attestor_template: + fail("the attestor must use the supported GitHub App client-id input") + if attestor_template.count( + "if: steps.subject.outputs.kind == 'pull_request'" + ) != 2: + fail("only PR-head attestations may approve or hand off to native enqueue") + if "gh pr merge" in attestor_template or "enqueuePullRequest" in attestor_template: + fail("the attestor must not merge or enqueue; the protected enqueue controller owns that mutation") + if "enqueuePullRequest" not in enqueue_template: + fail("the protected native enqueue controller is missing") + if "pull_request_target" in attestor_template: + fail("attestor delivery recovery must never use pull_request_target") + '''.replace(' ', '') + if old_verifier not in verifier: + raise SystemExit('governance verifier block changed unexpectedly') + verifier = verifier.replace(old_verifier, new_verifier, 1) + verifier_path.write_text(verifier, encoding='utf-8') + + contract = '''#!/usr/bin/env python3 + from __future__ import annotations + + from pathlib import Path + import unittest + + ROOT = Path(__file__).resolve().parents[2] + ATTESTOR = ROOT / ".github/workflows/attest-and-approve.yml" + ENQUEUE = ROOT / ".github/workflows/merge-queue-enqueue.yml" + + + class AttestorDeliveryTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.attestor = ATTESTOR.read_text(encoding="utf-8") + cls.enqueue = ENQUEUE.read_text(encoding="utf-8") + + def test_default_branch_has_immediate_and_recovery_delivery(self) -> None: + for marker in ( + "workflow_run:", + "workflow_dispatch:", + "schedule:", + "Recover missed workflow-run delivery from protected main", + "gh workflow run attest-and-approve.yml", + ): + self.assertIn(marker, self.attestor) + + def test_dispatch_tuple_is_revalidated(self) -> None: + for marker in ( + "dispatch pull_request is not numeric", + "dispatch head_sha is not canonical", + "dispatch gate_run_id is not numeric", + "dispatch source is not FORGE-9 gates", + "dispatch source head mismatch", + "dispatch PR head mismatch", + "dispatch PR is cross-repository", + ): + self.assertIn(marker, self.attestor) + + def test_qillqaq_remains_default_branch_only(self) -> None: + self.assertIn("secrets.QILLQAQ_PRIVATE_KEY", self.attestor) + self.assertNotIn("pull_request_target", self.attestor) + self.assertNotIn("QILLQAQ_PRIVATE_KEY", self.enqueue) + + def test_attestor_never_merges_or_enqueues(self) -> None: + self.assertNotIn("gh pr merge", self.attestor) + self.assertNotIn("enqueuePullRequest", self.attestor) + self.assertIn("enqueuePullRequest", self.enqueue) + self.assertIn("Hand off to protected native enqueue controller", self.attestor) + + def test_recovery_receipt_contains_no_secret_material(self) -> None: + for marker in ( + "default_branch_code: true", + "pull_request_target_used: false", + "custom_secret_value_recorded: false", + "retention-days: 90", + ): + self.assertIn(marker, self.attestor) + self.assertNotIn("set -x", self.attestor) + + + if __name__ == "__main__": + unittest.main(verbosity=2) + '''.replace(' ', '') + contract_path.write_text(contract, encoding='utf-8') + + tests = tests_path.read_text(encoding='utf-8') + marker = ' - name: HF deploy-from-Dockerfile derivation self-test\n run: python3 .github/scripts/test_hf_deploy_from_dockerfile.py\n' + if marker not in tests: + raise SystemExit('tests workflow tail changed unexpectedly') + tests = tests.replace( + marker, + marker + + '\n # Pins default-branch recovery when GitHub omits a workflow_run delivery.\n' + + ' - name: Attestor delivery recovery contract self-test\n' + + ' run: python3 .github/scripts/test_attestor_delivery.py\n', + 1, + ) + tests_path.write_text(tests, encoding='utf-8') + + - name: Validate generated governance repair + run: | + set -euo pipefail + python -m py_compile \ + .github/scripts/verify_forge9_governance.py \ + .github/scripts/test_attestor_delivery.py + python .github/scripts/test_attestor_delivery.py + python .github/scripts/verify_forge9_governance.py + git diff --check + + - name: Create one GitHub-signed protected repair commit + id: commit + shell: python + run: | + import base64 + import json + import os + import urllib.error + import urllib.request + from pathlib import Path + + additions = [ + '.github/workflows/attest-and-approve.yml', + '.github/scripts/verify_forge9_governance.py', + '.github/scripts/test_attestor_delivery.py', + '.github/workflows/tests.yml', + ] + query = ''' + mutation($input: CreateCommitOnBranchInput!) { + createCommitOnBranch(input: $input) { + commit { oid url } + } + } + ''' + variables = { + 'input': { + 'branch': { + 'repositoryNameWithOwner': os.environ['REPOSITORY'], + 'branchName': os.environ['TARGET_BRANCH'], + }, + 'message': { + 'headline': 'fix(attestor): recover missed gate delivery', + 'body': 'Signed-off-by: Stephen Lutar ', + }, + 'expectedHeadOid': os.environ['EXPECTED_PARENT'], + 'fileChanges': { + 'additions': [ + { + 'path': path, + 'contents': base64.b64encode(Path(path).read_bytes()).decode('ascii'), + } + for path in additions + ], + }, + } + } + request = urllib.request.Request( + 'https://api.github.com/graphql', + data=json.dumps({'query': query, 'variables': variables}).encode('utf-8'), + method='POST', + headers={ + 'Authorization': f"Bearer {os.environ['GH_TOKEN']}", + 'Accept': 'application/vnd.github+json', + 'Content-Type': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'szl-attestor-delivery-materializer/1', + }, + ) + try: + with urllib.request.urlopen(request, timeout=90) as response: + payload = json.loads(response.read()) + except urllib.error.HTTPError as exc: + raise SystemExit(f'GraphQL HTTP {exc.code}') from exc + if payload.get('errors'): + print(json.dumps(payload['errors'], indent=2)) + raise SystemExit(1) + commit = payload['data']['createCommitOnBranch']['commit'] + Path('signed-attestor-delivery.json').write_text( + json.dumps(commit, indent=2, sort_keys=True) + '\n', encoding='utf-8' + ) + print(json.dumps(commit, indent=2, sort_keys=True)) + with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output: + output.write(f"sha={commit['oid']}\n") + + - name: Verify signed target commit + env: + COMMIT_SHA: ${{ steps.commit.outputs.sha }} + run: | + set -euo pipefail + test "$(gh api "repos/$REPOSITORY/branches/$TARGET_BRANCH" --jq .commit.sha)" = "$COMMIT_SHA" + test "$(gh api "repos/$REPOSITORY/commits/$COMMIT_SHA" --jq .commit.verification.verified)" = 'true' + test "$(gh api "repos/$REPOSITORY/commits/$COMMIT_SHA" --jq .commit.verification.reason)" = 'valid' + + - name: Upload immutable materialization receipt + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signed-attestor-delivery-${{ github.run_id }} + path: signed-attestor-delivery.json + if-no-files-found: error + retention-days: 90 From 8b85cbdbd1953bda392031b7d46a47edc4514a8a Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:03:58 -0400 Subject: [PATCH 04/10] ci(attestor): isolate signed repair generator Signed-off-by: Stephen Lutar --- .../materialize_attestor_delivery_repair.py | 767 ++++++++++++++++++ 1 file changed, 767 insertions(+) create mode 100644 .github/scripts/materialize_attestor_delivery_repair.py diff --git a/.github/scripts/materialize_attestor_delivery_repair.py b/.github/scripts/materialize_attestor_delivery_repair.py new file mode 100644 index 0000000..bfbe6a0 --- /dev/null +++ b/.github/scripts/materialize_attestor_delivery_repair.py @@ -0,0 +1,767 @@ +#!/usr/bin/env python3 +"""Materialize the protected default-branch attestor delivery repair. + +This script runs only from the bounded diagnostic PR. It writes the reviewed +permanent files into the runner worktree, executes their network-free contracts, +and creates one GitHub-signed commit on a clean branch rooted at protected main. +No diagnostic controller is included in the target tree. +""" +from __future__ import annotations + +import base64 +import json +import os +from pathlib import Path +import subprocess +import urllib.error +import urllib.request +from typing import Any + +ROOT = Path(__file__).resolve().parents[2] +REPOSITORY = os.environ["REPOSITORY"] +TARGET_BRANCH = os.environ["TARGET_BRANCH"] +EXPECTED_PARENT = os.environ["EXPECTED_PARENT"] +TOKEN = os.environ["GH_TOKEN"] + +ATTESTOR_PATH = ROOT / ".github/workflows/attest-and-approve.yml" +VERIFIER_PATH = ROOT / ".github/scripts/verify_forge9_governance.py" +TESTS_WORKFLOW_PATH = ROOT / ".github/workflows/tests.yml" +CONTRACT_PATH = ROOT / ".github/scripts/test_attestor_delivery.py" + +ATTESTOR = r'''name: FORGE-9 attest and approve + +on: + workflow_run: + workflows: + - FORGE-9 gates + types: + - completed + workflow_dispatch: + inputs: + pull_request: + description: "Exact open pull request number" + required: true + type: string + head_sha: + description: "Exact 40-character pull request head SHA" + required: true + type: string + gate_run_id: + description: "Successful FORGE-9 gates workflow run ID for the exact head" + required: true + type: string + schedule: + - cron: "*/5 * * * *" + +permissions: + actions: write + contents: write + id-token: write + pull-requests: write + +concurrency: + group: forge9-attest-${{ github.event.workflow_run.head_sha || inputs.head_sha || github.run_id }} + cancel-in-progress: false + +jobs: + attest: + if: >- + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || + github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + SUBJECT_HEAD_SHA: ${{ github.event.workflow_run.head_sha || inputs.head_sha }} + SUBJECT_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch || '' }} + SUBJECT_SOURCE_EVENT: ${{ github.event.workflow_run.event || 'pull_request' }} + SUBJECT_SOURCE_RUN_ID: ${{ github.event.workflow_run.id || inputs.gate_run_id }} + steps: + - name: Mint the independent App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.QILLQAQ_CLIENT_ID }} + private-key: ${{ secrets.QILLQAQ_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Resolve the attestation subject + id: subject + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + HEAD_BRANCH: ${{ env.SUBJECT_HEAD_BRANCH }} + SOURCE_EVENT: ${{ env.SUBJECT_SOURCE_EVENT }} + SOURCE_RUN_ID: ${{ env.SUBJECT_SOURCE_RUN_ID }} + DISPATCH_PR: ${{ inputs.pull_request || '' }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + if [ -n "$DISPATCH_PR" ]; then + [[ "$DISPATCH_PR" =~ ^[0-9]+$ ]] \ + || { echo "::error::dispatch pull_request is not numeric"; exit 1; } + [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] \ + || { echo "::error::dispatch head_sha is not canonical"; exit 1; } + [[ "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]] \ + || { echo "::error::dispatch gate_run_id is not numeric"; exit 1; } + + gh api "repos/$REPOSITORY/actions/runs/$SOURCE_RUN_ID" \ + > "$RUNNER_TEMP/source-run.json" + [ "$(jq -r .name "$RUNNER_TEMP/source-run.json")" = "FORGE-9 gates" ] \ + || { echo "::error::dispatch source is not FORGE-9 gates"; exit 1; } + [ "$(jq -r .event "$RUNNER_TEMP/source-run.json")" = "pull_request" ] \ + || { echo "::error::dispatch source event is not pull_request"; exit 1; } + [ "$(jq -r .conclusion "$RUNNER_TEMP/source-run.json")" = "success" ] \ + || { echo "::error::dispatch source run is not successful"; exit 1; } + [ "$(jq -r .head_sha "$RUNNER_TEMP/source-run.json")" = "$HEAD_SHA" ] \ + || { echo "::error::dispatch source head mismatch"; exit 1; } + [ "$(jq -r .head_repository.full_name "$RUNNER_TEMP/source-run.json")" = "$REPOSITORY" ] \ + || { echo "::error::dispatch source repository mismatch"; exit 1; } + + gh api "repos/$REPOSITORY/pulls/$DISPATCH_PR" > "$RUNNER_TEMP/pr.json" + [ "$(jq -r .state "$RUNNER_TEMP/pr.json")" = "open" ] \ + || { echo "::error::dispatch PR is not open"; exit 1; } + [ "$(jq -r .draft "$RUNNER_TEMP/pr.json")" = "false" ] \ + || { echo "::error::dispatch PR is draft"; exit 1; } + [ "$(jq -r .head.sha "$RUNNER_TEMP/pr.json")" = "$HEAD_SHA" ] \ + || { echo "::error::dispatch PR head mismatch"; exit 1; } + [ "$(jq -r .head.repo.full_name "$RUNNER_TEMP/pr.json")" = "$REPOSITORY" ] \ + || { echo "::error::dispatch PR is cross-repository"; exit 1; } + BASE_REF="$(jq -r .base.ref "$RUNNER_TEMP/pr.json")" + { [ "$BASE_REF" = "main" ] || [[ "$BASE_REF" == release/* ]]; } \ + || { echo "::error::dispatch PR base is not governed"; exit 1; } + echo "kind=pull_request" >> "$GITHUB_OUTPUT" + echo "number=$DISPATCH_PR" >> "$GITHUB_OUTPUT" + echo "node_id=$(jq -r .node_id "$RUNNER_TEMP/pr.json")" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "$SOURCE_EVENT" = "merge_group" ]; then + [[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]] \ + || { echo "::error::unexpected merge-group branch: $HEAD_BRANCH"; exit 1; } + echo "kind=merge_group" >> "$GITHUB_OUTPUT" + echo "number=" >> "$GITHUB_OUTPUT" + echo "node_id=" >> "$GITHUB_OUTPUT" + exit 0 + fi + [ "$SOURCE_EVENT" = "pull_request" ] \ + || { echo "::error::unsupported source event: $SOURCE_EVENT"; exit 1; } + + gh api \ + -H "Accept: application/vnd.github+json" \ + "repos/$REPOSITORY/commits/$HEAD_SHA/pulls?per_page=100" \ + > "$RUNNER_TEMP/pulls.json" + mapfile -t PRS < <( + jq -r --arg sha "$HEAD_SHA" \ + '.[] | select( + .state == "open" + and .head.sha == $sha + and ( + .base.ref == "main" + or (.base.ref | startswith("release/")) + ) + ) | .number' \ + "$RUNNER_TEMP/pulls.json" + ) + if [ "${#PRS[@]}" -ne 1 ]; then + echo "::error::expected exactly one open main or release/* PR for $HEAD_SHA; found ${#PRS[@]}" + exit 1 + fi + PR="${PRS[0]}" + gh api "repos/$REPOSITORY/pulls/$PR" > "$RUNNER_TEMP/pr.json" + echo "kind=pull_request" >> "$GITHUB_OUTPUT" + echo "number=$PR" >> "$GITHUB_OUTPUT" + echo "node_id=$(jq -r .node_id "$RUNNER_TEMP/pr.json")" >> "$GITHUB_OUTPUT" + + - name: Verify gates and preconditions P1-P7 + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + SUBJECT_KIND: ${{ steps.subject.outputs.kind }} + PR: ${{ steps.subject.outputs.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + if [ "$SUBJECT_KIND" = "pull_request" ]; then + BODY=$(jq -r '.body // ""' "$RUNNER_TEMP/pr.json") + + grep -Eq 'Satisfies:[[:space:]]*(AT-[0-9]+|C-[0-9]+)' <<<"$BODY" \ + || { echo "::error::P1 missing Satisfies: AT-n or C-n"; exit 1; } + grep -Eq 'Rollback:[[:space:]]*\S' <<<"$BODY" \ + || { echo "::error::P2 missing Rollback"; exit 1; } + grep -Eq 'Labels:[[:space:]]*\S' <<<"$BODY" \ + || { echo "::error::P3 missing Labels"; exit 1; } + grep -Eq 'Risk:[[:space:]]*[ABCD][[:space:]]*[-—]' <<<"$BODY" \ + || { echo "::error::missing Risk: A-D classification"; exit 1; } + + gh api "repos/$REPOSITORY/pulls/$PR/files?per_page=100" --paginate \ + --jq '.[].filename' > "$RUNNER_TEMP/files.txt" + if grep -Eq '^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)\.ya?ml|\.governance/)' \ + "$RUNNER_TEMP/files.txt"; then + grep -Eq 'Solo-Operator-Authorization:[[:space:]]*confirmed' <<<"$BODY" \ + || { echo "::error::P5 governance change lacks solo-operator authorization"; exit 1; } + grep -Eq 'Risk:[[:space:]]*D[[:space:]]*[-—]' <<<"$BODY" \ + || { echo "::error::P5 governance change must be classified Risk D"; exit 1; } + fi + fi + + gh api \ + -H "Accept: application/vnd.github+json" \ + "repos/$REPOSITORY/commits/$HEAD_SHA/check-runs?filter=latest&per_page=100" \ + > "$RUNNER_TEMP/check-runs.json" + REQUIRED=( + gate/ground-truth + gate/labels + gate/schema + gate/adversarial + gate/verify-all + gate/provenance + gate/a11y-perf + gate/lean + ) + for CHECK in "${REQUIRED[@]}"; do + COUNT=$(jq --arg check "$CHECK" \ + '[.check_runs[] | select(.name == $check and .conclusion == "success")] | length' \ + "$RUNNER_TEMP/check-runs.json") + [ "$COUNT" -ge 1 ] \ + || { echo "::error::required check is not green: $CHECK"; exit 1; } + done + + HAS_QUEUE=0 + gh api "repos/$REPOSITORY/rulesets?per_page=100" --paginate \ + --jq '.[].id' > "$RUNNER_TEMP/ruleset-ids.txt" + [ -s "$RUNNER_TEMP/ruleset-ids.txt" ] \ + || { echo "::error::P6 found no rulesets"; exit 1; } + while read -r ID; do + gh api "repos/$REPOSITORY/rulesets/$ID" > "$RUNNER_TEMP/ruleset-$ID.json" + BYPASS=$(jq '.bypass_actors | length' "$RUNNER_TEMP/ruleset-$ID.json") + [ "$BYPASS" -eq 0 ] \ + || { echo "::error::P6 ruleset $ID has $BYPASS bypass actors"; exit 1; } + if jq -e '.rules[] | select(.type == "merge_queue")' \ + "$RUNNER_TEMP/ruleset-$ID.json" >/dev/null; then + HAS_QUEUE=1 + fi + done < "$RUNNER_TEMP/ruleset-ids.txt" + [ "$HAS_QUEUE" -eq 1 ] \ + || { echo "::error::merge queue rule is not active"; exit 1; } + + CAN_APPROVE=$(gh api "repos/$REPOSITORY/actions/permissions/workflow" \ + --jq '.can_approve_pull_request_reviews') + [ "$CAN_APPROVE" = "false" ] \ + || { echo "::error::P7 default GITHUB_TOKEN can approve reviews"; exit 1; } + + - name: Build and sign the merge BAP + env: + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + SUBJECT_KIND: ${{ steps.subject.outputs.kind }} + PR: ${{ steps.subject.outputs.number }} + REPOSITORY: ${{ github.repository }} + SOURCE_RUN_ID: ${{ env.SUBJECT_SOURCE_RUN_ID }} + DELIVERY_MODE: ${{ github.event_name }} + ATTESTOR_RUN_ID: ${{ github.run_id }} + run: | + set -euo pipefail + jq -n \ + --arg schema "https://a11oy.net/schemas/forge9/merge-bap/v1" \ + --arg repository "$REPOSITORY" \ + --arg subject_kind "$SUBJECT_KIND" \ + --arg pull_request "$PR" \ + --arg head_sha "$HEAD_SHA" \ + --arg principal "spiffe://a11oy.net/ns/ci/sa/qillqaq-attestor" \ + --arg policy_id "merge-gate-v1" \ + --arg source_run_id "$SOURCE_RUN_ID" \ + --arg delivery_mode "$DELIVERY_MODE" \ + --arg attestor_run_id "$ATTESTOR_RUN_ID" \ + --arg issued_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --slurpfile checks "$RUNNER_TEMP/check-runs.json" \ + '{ + schema: $schema, + repository: $repository, + subject_kind: $subject_kind, + pull_request: ( + if $pull_request == "" + then null + else ($pull_request | tonumber) + end + ), + head_sha: $head_sha, + principal: $principal, + policy_id: $policy_id, + source_workflow_run_id: $source_run_id, + attestor_run_id: $attestor_run_id, + delivery_mode: $delivery_mode, + issued_at: $issued_at, + checks: [ + $checks[0].check_runs[] + | select(.name | startswith("gate/")) + | {name, conclusion, details_url} + ], + breakglass: null + }' > merge-receipt.json + + - name: Install Cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + + - name: Sign the merge BAP with Actions OIDC + run: cosign sign-blob --yes --bundle merge-receipt.bundle merge-receipt.json + + - name: Upload the signed merge BAP + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: merge-receipt-${{ env.SUBJECT_HEAD_SHA }} + path: | + merge-receipt.json + merge-receipt.bundle + if-no-files-found: error + retention-days: 90 + + - name: Approve as the App and verify the review identity + if: steps.subject.outputs.kind == 'pull_request' + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + PR: ${{ steps.subject.outputs.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + DIGEST=$(sha256sum merge-receipt.json | cut -d' ' -f1) + gh api -X POST "repos/$REPOSITORY/pulls/$PR/reviews" \ + -f event=APPROVE \ + -f body="ATTESTED. All Section 18 gates green. Merge BAP sha256: $DIGEST" + COUNT=$(gh api "repos/$REPOSITORY/pulls/$PR/reviews?per_page=100" --paginate \ + --jq "[.[] | select(.user.login == \"qillqaq-attestor[bot]\" and .state == \"APPROVED\" and .commit_id == \"$HEAD_SHA\")] | length") + [ "$COUNT" -ge 1 ] \ + || { echo "::error::App approval was not recorded for the exact head SHA"; exit 1; } + + - name: Publish required App attestation status + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + SUBJECT_KIND: ${{ steps.subject.outputs.kind }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + if [ "$SUBJECT_KIND" = "pull_request" ]; then + DESCRIPTION="Signed BAP and exact-head App review verified" + else + DESCRIPTION="Signed BAP verified for merge-group gates" + fi + gh api \ + --method POST \ + "repos/$REPOSITORY/statuses/$HEAD_SHA" \ + -f state=success \ + -f context=attestation/qillqaq \ + -f description="$DESCRIPTION" \ + -f target_url="https://github.com/$REPOSITORY/actions/runs/$GITHUB_RUN_ID" + echo "Published attestation/qillqaq for $HEAD_SHA" + + - name: Hand off to protected native enqueue controller + if: steps.subject.outputs.kind == 'pull_request' + env: + PR: ${{ steps.subject.outputs.number }} + HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} + run: | + set -euo pipefail + echo "Exact-head qillqaq evidence is published for PR $PR at $HEAD_SHA." + echo "Protected Merge Queue Enqueue owns native enqueuePullRequest delivery." + + recover-missed-delivery: + name: Recover missed workflow-run delivery from protected main + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Mint qillqaq read identity + id: recovery-app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.QILLQAQ_CLIENT_ID }} + private-key: ${{ secrets.QILLQAQ_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + + - name: Dispatch exact heads whose workflow-run delivery was missed + env: + READ_TOKEN: ${{ steps.recovery-app-token.outputs.token }} + DISPATCH_TOKEN: ${{ github.token }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + mkdir -p "$RUNNER_TEMP/attestor-recovery" + GH_TOKEN="$READ_TOKEN" gh api \ + "repos/$REPOSITORY/pulls?state=open&per_page=100" \ + --paginate --slurp > "$RUNNER_TEMP/attestor-recovery/pull-pages.json" + jq -c --arg repo "$REPOSITORY" '[.[][] | select( + .draft == false + and .head.repo.full_name == $repo + and (.base.ref == "main" or (.base.ref | startswith("release/"))) + )] | .[]' "$RUNNER_TEMP/attestor-recovery/pull-pages.json" \ + > "$RUNNER_TEMP/attestor-recovery/targets.jsonl" + + dispatched=0 + skipped=0 + while IFS= read -r target; do + PR="$(jq -r .number <<<"$target")" + HEAD_SHA="$(jq -r .head.sha <<<"$target")" + GH_TOKEN="$READ_TOKEN" gh api \ + "repos/$REPOSITORY/pulls/$PR/reviews?per_page=100" \ + > "$RUNNER_TEMP/attestor-recovery/reviews-$PR.json" + APPROVALS="$(jq --arg head "$HEAD_SHA" '[.[] | select( + (.user.login == "qillqaq-attestor[bot]" or .user.login == "qillqaq-attestor") + and .state == "APPROVED" + and .commit_id == $head + )] | length' "$RUNNER_TEMP/attestor-recovery/reviews-$PR.json")" + GH_TOKEN="$READ_TOKEN" gh api \ + "repos/$REPOSITORY/commits/$HEAD_SHA/status" \ + > "$RUNNER_TEMP/attestor-recovery/status-$PR.json" + STATUSES="$(jq '[.statuses[] | select( + .context == "attestation/qillqaq" and .state == "success" + )] | length' "$RUNNER_TEMP/attestor-recovery/status-$PR.json")" + if [ "$APPROVALS" -ge 1 ] && [ "$STATUSES" -ge 1 ]; then + skipped=$((skipped + 1)) + continue + fi + + GH_TOKEN="$READ_TOKEN" gh api \ + "repos/$REPOSITORY/actions/workflows/gates.yml/runs?event=pull_request&head_sha=$HEAD_SHA&per_page=100" \ + > "$RUNNER_TEMP/attestor-recovery/gates-$PR.json" + GATE_RUN_ID="$(jq -r --arg head "$HEAD_SHA" --arg repo "$REPOSITORY" '[ + .workflow_runs[] | select( + .head_sha == $head + and .event == "pull_request" + and .conclusion == "success" + and .head_repository.full_name == $repo + ) + ] | sort_by(.created_at) | last | .id // empty' \ + "$RUNNER_TEMP/attestor-recovery/gates-$PR.json")" + if [ -z "$GATE_RUN_ID" ]; then + skipped=$((skipped + 1)) + continue + fi + GH_TOKEN="$DISPATCH_TOKEN" gh workflow run attest-and-approve.yml \ + --repo "$REPOSITORY" \ + --ref main \ + -f pull_request="$PR" \ + -f head_sha="$HEAD_SHA" \ + -f gate_run_id="$GATE_RUN_ID" + dispatched=$((dispatched + 1)) + done < "$RUNNER_TEMP/attestor-recovery/targets.jsonl" + + jq -n \ + --arg schema 'szl.attestor-delivery-recovery/v1' \ + --argjson dispatched "$dispatched" \ + --argjson skipped "$skipped" \ + '{ + schema: $schema, + dispatched: $dispatched, + skipped: $skipped, + default_branch_code: true, + pull_request_target_used: false, + custom_secret_value_recorded: false + }' > attestor-delivery-recovery.json + cat attestor-delivery-recovery.json + + - name: Upload immutable recovery receipt + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: attestor-delivery-recovery-${{ github.run_id }} + path: attestor-delivery-recovery.json + if-no-files-found: error + retention-days: 90 +''' + +CONTRACT = r'''#!/usr/bin/env python3 +from __future__ import annotations + +from pathlib import Path +import unittest + +ROOT = Path(__file__).resolve().parents[2] +ATTESTOR = ROOT / ".github/workflows/attest-and-approve.yml" +ENQUEUE = ROOT / ".github/workflows/merge-queue-enqueue.yml" + + +class AttestorDeliveryTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.attestor = ATTESTOR.read_text(encoding="utf-8") + cls.enqueue = ENQUEUE.read_text(encoding="utf-8") + + def test_default_branch_has_immediate_and_recovery_delivery(self) -> None: + for marker in ( + "workflow_run:", + "workflow_dispatch:", + "schedule:", + "Recover missed workflow-run delivery from protected main", + "gh workflow run attest-and-approve.yml", + ): + self.assertIn(marker, self.attestor) + + def test_dispatch_tuple_is_revalidated(self) -> None: + for marker in ( + "dispatch pull_request is not numeric", + "dispatch head_sha is not canonical", + "dispatch gate_run_id is not numeric", + "dispatch source is not FORGE-9 gates", + "dispatch source head mismatch", + "dispatch PR head mismatch", + "dispatch PR is cross-repository", + ): + self.assertIn(marker, self.attestor) + + def test_qillqaq_remains_default_branch_only(self) -> None: + self.assertIn("secrets.QILLQAQ_PRIVATE_KEY", self.attestor) + self.assertNotIn("pull_request_target", self.attestor) + self.assertNotIn("QILLQAQ_PRIVATE_KEY", self.enqueue) + + def test_attestor_never_merges_or_enqueues(self) -> None: + self.assertNotIn("gh pr merge", self.attestor) + self.assertNotIn("enqueuePullRequest", self.attestor) + self.assertIn("enqueuePullRequest", self.enqueue) + self.assertIn("Hand off to protected native enqueue controller", self.attestor) + + def test_recovery_receipt_contains_no_secret_material(self) -> None: + for marker in ( + "default_branch_code: true", + "pull_request_target_used: false", + "custom_secret_value_recorded: false", + "retention-days: 90", + ): + self.assertIn(marker, self.attestor) + self.assertNotIn("set -x", self.attestor) + + +if __name__ == "__main__": + unittest.main(verbosity=2) +''' + + +def patch_verifier(source: str) -> str: + source = source.replace( + r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging)' + r'\.ya?ml|\.governance/)', + r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)' + r'\.ya?ml|\.governance/)', + 1, + ) + source = source.replace( + ' ".github/workflows/forge9-staging.yaml",\n' + ' ".governance/gates.json",\n', + ' ".github/workflows/forge9-staging.yaml",\n' + ' ".github/workflows/merge-queue-enqueue.yml",\n' + ' ".github/workflows/merge-queue-enqueue.yaml",\n' + ' ".governance/gates.json",\n', + 1, + ) + old = ''' if GOVERNED_BASE_FILTER not in attestor_template: + fail("attestor must resolve PRs targeting main and release/*") + if "environment: production" in attestor_template: + fail("the merge attestor must not consume the production deployment gate") + if "GH_TOKEN: ${{ github.token }}" not in attestor_template: + fail("the protected queue request must use the ephemeral workflow token") + for marker in ( + "Publish required App attestation status", + "context=attestation/qillqaq", + "GH_TOKEN: ${{ steps.app-token.outputs.token }}", + "client-id: ${{ vars.QILLQAQ_CLIENT_ID }}", + 'SOURCE_EVENT: ${{ github.event.workflow_run.event }}', + 'if [ "$SOURCE_EVENT" = "merge_group" ]; then', + '[[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]]', + "subject_kind: $subject_kind", + ): + if marker not in attestor_template: + fail(f"attestor status publication is missing {marker!r}") + if "app-id:" in attestor_template: + fail("the attestor must use the supported GitHub App client-id input") + if attestor_template.count( + "if: steps.subject.outputs.kind == 'pull_request'" + ) != 2: + fail("only PR-head attestations may approve or request a queue entry") + if 'gh pr merge "$PR" --repo "$REPOSITORY" --auto --squash' not in ( + attestor_template + ): + fail("the attestor must use GitHub's supported merge-queue CLI path") +''' + new = ''' if GOVERNED_BASE_FILTER not in attestor_template: + fail("attestor must resolve PRs targeting main and release/*") + if "environment: production" in attestor_template: + fail("the merge attestor must not consume the production deployment gate") + enqueue_template = ( + ROOT / ".github/workflows/merge-queue-enqueue.yml" + ).read_text(encoding="utf-8") + for marker in ( + "Publish required App attestation status", + "context=attestation/qillqaq", + "GH_TOKEN: ${{ steps.app-token.outputs.token }}", + "client-id: ${{ vars.QILLQAQ_CLIENT_ID }}", + "workflow_dispatch:", + "schedule:", + "Recover missed workflow-run delivery from protected main", + "gh workflow run attest-and-approve.yml", + "SUBJECT_HEAD_SHA: ${{ github.event.workflow_run.head_sha || inputs.head_sha }}", + "DISPATCH_PR: ${{ inputs.pull_request || '' }}", + "source_workflow_run_id", + 'if [ "$SOURCE_EVENT" = "merge_group" ]; then', + '[[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]]', + "subject_kind: $subject_kind", + "Hand off to protected native enqueue controller", + ): + if marker not in attestor_template: + fail(f"attestor delivery contract is missing {marker!r}") + if "app-id:" in attestor_template: + fail("the attestor must use the supported GitHub App client-id input") + if attestor_template.count( + "if: steps.subject.outputs.kind == 'pull_request'" + ) != 2: + fail("only PR-head attestations may approve or hand off to native enqueue") + if "gh pr merge" in attestor_template or "enqueuePullRequest" in attestor_template: + fail("the attestor must not merge or enqueue; native enqueue owns that mutation") + if "enqueuePullRequest" not in enqueue_template: + fail("the protected native enqueue controller is missing") + if "pull_request_target" in attestor_template: + fail("attestor recovery must never use pull_request_target") +''' + if old not in source: + raise SystemExit("governance verifier contract changed unexpectedly") + return source.replace(old, new, 1) + + +def patch_tests_workflow(source: str) -> str: + marker = ( + " - name: HF deploy-from-Dockerfile derivation self-test\n" + " run: python3 .github/scripts/test_hf_deploy_from_dockerfile.py\n" + ) + if marker not in source: + raise SystemExit("tests workflow tail changed unexpectedly") + return source.replace( + marker, + marker + + "\n # Pins protected-main recovery when workflow_run delivery is omitted.\n" + + " - name: Attestor delivery recovery contract self-test\n" + + " run: python3 .github/scripts/test_attestor_delivery.py\n", + 1, + ) + + +def request(url: str, *, data: dict[str, Any] | None = None) -> Any: + req = urllib.request.Request( + url, + data=(json.dumps(data).encode("utf-8") if data is not None else None), + method=("POST" if data is not None else "GET"), + headers={ + "Authorization": f"Bearer {TOKEN}", + "Accept": "application/vnd.github+json", + "Content-Type": "application/json", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "szl-attestor-delivery-materializer/2", + }, + ) + try: + with urllib.request.urlopen(req, timeout=90) as response: + return json.loads(response.read()) + except urllib.error.HTTPError as exc: + body = exc.read().decode("utf-8", "replace")[:2000] + raise SystemExit(f"HTTP {exc.code}: {body}") from exc + + +def main() -> int: + ATTESTOR_PATH.write_text(ATTESTOR, encoding="utf-8") + CONTRACT_PATH.write_text(CONTRACT, encoding="utf-8") + VERIFIER_PATH.write_text( + patch_verifier(VERIFIER_PATH.read_text(encoding="utf-8")), + encoding="utf-8", + ) + TESTS_WORKFLOW_PATH.write_text( + patch_tests_workflow(TESTS_WORKFLOW_PATH.read_text(encoding="utf-8")), + encoding="utf-8", + ) + + subprocess.run( + [ + "python", + "-m", + "py_compile", + str(VERIFIER_PATH), + str(CONTRACT_PATH), + ], + cwd=ROOT, + check=True, + ) + subprocess.run(["python", str(CONTRACT_PATH)], cwd=ROOT, check=True) + subprocess.run(["python", str(VERIFIER_PATH)], cwd=ROOT, check=True) + subprocess.run(["git", "diff", "--check"], cwd=ROOT, check=True) + + additions = [ + ".github/workflows/attest-and-approve.yml", + ".github/scripts/verify_forge9_governance.py", + ".github/scripts/test_attestor_delivery.py", + ".github/workflows/tests.yml", + ] + mutation = ''' + mutation($input: CreateCommitOnBranchInput!) { + createCommitOnBranch(input: $input) { + commit { oid url } + } + } + ''' + variables = { + "input": { + "branch": { + "repositoryNameWithOwner": REPOSITORY, + "branchName": TARGET_BRANCH, + }, + "message": { + "headline": "fix(attestor): recover missed gate delivery", + "body": "Signed-off-by: Stephen Lutar ", + }, + "expectedHeadOid": EXPECTED_PARENT, + "fileChanges": { + "additions": [ + { + "path": path, + "contents": base64.b64encode((ROOT / path).read_bytes()).decode( + "ascii" + ), + } + for path in additions + ] + }, + } + } + payload = request( + "https://api.github.com/graphql", + data={"query": mutation, "variables": variables}, + ) + if payload.get("errors"): + raise SystemExit(json.dumps(payload["errors"], indent=2)) + commit = payload["data"]["createCommitOnBranch"]["commit"] + sha = commit["oid"] + + verification = request(f"https://api.github.com/repos/{REPOSITORY}/commits/{sha}")[ + "commit" + ]["verification"] + if verification.get("verified") is not True or verification.get("reason") != "valid": + raise SystemExit(f"target commit is not GitHub-verified: {verification}") + + receipt = { + "schema": "szl.attestor-delivery-materialization/v1", + "parent": EXPECTED_PARENT, + "target_branch": TARGET_BRANCH, + "commit": commit, + "verification": { + "verified": verification.get("verified"), + "reason": verification.get("reason"), + }, + "files": additions, + "diagnostic_controller_included": False, + } + (ROOT / "signed-attestor-delivery.json").write_text( + json.dumps(receipt, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + print(json.dumps(receipt, indent=2, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 2a2fc24a42f90a99a77dea4038d19c5bba67f402 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:19:07 -0400 Subject: [PATCH 05/10] fix(attestor): execute signed materializer from source Signed-off-by: Stephen Lutar --- .../materialize-attestor-delivery-repair.yml | 577 +----------------- 1 file changed, 5 insertions(+), 572 deletions(-) diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml index 6318e98..d20a2eb 100644 --- a/.github/workflows/materialize-attestor-delivery-repair.yml +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -5,6 +5,7 @@ on: branches: [main] paths: - .github/workflows/materialize-attestor-delivery-repair.yml + - .github/scripts/materialize_attestor_delivery_repair.py permissions: contents: write @@ -28,583 +29,15 @@ jobs: TARGET_BRANCH: fix/attestor-delivery-recovery-v1-signed EXPECTED_PARENT: 5aa61bb92ba4a1cd1b59c09c4f82b902d1bf7917 steps: - - name: Checkout controller and protected source + - name: Checkout exact recovery source uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: + ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false fetch-depth: 1 - - name: Verify target lineage - run: | - set -euo pipefail - test "$(gh api "repos/$REPOSITORY/branches/main" --jq .commit.sha)" = "$EXPECTED_PARENT" - test "$(gh api "repos/$REPOSITORY/branches/$TARGET_BRANCH" --jq .commit.sha)" = "$EXPECTED_PARENT" - - - name: Build protected default-branch attestor recovery - shell: python - run: | - from __future__ import annotations - - from pathlib import Path - - attestor_path = Path('.github/workflows/attest-and-approve.yml') - verifier_path = Path('.github/scripts/verify_forge9_governance.py') - tests_path = Path('.github/workflows/tests.yml') - contract_path = Path('.github/scripts/test_attestor_delivery.py') - - attestor = attestor_path.read_text(encoding='utf-8') - old_trigger = '''on: - workflow_run: - workflows: - - FORGE-9 gates - types: - - completed - '''.replace(' ', '') - new_trigger = '''on: - workflow_run: - workflows: - - FORGE-9 gates - types: - - completed - workflow_dispatch: - inputs: - pull_request: - description: "Exact open pull request number" - required: true - type: string - head_sha: - description: "Exact 40-character pull request head SHA" - required: true - type: string - gate_run_id: - description: "Successful FORGE-9 gates workflow run ID for the exact head" - required: true - type: string - schedule: - - cron: "*/5 * * * *" - '''.replace(' ', '') - if old_trigger not in attestor: - raise SystemExit('attestor trigger block changed unexpectedly') - attestor = attestor.replace(old_trigger, new_trigger, 1) - attestor = attestor.replace( - 'permissions:\n actions: read\n', - 'permissions:\n actions: write\n', - 1, - ) - attestor = attestor.replace( - ' group: forge9-attest-${{ github.event.workflow_run.head_sha }}', - ' group: forge9-attest-${{ github.event.workflow_run.head_sha || inputs.head_sha || github.run_id }}', - 1, - ) - old_job = ''' attest: - if: github.event.workflow_run.conclusion == 'success' - runs-on: ubuntu-latest - steps: - '''.replace(' ', '') - new_job = ''' attest: - if: >- - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') || - github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - timeout-minutes: 30 - env: - SUBJECT_HEAD_SHA: ${{ github.event.workflow_run.head_sha || inputs.head_sha }} - SUBJECT_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch || '' }} - SUBJECT_SOURCE_EVENT: ${{ github.event.workflow_run.event || 'pull_request' }} - SUBJECT_SOURCE_RUN_ID: ${{ github.event.workflow_run.id || inputs.gate_run_id }} - steps: - '''.replace(' ', '') - if old_job not in attestor: - raise SystemExit('attestor job header changed unexpectedly') - attestor = attestor.replace(old_job, new_job, 1) - old_resolve_env = ''' HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} - SOURCE_EVENT: ${{ github.event.workflow_run.event }} - REPOSITORY: ${{ github.repository }} - '''.replace(' ', '') - new_resolve_env = ''' HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} - HEAD_BRANCH: ${{ env.SUBJECT_HEAD_BRANCH }} - SOURCE_EVENT: ${{ env.SUBJECT_SOURCE_EVENT }} - SOURCE_RUN_ID: ${{ env.SUBJECT_SOURCE_RUN_ID }} - DISPATCH_PR: ${{ inputs.pull_request || '' }} - REPOSITORY: ${{ github.repository }} - '''.replace(' ', '') - if old_resolve_env not in attestor: - raise SystemExit('resolve env block changed unexpectedly') - attestor = attestor.replace(old_resolve_env, new_resolve_env, 1) - old_resolve_start = ''' set -euo pipefail - if [ "$SOURCE_EVENT" = "merge_group" ]; then - '''.replace(' ', '') - new_resolve_start = ''' set -euo pipefail - if [ -n "$DISPATCH_PR" ]; then - [[ "$DISPATCH_PR" =~ ^[0-9]+$ ]] \\ - || { echo "::error::dispatch pull_request is not numeric"; exit 1; } - [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] \\ - || { echo "::error::dispatch head_sha is not canonical"; exit 1; } - [[ "$SOURCE_RUN_ID" =~ ^[0-9]+$ ]] \\ - || { echo "::error::dispatch gate_run_id is not numeric"; exit 1; } - - gh api "repos/$REPOSITORY/actions/runs/$SOURCE_RUN_ID" \\ - > "$RUNNER_TEMP/source-run.json" - [ "$(jq -r .name "$RUNNER_TEMP/source-run.json")" = "FORGE-9 gates" ] \\ - || { echo "::error::dispatch source is not FORGE-9 gates"; exit 1; } - [ "$(jq -r .event "$RUNNER_TEMP/source-run.json")" = "pull_request" ] \\ - || { echo "::error::dispatch source event is not pull_request"; exit 1; } - [ "$(jq -r .conclusion "$RUNNER_TEMP/source-run.json")" = "success" ] \\ - || { echo "::error::dispatch source run is not successful"; exit 1; } - [ "$(jq -r .head_sha "$RUNNER_TEMP/source-run.json")" = "$HEAD_SHA" ] \\ - || { echo "::error::dispatch source head mismatch"; exit 1; } - - gh api "repos/$REPOSITORY/pulls/$DISPATCH_PR" > "$RUNNER_TEMP/pr.json" - [ "$(jq -r .state "$RUNNER_TEMP/pr.json")" = "open" ] \\ - || { echo "::error::dispatch PR is not open"; exit 1; } - [ "$(jq -r .draft "$RUNNER_TEMP/pr.json")" = "false" ] \\ - || { echo "::error::dispatch PR is draft"; exit 1; } - [ "$(jq -r .head.sha "$RUNNER_TEMP/pr.json")" = "$HEAD_SHA" ] \\ - || { echo "::error::dispatch PR head mismatch"; exit 1; } - [ "$(jq -r .head.repo.full_name "$RUNNER_TEMP/pr.json")" = "$REPOSITORY" ] \\ - || { echo "::error::dispatch PR is cross-repository"; exit 1; } - BASE_REF="$(jq -r .base.ref "$RUNNER_TEMP/pr.json")" - { [ "$BASE_REF" = "main" ] || [[ "$BASE_REF" == release/* ]]; } \\ - || { echo "::error::dispatch PR base is not governed"; exit 1; } - echo "kind=pull_request" >> "$GITHUB_OUTPUT" - echo "number=$DISPATCH_PR" >> "$GITHUB_OUTPUT" - echo "node_id=$(jq -r .node_id "$RUNNER_TEMP/pr.json")" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [ "$SOURCE_EVENT" = "merge_group" ]; then - '''.replace(' ', '') - if old_resolve_start not in attestor: - raise SystemExit('resolve shell changed unexpectedly') - attestor = attestor.replace(old_resolve_start, new_resolve_start, 1) - attestor = attestor.replace( - 'HEAD_SHA: ${{ github.event.workflow_run.head_sha }}', - 'HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }}', - ) - attestor = attestor.replace( - 'name: merge-receipt-${{ github.event.workflow_run.head_sha }}', - 'name: merge-receipt-${{ env.SUBJECT_HEAD_SHA }}', - 1, - ) - attestor = attestor.replace( - ' RUN_ID: ${{ github.run_id }}\n', - ' SOURCE_RUN_ID: ${{ env.SUBJECT_SOURCE_RUN_ID }}\n' - ' DELIVERY_MODE: ${{ github.event_name }}\n' - ' ATTESTOR_RUN_ID: ${{ github.run_id }}\n', - 1, - ) - attestor = attestor.replace( - ' --arg run_id "$RUN_ID" \\\n', - ' --arg source_run_id "$SOURCE_RUN_ID" \\\n' - ' --arg delivery_mode "$DELIVERY_MODE" \\\n' - ' --arg attestor_run_id "$ATTESTOR_RUN_ID" \\\n', - 1, - ) - attestor = attestor.replace( - ' workflow_run_id: $run_id,\n', - ' source_workflow_run_id: $source_run_id,\n' - ' attestor_run_id: $attestor_run_id,\n' - ' delivery_mode: $delivery_mode,\n', - 1, - ) - attestor = attestor.replace( - r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging)\.ya?ml|\.governance/)', - r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)\.ya?ml|\.governance/)', - 1, - ) - old_tail = ''' - name: Request protected merge after attestation - if: steps.subject.outputs.kind == 'pull_request' - env: - GH_TOKEN: ${{ github.token }} - PR: ${{ steps.subject.outputs.number }} - REPOSITORY: ${{ github.repository }} - run: | - set -euo pipefail - gh pr merge "$PR" --repo "$REPOSITORY" --auto --squash - echo "Requested the protected merge path for PR $PR" - '''.replace(' ', '') - new_tail = ''' - name: Hand off to protected native enqueue controller - if: steps.subject.outputs.kind == 'pull_request' - env: - PR: ${{ steps.subject.outputs.number }} - HEAD_SHA: ${{ env.SUBJECT_HEAD_SHA }} - run: | - set -euo pipefail - echo "Exact-head qillqaq evidence is published for PR $PR at $HEAD_SHA." - echo "Protected Merge Queue Enqueue owns native enqueuePullRequest delivery." - - recover-missed-delivery: - name: Recover missed workflow-run delivery from protected main - if: github.event_name == 'schedule' - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Mint qillqaq read identity - id: recovery-app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - client-id: ${{ vars.QILLQAQ_CLIENT_ID }} - private-key: ${{ secrets.QILLQAQ_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: ${{ github.event.repository.name }} - - - name: Dispatch exact heads whose workflow-run delivery was missed - env: - READ_TOKEN: ${{ steps.recovery-app-token.outputs.token }} - DISPATCH_TOKEN: ${{ github.token }} - REPOSITORY: ${{ github.repository }} - run: | - set -euo pipefail - mkdir -p "$RUNNER_TEMP/attestor-recovery" - GH_TOKEN="$READ_TOKEN" gh api \\ - "repos/$REPOSITORY/pulls?state=open&per_page=100" \\ - --paginate --slurp > "$RUNNER_TEMP/attestor-recovery/pull-pages.json" - jq -c --arg repo "$REPOSITORY" '[.[][] | select( - .draft == false - and .head.repo.full_name == $repo - and (.base.ref == "main" or (.base.ref | startswith("release/"))) - )] | .[]' "$RUNNER_TEMP/attestor-recovery/pull-pages.json" \\ - > "$RUNNER_TEMP/attestor-recovery/targets.jsonl" - - dispatched=0 - skipped=0 - while IFS= read -r target; do - PR="$(jq -r .number <<<"$target")" - HEAD_SHA="$(jq -r .head.sha <<<"$target")" - GH_TOKEN="$READ_TOKEN" gh api \\ - "repos/$REPOSITORY/pulls/$PR/reviews?per_page=100" \\ - > "$RUNNER_TEMP/attestor-recovery/reviews-$PR.json" - APPROVALS="$(jq --arg head "$HEAD_SHA" '[.[] | select( - (.user.login == "qillqaq-attestor[bot]" or .user.login == "qillqaq-attestor") - and .state == "APPROVED" - and .commit_id == $head - )] | length' "$RUNNER_TEMP/attestor-recovery/reviews-$PR.json")" - GH_TOKEN="$READ_TOKEN" gh api \\ - "repos/$REPOSITORY/commits/$HEAD_SHA/status" \\ - > "$RUNNER_TEMP/attestor-recovery/status-$PR.json" - STATUSES="$(jq '[.statuses[] | select( - .context == "attestation/qillqaq" and .state == "success" - )] | length' "$RUNNER_TEMP/attestor-recovery/status-$PR.json")" - if [ "$APPROVALS" -ge 1 ] && [ "$STATUSES" -ge 1 ]; then - skipped=$((skipped + 1)) - continue - fi - - GH_TOKEN="$READ_TOKEN" gh api \\ - "repos/$REPOSITORY/actions/workflows/gates.yml/runs?event=pull_request&head_sha=$HEAD_SHA&per_page=100" \\ - > "$RUNNER_TEMP/attestor-recovery/gates-$PR.json" - GATE_RUN_ID="$(jq -r --arg head "$HEAD_SHA" --arg repo "$REPOSITORY" '[ - .workflow_runs[] | select( - .head_sha == $head - and .event == "pull_request" - and .conclusion == "success" - and .head_repository.full_name == $repo - ) - ] | sort_by(.created_at) | last | .id // empty' \\ - "$RUNNER_TEMP/attestor-recovery/gates-$PR.json")" - if [ -z "$GATE_RUN_ID" ]; then - skipped=$((skipped + 1)) - continue - fi - GH_TOKEN="$DISPATCH_TOKEN" gh workflow run attest-and-approve.yml \\ - --repo "$REPOSITORY" \\ - --ref main \\ - -f pull_request="$PR" \\ - -f head_sha="$HEAD_SHA" \\ - -f gate_run_id="$GATE_RUN_ID" - dispatched=$((dispatched + 1)) - done < "$RUNNER_TEMP/attestor-recovery/targets.jsonl" - - jq -n \\ - --arg schema 'szl.attestor-delivery-recovery/v1' \\ - --argjson dispatched "$dispatched" \\ - --argjson skipped "$skipped" \\ - '{ - schema: $schema, - dispatched: $dispatched, - skipped: $skipped, - default_branch_code: true, - pull_request_target_used: false, - custom_secret_value_recorded: false - }' > attestor-delivery-recovery.json - cat attestor-delivery-recovery.json - - - name: Upload immutable recovery receipt - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: attestor-delivery-recovery-${{ github.run_id }} - path: attestor-delivery-recovery.json - if-no-files-found: error - retention-days: 90 - '''.replace(' ', '') - if old_tail not in attestor: - raise SystemExit('attestor tail changed unexpectedly') - attestor = attestor.replace(old_tail, new_tail, 1) - attestor_path.write_text(attestor, encoding='utf-8') - - verifier = verifier_path.read_text(encoding='utf-8') - verifier = verifier.replace( - r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging)' - r'\.ya?ml|\.governance/)', - r'^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)' - r'\.ya?ml|\.governance/)', - 1, - ) - verifier = verifier.replace( - ' ".github/workflows/forge9-staging.yaml",\n' - ' ".governance/gates.json",\n', - ' ".github/workflows/forge9-staging.yaml",\n' - ' ".github/workflows/merge-queue-enqueue.yml",\n' - ' ".github/workflows/merge-queue-enqueue.yaml",\n' - ' ".governance/gates.json",\n', - 1, - ) - old_verifier = ''' if GOVERNED_BASE_FILTER not in attestor_template: - fail("attestor must resolve PRs targeting main and release/*") - if "environment: production" in attestor_template: - fail("the merge attestor must not consume the production deployment gate") - if "GH_TOKEN: ${{ github.token }}" not in attestor_template: - fail("the protected queue request must use the ephemeral workflow token") - for marker in ( - "Publish required App attestation status", - "context=attestation/qillqaq", - "GH_TOKEN: ${{ steps.app-token.outputs.token }}", - "client-id: ${{ vars.QILLQAQ_CLIENT_ID }}", - 'SOURCE_EVENT: ${{ github.event.workflow_run.event }}', - 'if [ "$SOURCE_EVENT" = "merge_group" ]; then', - '[[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]]', - "subject_kind: $subject_kind", - ): - if marker not in attestor_template: - fail(f"attestor status publication is missing {marker!r}") - if "app-id:" in attestor_template: - fail("the attestor must use the supported GitHub App client-id input") - if attestor_template.count( - "if: steps.subject.outputs.kind == 'pull_request'" - ) != 2: - fail("only PR-head attestations may approve or request a queue entry") - if 'gh pr merge "$PR" --repo "$REPOSITORY" --auto --squash' not in ( - attestor_template - ): - fail("the attestor must use GitHub's supported merge-queue CLI path") - '''.replace(' ', '') - new_verifier = ''' if GOVERNED_BASE_FILTER not in attestor_template: - fail("attestor must resolve PRs targeting main and release/*") - if "environment: production" in attestor_template: - fail("the merge attestor must not consume the production deployment gate") - enqueue_template = ( - ROOT / ".github/workflows/merge-queue-enqueue.yml" - ).read_text(encoding="utf-8") - for marker in ( - "Publish required App attestation status", - "context=attestation/qillqaq", - "GH_TOKEN: ${{ steps.app-token.outputs.token }}", - "client-id: ${{ vars.QILLQAQ_CLIENT_ID }}", - "workflow_dispatch:", - "schedule:", - "Recover missed workflow-run delivery from protected main", - "gh workflow run attest-and-approve.yml", - "SUBJECT_HEAD_SHA: ${{ github.event.workflow_run.head_sha || inputs.head_sha }}", - "DISPATCH_PR: ${{ inputs.pull_request || '' }}", - "source_workflow_run_id", - 'if [ "$SOURCE_EVENT" = "merge_group" ]; then', - '[[ "$HEAD_BRANCH" == gh-readonly-queue/main/* ]]', - "subject_kind: $subject_kind", - "Hand off to protected native enqueue controller", - ): - if marker not in attestor_template: - fail(f"attestor delivery contract is missing {marker!r}") - if "app-id:" in attestor_template: - fail("the attestor must use the supported GitHub App client-id input") - if attestor_template.count( - "if: steps.subject.outputs.kind == 'pull_request'" - ) != 2: - fail("only PR-head attestations may approve or hand off to native enqueue") - if "gh pr merge" in attestor_template or "enqueuePullRequest" in attestor_template: - fail("the attestor must not merge or enqueue; the protected enqueue controller owns that mutation") - if "enqueuePullRequest" not in enqueue_template: - fail("the protected native enqueue controller is missing") - if "pull_request_target" in attestor_template: - fail("attestor delivery recovery must never use pull_request_target") - '''.replace(' ', '') - if old_verifier not in verifier: - raise SystemExit('governance verifier block changed unexpectedly') - verifier = verifier.replace(old_verifier, new_verifier, 1) - verifier_path.write_text(verifier, encoding='utf-8') - - contract = '''#!/usr/bin/env python3 - from __future__ import annotations - - from pathlib import Path - import unittest - - ROOT = Path(__file__).resolve().parents[2] - ATTESTOR = ROOT / ".github/workflows/attest-and-approve.yml" - ENQUEUE = ROOT / ".github/workflows/merge-queue-enqueue.yml" - - - class AttestorDeliveryTests(unittest.TestCase): - @classmethod - def setUpClass(cls) -> None: - cls.attestor = ATTESTOR.read_text(encoding="utf-8") - cls.enqueue = ENQUEUE.read_text(encoding="utf-8") - - def test_default_branch_has_immediate_and_recovery_delivery(self) -> None: - for marker in ( - "workflow_run:", - "workflow_dispatch:", - "schedule:", - "Recover missed workflow-run delivery from protected main", - "gh workflow run attest-and-approve.yml", - ): - self.assertIn(marker, self.attestor) - - def test_dispatch_tuple_is_revalidated(self) -> None: - for marker in ( - "dispatch pull_request is not numeric", - "dispatch head_sha is not canonical", - "dispatch gate_run_id is not numeric", - "dispatch source is not FORGE-9 gates", - "dispatch source head mismatch", - "dispatch PR head mismatch", - "dispatch PR is cross-repository", - ): - self.assertIn(marker, self.attestor) - - def test_qillqaq_remains_default_branch_only(self) -> None: - self.assertIn("secrets.QILLQAQ_PRIVATE_KEY", self.attestor) - self.assertNotIn("pull_request_target", self.attestor) - self.assertNotIn("QILLQAQ_PRIVATE_KEY", self.enqueue) - - def test_attestor_never_merges_or_enqueues(self) -> None: - self.assertNotIn("gh pr merge", self.attestor) - self.assertNotIn("enqueuePullRequest", self.attestor) - self.assertIn("enqueuePullRequest", self.enqueue) - self.assertIn("Hand off to protected native enqueue controller", self.attestor) - - def test_recovery_receipt_contains_no_secret_material(self) -> None: - for marker in ( - "default_branch_code: true", - "pull_request_target_used: false", - "custom_secret_value_recorded: false", - "retention-days: 90", - ): - self.assertIn(marker, self.attestor) - self.assertNotIn("set -x", self.attestor) - - - if __name__ == "__main__": - unittest.main(verbosity=2) - '''.replace(' ', '') - contract_path.write_text(contract, encoding='utf-8') - - tests = tests_path.read_text(encoding='utf-8') - marker = ' - name: HF deploy-from-Dockerfile derivation self-test\n run: python3 .github/scripts/test_hf_deploy_from_dockerfile.py\n' - if marker not in tests: - raise SystemExit('tests workflow tail changed unexpectedly') - tests = tests.replace( - marker, - marker - + '\n # Pins default-branch recovery when GitHub omits a workflow_run delivery.\n' - + ' - name: Attestor delivery recovery contract self-test\n' - + ' run: python3 .github/scripts/test_attestor_delivery.py\n', - 1, - ) - tests_path.write_text(tests, encoding='utf-8') - - - name: Validate generated governance repair - run: | - set -euo pipefail - python -m py_compile \ - .github/scripts/verify_forge9_governance.py \ - .github/scripts/test_attestor_delivery.py - python .github/scripts/test_attestor_delivery.py - python .github/scripts/verify_forge9_governance.py - git diff --check - - - name: Create one GitHub-signed protected repair commit - id: commit - shell: python - run: | - import base64 - import json - import os - import urllib.error - import urllib.request - from pathlib import Path - - additions = [ - '.github/workflows/attest-and-approve.yml', - '.github/scripts/verify_forge9_governance.py', - '.github/scripts/test_attestor_delivery.py', - '.github/workflows/tests.yml', - ] - query = ''' - mutation($input: CreateCommitOnBranchInput!) { - createCommitOnBranch(input: $input) { - commit { oid url } - } - } - ''' - variables = { - 'input': { - 'branch': { - 'repositoryNameWithOwner': os.environ['REPOSITORY'], - 'branchName': os.environ['TARGET_BRANCH'], - }, - 'message': { - 'headline': 'fix(attestor): recover missed gate delivery', - 'body': 'Signed-off-by: Stephen Lutar ', - }, - 'expectedHeadOid': os.environ['EXPECTED_PARENT'], - 'fileChanges': { - 'additions': [ - { - 'path': path, - 'contents': base64.b64encode(Path(path).read_bytes()).decode('ascii'), - } - for path in additions - ], - }, - } - } - request = urllib.request.Request( - 'https://api.github.com/graphql', - data=json.dumps({'query': query, 'variables': variables}).encode('utf-8'), - method='POST', - headers={ - 'Authorization': f"Bearer {os.environ['GH_TOKEN']}", - 'Accept': 'application/vnd.github+json', - 'Content-Type': 'application/json', - 'X-GitHub-Api-Version': '2022-11-28', - 'User-Agent': 'szl-attestor-delivery-materializer/1', - }, - ) - try: - with urllib.request.urlopen(request, timeout=90) as response: - payload = json.loads(response.read()) - except urllib.error.HTTPError as exc: - raise SystemExit(f'GraphQL HTTP {exc.code}') from exc - if payload.get('errors'): - print(json.dumps(payload['errors'], indent=2)) - raise SystemExit(1) - commit = payload['data']['createCommitOnBranch']['commit'] - Path('signed-attestor-delivery.json').write_text( - json.dumps(commit, indent=2, sort_keys=True) + '\n', encoding='utf-8' - ) - print(json.dumps(commit, indent=2, sort_keys=True)) - with open(os.environ['GITHUB_OUTPUT'], 'a', encoding='utf-8') as output: - output.write(f"sha={commit['oid']}\n") - - - name: Verify signed target commit - env: - COMMIT_SHA: ${{ steps.commit.outputs.sha }} - run: | - set -euo pipefail - test "$(gh api "repos/$REPOSITORY/branches/$TARGET_BRANCH" --jq .commit.sha)" = "$COMMIT_SHA" - test "$(gh api "repos/$REPOSITORY/commits/$COMMIT_SHA" --jq .commit.verification.verified)" = 'true' - test "$(gh api "repos/$REPOSITORY/commits/$COMMIT_SHA" --jq .commit.verification.reason)" = 'valid' + - name: Build, test, and publish the signed permanent tree + run: python .github/scripts/materialize_attestor_delivery_repair.py - name: Upload immutable materialization receipt uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 From 921b7f9aa960a3f458728c7372464366ecdbbb45 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:20:38 -0400 Subject: [PATCH 06/10] ci(attestor): preserve materializer failure evidence Signed-off-by: Stephen Lutar --- .../materialize-attestor-delivery-repair.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml index d20a2eb..676ac0d 100644 --- a/.github/workflows/materialize-attestor-delivery-repair.yml +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -37,12 +37,19 @@ jobs: fetch-depth: 1 - name: Build, test, and publish the signed permanent tree - run: python .github/scripts/materialize_attestor_delivery_repair.py + shell: bash + run: | + set -o pipefail + python .github/scripts/materialize_attestor_delivery_repair.py \ + 2>&1 | tee signed-attestor-delivery.log - - name: Upload immutable materialization receipt + - name: Upload immutable materialization evidence + if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: signed-attestor-delivery-${{ github.run_id }} - path: signed-attestor-delivery.json + path: | + signed-attestor-delivery.log + signed-attestor-delivery.json if-no-files-found: error retention-days: 90 From fca953b333934182f7a6d62f9f3d2085baf07a14 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:23:57 -0400 Subject: [PATCH 07/10] fix(attestor): narrow forbidden-trigger contract Signed-off-by: Stephen Lutar --- .../materialize-attestor-delivery-repair.yml | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml index 676ac0d..02e9bc6 100644 --- a/.github/workflows/materialize-attestor-delivery-repair.yml +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -36,6 +36,34 @@ jobs: persist-credentials: false fetch-depth: 1 + - name: Reconcile exact source-contract predicates + shell: python + run: | + from pathlib import Path + + path = Path('.github/scripts/materialize_attestor_delivery_repair.py') + source = path.read_text(encoding='utf-8') + replacements = ( + ( + 'echo "Protected Merge Queue Enqueue owns native enqueuePullRequest delivery."', + 'echo "Protected Merge Queue Enqueue owns the native queue mutation."', + ), + ( + 'self.assertNotIn("pull_request_target", self.attestor)', + 'self.assertNotIn("\\n pull_request_target:", self.attestor)', + ), + ( + 'if "pull_request_target" in attestor_template:', + 'if "\\n pull_request_target:" in attestor_template:', + ), + ) + for old, new in replacements: + count = source.count(old) + if count != 1: + raise SystemExit(f'expected one exact source-contract marker, found {count}: {old}') + source = source.replace(old, new, 1) + path.write_text(source, encoding='utf-8') + - name: Build, test, and publish the signed permanent tree shell: bash run: | From 3381012dfcedf52c44e0ae99ca347216726a2315 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:26:17 -0400 Subject: [PATCH 08/10] fix(attestor): detect only executable privileged trigger Signed-off-by: Stephen Lutar --- .github/workflows/materialize-attestor-delivery-repair.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml index 02e9bc6..1ec8055 100644 --- a/.github/workflows/materialize-attestor-delivery-repair.yml +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -50,11 +50,11 @@ jobs: ), ( 'self.assertNotIn("pull_request_target", self.attestor)', - 'self.assertNotIn("\\n pull_request_target:", self.attestor)', + 'self.assertNotIn("pull_request_target:", self.attestor)', ), ( 'if "pull_request_target" in attestor_template:', - 'if "\\n pull_request_target:" in attestor_template:', + 'if "pull_request_target:" in attestor_template:', ), ) for old, new in replacements: From 608c62e1c6b9451501b935d5c5d5134a6e785ea3 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:29:23 -0400 Subject: [PATCH 09/10] fix(attestor): patch verifier guard as source text Signed-off-by: Stephen Lutar --- .../materialize-attestor-delivery-repair.yml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml index 1ec8055..1cd45b3 100644 --- a/.github/workflows/materialize-attestor-delivery-repair.yml +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -62,6 +62,31 @@ jobs: if count != 1: raise SystemExit(f'expected one exact source-contract marker, found {count}: {old}') source = source.replace(old, new, 1) + + old_guard_patch = ''' source = source.replace( + r'^(\\.github/workflows/(attest-and-approve|gates|forge9-staging)' + r'\\.ya?ml|\\.governance/)', + r'^(\\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)' + r'\\.ya?ml|\\.governance/)', + 1, + ) + ''' + new_guard_patch = ''' old_guard = r''' + "'''" + '''ATTESTOR_SELF_EDIT_GUARD = ( + r"^(\\.github/workflows/(attest-and-approve|gates|forge9-staging)" + r"\\.ya?ml|\\.governance/)" + )''' + "'''" + ''' + new_guard = r''' + "'''" + '''ATTESTOR_SELF_EDIT_GUARD = ( + r"^(\\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)" + r"\\.ya?ml|\\.governance/)" + )''' + "'''" + ''' + if old_guard not in source: + raise SystemExit("governance verifier self-edit guard changed unexpectedly") + source = source.replace(old_guard, new_guard, 1) + ''' + count = source.count(old_guard_patch) + if count != 1: + raise SystemExit(f'expected one verifier guard patch block, found {count}') + source = source.replace(old_guard_patch, new_guard_patch, 1) path.write_text(source, encoding='utf-8') - name: Build, test, and publish the signed permanent tree From 3ceb3af69a6f566daa6494d6ef3c788d15e51a33 Mon Sep 17 00:00:00 2001 From: "Lutar, Stephen P." Date: Mon, 27 Jul 2026 06:33:04 -0400 Subject: [PATCH 10/10] fix(attestor): patch generated verifier source directly Signed-off-by: Stephen Lutar --- .../materialize-attestor-delivery-repair.yml | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/.github/workflows/materialize-attestor-delivery-repair.yml b/.github/workflows/materialize-attestor-delivery-repair.yml index 1cd45b3..066fcd2 100644 --- a/.github/workflows/materialize-attestor-delivery-repair.yml +++ b/.github/workflows/materialize-attestor-delivery-repair.yml @@ -41,8 +41,8 @@ jobs: run: | from pathlib import Path - path = Path('.github/scripts/materialize_attestor_delivery_repair.py') - source = path.read_text(encoding='utf-8') + generator_path = Path('.github/scripts/materialize_attestor_delivery_repair.py') + source = generator_path.read_text(encoding='utf-8') replacements = ( ( 'echo "Protected Merge Queue Enqueue owns native enqueuePullRequest delivery."', @@ -62,32 +62,22 @@ jobs: if count != 1: raise SystemExit(f'expected one exact source-contract marker, found {count}: {old}') source = source.replace(old, new, 1) + generator_path.write_text(source, encoding='utf-8') - old_guard_patch = ''' source = source.replace( - r'^(\\.github/workflows/(attest-and-approve|gates|forge9-staging)' - r'\\.ya?ml|\\.governance/)', - r'^(\\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)' - r'\\.ya?ml|\\.governance/)', - 1, - ) - ''' - new_guard_patch = ''' old_guard = r''' + "'''" + '''ATTESTOR_SELF_EDIT_GUARD = ( - r"^(\\.github/workflows/(attest-and-approve|gates|forge9-staging)" - r"\\.ya?ml|\\.governance/)" - )''' + "'''" + ''' - new_guard = r''' + "'''" + '''ATTESTOR_SELF_EDIT_GUARD = ( - r"^(\\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)" - r"\\.ya?ml|\\.governance/)" - )''' + "'''" + ''' - if old_guard not in source: - raise SystemExit("governance verifier self-edit guard changed unexpectedly") - source = source.replace(old_guard, new_guard, 1) - ''' - count = source.count(old_guard_patch) + verifier_path = Path('.github/scripts/verify_forge9_governance.py') + verifier = verifier_path.read_text(encoding='utf-8') + old_guard = r'''ATTESTOR_SELF_EDIT_GUARD = ( + r"^(\.github/workflows/(attest-and-approve|gates|forge9-staging)" + r"\.ya?ml|\.governance/)" + )''' + new_guard = r'''ATTESTOR_SELF_EDIT_GUARD = ( + r"^(\.github/workflows/(attest-and-approve|gates|forge9-staging|merge-queue-enqueue)" + r"\.ya?ml|\.governance/)" + )''' + count = verifier.count(old_guard) if count != 1: - raise SystemExit(f'expected one verifier guard patch block, found {count}') - source = source.replace(old_guard_patch, new_guard_patch, 1) - path.write_text(source, encoding='utf-8') + raise SystemExit(f'expected one verifier self-edit guard, found {count}') + verifier_path.write_text(verifier.replace(old_guard, new_guard, 1), encoding='utf-8') - name: Build, test, and publish the signed permanent tree shell: bash