From 941e5e0098d1831d51ea44261211f6c7d6a16771 Mon Sep 17 00:00:00 2001 From: Lucian Behind The Scenes Date: Tue, 28 Jul 2026 19:59:09 +0300 Subject: [PATCH] fix: publish automation PR statuses --- .github/workflows/maintenance-implementation.yml | 1 + .github/workflows/maintenance-release.yml | 1 + .github/workflows/maintenance-watch.yml | 1 + docs/repository-settings.md | 3 ++- scripts/dispatch-pr-checks | 10 ++++++++++ tests/test_maintenance.py | 2 ++ 6 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maintenance-implementation.yml b/.github/workflows/maintenance-implementation.yml index c6e3c9f..a0ee9ea 100644 --- a/.github/workflows/maintenance-implementation.yml +++ b/.github/workflows/maintenance-implementation.yml @@ -309,6 +309,7 @@ jobs: pull-requests: write checks: write actions: write + statuses: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: diff --git a/.github/workflows/maintenance-release.yml b/.github/workflows/maintenance-release.yml index 0147633..fd39dea 100644 --- a/.github/workflows/maintenance-release.yml +++ b/.github/workflows/maintenance-release.yml @@ -26,6 +26,7 @@ permissions: actions: write checks: write pull-requests: write + statuses: write concurrency: group: guarded-release-${{ inputs.version }} diff --git a/.github/workflows/maintenance-watch.yml b/.github/workflows/maintenance-watch.yml index 7593d42..639f827 100644 --- a/.github/workflows/maintenance-watch.yml +++ b/.github/workflows/maintenance-watch.yml @@ -150,6 +150,7 @@ jobs: contents: write id-token: write pull-requests: write + statuses: write issues: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 diff --git a/docs/repository-settings.md b/docs/repository-settings.md index 4ce3010..cfa994f 100644 --- a/docs/repository-settings.md +++ b/docs/repository-settings.md @@ -38,7 +38,8 @@ Required repository state: deterministic PR coordinator explicitly dispatches `ci.yml` and `protected-controls.yml` at the exact PR branch, accepts only newly created successful validator runs for that head SHA, and only then publishes the - Actions-owned required checks onto the PR rollup with the validator URLs. + Actions-owned check evidence plus PR-visible commit statuses with the exact + validator URLs. - Allow GitHub-owned Actions plus only `openai/codex-action` and `jdx/mise-action`, and require every Action reference to use a full commit SHA. diff --git a/scripts/dispatch-pr-checks b/scripts/dispatch-pr-checks index 3c1b182..c19cc15 100755 --- a/scripts/dispatch-pr-checks +++ b/scripts/dispatch-pr-checks @@ -102,6 +102,16 @@ while ((SECONDS < deadline)); do # after both new validator runs pass, retaining their URLs as evidence. primary_gate="$(publish_gate "$primary")" protected_gate="$(publish_gate "$protected")" + for validator in "$primary" "$protected"; do + name="$(jq -r .name <<<"$validator")" + details="$(jq -r .details_url <<<"$validator")" + gh api --method POST -H "Accept: application/vnd.github+json" \ + "repos/$repository/statuses/$head_sha" \ + -f state=success \ + -f "context=$name" \ + -f "target_url=$details" \ + -f description="Exact-head dispatched validator passed" >/dev/null + done for _ in {1..24}; do rollup="$(gh pr checks "$pr_number" --repo "$repository" --required \ --json name,bucket,link 2>/dev/null || true)" diff --git a/tests/test_maintenance.py b/tests/test_maintenance.py index 354c323..75ac157 100644 --- a/tests/test_maintenance.py +++ b/tests/test_maintenance.py @@ -287,6 +287,7 @@ def test_token_created_prs_explicitly_dispatch_required_checks(self): self.assertIn("gh workflow run ci.yml", dispatcher) self.assertIn("gh workflow run protected-controls.yml", dispatcher) self.assertIn('"repos/$repository/check-runs"', dispatcher) + self.assertIn('"repos/$repository/statuses/$head_sha"', dispatcher) self.assertIn("Exact-head validator passed", dispatcher) for workflow in ( "maintenance-watch.yml", @@ -297,6 +298,7 @@ def test_token_created_prs_explicitly_dispatch_required_checks(self): self.assertIn("./scripts/dispatch-pr-checks", body) self.assertNotIn("gh pr checks", body) self.assertIn("checks: write", body) + self.assertIn("statuses: write", body) def test_malformed_contract_shapes_fail_closed(self): contract = self._contract()