Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
340ed99
fix(ci): cap merge-scheduler review dispatches at 1 per run
seonghobae Sep 18, 2026
6af39ec
fix(ci): differentiate merge-scheduler budgets by trigger
seonghobae Sep 18, 2026
75c3d32
fix(ci): fail loud when schedule recovery silently skips dispatch
seonghobae Sep 18, 2026
a83d6d3
fix(security): allowlist https://api.github.com before urllib urlopen
seonghobae Sep 18, 2026
18dbe00
fix(scheduler): fail closed empty review_dispatch_limit to 1
seonghobae Sep 18, 2026
6000222
fix(ci): let schedule recovery update despite in-flight checks
seonghobae Sep 18, 2026
5b9e864
fix(sast): clear the three Semgrep findings that fail every PR here
seonghobae Sep 18, 2026
225260a
test(security): pin GitHub API redirect credential boundary
seonghobae Sep 18, 2026
0ae2204
fix(security): contain GitHub API redirects to admitted origin
seonghobae Sep 18, 2026
062663a
test(security): pin Strix redirect credential boundary
seonghobae Sep 18, 2026
2708a6b
fix(security): contain Strix GitHub API redirects
seonghobae Sep 18, 2026
bb9413a
test(security): pin Pages caller-input shell boundary
seonghobae Sep 18, 2026
3758b89
chore(deps): bump anyio from 4.14.0 to 4.14.2
dependabot[bot] Sep 18, 2026
4dcd25c
test(security): align Strix transport seam with dedicated opener
seonghobae Sep 19, 2026
4967d66
test(security): execute Pages shell-input regression
seonghobae Sep 19, 2026
c797f28
test(scheduler): require explicit review dispatch authority (RED)
seonghobae Sep 19, 2026
df17dd4
test(scheduler): reject implicit dispatch budget in agent contract (RED)
seonghobae Sep 19, 2026
1214600
test(scheduler): fail closed unset review budget in Strix gate (RED)
seonghobae Sep 19, 2026
4f76aa0
fix(scheduler): fail closed without dispatch-budget authority
seonghobae Sep 19, 2026
d0d91a2
docs(scheduler): record explicit budget authority and fail-closed abs…
seonghobae Sep 19, 2026
1ee8ef1
merge(security): carry #2269 urllib GitHub API opener into #2267
seonghobae Sep 19, 2026
8b48ec6
merge(deps): carry #2278 anyio 4.14.2 into #2267
seonghobae Sep 19, 2026
ba7f41f
fix(sast): silence Bandit B310 on the same two hardened openers
Sep 19, 2026
e0b6e70
test(sast): cover the codeql opener's origin pin, not just strix's
Sep 19, 2026
174592e
fix(scheduler): preserve structured schedule recovery
seonghobae Sep 19, 2026
7694a8d
test(scheduler): cover structured recovery taxonomy
seonghobae Sep 19, 2026
3923b19
merge: integrate GitHub API redirect authority successor
seonghobae Sep 19, 2026
5896e60
docs(sast): record lossless successor stack
seonghobae Sep 19, 2026
1a9074b
fix(scheduler): integrate canonical GitHub API authority
seonghobae Sep 19, 2026
338acae
fix(scheduler): integrate explicit mutation budget authority
seonghobae Sep 19, 2026
fb202c1
merge: carry current GitHub authority evidence into scheduler
seonghobae Sep 19, 2026
71398d9
fix(scheduler): restore verified integration tree
seonghobae Sep 19, 2026
5c71e88
merge: carry current GitHub authority evidence into SAST repair
seonghobae Sep 19, 2026
c870496
test(scheduler): specify zero and unlimited budget contracts
seonghobae Sep 19, 2026
f275c57
fix(scheduler): preserve zero and unlimited budget authority
seonghobae Sep 19, 2026
e8252ce
docs(scheduler): record zero-budget authority repair
seonghobae Sep 19, 2026
5f51355
merge: integrate current GitHub API authority owner
seonghobae Sep 19, 2026
03bc3aa
test(ci): prove G-17 ancestry guard independently
seonghobae Sep 19, 2026
cd3b41b
merge: carry current GitHub authority owner into SAST repair
seonghobae Sep 19, 2026
f27626a
merge: align scheduler repair with protected owner
seonghobae Sep 19, 2026
3eb0a5c
fix(scope): leave AnyIO lock update with dependency owner
seonghobae Sep 19, 2026
857e788
test(strix): require fixture evidence binder
seonghobae Sep 19, 2026
89cee55
fix(strix): materialize evidence binder in fixtures
seonghobae Sep 19, 2026
1eb03c7
docs(strix): record fixture runtime RCA
seonghobae Sep 19, 2026
8f66ead
test(strix): require complete isolated fixture runtime
seonghobae Sep 19, 2026
354692e
merge: adopt protected main before Strix repair
seonghobae Sep 19, 2026
4e8829f
fix(strix): restore and close isolated fixture runtime
seonghobae Sep 19, 2026
0d32464
fix(ci): adopt Strix fixture runtime owner repair
seonghobae Sep 19, 2026
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
46 changes: 46 additions & 0 deletions .github/workflows/deploy-pages-input-security-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Deploy Pages Input Security CI

