diff --git a/.github/workflows/strix.yml b/.github/workflows/strix.yml index 508ee6d3b..47b00d375 100644 --- a/.github/workflows/strix.yml +++ b/.github/workflows/strix.yml @@ -113,6 +113,9 @@ jobs: if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc" fi + if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then + git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py" + fi git -C "$TRUSTED_WORKSPACE" update-ref "refs/remotes/pull/${PR_NUMBER}/head" "$PR_HEAD_SHA" exit 0 fi @@ -124,6 +127,9 @@ jobs: if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:opencode.jsonc" 2>/dev/null; then git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc" fi + if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" 2>/dev/null; then + git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py" + fi exit 0 fi if [ "$pr_head_fetch_attempt" -lt 6 ]; then diff --git a/PR_GOVERNANCE_AUDIT.md b/PR_GOVERNANCE_AUDIT.md index 151f2a0ec..35195d59e 100644 --- a/PR_GOVERNANCE_AUDIT.md +++ b/PR_GOVERNANCE_AUDIT.md @@ -18,7 +18,7 @@ OpenCode decides; GitHub Actions mutates. ## Live Repository Inventory -Live generated: 2026-06-23 04:18 KST. PR #28 post-merge refresh: 2026-06-23 16:05 KST. +Live generated: 2026-06-23 04:18 KST. PR #28 post-merge refresh: 2026-06-23 16:05 KST. PR #37 post-merge refresh: 2026-06-23 21:50 KST. | Repo | Flow | Default | Auto | Rulesets | Required checks | Stale dismissal | Merge queue | Workflows | Recent merged actor | |---|---:|---:|---:|---|---|---:|---:|---|---| @@ -38,10 +38,10 @@ Live generated: 2026-06-23 04:18 KST. PR #28 post-merge refresh: 2026-06-23 16:0 | Repo | Gap | |---|---| -| `.github` | PR #28 is merged at `a025be1` after same-head manual Strix run `28007326148`, same-head OpenCode run `28008174977`, unresolved review threads `0`, and guarded merge against head `811446d`. Remaining open PRs #19-#27 and #29-#36 are still blocked by `CHANGES_REQUESTED` and/or `DIRTY`; the scheduler dry run inspected 17 PRs and made no mutation. | +| `.github` | PR #37 is merged at `3c3695f` after current-head Strix run `28025893898`, current-head manual OpenCode run `28026724674`, unresolved review threads `0`, and guarded merge against head `8b25761`. Remaining open PRs #19-#27 and #29-#36 are still blocked by `CHANGES_REQUESTED` and/or `DIRTY`. | | `bandscope` | Required checks are repo-specific and broad; keep GitHub native auto-merge as the check interpreter. | -| `clearfolio` | Auto-merge is off and the PR Review Merge Scheduler is missing. | -| `codec-carver` | Latest merged sample #94 still used `opencode-agent`; replace the legacy scheduler with the central GitHub Actions path. | +| `clearfolio` | Auto-merge is off. PR #13 adds the central PR Review Merge Scheduler and `opencode.jsonc`; current PR-target Strix still fails because base `strix.yml` does not copy PR-head `opencode.jsonc` into the trusted workspace. | +| `codec-carver` | Latest merged sample #94 still used `opencode-agent`; PR #98 replaces the legacy scheduler with the central GitHub Actions path and is waiting on existing OpenCode/Strix checks. | | `contextual-orchestrator` | No matching rulesets or review workflows; either opt in deliberately or mark unmanaged. | | `naruon` | Canonical strict check source, but open PRs still need the updated contract observed through one full outdated -> update -> new-head review trace. | | `newsdom-api` | Ruleset-required checks must stay GitHub-interpreted; open queue is mostly review/check blocked. | @@ -104,8 +104,8 @@ PR #36: block: merge conflict: DIRTY ## Rollout List 1. Keep `naruon`, `.github`, `VibeSec`, `bandscope`, `newsdom-api`, `pg-erd-cloud`, and `scopeweave` on `PR Review Merge Scheduler`. -2. Replace `codec-carver` legacy `Scheduled PR Review Merge` with `PR Review Merge Scheduler`. -3. Add `PR Review Merge Scheduler` to `clearfolio` or explicitly mark it unmanaged; auto-merge is currently off. +2. Merge `codec-carver` PR #98 to replace legacy `Scheduled PR Review Merge` with `PR Review Merge Scheduler`; current checks were still in progress at the 2026-06-23 22:13 KST snapshot. +3. Resolve `clearfolio` PR #13's trusted-base Strix blocker, then merge it to add `PR Review Merge Scheduler`; auto-merge is currently off. 4. Decide whether `contextual-orchestrator` should join the central PR governance surface; no matching workflows or rulesets were returned. 5. Keep `pg-erd-cloud` autofix workflows repo-local; do not make autofix part of the central merge contract. @@ -128,3 +128,7 @@ PR #36: block: merge conflict: DIRTY - Strix run `28022323798` caught that the first label repair changed normalizer parsing too narrowly: inline approval summaries in `test_strix_quick_gate.sh` no longer normalized. Label parsing now accepts inline verification labels while excluding the `Coverage:` suffix inside `Docstring coverage:`, preserving both inline transcript controls and appended evidence repair. - PR #37 same-head manual Strix run `28023392848` succeeded for head `07a6b76`, but the concurrently dispatched same-head manual OpenCode run `28023401894` spent its early lifetime waiting in `Prepare bounded OpenCode review evidence`. That exposed a scheduler-level resource issue: dispatching Strix and OpenCode together can turn OpenCode into a long poller whenever Strix is queued or slow. The scheduler now serializes the process: first dispatch Strix, then wait for a later scheduler pass to dispatch OpenCode after Strix evidence is complete. - The base-branch automatic OpenCode run `28025023007` still posted a current-head `CHANGES_REQUESTED` review before cancellation on head `1d05f52`, even though that automatic trigger is removed by this PR. The scheduler previously treated any current-head OpenCode `CHANGES_REQUESTED` as permanent. It now reads the latest OpenCode review on the current head, so a later same-head OpenCode approval can supersede an earlier false negative from the same reviewer. +- `clearfolio` PR #13 and `codec-carver` PR #98 are opened as thin rollouts. Their scheduler workflows now pin `scripts/ci/pr_review_merge_scheduler.py` to central commit `7be2d99` and verify SHA-256 `f954b62efa4ad60964a65501d777cb4ba26f1ac5746c2d11406d610a5ab695f6` before running the script self-test and inspecting their own PR queues. `codec-carver` PR #98 also deletes the legacy OpenCode app-token merge workflow. +- `clearfolio` PR #13 first failed Strix run `28027843973` because `opencode.jsonc` was missing. Commit `38e9a82` added the central `opencode.jsonc`, but Strix runs `28028155386`, `28030126946`, and `28030438259` still failed because clearfolio's trusted-base `strix.yml` did not copy `PR_HEAD_SHA:opencode.jsonc` into the trusted workspace. Commit `2618c41` adds the PR-head `opencode.jsonc` and scheduler-policy materialization to `strix.yml`; PR-target Strix run `28030872994` and same-head manual Strix run `28030912898` were still in progress or queued at the 2026-06-23 22:48 KST snapshot. +- `codec-carver` PR #98 already has base `opencode.jsonc`. PR #98 now pins the central scheduler instead of downloading from `main`; same-head Strix run `28030439830` and OpenCode runs `28030438605`/`28030439065` were still in progress at the 2026-06-23 22:48 KST snapshot. +- `.github` PR #38 exposed two central gaps after PR #37 merged: the `review_dispatch` reason lost the `same-head Strix and OpenCode dispatched` contract string, and `failed_status_checks()` treated failed PR-target Strix check runs as blockers even when a later manual `strix` status could supersede them. Commit `7be2d99` restores the reason string, materializes PR-head scheduler policy as non-executed data for Strix self-test, and ignores stale Strix check-run failures when the same head has a successful `strix` status context. Manual Strix run `28030448032` had passed self-test and was still running `Run Strix (quick)` at the 2026-06-23 22:48 KST snapshot. diff --git a/scripts/ci/pr_review_merge_scheduler.py b/scripts/ci/pr_review_merge_scheduler.py index 609fd51c8..babc85e59 100644 --- a/scripts/ci/pr_review_merge_scheduler.py +++ b/scripts/ci/pr_review_merge_scheduler.py @@ -248,10 +248,18 @@ def has_current_head_changes_requested(pr: dict[str, Any]) -> bool: def failed_status_checks(pr: dict[str, Any]) -> list[str]: """Return failing check or status context names from the PR rollup.""" failed: list[str] = [] + successful_status_contexts = { + node.get("context") + for node in context_nodes(pr) + if node.get("__typename") != "CheckRun" + and (node.get("state") or "").upper() == "SUCCESS" + } for node in context_nodes(pr): if node.get("__typename") == "CheckRun": conclusion = (node.get("conclusion") or "").upper() if conclusion in {"FAILURE", "ERROR", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE"}: + if is_strix_context(node) and "strix" in successful_status_contexts: + continue failed.append(node.get("name") or "check-run") else: state = (node.get("state") or "").upper() @@ -410,7 +418,7 @@ def inspect_pr( return Decision( number, "review_dispatch", - "current head has completed Strix evidence; same-head OpenCode dispatched", + "current head has completed Strix evidence; same-head Strix and OpenCode dispatched", ) return Decision(number, "block", "current head has no OpenCode approval") diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 8ea3cb11d..ea5d0ddc8 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -125,6 +125,8 @@ assert_strix_workflow_pr_trigger_hardened() { assert_file_contains "$workflow_file" 'fetch --no-tags --depth=1 origin "$PR_BASE_SHA"' "strix workflow fetches manual PR-scope base commit for diffing" assert_file_contains "$workflow_file" 'cat-file -e "$PR_HEAD_SHA:opencode.jsonc"' "strix workflow checks for PR-head OpenCode config without executing it" assert_file_contains "$workflow_file" 'show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc"' "strix workflow materializes PR-head OpenCode config as data for self-test assertions" + assert_file_contains "$workflow_file" 'cat-file -e "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py"' "strix workflow checks for PR-head scheduler policy without executing it" + assert_file_contains "$workflow_file" 'show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py"' "strix workflow materializes PR-head scheduler policy as data for self-test assertions" assert_file_contains "$workflow_file" "refs/remotes/pull" "strix workflow verifies fetched PR head ref" local pr_head_fetch_block pr_head_fetch_block="$( @@ -141,8 +143,8 @@ assert_strix_workflow_pr_trigger_hardened() { record_failure "strix workflow configures git credentials in PR head fetch step" fi case "$pr_head_fetch_block" in - *'fetch --no-tags --depth=1 origin "$PR_HEAD_SHA"'*'show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc"'*) ;; - *) record_failure "strix workflow materializes PR-head OpenCode config only after fetching the PR head commit" ;; + *'fetch --no-tags --depth=1 origin "$PR_HEAD_SHA"'*'show "$PR_HEAD_SHA:opencode.jsonc" > "$TRUSTED_WORKSPACE/opencode.jsonc"'*'show "$PR_HEAD_SHA:scripts/ci/pr_review_merge_scheduler.py" > "$TRUSTED_WORKSPACE/scripts/ci/pr_review_merge_scheduler.py"'*) ;; + *) record_failure "strix workflow materializes PR-head review policy files only after fetching the PR head commit" ;; esac assert_file_contains "$workflow_file" "for pr_head_fetch_attempt in 1 2 3 4 5 6" "strix workflow retries stale PR head ref propagation" assert_file_contains "$workflow_file" "PR head ref did not resolve to expected commit" "strix workflow fails closed when PR head ref remains stale" diff --git a/tests/test_pr_review_merge_scheduler.py b/tests/test_pr_review_merge_scheduler.py index e89af16c9..1245196eb 100644 --- a/tests/test_pr_review_merge_scheduler.py +++ b/tests/test_pr_review_merge_scheduler.py @@ -200,6 +200,18 @@ def test_review_state_and_failed_checks(): } ) assert sched.failed_status_checks(failed) == ["strix", "lint"] + manual_strix_supersedes_pr_target_failure = make_pr( + statusCheckRollup={ + "contexts": { + "nodes": [ + {"__typename": "CheckRun", "name": "strix", "conclusion": "FAILURE"}, + {"context": "strix", "state": "SUCCESS"}, + {"context": "lint", "state": "ERROR"}, + ] + } + } + ) + assert sched.failed_status_checks(manual_strix_supersedes_pr_target_failure) == ["lint"] def test_actions_call_gh_with_expected_arguments(monkeypatch):