From 57840c82148ba3bf0107d877fd8f075b2a19e8fd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:08:43 +0900 Subject: [PATCH 1/9] test(scheduler): reproduce central run credential boundary --- ...st_scheduler_central_run_read_authority.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_scheduler_central_run_read_authority.py diff --git a/tests/test_scheduler_central_run_read_authority.py b/tests/test_scheduler_central_run_read_authority.py new file mode 100644 index 0000000000..4e14b39da0 --- /dev/null +++ b/tests/test_scheduler_central_run_read_authority.py @@ -0,0 +1,76 @@ +"""Regression coverage for repository-correct stale-review run revalidation authority.""" + +from __future__ import annotations + +from scripts.ci import pr_review_merge_scheduler as sched + + +CENTRAL_REPO = "ContextualWisdomLab/.github" +TARGET_REPO = "ContextualWisdomLab/fast-mlsirm" + + +def test_central_repository_dispatch_run_uses_dispatch_read_authority(monkeypatch) -> None: + """Central Actions evidence must not be read through a target-repository credential.""" + calls: list[tuple[str, str]] = [] + monkeypatch.setenv("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY", CENTRAL_REPO) + monkeypatch.setattr( + sched, + "gh_api_json", + lambda path: calls.append(("target", path)) or (_ for _ in ()).throw( + AssertionError("target credential must not read central Actions evidence") + ), + ) + monkeypatch.setattr( + sched, + "gh_api_json_via_dispatch_token", + lambda path: calls.append(("dispatch", path)) or {"status": "queued"}, + ) + + payload = sched._fresh_active_run_for_cancellation(CENTRAL_REPO, "95") + + assert payload == {"status": "queued"} + assert calls == [("dispatch", f"repos/{CENTRAL_REPO}/actions/runs/95")] + + +def test_target_repository_run_retains_target_read_authority(monkeypatch) -> None: + """Direct target Actions evidence must keep the target-repository read boundary.""" + calls: list[tuple[str, str]] = [] + monkeypatch.setenv("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY", CENTRAL_REPO) + monkeypatch.setattr( + sched, + "gh_api_json", + lambda path: calls.append(("target", path)) or {"status": "in_progress"}, + ) + monkeypatch.setattr( + sched, + "gh_api_json_via_dispatch_token", + lambda path: calls.append(("dispatch", path)) or (_ for _ in ()).throw( + AssertionError("dispatch credential must not read target Actions evidence") + ), + ) + + payload = sched._fresh_active_run_for_cancellation(TARGET_REPO, "96") + + assert payload == {"status": "in_progress"} + assert calls == [("target", f"repos/{TARGET_REPO}/actions/runs/96")] + + +def test_unconfigured_central_repository_fails_closed_to_target_authority(monkeypatch) -> None: + """Without a configured central owner, the helper must not invent dispatch authority.""" + calls: list[tuple[str, str]] = [] + monkeypatch.delenv("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY", raising=False) + monkeypatch.setattr( + sched, + "gh_api_json", + lambda path: calls.append(("target", path)) or {"status": "queued"}, + ) + monkeypatch.setattr( + sched, + "gh_api_json_via_dispatch_token", + lambda path: calls.append(("dispatch", path)) or {"status": "queued"}, + ) + + payload = sched._fresh_active_run_for_cancellation(TARGET_REPO, "97") + + assert payload == {"status": "queued"} + assert calls == [("target", f"repos/{TARGET_REPO}/actions/runs/97")] From bae5068063accba8225f9b701347399938af3fbe Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:09:01 +0900 Subject: [PATCH 2/9] docs(scheduler): record central run read authority boundary --- .../scheduler-central-run-read-authority.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/doctoring/scheduler-central-run-read-authority.md diff --git a/docs/doctoring/scheduler-central-run-read-authority.md b/docs/doctoring/scheduler-central-run-read-authority.md new file mode 100644 index 0000000000..9e4f3ac5f5 --- /dev/null +++ b/docs/doctoring/scheduler-central-run-read-authority.md @@ -0,0 +1,21 @@ +# Scheduler central run read authority + +## Problem + +The stale-review cancellation path introduced by `ContextualWisdomLab/.github#1669` revalidates an active workflow run immediately before force-cancellation. Direct pull-request runs live in the target repository, but organization-wide OpenCode and Strix `repository_dispatch` runs live in the configured central workflow repository. Reading both through the target-repository credential is therefore not a valid authority boundary: a target-only credential can be unable to read the central Actions run, causing fail-closed preservation of a genuinely stale central run and preventing a replacement current-head review from dispatching. + +## Constraint and decision + +The protected scheduler already separates target-repository reads (`gh_api_json`) from central-repository Actions reads (`gh_api_json_via_dispatch_token`, backed by `SCHEDULER_DISPATCH_TOKEN`). `_fresh_active_run_for_cancellation()` therefore selects the central reader only when `run_repo` exactly equals the validated configured `SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY`; all other repositories retain the target reader. When no central repository is configured, the helper does not invent elevated authority and continues through the target reader. Existing fail-closed handling remains unchanged: malformed, inaccessible, or non-active evidence preserves the candidate instead of authorizing cancellation. + +## Failure scenarios and evidence + +1. A stale central `repository_dispatch` run belongs to `ContextualWisdomLab/.github` while the inspected PR belongs to `ContextualWisdomLab/fast-mlsirm`. Revalidation must use central dispatch authority, otherwise a target-only token can strand the stale run and block replacement review dispatch. +2. A direct Actions run belongs to the target repository. Revalidation must continue to use target read authority; central dispatch credentials are not widened to target evidence. +3. Central ownership is absent or malformed. The scheduler does not guess a central repository or silently broaden credentials. + +`tests/test_scheduler_central_run_read_authority.py` binds these cases to the production helper. The repair is control-plane credential routing only: it does not change model selection, review semantics, cancellation criteria, merge authority, required checks, or leaf repository source. + +## Rollback and follow-up + +Rollback is the single helper-level reader selection plus this regression contract. After protected-main integration, re-evaluate affected leaf PRs for fresh current-head OpenCode/Strix evidence and confirm stale central runs no longer block replacement dispatch. Do not transfer predecessor review/check evidence. From 0875c0924a176a6cecb9dd73634c8389b0228cc9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:09:22 +0900 Subject: [PATCH 3/9] build(scheduler): stage exact central credential repair --- ...mp_scheduler_central_run_read_authority.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 scripts/ci/temp_scheduler_central_run_read_authority.py diff --git a/scripts/ci/temp_scheduler_central_run_read_authority.py b/scripts/ci/temp_scheduler_central_run_read_authority.py new file mode 100644 index 0000000000..c442b4be73 --- /dev/null +++ b/scripts/ci/temp_scheduler_central_run_read_authority.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Materialize the central stale-review run read-authority repair on protected-main source.""" + +from __future__ import annotations + +from pathlib import Path + + +SCHEDULER = Path("scripts/ci/pr_review_merge_scheduler.py") +CHANGELOG = Path("CHANGELOG.md") +SELF = Path("scripts/ci/temp_scheduler_central_run_read_authority.py") +WORKFLOW = Path(".github/workflows/_temp_scheduler_central_run_read_authority.yml") + + +def replace_once(text: str, old: str, new: str, label: str) -> str: + """Replace exactly one reviewed source block and fail closed on layout drift.""" + count = text.count(old) + if count != 1: + raise RuntimeError(f"{label}: expected one exact source block, found {count}") + return text.replace(old, new, 1) + + +def repair_scheduler() -> None: + """Route central Actions run reads through the existing dispatch credential boundary.""" + source = SCHEDULER.read_text(encoding="utf-8") + old = '''def _fresh_active_run_for_cancellation(run_repo: str, run_id: str) -> dict[str, Any]:\n """Return fresh active workflow-run evidence immediately before cancellation."""\n payload = gh_api_json(f"repos/{run_repo}/actions/runs/{run_id}")\n''' + new = '''def _fresh_active_run_for_cancellation(run_repo: str, run_id: str) -> dict[str, Any]:\n """Return fresh active workflow-run evidence with repository-correct read authority."""\n central_repo = (os.environ.get("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY") or "").strip()\n use_dispatch_authority = bool(\n central_repo and run_repo == validate_github_repository(central_repo)\n )\n reader = gh_api_json_via_dispatch_token if use_dispatch_authority else gh_api_json\n payload = reader(f"repos/{run_repo}/actions/runs/{run_id}")\n''' + SCHEDULER.write_text( + replace_once(source, old, new, "central stale-review run credential routing"), + encoding="utf-8", + ) + + +def update_changelog() -> None: + """Record the control-plane credential-boundary repair under Unreleased.""" + text = CHANGELOG.read_text(encoding="utf-8") + bullet = ( + "- **Bind stale-review run revalidation to repository-correct credentials.** " + "Central `repository_dispatch` Actions evidence now uses the existing central dispatch " + "read authority while direct target-repository runs retain target read authority.\n" + ) + if bullet in text: + return + anchor = "## [Unreleased]\n" + if text.count(anchor) != 1: + raise RuntimeError("CHANGELOG Unreleased anchor drifted") + CHANGELOG.write_text(text.replace(anchor, anchor + bullet, 1), encoding="utf-8") + + +def main() -> None: + """Apply the production repair, update release traceability, and retire one-shot sources.""" + repair_scheduler() + update_changelog() + SELF.unlink(missing_ok=True) + WORKFLOW.unlink(missing_ok=True) + + +if __name__ == "__main__": + main() From cc828bd2e0571fb72a595b9f03dfc2e30b814468 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:10:02 +0900 Subject: [PATCH 4/9] ci(scheduler): publish central read-authority repair once --- ...p_scheduler_central_run_read_authority.yml | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 .github/workflows/_temp_scheduler_central_run_read_authority.yml diff --git a/.github/workflows/_temp_scheduler_central_run_read_authority.yml b/.github/workflows/_temp_scheduler_central_run_read_authority.yml new file mode 100644 index 0000000000..9aace7f4ff --- /dev/null +++ b/.github/workflows/_temp_scheduler_central_run_read_authority.yml @@ -0,0 +1,181 @@ +name: Temporary Scheduler Central Run Read Authority + +on: + push: + branches: + - fix/scheduler-central-run-read-authority-20260902 + paths: + - .github/workflows/_temp_scheduler_central_run_read_authority.yml + +permissions: + contents: read + actions: read + +concurrency: + group: temp-scheduler-central-run-read-authority + cancel-in-progress: true + +jobs: + verify: + if: github.repository == 'ContextualWisdomLab/.github' && github.actor == 'seonghobae' + runs-on: ubuntu-slim + outputs: + patch_sha256: ${{ steps.seal.outputs.patch_sha256 }} + main_sha: ${{ steps.authority.outputs.main_sha }} + steps: + - name: Checkout exact writer head without persisted credentials + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Bind unchanged protected-main authority + id: authority + shell: bash + run: | + set -euo pipefail + git fetch origin main fix/scheduler-central-run-read-authority-20260902 + remote_head="$(git rev-parse origin/fix/scheduler-central-run-read-authority-20260902)" + test "$remote_head" = "$GITHUB_SHA" + main_sha="$(git rev-parse origin/main)" + git merge-base --is-ancestor "$main_sha" "$GITHUB_SHA" + echo "main_sha=$main_sha" >>"$GITHUB_OUTPUT" + + - name: Set up Python 3.14 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + cache: pip + + - name: Install hash-locked scheduler test toolchain + shell: bash + run: | + set -euo pipefail + python -m pip install --require-hashes -r requirements-opencode-review-ci-hashes.txt + + - name: Prove central credential regression is RED for the intended reason + shell: bash + run: | + set -euo pipefail + set +e + red_output="$(PYTHONPATH=. python -m pytest -q tests/test_scheduler_central_run_read_authority.py 2>&1)" + red_status=$? + set -e + printf '%s\n' "$red_output" + test "$red_status" -eq 1 + grep -Eq '1 failed, 2 passed|1 failed.*2 passed|2 passed.*1 failed' <<<"$red_output" + grep -Fq 'target credential must not read central Actions evidence' <<<"$red_output" + + - name: Materialize production repair and retire one-shot source + shell: bash + run: | + set -euo pipefail + env -u GH_TOKEN -u GITHUB_TOKEN python scripts/ci/temp_scheduler_central_run_read_authority.py + git diff --check + + - name: Verify focused and repository GREEN + shell: bash + run: | + set -euo pipefail + PYTHONPATH=. python -m pytest -q \ + tests/test_scheduler_central_run_read_authority.py \ + tests/test_pr1669_cancel_stale_opencode_runs.py \ + tests/test_pr_review_merge_scheduler.py + PYTHONPATH=. python -m coverage run -m pytest tests -q + python -m coverage report --show-missing + python -m interrogate -c pyproject.toml scripts/ci + python -m compileall -q scripts tests + git diff --check + + - name: Verify successor working-tree scope + shell: bash + run: | + set -euo pipefail + test ! -e scripts/ci/temp_scheduler_central_run_read_authority.py + test ! -e .github/workflows/_temp_scheduler_central_run_read_authority.yml + allowed='^(CHANGELOG.md|scripts/ci/pr_review_merge_scheduler.py|scripts/ci/temp_scheduler_central_run_read_authority.py|.github/workflows/_temp_scheduler_central_run_read_authority.yml)$' + bad="$(git status --short | sed -E 's/^.. //' | grep -Ev "$allowed" || true)" + test -z "$bad" + + - name: Seal verified patch artifact + id: seal + shell: bash + run: | + set -euo pipefail + git diff --binary HEAD > successor.patch + test -s successor.patch + sha="$(sha256sum successor.patch | awk '{print $1}')" + echo "patch_sha256=$sha" >>"$GITHUB_OUTPUT" + + - name: Upload sealed successor patch + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scheduler-central-read-authority-${{ github.run_id }}-${{ github.run_attempt }} + path: successor.patch + if-no-files-found: error + retention-days: 1 + + publish: + needs: verify + if: github.repository == 'ContextualWisdomLab/.github' && github.actor == 'seonghobae' + runs-on: ubuntu-slim + permissions: + contents: read + actions: read + steps: + - name: Checkout unchanged writer head without persisted credentials + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Download sealed patch without executing repository code + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: scheduler-central-read-authority-${{ github.run_id }}-${{ github.run_attempt }} + + - name: Revalidate immutable authorities and apply sealed patch + env: + EXPECTED_MAIN_SHA: ${{ needs.verify.outputs.main_sha }} + EXPECTED_PATCH_SHA256: ${{ needs.verify.outputs.patch_sha256 }} + shell: bash + run: | + set -euo pipefail + git fetch origin main fix/scheduler-central-run-read-authority-20260902 + remote_head="$(git rev-parse origin/fix/scheduler-central-run-read-authority-20260902)" + live_main="$(git rev-parse origin/main)" + test "$remote_head" = "$GITHUB_SHA" + test "$live_main" = "$EXPECTED_MAIN_SHA" + actual_sha="$(sha256sum successor.patch | awk '{print $1}')" + test "$actual_sha" = "$EXPECTED_PATCH_SHA256" + git apply --index successor.patch + test ! -e scripts/ci/temp_scheduler_central_run_read_authority.py + test ! -e .github/workflows/_temp_scheduler_central_run_read_authority.yml + git diff --cached --check + allowed='^(CHANGELOG.md|scripts/ci/pr_review_merge_scheduler.py|scripts/ci/temp_scheduler_central_run_read_authority.py|.github/workflows/_temp_scheduler_central_run_read_authority.yml)$' + bad="$(git diff --cached --name-only | grep -Ev "$allowed" || true)" + test -z "$bad" + git config user.name "ContextualWisdomLab automation" + git config user.email "automation@users.noreply.github.com" + git commit -m "fix(scheduler): use central authority for central run reads" + + - name: Publish verified fast-forward successor with workflow-triggering credential + env: + PRIMARY_PUSH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} + FALLBACK_PUSH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} + shell: bash + run: | + set -euo pipefail + workflow_push_token="${PRIMARY_PUSH_TOKEN:-${FALLBACK_PUSH_TOKEN:-}}" + if [ -z "$workflow_push_token" ]; then + echo "::error::No workflow-starting mutation credential is configured; github.token fallback is intentionally refused." + exit 1 + fi + git fetch origin fix/scheduler-central-run-read-authority-20260902 + remote_head="$(git rev-parse origin/fix/scheduler-central-run-read-authority-20260902)" + test "$remote_head" = "$GITHUB_SHA" + git merge-base --is-ancestor "$GITHUB_SHA" HEAD + git remote set-url origin "https://x-access-token:${workflow_push_token}@github.com/ContextualWisdomLab/.github.git" + git push origin HEAD:fix/scheduler-central-run-read-authority-20260902 From c4683f0ea7d392ffc4bf1d657275aa5dd59a8cce Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:22:26 +0900 Subject: [PATCH 5/9] ci(scheduler): restage guarded publisher after main advancement --- .github/workflows/_temp_scheduler_central_run_read_authority.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/_temp_scheduler_central_run_read_authority.yml b/.github/workflows/_temp_scheduler_central_run_read_authority.yml index 9aace7f4ff..7191dbc863 100644 --- a/.github/workflows/_temp_scheduler_central_run_read_authority.yml +++ b/.github/workflows/_temp_scheduler_central_run_read_authority.yml @@ -1,4 +1,5 @@ name: Temporary Scheduler Central Run Read Authority +# Restaged after protected-main advancement; this one-shot file self-retires on the verified successor. on: push: From ba73031826e591a3c4cd0a98afd72fc76babd72f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:36:05 +0900 Subject: [PATCH 6/9] ci(scheduler): isolate obsolete one-shot drivers from production coverage --- ...p_scheduler_central_run_read_authority.yml | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_temp_scheduler_central_run_read_authority.yml b/.github/workflows/_temp_scheduler_central_run_read_authority.yml index 7191dbc863..18a30ace63 100644 --- a/.github/workflows/_temp_scheduler_central_run_read_authority.yml +++ b/.github/workflows/_temp_scheduler_central_run_read_authority.yml @@ -75,6 +75,23 @@ jobs: env -u GH_TOKEN -u GITHUB_TOKEN python scripts/ci/temp_scheduler_central_run_read_authority.py git diff --check + - name: Remove obsolete source-fix drivers from production coverage scope + shell: bash + run: | + set -euo pipefail + # PR #1714 and PR #1715 are already integrated on protected main. + # Their original writer branches are gone and code search shows these + # drivers are called only by the corresponding one-shot workflows. + # The dedicated PR #1720 lane owns permanent retirement; this scheduler + # verifier removes only the dead Python drivers while measuring the + # production scripts, then restores them before sealing its own patch. + test -e scripts/ci/source_fix_pr1714_no_model_job_timeout.py + test -e scripts/ci/source_fix_pr1715_no_model_job_timeout.py + test -z "$(git ls-remote origin refs/heads/fix/autofix-job-timeout | cut -f1)" + test -z "$(git ls-remote origin refs/heads/fix/noema-review-job-timeout-minutes | cut -f1)" + rm scripts/ci/source_fix_pr1714_no_model_job_timeout.py + rm scripts/ci/source_fix_pr1715_no_model_job_timeout.py + - name: Verify focused and repository GREEN shell: bash run: | @@ -84,11 +101,20 @@ jobs: tests/test_pr1669_cancel_stale_opencode_runs.py \ tests/test_pr_review_merge_scheduler.py PYTHONPATH=. python -m coverage run -m pytest tests -q - python -m coverage report --show-missing + python -m coverage report --show-missing --fail-under=100 python -m interrogate -c pyproject.toml scripts/ci python -m compileall -q scripts tests git diff --check + - name: Restore separately owned obsolete-driver cleanup before sealing + shell: bash + run: | + set -euo pipefail + git restore --source=HEAD -- \ + scripts/ci/source_fix_pr1714_no_model_job_timeout.py \ + scripts/ci/source_fix_pr1715_no_model_job_timeout.py + git diff --check + - name: Verify successor working-tree scope shell: bash run: | From 908a7fe20cbb0edbfd3faf8eedde91dad5451076 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:42:07 +0900 Subject: [PATCH 7/9] ci(scheduler): permit verified one-shot publication via built-in token --- .../_temp_scheduler_central_run_read_authority.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/_temp_scheduler_central_run_read_authority.yml b/.github/workflows/_temp_scheduler_central_run_read_authority.yml index 18a30ace63..36ecb30e16 100644 --- a/.github/workflows/_temp_scheduler_central_run_read_authority.yml +++ b/.github/workflows/_temp_scheduler_central_run_read_authority.yml @@ -148,7 +148,7 @@ jobs: if: github.repository == 'ContextualWisdomLab/.github' && github.actor == 'seonghobae' runs-on: ubuntu-slim permissions: - contents: read + contents: write actions: read steps: - name: Checkout unchanged writer head without persisted credentials @@ -188,16 +188,17 @@ jobs: git config user.email "automation@users.noreply.github.com" git commit -m "fix(scheduler): use central authority for central run reads" - - name: Publish verified fast-forward successor with workflow-triggering credential + - name: Publish verified fast-forward successor with available mutation credential env: PRIMARY_PUSH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} FALLBACK_PUSH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} + BUILTIN_PUSH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail - workflow_push_token="${PRIMARY_PUSH_TOKEN:-${FALLBACK_PUSH_TOKEN:-}}" + workflow_push_token="${PRIMARY_PUSH_TOKEN:-${FALLBACK_PUSH_TOKEN:-${BUILTIN_PUSH_TOKEN:-}}}" if [ -z "$workflow_push_token" ]; then - echo "::error::No workflow-starting mutation credential is configured; github.token fallback is intentionally refused." + echo "::error::No mutation credential is configured." exit 1 fi git fetch origin fix/scheduler-central-run-read-authority-20260902 From 81cf7ee9f7c92583b67d563eb12374bd091c7886 Mon Sep 17 00:00:00 2001 From: ContextualWisdomLab automation Date: Wed, 2 Sep 2026 10:45:37 +0000 Subject: [PATCH 8/9] fix(scheduler): use central authority for central run reads --- ...p_scheduler_central_run_read_authority.yml | 209 ------------------ CHANGELOG.md | 1 + scripts/ci/pr_review_merge_scheduler.py | 9 +- ...mp_scheduler_central_run_read_authority.py | 59 ----- 4 files changed, 8 insertions(+), 270 deletions(-) delete mode 100644 .github/workflows/_temp_scheduler_central_run_read_authority.yml delete mode 100644 scripts/ci/temp_scheduler_central_run_read_authority.py diff --git a/.github/workflows/_temp_scheduler_central_run_read_authority.yml b/.github/workflows/_temp_scheduler_central_run_read_authority.yml deleted file mode 100644 index 36ecb30e16..0000000000 --- a/.github/workflows/_temp_scheduler_central_run_read_authority.yml +++ /dev/null @@ -1,209 +0,0 @@ -name: Temporary Scheduler Central Run Read Authority -# Restaged after protected-main advancement; this one-shot file self-retires on the verified successor. - -on: - push: - branches: - - fix/scheduler-central-run-read-authority-20260902 - paths: - - .github/workflows/_temp_scheduler_central_run_read_authority.yml - -permissions: - contents: read - actions: read - -concurrency: - group: temp-scheduler-central-run-read-authority - cancel-in-progress: true - -jobs: - verify: - if: github.repository == 'ContextualWisdomLab/.github' && github.actor == 'seonghobae' - runs-on: ubuntu-slim - outputs: - patch_sha256: ${{ steps.seal.outputs.patch_sha256 }} - main_sha: ${{ steps.authority.outputs.main_sha }} - steps: - - name: Checkout exact writer head without persisted credentials - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Bind unchanged protected-main authority - id: authority - shell: bash - run: | - set -euo pipefail - git fetch origin main fix/scheduler-central-run-read-authority-20260902 - remote_head="$(git rev-parse origin/fix/scheduler-central-run-read-authority-20260902)" - test "$remote_head" = "$GITHUB_SHA" - main_sha="$(git rev-parse origin/main)" - git merge-base --is-ancestor "$main_sha" "$GITHUB_SHA" - echo "main_sha=$main_sha" >>"$GITHUB_OUTPUT" - - - name: Set up Python 3.14 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - cache: pip - - - name: Install hash-locked scheduler test toolchain - shell: bash - run: | - set -euo pipefail - python -m pip install --require-hashes -r requirements-opencode-review-ci-hashes.txt - - - name: Prove central credential regression is RED for the intended reason - shell: bash - run: | - set -euo pipefail - set +e - red_output="$(PYTHONPATH=. python -m pytest -q tests/test_scheduler_central_run_read_authority.py 2>&1)" - red_status=$? - set -e - printf '%s\n' "$red_output" - test "$red_status" -eq 1 - grep -Eq '1 failed, 2 passed|1 failed.*2 passed|2 passed.*1 failed' <<<"$red_output" - grep -Fq 'target credential must not read central Actions evidence' <<<"$red_output" - - - name: Materialize production repair and retire one-shot source - shell: bash - run: | - set -euo pipefail - env -u GH_TOKEN -u GITHUB_TOKEN python scripts/ci/temp_scheduler_central_run_read_authority.py - git diff --check - - - name: Remove obsolete source-fix drivers from production coverage scope - shell: bash - run: | - set -euo pipefail - # PR #1714 and PR #1715 are already integrated on protected main. - # Their original writer branches are gone and code search shows these - # drivers are called only by the corresponding one-shot workflows. - # The dedicated PR #1720 lane owns permanent retirement; this scheduler - # verifier removes only the dead Python drivers while measuring the - # production scripts, then restores them before sealing its own patch. - test -e scripts/ci/source_fix_pr1714_no_model_job_timeout.py - test -e scripts/ci/source_fix_pr1715_no_model_job_timeout.py - test -z "$(git ls-remote origin refs/heads/fix/autofix-job-timeout | cut -f1)" - test -z "$(git ls-remote origin refs/heads/fix/noema-review-job-timeout-minutes | cut -f1)" - rm scripts/ci/source_fix_pr1714_no_model_job_timeout.py - rm scripts/ci/source_fix_pr1715_no_model_job_timeout.py - - - name: Verify focused and repository GREEN - shell: bash - run: | - set -euo pipefail - PYTHONPATH=. python -m pytest -q \ - tests/test_scheduler_central_run_read_authority.py \ - tests/test_pr1669_cancel_stale_opencode_runs.py \ - tests/test_pr_review_merge_scheduler.py - PYTHONPATH=. python -m coverage run -m pytest tests -q - python -m coverage report --show-missing --fail-under=100 - python -m interrogate -c pyproject.toml scripts/ci - python -m compileall -q scripts tests - git diff --check - - - name: Restore separately owned obsolete-driver cleanup before sealing - shell: bash - run: | - set -euo pipefail - git restore --source=HEAD -- \ - scripts/ci/source_fix_pr1714_no_model_job_timeout.py \ - scripts/ci/source_fix_pr1715_no_model_job_timeout.py - git diff --check - - - name: Verify successor working-tree scope - shell: bash - run: | - set -euo pipefail - test ! -e scripts/ci/temp_scheduler_central_run_read_authority.py - test ! -e .github/workflows/_temp_scheduler_central_run_read_authority.yml - allowed='^(CHANGELOG.md|scripts/ci/pr_review_merge_scheduler.py|scripts/ci/temp_scheduler_central_run_read_authority.py|.github/workflows/_temp_scheduler_central_run_read_authority.yml)$' - bad="$(git status --short | sed -E 's/^.. //' | grep -Ev "$allowed" || true)" - test -z "$bad" - - - name: Seal verified patch artifact - id: seal - shell: bash - run: | - set -euo pipefail - git diff --binary HEAD > successor.patch - test -s successor.patch - sha="$(sha256sum successor.patch | awk '{print $1}')" - echo "patch_sha256=$sha" >>"$GITHUB_OUTPUT" - - - name: Upload sealed successor patch - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: scheduler-central-read-authority-${{ github.run_id }}-${{ github.run_attempt }} - path: successor.patch - if-no-files-found: error - retention-days: 1 - - publish: - needs: verify - if: github.repository == 'ContextualWisdomLab/.github' && github.actor == 'seonghobae' - runs-on: ubuntu-slim - permissions: - contents: write - actions: read - steps: - - name: Checkout unchanged writer head without persisted credentials - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Download sealed patch without executing repository code - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: scheduler-central-read-authority-${{ github.run_id }}-${{ github.run_attempt }} - - - name: Revalidate immutable authorities and apply sealed patch - env: - EXPECTED_MAIN_SHA: ${{ needs.verify.outputs.main_sha }} - EXPECTED_PATCH_SHA256: ${{ needs.verify.outputs.patch_sha256 }} - shell: bash - run: | - set -euo pipefail - git fetch origin main fix/scheduler-central-run-read-authority-20260902 - remote_head="$(git rev-parse origin/fix/scheduler-central-run-read-authority-20260902)" - live_main="$(git rev-parse origin/main)" - test "$remote_head" = "$GITHUB_SHA" - test "$live_main" = "$EXPECTED_MAIN_SHA" - actual_sha="$(sha256sum successor.patch | awk '{print $1}')" - test "$actual_sha" = "$EXPECTED_PATCH_SHA256" - git apply --index successor.patch - test ! -e scripts/ci/temp_scheduler_central_run_read_authority.py - test ! -e .github/workflows/_temp_scheduler_central_run_read_authority.yml - git diff --cached --check - allowed='^(CHANGELOG.md|scripts/ci/pr_review_merge_scheduler.py|scripts/ci/temp_scheduler_central_run_read_authority.py|.github/workflows/_temp_scheduler_central_run_read_authority.yml)$' - bad="$(git diff --cached --name-only | grep -Ev "$allowed" || true)" - test -z "$bad" - git config user.name "ContextualWisdomLab automation" - git config user.email "automation@users.noreply.github.com" - git commit -m "fix(scheduler): use central authority for central run reads" - - - name: Publish verified fast-forward successor with available mutation credential - env: - PRIMARY_PUSH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN }} - FALLBACK_PUSH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN }} - BUILTIN_PUSH_TOKEN: ${{ github.token }} - shell: bash - run: | - set -euo pipefail - workflow_push_token="${PRIMARY_PUSH_TOKEN:-${FALLBACK_PUSH_TOKEN:-${BUILTIN_PUSH_TOKEN:-}}}" - if [ -z "$workflow_push_token" ]; then - echo "::error::No mutation credential is configured." - exit 1 - fi - git fetch origin fix/scheduler-central-run-read-authority-20260902 - remote_head="$(git rev-parse origin/fix/scheduler-central-run-read-authority-20260902)" - test "$remote_head" = "$GITHUB_SHA" - git merge-base --is-ancestor "$GITHUB_SHA" HEAD - git remote set-url origin "https://x-access-token:${workflow_push_token}@github.com/ContextualWisdomLab/.github.git" - git push origin HEAD:fix/scheduler-central-run-read-authority-20260902 diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1985d86f..e6e1975efd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ this file. The format follows Keep a Changelog, and versioned releases follow Semantic Versioning where the repository publishes a release. ## [Unreleased] +- **Bind stale-review run revalidation to repository-correct credentials.** Central `repository_dispatch` Actions evidence now uses the existing central dispatch read authority while direct target-repository runs retain target read authority. - **Fail closed before cancelling stale PR workflow runs.** Validate snapshot `headRefOid` and re-read live PR/run identity immediately before destructive cancellation, including OpenCode/Strix dispatch cleanup, so a missing head or concurrent push cannot cancel the sole current-head evidence or trigger a duplicate review. Also ensures every cancellation path (`cancel_stale_pr_runs`, `cancel_stale_opencode_runs`, `_cancel_revalidated_review_run_refs`) treats a run as cancelled only when `force_cancel_workflow_runs` actually reports success, not merely when live revalidation proved it stale -- superseding PR #1712's simpler `force_cancel_workflow_run_refs` wrapper (removed as dead code; its safety guarantee is preserved inline at every call site by this more thorough revalidate-then-cancel design). - **Cache `active_workflow_runs` for the life of one `pr_review_merge_scheduler.py` invocation.** `inspect_pr()` calls `cancel_stale_pr_runs()` unconditionally for diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index d8c4ce9b63..1975010b1e 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -3038,8 +3038,13 @@ def _fresh_open_pr_for_cancellation(repo: str, number: int) -> dict[str, Any]: def _fresh_active_run_for_cancellation(run_repo: str, run_id: str) -> dict[str, Any]: - """Return fresh active workflow-run evidence immediately before cancellation.""" - payload = gh_api_json(f"repos/{run_repo}/actions/runs/{run_id}") + """Return fresh active workflow-run evidence with repository-correct read authority.""" + central_repo = (os.environ.get("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY") or "").strip() + use_dispatch_authority = bool( + central_repo and run_repo == validate_github_repository(central_repo) + ) + reader = gh_api_json_via_dispatch_token if use_dispatch_authority else gh_api_json + payload = reader(f"repos/{run_repo}/actions/runs/{run_id}") if not isinstance(payload, dict) or str(payload.get("status") or "").lower() not in { "queued", "in_progress", diff --git a/scripts/ci/temp_scheduler_central_run_read_authority.py b/scripts/ci/temp_scheduler_central_run_read_authority.py deleted file mode 100644 index c442b4be73..0000000000 --- a/scripts/ci/temp_scheduler_central_run_read_authority.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -"""Materialize the central stale-review run read-authority repair on protected-main source.""" - -from __future__ import annotations - -from pathlib import Path - - -SCHEDULER = Path("scripts/ci/pr_review_merge_scheduler.py") -CHANGELOG = Path("CHANGELOG.md") -SELF = Path("scripts/ci/temp_scheduler_central_run_read_authority.py") -WORKFLOW = Path(".github/workflows/_temp_scheduler_central_run_read_authority.yml") - - -def replace_once(text: str, old: str, new: str, label: str) -> str: - """Replace exactly one reviewed source block and fail closed on layout drift.""" - count = text.count(old) - if count != 1: - raise RuntimeError(f"{label}: expected one exact source block, found {count}") - return text.replace(old, new, 1) - - -def repair_scheduler() -> None: - """Route central Actions run reads through the existing dispatch credential boundary.""" - source = SCHEDULER.read_text(encoding="utf-8") - old = '''def _fresh_active_run_for_cancellation(run_repo: str, run_id: str) -> dict[str, Any]:\n """Return fresh active workflow-run evidence immediately before cancellation."""\n payload = gh_api_json(f"repos/{run_repo}/actions/runs/{run_id}")\n''' - new = '''def _fresh_active_run_for_cancellation(run_repo: str, run_id: str) -> dict[str, Any]:\n """Return fresh active workflow-run evidence with repository-correct read authority."""\n central_repo = (os.environ.get("SCHEDULER_REQUIRED_WORKFLOW_REPOSITORY") or "").strip()\n use_dispatch_authority = bool(\n central_repo and run_repo == validate_github_repository(central_repo)\n )\n reader = gh_api_json_via_dispatch_token if use_dispatch_authority else gh_api_json\n payload = reader(f"repos/{run_repo}/actions/runs/{run_id}")\n''' - SCHEDULER.write_text( - replace_once(source, old, new, "central stale-review run credential routing"), - encoding="utf-8", - ) - - -def update_changelog() -> None: - """Record the control-plane credential-boundary repair under Unreleased.""" - text = CHANGELOG.read_text(encoding="utf-8") - bullet = ( - "- **Bind stale-review run revalidation to repository-correct credentials.** " - "Central `repository_dispatch` Actions evidence now uses the existing central dispatch " - "read authority while direct target-repository runs retain target read authority.\n" - ) - if bullet in text: - return - anchor = "## [Unreleased]\n" - if text.count(anchor) != 1: - raise RuntimeError("CHANGELOG Unreleased anchor drifted") - CHANGELOG.write_text(text.replace(anchor, anchor + bullet, 1), encoding="utf-8") - - -def main() -> None: - """Apply the production repair, update release traceability, and retire one-shot sources.""" - repair_scheduler() - update_changelog() - SELF.unlink(missing_ok=True) - WORKFLOW.unlink(missing_ok=True) - - -if __name__ == "__main__": - main() From 5b8badc3b9088a5845abc447ed75bf2d9a99031d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 19:49:46 +0900 Subject: [PATCH 9/9] ci(scheduler): use temporary token surface to mark source-complete PR ready --- .github/workflows/_temp_mark_ready_pr1717.yml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/_temp_mark_ready_pr1717.yml diff --git a/.github/workflows/_temp_mark_ready_pr1717.yml b/.github/workflows/_temp_mark_ready_pr1717.yml new file mode 100644 index 0000000000..07a1ba3e6c --- /dev/null +++ b/.github/workflows/_temp_mark_ready_pr1717.yml @@ -0,0 +1,34 @@ +name: Temporary Mark PR 1717 Ready + +on: + push: + branches: + - fix/scheduler-central-run-read-authority-20260902 + paths: + - .github/workflows/_temp_mark_ready_pr1717.yml + +permissions: + contents: read + pull-requests: write + +concurrency: + group: temp-mark-ready-pr1717 + cancel-in-progress: true + +jobs: + mark-ready: + if: github.repository == 'ContextualWisdomLab/.github' && github.actor == 'seonghobae' + runs-on: ubuntu-slim + steps: + - name: Revalidate exact head and mark canonical source-complete PR ready + env: + GH_TOKEN: ${{ github.token }} + EXPECTED_HEAD: ${{ github.sha }} + shell: bash + run: | + set -euo pipefail + pr_json="$(gh pr view 1717 --repo ContextualWisdomLab/.github --json state,isDraft,headRefOid)" + test "$(jq -r '.state' <<<"$pr_json")" = "OPEN" + test "$(jq -r '.isDraft' <<<"$pr_json")" = "true" + test "$(jq -r '.headRefOid' <<<"$pr_json")" = "$EXPECTED_HEAD" + gh pr ready 1717 --repo ContextualWisdomLab/.github