Hourly PR Queue Governance #518
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Hourly PR Queue Governance | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 * * * *" | |
| push: | |
| branches: [main] | |
| paths: | |
| - .github/workflows/hourly-pr-governance.yml | |
| - scripts/audit_workflow_registry.py | |
| - scripts/build_pr_queue_governance.py | |
| - scripts/capture_pr_queue_snapshot.py | |
| - tests/test_capture_pr_queue_snapshot.py | |
| - tests/test_capture_pr_queue_snapshot_missing_branch.py | |
| - tests/test_hourly_pr_governance_workflow.py | |
| - tests/test_hourly_snapshot_split_workflow.py | |
| - tests/test_pr_queue_governance.py | |
| - tests/test_workflow_registry_audit.py | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: read | |
| concurrency: | |
| group: hourly-pr-governance-${{ github.repository }} | |
| cancel-in-progress: true | |
| jobs: | |
| governance-evidence: | |
| name: Hourly PR queue governance evidence | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Check out reviewed source | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 | |
| with: | |
| python-version: "3.12" | |
| - name: Verify the workflow contract without third-party dependencies | |
| run: | | |
| python - <<'PY' | |
| import runpy | |
| namespace = runpy.run_path("tests/test_hourly_pr_governance_workflow.py") | |
| test_names = [ | |
| name | |
| for name in sorted(namespace) | |
| if name.startswith("test_hourly_governance_workflow_") | |
| ] | |
| if not test_names: | |
| raise RuntimeError("no hourly governance contract tests were discovered") | |
| for name in test_names: | |
| namespace[name]() | |
| PY | |
| - name: Build live PR queue governance evidence | |
| id: governance | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| is_retryable_github_failure() { | |
| python - <<'PY' | |
| import json | |
| from pathlib import Path | |
| import re | |
| path = Path("hourly-pr-queue-governance/pr_queue_governance_manifest.json") | |
| if not path.is_file(): | |
| print("false") | |
| raise SystemExit | |
| try: | |
| manifest = json.loads(path.read_text(encoding="utf-8")) | |
| except (OSError, UnicodeError, json.JSONDecodeError): | |
| print("false") | |
| raise SystemExit | |
| status_failed = manifest.get("status") == "failed" | |
| if not status_failed: | |
| print("false") | |
| raise SystemExit | |
| failed_checks = manifest.get("failed_checks") | |
| retryable_failed_check_names = {"github:snapshot", "github:base_sha"} | |
| if ( | |
| not isinstance(failed_checks, list) | |
| or not failed_checks | |
| or not all( | |
| isinstance(check, dict) and check.get("ok") is False | |
| for check in failed_checks | |
| ) | |
| ): | |
| print("false") | |
| raise SystemExit | |
| failed_check_names = { | |
| str(check.get("name", "")) for check in failed_checks | |
| } | |
| only_transient_snapshot_checks_failed = ( | |
| bool(failed_check_names) | |
| and failed_check_names.issubset(retryable_failed_check_names) | |
| ) | |
| github = manifest.get("github", {}) | |
| errors = github.get("errors", []) if isinstance(github, dict) else [] | |
| retry_status = re.compile(r"\bHTTP (?:502|503|504)\b", re.IGNORECASE) | |
| retryable = ( | |
| only_transient_snapshot_checks_failed | |
| and bool(errors) | |
| and all( | |
| isinstance(error, dict) | |
| and type(error.get("returncode")) is int | |
| and error.get("returncode") != 0 | |
| and isinstance(error.get("stderr"), str) | |
| and retry_status.search(error.get("stderr", "")) is not None | |
| for error in errors | |
| ) | |
| ) | |
| print("true" if retryable else "false") | |
| PY | |
| } | |
| max_attempts=3 | |
| attempt=1 | |
| while true; do | |
| rm -rf hourly-pr-queue-governance | |
| mkdir -p hourly-pr-queue-governance | |
| snapshot_rc=0 | |
| python scripts/capture_pr_queue_snapshot.py \ | |
| --repo ContextualWisdomLab/fast-mlsirm \ | |
| --out hourly-pr-queue-governance/github_snapshot.json \ | |
| || snapshot_rc=$? | |
| builder_rc=0 | |
| python scripts/build_pr_queue_governance.py \ | |
| --repo ContextualWisdomLab/fast-mlsirm \ | |
| --out hourly-pr-queue-governance \ | |
| --offline-snapshot hourly-pr-queue-governance/github_snapshot.json \ | |
| || builder_rc=$? | |
| if [[ "$snapshot_rc" -eq 0 && "$builder_rc" -eq 0 ]]; then | |
| break | |
| fi | |
| retryable="$(is_retryable_github_failure)" | |
| if [[ "$retryable" != "true" || "$attempt" -ge "$max_attempts" ]]; then | |
| exit 1 | |
| fi | |
| delay=$((10 * (2 ** (attempt - 1)))) | |
| echo "::warning::Transient GitHub API failure on attempt ${attempt}; retrying in ${delay}s." | |
| sleep "$delay" | |
| attempt=$((attempt + 1)) | |
| done | |
| - name: Build live workflow registry drift evidence | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| python scripts/audit_workflow_registry.py \ | |
| --repo ContextualWisdomLab/fast-mlsirm \ | |
| --out hourly-pr-queue-governance/workflow_registry_audit.json | |
| - name: Publish hourly governance evidence | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a | |
| with: | |
| name: hourly-pr-queue-governance-${{ github.run_id }} | |
| path: | | |
| hourly-pr-queue-governance/github_snapshot.json | |
| hourly-pr-queue-governance/pr_queue_governance_manifest.json | |
| hourly-pr-queue-governance/pr_queue_governance_report.html | |
| hourly-pr-queue-governance/workflow_registry_audit.json | |
| if-no-files-found: error | |
| retention-days: 30 |