on:
pull_request:
branches: [main]
paths:
- ".github/workflows/deploy-pages.yml"
- ".github/workflows/deploy-pages-input-security-ci.yml"
- "tests/test_deploy_pages_input_shell_boundary.py"

permissions:
contents: read

concurrency:
group: deploy-pages-input-security-${{ github.repository }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
pages_input_shell_boundary:
name: pages-input-shell-boundary
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact pull request head
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 1
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"

- name: Verify exact-head Pages shell-input boundary
shell: bash --noprofile --norc -e -o pipefail {0}
run: |
test "$(git rev-parse HEAD)" = "${{ github.event.pull_request.head.sha }}"
python -m unittest -q tests/test_deploy_pages_input_shell_boundary.py
python -m compileall -q tests/test_deploy_pages_input_shell_boundary.py
14 changes: 11 additions & 3 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,21 @@ jobs:
fi
fi

# Caller inputs reach the shell through env, never through ${{ }}
# interpolation into the script body: a project name containing shell
# metacharacters would otherwise execute here. Same defect class that
# Semgrep's run-shell-injection rule flags elsewhere in this repo.
- name: Summary
if: always()
env:
PROJECT_NAME: ${{ inputs.project_name }}
BUILD_DIR: ${{ inputs.build_dir }}
CUSTOM_DOMAIN: ${{ inputs.custom_domain }}
run: |
{
echo "## Cloudflare Pages deploy"
echo ""
echo "- **Project:** \`${{ inputs.project_name }}\`"
echo "- **Build dir:** \`${{ inputs.build_dir }}\`"
echo "- **Custom domain:** \`${{ inputs.custom_domain || '(none)' }}\`"
echo "- **Project:** \`${PROJECT_NAME}\`"
echo "- **Build dir:** \`${BUILD_DIR}\`"
echo "- **Custom domain:** \`${CUSTOM_DOMAIN:-(none)}\`"
} >> "$GITHUB_STEP_SUMMARY"
30 changes: 22 additions & 8 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ on:
review_dispatch_limit:
description: OpenCode/Strix review dispatch budget per scheduler run (-1 dispatches every eligible current-head review)
required: false
default: "1"
type: string
admission_dispatch_budget:
description: Review-admission dispatch budget per scheduler run (-1 disables the per-run admission cap)
required: false
type: string
branch_update_limit:
description: Branch update budget per scheduler run (-1 updates every eligible outdated branch)
required: false
default: "1"
type: string
enable_auto_merge:
description: Enable auto-merge for current-head approved PRs
Expand Down Expand Up @@ -132,9 +134,9 @@ jobs:
PROJECT_FLOW_INPUT: ${{ github.event.client_payload.project_flow || inputs.project_flow || vars.PROJECT_FLOW || '' }}
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || github.event.client_payload.pr_number || inputs.pr_number || '' }}
TRIGGER_REVIEWS: ${{ github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event_name == 'pull_request_review' || (github.event_name == 'repository_dispatch' && github.event.client_payload.trigger_reviews != false) || inputs.trigger_reviews == true }}
REVIEW_DISPATCH_LIMIT_INPUT: ${{ github.event.client_payload.review_dispatch_limit || inputs.review_dispatch_limit || vars.REVIEW_DISPATCH_LIMIT || '1' }}
REVIEW_ADMISSION_DISPATCH_BUDGET: ${{ vars.REVIEW_ADMISSION_DISPATCH_BUDGET || '1' }}
BRANCH_UPDATE_LIMIT_INPUT: ${{ github.event.client_payload.branch_update_limit || inputs.branch_update_limit || vars.BRANCH_UPDATE_LIMIT || '1' }}
REVIEW_DISPATCH_LIMIT_INPUT: ${{ format('{0}', github.event.client_payload.review_dispatch_limit) || inputs.review_dispatch_limit || vars.REVIEW_DISPATCH_LIMIT || '' }}
REVIEW_ADMISSION_DISPATCH_BUDGET: ${{ format('{0}', github.event.client_payload.admission_dispatch_budget) || inputs.admission_dispatch_budget || vars.REVIEW_ADMISSION_DISPATCH_BUDGET || '' }}
BRANCH_UPDATE_LIMIT_INPUT: ${{ format('{0}', github.event.client_payload.branch_update_limit) || inputs.branch_update_limit || vars.BRANCH_UPDATE_LIMIT || '' }}
ENABLE_AUTO_MERGE: ${{ github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'pull_request_target' || (github.event_name == 'repository_dispatch' && github.event.client_payload.enable_auto_merge != false) || inputs.enable_auto_merge == true }}
MERGE_MODE: ${{ github.event.client_payload.merge_mode || inputs.merge_mode || vars.PR_MERGE_MODE || 'direct_or_auto' }}
UPDATE_BRANCHES: ${{ github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'pull_request_target' || (github.event_name == 'repository_dispatch' && github.event.client_payload.update_branches != false) || inputs.update_branches == true }}
Expand Down Expand Up @@ -527,12 +529,24 @@ jobs:
fi
review_dispatch_limit="$REVIEW_DISPATCH_LIMIT_INPUT"
if [ -z "$review_dispatch_limit" ]; then
review_dispatch_limit="-1"
echo "::error::REVIEW_DISPATCH_LIMIT must be explicitly configured" >&2
exit 1
fi
branch_update_limit="$BRANCH_UPDATE_LIMIT_INPUT"
if [ -z "$branch_update_limit" ]; then
branch_update_limit="1"
echo "::error::BRANCH_UPDATE_LIMIT must be explicitly configured" >&2
exit 1
fi
admission_dispatch_budget="$REVIEW_ADMISSION_DISPATCH_BUDGET"
if [ -z "$admission_dispatch_budget" ]; then
echo "::error::REVIEW_ADMISSION_DISPATCH_BUDGET must be explicitly configured" >&2
exit 1
fi
printf 'scheduler_effective_limits event=%s review_dispatch=%s branch_update=%s admission=%s\n' \
"${GITHUB_EVENT_NAME}" \
"$review_dispatch_limit" \
"$branch_update_limit" \
"$admission_dispatch_budget"
args=(
--repo "$TARGET_REPOSITORY"
--base-branch "$TARGET_DEFAULT_BRANCH"
Expand All @@ -541,7 +555,7 @@ jobs:
--review-workflow "Required OpenCode Review"
--review-dispatch-limit "$review_dispatch_limit"
--admission-state-path "${RUNNER_TEMP}/review-admission/state.json"
--admission-dispatch-budget "$REVIEW_ADMISSION_DISPATCH_BUDGET"
--admission-dispatch-budget "$admission_dispatch_budget"
--admission-sequence "$GITHUB_RUN_ID"
--branch-update-limit "$branch_update_limit"
--stale-opencode-minutes "$STALE_OPENCODE_MINUTES"
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
### Merge-scheduler preserves explicit zero and unlimited mutation budgets

- Repository-dispatch payloads for review dispatch, admission dispatch, and branch update now stringify the supplied value before fallback selection. GitHub Actions treats numeric `0` as falsy, so the prior `payload || input || repository variable` expression could replace an explicit zero with a positive repository variable and authorize mutation the caller denied. The admission controllers also preserve explicit `-1` unlimited authority while rejecting values below `-1`. Contract coverage pins numeric-zero and unlimited behavior at the workflow and owner modules. Refs #2267.

### Strix isolated fixtures preserve the complete evidence-binding runtime

- Runtime Quality runs `35445211402` (`#2272`) and `35448837045` (`#2109`) failed with the same first causal error: isolated Strix fixtures copied the gate and model helpers but omitted `strix_evidence_binding.py`. The first attempted repair then truncated the 13,138-line shell contract, its Python regression, CHANGELOG, and product-gap baseline. This ordinary-forward repair restores those four authorities, adopts protected `main` as a second parent, and adds the binder beside the model helper in all 25 isolated fixture runtimes. A source-first regression now requires the complete 25/25 runtime closure.

### SAST successor restores lost Pages evidence and inherits redirect authority

- `.github#2272` was briefly force-moved from `4967d66f` to sibling `1ca50644`, dropping the dedicated Pages caller-input security workflow and its executable regression. Before this repair published, a second concurrent rewrite produced `e0b6e70f` with `4967d66f` restored as an ancestor. Ordinary merge `3923b196` keeps that complete current lineage as first parent and stacks the canonical GitHub REST redirect-authority successor `.github#2279@9c19c6e` as second parent. The resulting Draft preserves the Pages `env` shell boundary, its exact-head hosted test, both initial-origin regressions, and the production no-redirect opener/source/tests without another Force Push, scanner suppression, or gate weakening.

### Noema transport capacity schedules a bounded continuation re-dispatch

- After gateway failover, HTTP 429/5xx no longer end only as a permanent required-check failure with `caller attempts=1`. ADR-0031 classifies that class as `provider_capacity_unavailable`, keeps the single gateway request per job, surfaces `provider_attempt_count` from the orchestrator error envelope, and authorizes at most two same-head `repository_dispatch` retries after a capped `Retry-After` or deterministic 60–180 s jitter. Review is never skipped. Refs #2165.
Expand Down Expand Up @@ -96,6 +108,7 @@
- Raised `hourly-review-repair.yml`'s discovery ceiling from 50 to 200 while rotating deterministic 50-PR deep-inspection windows by hourly run number. The scheduler hydrates only the selected window and stops immediately after its single dispatch, preserving access to newer PRs without quadrupling expensive review/check/comment work. See `docs/doctoring/hourly-review-repair-single-file-consolidation.md`'s 2026-09-03 follow-up.

## [Unreleased]
- **Fail closed when scheduler mutation budgets have no operator authority.** Merge-scheduler recovery keeps its structured outdated-head taxonomy and update/dispatch fallthrough, but no longer derives review, branch-update, or admission limits from one observed backlog. Each budget must be supplied explicitly by dispatch/input/repository variable; absent authority stops before PR mutation.
- **Bind GitHub REST redirect evidence to both production opener chains.** `.github#2279` now feeds a synthetic same-authority 302 through the CodeQL identity and Strix evidence clients' real module-level openers, proving the redirect target is never contacted and the bearer header is never forwarded. Removing `_RejectRedirects` from either opener makes the contract fail on the forbidden second request. Four stale Strix HTTP/transport/JSON fixtures now patch that same production seam; direct handler unit cases and standalone CodeQL materialization remain unchanged.
- **Define an evidence-backed repository README quality standard.** Added `docs/repository-readme-quality-standard.md` as the shared review contract for product-first structure, code-current onboarding, authority boundaries, durable quality signals, and repository/source/dependency license due diligence. Product repositories continue to own their own README prose; the standard is linked from the root documentation map and does not centralize or generate product claims.
- Include merge-scheduler entrypoint, core, and regression-test changes in
Expand Down
5 changes: 4 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ carry an independent proof and source-line digest; it never invents observed
results. The scheduler updates a PR branch in two cases: after approval, when no current-head check
has failed and GitHub reports the PR as behind; and before review dispatch, when the PR is behind and
no current-head check is still queued or running (an in-flight check is evidence the update would
discard; see #1935). The mechanical merge scheduler itself never synthesizes a fix: it gives `DIRTY`/`CONFLICTING`
discard; see #1935). Daily `schedule` recovery is the exception: it may update (or, if the update
budget is exhausted, dispatch review on) an outdated OpenCode-needing head despite in-flight checks,
with a loud warning, so recovery is not inert under queue saturation (see
`docs/doctoring/schedule-recovery-inflight-update-bypass-20260918.md`). The mechanical merge scheduler itself never synthesizes a fix: it gives `DIRTY`/`CONFLICTING`
PRs repair guidance. A separate edit-capable autofix flow
(`scripts/ci/pr_review_fix_scheduler.py` → `.github/workflows/pr-review-autofix.yml`) may, for an
approved same-repository-head PR, merge the base into the head and resolve the conflict markers; the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Doctoring record: merge-scheduler `REVIEW_DISPATCH_LIMIT` throughput shaping (2026-09-18)

- **Date:** 2026-09-18
- **Subject:** Cap per-run OpenCode/Strix review dispatch budget so the merge
scheduler shapes throughput under the org Actions plan ceiling, without
disabling review dispatch.
- **Decision record:** operational var change plus fail-closed validation. The
measured repo variable remains the authority; unset or blank has no invented fallback.
- **PR:** this commit's pull request.

## What changed (operational + durable)

| Lever | Before | After |
|---|---|---|
| Repo var `REVIEW_DISPATCH_LIMIT` | **4** | **1** (lead set at `2026-09-18T06:19:47Z` for immediate effect) |
| `workflow_call` input `review_dispatch_limit` default | `"1"` | none; caller or repo variable must provide authority |
| Shell empty fallback in `pr-review-merge-scheduler.yml` | `-1` (unlimited) | fail closed with an explicit configuration error |
| Explicit input/var value `-1` | unlimited | still unlimited when set deliberately |

The same authority rule applies to this scheduler's sibling mutation budgets:
`REVIEW_ADMISSION_DISPATCH_BUDGET` and `BRANCH_UPDATE_LIMIT` must come from an
explicit dispatch payload, reusable-workflow input, or repository variable.
They have no inferred trigger-specific fallback. `ORG_SWEEP_REVIEW_DISPATCH_LIMIT`
and fix-scheduler `MAX_DISPATCHES` remain separate operational authorities and
are not reused as fallbacks for this scheduler.

## Why 4 → 1 (not disable)

Each ruleset-injected merge-scheduler run can fan out up to `REVIEW_DISPATCH_LIMIT`
AI review dispatches (OpenCode / Strix / related). At **4**, concurrent scheduler
runs across repositories multiply that fan-out against an org concurrent-job
ceiling of roughly **60** (`docs/doctoring/actions-plan-concurrency-ceiling-20260903.md`).

Live queue snapshot before the var change (path
`~/.local/orca-watchdog/queue-before-061947.json`, measured
`2026-09-18T10:20:32Z` UTC — note the filename marks the earlier operational
cutover `061947Z`):

| Signal | Value |
|---|---|
| Org `queued` (sample) | ≈390 |
| Org `in_progress` | ≈17 |
| `.github` eligible non-draft unapproved proxy | ≈31 |
| Queued OpenCode Dispatch | ≈118 |
| Then-current `REVIEW_DISPATCH_LIMIT` | 4 |

This is **throughput shaping**, not a kill switch: reviews still dispatch up to
the explicitly configured current-head budget. Work continues; only the
configured per-run burst width changes. `cancel-in-progress` concurrency
is already correct and was not touched. No age-based cancel. If the configured
variable is absent, dispatch stops with a configuration error rather than
silently choosing a rule-of-thumb budget.

## Reversibility

Raise the repo variable (or pass explicit `workflow_call` /
`repository_dispatch` values) to restore wider fan-out. Explicit **`-1`**
retains each command's documented unlimited behavior. Leaving any of the three
mutation authorities unset or blank now fails closed; it does not infer **1**,
**8/20/8**, unlimited, or any other decision-affecting budget.

## Out of scope

Trigger-narrowing and `ready_for_review` / synchronize deferral for AI-review
workflows are a separate thread and must not be reopened here.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Doctoring record: schedule recovery bypasses #1935 in-flight hold (2026-09-18)

- **Date:** 2026-09-18
- **Subject:** Daily merge-scheduler recovery (`GITHUB_EVENT_NAME=schedule`) was
inert under Actions queue saturation: OpenCode-needing heads were behind, so
review could not dispatch, and `#1935`'s in-flight check hold blocked
`update_branch`, so recovery neither updated nor dispatched — yet exited
success. Same class as `fmls#2006`.
- **Holds:** ContextualWisdomLab/.github#2267 (fail-loud taxonomy + effective
limits) until this dispatch path is proven; do not merge on taxonomy alone.

