Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/scheduler-independent-approval-quality-ci.yml
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)"
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,14 @@ Semantic Versioning where the repository publishes a release.

### Fixed

- Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched.
- Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109).
- Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109).
- Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories.
- Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched.
- Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109).
- Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109).
- Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories.
- Required the merge scheduler to fail closed unless GitHub reviewDecision is APPROVED and an independent non-author, non-OpenCode reviewer formally approved the exact live head.
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Required the merge scheduler to fail closed unless GitHub reviewDecision is APPROVED and an independent non-author, non-OpenCode reviewer formally approved the exact live head.
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).
- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics.
- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.
Expand Down
74 changes: 74 additions & 0 deletions docs/doctoring/scheduler-independent-current-head-approval.md
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.
Comment thread
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 (APA 7th)

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
Loading
Loading