-
Notifications
You must be signed in to change notification settings - Fork 0
fix(scheduler): require independent exact-head approval #828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
seonghobae
wants to merge
15
commits into
main
Choose a base branch
from
fix/scheduler-independent-current-head-approval
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
c7011f2
test(scheduler): require independent current-head approval
seonghobae 5735b34
ci(scheduler): prove independent approval gate
seonghobae e6e87a5
test(scheduler): cover independent approval authority
seonghobae 491bdb9
fix(scheduler): require independent exact-head approval
seonghobae 51f49e6
test(scheduler): bind approval gate to scheduler core
seonghobae 63af540
ci(scheduler): track independent approval core
seonghobae 9203536
docs(scheduler): record independent approval gate
seonghobae e3fdd0a
docs(changelog): record scheduler approval hardening
seonghobae 8abddc8
test(scheduler): bind dispatch contract to core module
seonghobae c8bf366
test(scheduler): model independent merge approval fixtures
seonghobae c10607a
test(scheduler): preserve delegated core static contract
seonghobae 4f5cb3a
docs(scheduler): preserve current-main Strix changelog entry
seonghobae 61b5ee0
docs(scheduler): isolate approval-gate security changelog entry
seonghobae 96d9d6f
Merge branch 'main' into fix/scheduler-independent-current-head-approval
opencode-agent[bot] ea7e80a
test: require full scheduler approval validation
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
.github/workflows/scheduler-independent-approval-quality-ci.yml
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| name: Scheduler Independent Approval Quality CI | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| paths: | ||
| - ".github/workflows/scheduler-independent-approval-quality-ci.yml" | ||
| - "scripts/ci/pr_review_merge_scheduler.py" | ||
| - "scripts/ci/_pr_review_merge_scheduler_core.py" | ||
| - "tests/test_scheduler_independent_approval_gate.py" | ||
| - "tests/test_pr_review_merge_scheduler.py" | ||
| - "docs/doctoring/scheduler-independent-current-head-approval.md" | ||
| - "CHANGELOG.md" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: scheduler-independent-approval-quality-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| exact-head-independent-approval: | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - name: Checkout exact source revision | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| persist-credentials: false | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | ||
| with: | ||
| python-version: "3.14" | ||
|
|
||
| - name: Install exact hash-verified test runner dependencies | ||
| env: | ||
| PIP_DISABLE_PIP_VERSION_CHECK: "1" | ||
| PIP_NO_INPUT: "1" | ||
| shell: bash --noprofile --norc -e -o pipefail {0} | ||
| run: | | ||
| cat >"${RUNNER_TEMP}/scheduler-approval-quality-requirements.txt" <<'EOF' | ||
| iniconfig==2.1.0 --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 | ||
| packaging==26.2 --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e | ||
| pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 | ||
| pygments==2.20.0 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 | ||
| pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c | ||
| EOF | ||
| python -m pip install \ | ||
| --only-binary=:all: \ | ||
| --require-hashes \ | ||
| -r "${RUNNER_TEMP}/scheduler-approval-quality-requirements.txt" | ||
|
|
||
| - name: Prove independent current-head approval contract | ||
| shell: bash --noprofile --norc -e -o pipefail {0} | ||
| run: | | ||
| test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha || github.sha }}" | ||
| python -m pytest -q | ||
| python -m compileall -q \ | ||
| scripts/ci/pr_review_merge_scheduler.py \ | ||
| scripts/ci/_pr_review_merge_scheduler_core.py \ | ||
| tests/test_scheduler_independent_approval_gate.py | ||
| test -z "$(git status --porcelain=v1 --untracked-files=all)" |
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
74 changes: 74 additions & 0 deletions
74
docs/doctoring/scheduler-independent-current-head-approval.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Scheduler independent exact-head approval gate | ||
|
|
||
| ## Decision record | ||
|
|
||
| The organization merge scheduler must fail closed unless GitHub's current pull-request review policy is satisfied **and** an independent reviewer has submitted a formal `APPROVED` review bound to the exact live pull-request head. Exact-head OpenCode approval remains necessary where configured, but it is not sufficient merge authority. | ||
|
|
||
| This control repairs the governance defect tracked in #771: an automation credential that can merge or enable auto-merge must not infer separation of duties from an advisory model review, a predecessor-head approval, the pull-request author's own identity, a status context, or absent review metadata. | ||
|
|
||
| ## Threat and failure model | ||
|
|
||
| A repository or organization ruleset can permit selected users, roles, teams, or GitHub Apps to bypass rules. Consequently, relying on GitHub to reject an unsafe scheduler mutation is insufficient when the scheduler credential could ever be granted bypass capability. The scheduler therefore applies an application-level gate before either direct merge or native auto-merge entrypoints. | ||
|
|
||
| The fail-closed decision requires all of the following review evidence on the current live pull request: | ||
|
|
||
| 1. `reviewDecision` is exactly `APPROVED`; | ||
| 2. the authoritative pull-request evidence includes a non-empty author login; | ||
| 3. an independent review has state `APPROVED`; | ||
| 4. that formal review is bound to the exact current head SHA under the scheduler's existing review/head-evidence rules; | ||
| 5. the reviewer identity is non-empty, differs from the pull-request author, and is not the OpenCode automated reviewer; and | ||
| 6. every pre-existing scheduler gate for current-head OpenCode evidence, Strix/security evidence, unresolved threads, checks, mergeability, head freshness, branch update safety, and expected-head merge semantics remains in force. | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| `REVIEW_REQUIRED`, missing review state, stale/dismissed/comment-only reviews, unknown author identity, author self-review, OpenCode-only review, predecessor-head approval, status-only evidence, and synthetic merge evidence never satisfy this gate. | ||
|
|
||
| ## Implementation boundary | ||
|
|
||
| `scripts/ci/pr_review_merge_scheduler.py` is the auditable approval-policy facade. The mature scheduler engine remains in `scripts/ci/_pr_review_merge_scheduler_core.py`; the facade adds the smallest merge-authorization boundary without duplicating or weakening the established check, cleanup, branch-update, review-dispatch, conflict, and expected-head machinery. | ||
|
|
||
| The authoritative GraphQL pull-request envelope is extended with `author { login }`. The REST fallback records the same author identity from GitHub's pull-request user field. If that identity is absent, independence cannot be established and the result is `WAIT`. | ||
|
|
||
| When the independent-approval gate is unsatisfied, the facade invokes the existing scheduler engine with merge entrypoints mechanically disabled. This preserves productive non-merge maintenance while preventing `merge_pr` or auto-merge enablement. If native auto-merge is already configured on an otherwise clean PR that no longer satisfies the gate, the scheduler disables it rather than allowing stale approval state to remain armed. | ||
|
|
||
| The split is permanent source structure, not a one-shot repair workflow or branch writer. The focused quality workflow tracks and compiles both the policy facade and the core module so edits to either surface regenerate exact-head evidence. | ||
|
|
||
| ## Test-first evidence | ||
|
|
||
| The permanent regression suite covers the following cases: | ||
|
|
||
| - GitHub `REVIEW_REQUIRED` blocks despite exact-head OpenCode and independent approvals; | ||
| - GitHub `APPROVED` without an exact-head independent approval blocks; | ||
| - predecessor-head independent approval blocks; | ||
| - pull-request-author self-approval blocks; | ||
| - missing pull-request author identity blocks; | ||
| - missing reviewer identity blocks; | ||
| - non-`APPROVED` independent review blocks; | ||
| - exact-head OpenCode plus exact-head non-author independent approval plus GitHub `APPROVED` preserves the normal merge path; and | ||
| - the permanent quality workflow tracks the scheduler core as well as the facade. | ||
|
|
||
| The focused exact-head workflow also verifies literal pull-request-head checkout with persisted credentials disabled, hash-verified test dependencies, Python compilation, and a clean worktree. Broader repository, security, supply-chain, automated-review, independent-review, and branch-protection evidence remains independently required before readiness or merge. | ||
|
|
||
| ## Relationship to GitHub rules | ||
|
|
||
| GitHub documents that protected branches and rulesets can require approving reviews and passing status checks. GitHub also documents that pull-request authors cannot approve their own pull requests; that stale approvals can be dismissed after code changes; and that rulesets can require approval from someone other than the most recent pusher. Rulesets may additionally define bypass actors. The scheduler's application-level gate intentionally complements these server-side controls rather than replacing or weakening them. | ||
|
|
||
| A GitHub `APPROVED` aggregate decision is therefore treated as necessary repository-policy evidence, while the exact-head independent-review check supplies an explicit automation-level separation-of-duties invariant. Neither condition substitutes for required checks, security gates, conversation resolution, or the repository's actual branch/ruleset evaluation. | ||
|
|
||
| ## Operations and rollback | ||
|
|
||
| Before every scheduler mutation, refetch the live pull-request head/base and the relevant target state. If the head or base changes, discard predecessor evidence and re-evaluate. Merge remains expected-head guarded. | ||
|
|
||
| If this repair causes an operational regression, rollback means reverting the reviewed scheduler change and restoring the last protected-main implementation while keeping merges disabled until an equivalent independent-approval control is available. Rollback must never be implemented by lowering required-review counts, granting routine bypass, synthesizing review state, accepting stale approvals, or re-enabling direct merge without an equivalent fail-closed authorization check. | ||
|
|
||
| ## Non-claims | ||
|
|
||
| This control does not prove that an approving reviewer is organizationally independent merely because GitHub identities differ. Repository permission, team membership, last-pusher rules, CODEOWNERS requirements, and organization policy remain authoritative. It also does not make an automated review a substitute for a human approval when repository policy requires a counted human reviewer. | ||
|
|
||
| ## References | ||
|
|
||
| GitHub. (n.d.). *About protected branches*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches | ||
|
|
||
| GitHub. (n.d.). *Approving a pull request with required reviews*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/pull-requests/how-tos/review-pull-requests/approving-a-pull-request-with-required-reviews | ||
|
|
||
| GitHub. (n.d.). *Available rules for rulesets*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets | ||
|
|
||
| GitHub. (n.d.). *About rulesets*. GitHub Docs. Retrieved August 8, 2026, from https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.