## Measured evidence (cron run 35202348887, 2026-09-17)

| Observation | Evidence |
|---|---|
| Reviews were triggered | Log: `TRIGGER_REVIEWS: true` |
| Budgets were non-zero | `REVIEW_DISPATCH_LIMIT_INPUT: 4`, `REVIEW_ADMISSION_DISPATCH_BUDGET: 1` |
| Local schedule scan ran | 100 decisions; not a targeted-dispatch reject |
| OpenCode-needing heads blocked on freshness | PR #834 `update_branch` (no in-flight); PRs #1198, #1215, #1238, #1519 `wait` with "outdated before review dispatch, but current-head checks are still queued or running" |
| Counts | `update_branch=1`, `wait=21`, **no `review_dispatch` key** (`dispatched=0`) |
| Contrast when head is current | Prior cron 35076102529: `PR #1519: review_dispatch: ... OpenCode dispatched` |

Root cause is a **definition / pre-dispatch filter mismatch**, not a zero budget
or a false `TRIGGER_REVIEWS`. The five OpenCode-needing heads were all outdated
before review dispatch; four were soft-held by `#1935`.

## Repair (smaller policy change)

On `GITHUB_EVENT_NAME=schedule` only, when an OpenCode-needing head is behind
and the only blocker is `#1935`'s in-flight check hold:

1. **Prefer `update_branch`** despite queued/running current-head checks, with a
loud `::warning` citing the `#1935` tradeoff (discard in-flight evidence so
daily recovery is not inert).
2. If the branch-update budget is already exhausted on that schedule tick,
**fall through to `review_dispatch` / `security_dispatch`** on the behind
head with an explicit warning, rather than soft-idle.

Event-driven paths (`pull_request_target`, `workflow_run`, …) keep the `#1935`
hold unchanged.

Companion observability (same PR #2267): `scheduler_effective_limits` prints
the live review-dispatch / branch-update / admission values every run;
`classify_review_recovery` / `emit_review_recovery_signal` fail loud when
schedule recovery finds outdated OpenCode-needing heads and still produces
neither update nor dispatch.

The measured backlog does not itself authorize a numeric mutation budget.
PR #2270 is integrated as the single-writer authority contract: review
dispatch, branch update, and review admission each require an explicit
dispatch payload, reusable-workflow input, or repository variable. Missing or
blank authority fails before the scheduler mutates a pull request. The observed
`5` review-needing and approximately `18` outdated heads remain operational
evidence, not a rule that rounds itself into `8/20/8` or an event default.

## Audit trail

- Cron logs for `35202348887` and `35076102529` (Daily Review Recovery).
- `#1935` hold rationale in `CHANGELOG.md` / `inspect_pr` comment.
- Implementation: `scripts/ci/pr_review_merge_scheduler_core.py` schedule
branch of the outdated-before-review path; tests in
`tests/test_pr_review_merge_scheduler.py`.
- Budget authority: `docs/doctoring/merge-scheduler-review-dispatch-budget-20260918.md`
and the workflow contract tests. Operators configure all three mutation
budgets before using this recovery path.
Loading
Loading