Skip to content

stack 2/5: gate review readiness and CodeRabbit - #901

Closed
Wibias wants to merge 5 commits into
agent/pr-contribution-firewallfrom
agent/pr-readiness-gate
Closed

stack 2/5: gate review readiness and CodeRabbit#901
Wibias wants to merge 5 commits into
agent/pr-contribution-firewallfrom
agent/pr-readiness-gate

Conversation

@Wibias

@Wibias Wibias commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Stack

2/5 — review readiness and CodeRabbit gate

Base: agent/pr-contribution-firewall (#900)
Next: new-contributor trust lane

Summary

  • classify PRs as awaiting-author, intake: validating, or awaiting-maintainer
  • automatically draft PRs while admission or automated checks are incomplete
  • restore ready-for-review only when the workflow owns the draft and all observed gates pass
  • reconcile on PR, status, check-run, and scheduled events
  • enable CodeRabbit request-changes workflow and blocking pre-merge checks
  • allow only requested reviewers to override CodeRabbit pre-merge failures

Verification

  • node --test .github/scripts/pr-readiness.test.cjs — 7 passed, 0 failed
  • workflow and CodeRabbit YAML parsed successfully
  • trusted workflow never checks out or executes PR-head code

Rollout

Promote the trusted files to the default branch and exercise a synthetic external PR before making readiness or CodeRabbit required checks.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a0b138d3-917c-479c-89e4-2e681dad6897

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@Wibias Wibias added the chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature). label Aug 2, 2026

Wibias commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

Stack navigation

  1. stack 1/5: add contribution intake firewall #900 — contribution intake firewall
  2. stack 2/5: gate review readiness and CodeRabbit #901 — readiness and CodeRabbit gate
  3. stack 3/5: add first-contributor trust lane #902 — first-contributor trust lane
  4. stack 4/5: add deterministic anti-slop CI #903 — deterministic anti-slop CI
  5. stack 5/5: define review lifecycle and governance rollout #905 — review lifecycle and governance rollout

Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer.

@Wibias
Wibias marked this pull request as ready for review August 3, 2026 01:42

Wibias commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

[shipping-github] Verdict: gated

PR: #901 — stack 2/5: gate review readiness and CodeRabbit
Head: 53ed63a1 on agent/pr-contribution-firewall (mergeable, updated from the current stack-1/5 head df2b9347)

Semantic propagation

  • Concepts audited: readiness state machine (author_action / validating / maintainer), intake labels (awaiting-author, awaiting-maintainer, intake: validating, intake: auto-drafted), admission-label contract, draft ownership, CodeRabbit gate config.
  • Authoritative sources: docs/superpowers/specs/2026-08-02-pr-readiness-gate-design.md, .github/scripts/pr-readiness.cjs, .coderabbit.yaml.
  • Producers and consumers checked: pr-readiness.yml; stack 1/5: add contribution intake firewall #900 admission (intake: admitted producer, awaiting-author co-manager); stack 1/5: add contribution intake firewall #900 stale workflow (closes awaiting-author only, never awaiting-maintainer); stack 5/5: define review lifecycle and governance rollout #905 lifecycle (removes awaiting-maintainer, adds review: round-*); stack 5/5: define review lifecycle and governance rollout #905 contributor docs.
  • Public/derived representations checked: label names/colors across workflows and docs; check-name ignore list (PR readiness / reconcile) vs job name; CodeRabbit pre-merge checks vs design doc list; staleness interplay (pending CI must not start the author timer).
  • Material variant partitions checked: admission passed/failed/pending; checks pending/failed/pass/absent; draft owned vs human-owned; events (PR/status/check_run/schedule); dispatch-style fallback.
  • Positive and negative assertions checked: 12 readiness tests, 287/287 full script suite, harness simulation of every reconcile branch including the admission race.
  • Unmapped surfaces: none.
  • Unproven equivalence assumptions: none.
  • Representation mismatches: none (design doc updated with the new admission-pending classification).
  • Variant coverage gaps: none blocking.
  • Axis verdict: pass.

Usefulness

High: this layer is what makes the stack safe to run. It separates "author must act" from "automation still running", so #900's five-day stale closure cannot close PRs that are merely pending CI, and it prevents human review from starting against red or incomplete gates. Auto-draft ownership (intake: auto-drafted) follows the enforce-pr-target precedent.

Bugs / correctness

  • Method: bug-review.md — Bugbot: n/a (Codex host, complementary only); complementary: done (silent_failures / resource_leaks / edge_cases).
  • F1 (confirmed, fixed): readiness treated a missing intake: admitted label as immediate author_action while admission was still running - auto-drafting compliant PRs, labeling them awaiting-author, posting "Author action is required", and setFailed (reproduced in a harness). Fixed in 14417405: a pending PR admission / admission check run now classifies as validating; failed or absent admission checks keep the author_action fallback. Design doc synced.
  • F3 (fixed): branch was 7 commits behind its parent; merged df2b9347 (f853ca71a, conflict-free), making the PR mergeable and validated against the current stack head.
  • Residual (no fix): the 15-minute sweep over all open PRs has cancel-in-progress: true (heavy sweeps can be cancelled, self-healing) and scales with open-PR count (rate-limit residual).

Security

  • Scope reviewed: pull_request_target trust boundary, job permissions, draft/ready GraphQL mutations, label/comment writes, scheduled sweep.
  • F2 (fixed): permissions moved from workflow scope into the reconcile job with permissions: {} at workflow level (53ed63a1), contents: write documented as required for the draft GraphQL mutations (matches enforce-pr-target precedent).
  • Findings: none confirmed after fixes. Trusted default-branch sparse checkout, persist-credentials: false, no PR-head execution, SHA pins verified (checkout v4.2.2, github-script v9), no secrets.
  • Residual: the sweep writes to any open PR with the job token by design; bounded only by GitHub's rate limits.

Spec / standards

  • Spec source: PR body + design doc; all six summary claims verified against the code, plus the CodeRabbit pre-merge check list matches the design doc.
  • Gaps: none blocking. The PR body verification count is stale after this review (7 passed -> 12) and no reviewers are requested yet - both worth refreshing before merge.

Reviews

  • Owners/maintainers: none requested yet; per MAINTAINERS.md this .github/** + .coderabbit.yaml security-boundary change needs explicit maintainer security review before merge.
  • Bots: none - CodeRabbit skips auto-review on branch bases (base_branches config), which the readiness gate tolerates as validating. Once this lands on dev, CodeRabbit's drafts: true + pre-merge checks become live.

Base / CI

  • Base: updated to df2b9347 (f853ca71a); mergeable: true, 0 behind.
  • Required checks: none configured for these paths (CI wiring is stack 5/5: define review lifecycle and governance rollout #905's stack job); React Doctor green on 53ed63a1 (2 runs); CodeRabbit success (skipped).
  • Local tip compile/tests: 287/287 script tests, both workflow YAMLs parse, full workflow script syntax-checked, end-to-end harness verified all reconcile branches.

Gate

Draft gate: the PR remains a GitHub draft, so this verdict is gated (not merge-ready). ship-gate.mjs could not run on this machine (gh token invalid); connector evidence used instead.

Simplification (approved S1)

  • 53ed63a1 also removes the dead managedLabels constant from pr-readiness.yml (declared, never referenced). No rollbacks; behavior preserved.

Bottom line

Substantively clean and approve-worthy: the race fix closes the one real correctness hole, permissions are job-scoped, and the branch is current with its parent. Merge remains gated by the draft status, the missing maintainer review request (security boundary), and the stack's standing requirement that the synthetic fork test pass before these checks become required. When #902 is reviewed, it will need the same base update - it is still based on the old 96b21918 head.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53ed63a1c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +88 to +91
if (pending.length > 0 || observed === 0) {
return { state: "validating", failed: [], pending };
}
return { state: "maintainer", failed: [], pending: [] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wait for required gates before declaring readiness

When a new PR passes admission before CodeRabbit or path-filtered CI has created a status/check run, the successful PR admission / admission run makes observed nonzero, so this branch immediately returns maintainer. The workflow can then apply awaiting-maintainer and restore an auto-drafted PR even though CodeRabbit and CI have not reported—and it remains incorrectly ready if an expected integration never reports at all. Track the required gate contexts explicitly, or exclude admission from the evidence that all post-admission gates have appeared.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Fixed in 1acd01d4: admission check runs are now excluded from post-admission evidence (ADMISSION_NAMES), so a PR whose only check is a successful admission run stays intake: validating until CodeRabbit/CI report. Covered by new unit tests and a harness probe.

Comment thread .github/workflows/pr-readiness.yml Outdated
permissions: {}

concurrency:
group: pr-readiness-${{ github.event.pull_request.number || github.event.check_run.head_sha || github.event.sha || 'sweep' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize all reconciliations for the same PR

The concurrency key uses the PR number for pull_request_target, the head SHA for status/check events, and sweep for scheduled runs, so reconciliations of the same PR can run concurrently instead of cancelling one another. If one run reads pending checks while another reads their completed state, whichever finishes its label/draft mutations last can overwrite the newer verdict—for example, restoring ready-for-review after a newly pending check or re-drafting after all checks pass—until another event or the next 15-minute sweep repairs it. Route every event through a PR-specific concurrency key.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Fixed in 1acd01d4: the concurrency key now routes PR, status, and check_run events through the head SHA (github.event.pull_request.head.sha || check_run.head_sha || sha), so reconciliations of the same head serialize instead of running concurrently. The scheduled sweep keeps its own group as before (residual, self-healing).

Comment on lines +131 to +133
const checkRuns = await github.paginate(github.rest.checks.listForRef, {
owner, repo, ref: pr.head.sha, filter: "latest", per_page: 100,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Ignore superseded check runs on the same SHA

The Checks API's latest filter returns the latest run from each check suite, not the single latest run for each check name, so repeated workflow invocations on an unchanged head SHA remain in this array. For example, editing a PR body after a failed admission run starts a new suite that can pass and apply intake: admitted, but the earlier failed PR admission / admission run is still classified as a failure and leaves the PR in awaiting-author until a new commit changes the SHA. Reduce runs with the same check name to the newest invocation before assessing readiness.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Fixed in 1acd01d4: latestByCheckName now reduces runs per check name to the newest invocation (by started_at) before classification, so a superseded failed admission run no longer keeps the PR in awaiting-author after a newer run passes. Unit-tested.

Comment thread .github/workflows/pr-readiness.yml Outdated
Comment on lines +188 to +189
await convertToDraft(pr.node_id);
await add("intake: auto-drafted");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve ownership when draft labeling fails

If convertToDraft succeeds but the following label API call fails transiently, the catch only logs a warning after the PR has already become a draft. Later reconciliations see pr.draft === true, skip this entire block, and can no longer distinguish that draft from an author-created one, so intake: auto-drafted is never applied and the workflow will never restore ready-for-review when checks pass. Make the conversion and ownership marker recoverable as separate operations, such as recording ownership first and rolling it back if conversion fails.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Fixed in 1acd01d4: intake: auto-drafted is now applied before convertToDraft and rolled back if the conversion fails, so a draft produced by the workflow always carries its ownership marker and remains restorable.

Comment on lines +171 to +175
if (result.state === "maintainer") {
await remove("awaiting-author");
await remove("intake: validating");
await add("awaiting-maintainer");
if (labels.has("intake: auto-drafted") && pr.draft) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep manually drafted PRs out of the maintainer queue

When an author-created draft has passing checks and lacks intake: auto-drafted, this branch still applies awaiting-maintainer and posts that maintainer review is next; the ownership check only prevents the GraphQL ready mutation. Maintainers triaging by this new label are therefore directed to review work the author has explicitly left as a draft. Treat pr.draft && !labels.has("intake: auto-drafted") as an author-controlled draft state and withhold the maintainer label/comment until a ready_for_review event.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Fixed in 1acd01d4: the maintainer branch now treats pr.draft && !labels.has("intake: auto-drafted") as an author-controlled draft and withholds awaiting-maintainer and the review-next comment until a ready_for_review event. Harness-verified.

Comment on lines +12 to +15
const IGNORED_NAMES = new Set([
"PR readiness / reconcile",
"PR readiness",
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Match the actual GitHub Actions check-run names

GitHub Actions exposes the job name in check_run.name, so these jobs report as reconcile and admission, not the workflow/job display strings stored here. Consequently the current in-progress reconcile run is included in every readiness assessment and keeps pending nonempty, while admissionCheckPending fails to recognize an admission job that is still running; the gate can therefore never reach maintainer during reconciliation and can transiently classify pending admission as an author failure. Match the actual job names, ideally together with the GitHub Actions app identity, when ignoring and locating these runs.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Fixed in 1acd01d4: check-run names now match the actual job names (reconcile / admission) with the display-name forms kept as fallbacks, in IGNORED_NAMES, ADMISSION_NAMES, and the job-level self-trigger guard. This also reactivates the admission-pending classification from 14417405, which previously never matched. Unit-tested for both name forms.

@@ -0,0 +1,121 @@
"use strict";

const { describe, it } = require("node:test");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the new readiness tests in CI

This new test file is not exercised by bun run test, which only runs tests/, and the unchanged issue-quality-tests.yml neither includes the readiness files in its path filters nor invokes this test. As a result, future changes can break the readiness classifier while every automated test job remains green; add both readiness files to that workflow's pull-request/push paths and run node --test .github/scripts/pr-readiness.test.cjs with the other CommonJS validator tests.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shipping-github] Declined here because it is already implemented later in this stack: #905 wires pr-readiness.test.cjs into the policy-test workflow (path filters + node --test line). Keeping the CI wiring in #905 avoids duplicating it in #901.

…pe runs, serialize reconciles, honor manual drafts
@lidge-jun

Copy link
Copy Markdown
Owner

Holding this one, and I want to be specific about why, because the other four in the stack are not in the same position.

I measured the lane before touching any of this. Over 33 recent Cross-platform CI runs: Windows was the last job to finish in 23 of 23 current-schema runs (median 17m41s, p90 18m54s) against ubuntu 5m58s and macos 5m06s, with a 43.5% failure rate of which 4 of 10 were runner flake rather than defects. That is the bottleneck, and #899 addresses it directly — its measured runs land at 4m26–4m30s wall clock.

Against that, the readiness gate moves in the opposite direction:

1. It makes LLM judgment blocking. .coderabbit.yaml:12-48 configures request-changes on "technically credible" reasons, unrelated scope, quality of validation evidence, and issue assessment. pr-readiness.yml:128-220 then treats a failed check as author-action and auto-drafts the PR, and only requested reviewers can override CodeRabbit. A legitimate contributor can be blocked by a model's opinion with no deterministic appeal. I am not willing to ship that; a gate that a contributor cannot reason about is a gate that teaches them to stop contributing.

2. It scales with the thing #899 is shrinking. It triggers per PR event, per status event, and per check_run created/completed, plus sweeps four times an hour. #899 raises the job count to 9–12, so this becomes up to 18–24 workflow invocations per update before concurrency coalescing. It also waits on individual checks even though #899 introduces exactly the stable aggregate (ci) that a readiness reconciler should consume instead.

3. It cannot stand alone. Without #900's admission label it holds PRs in author-action indefinitely.

What I would merge: the same reconciler consuming the single aggregate ci check plus narrowly named deterministic policy checks, with every CodeRabbit judgment demoted to advisory (comment, never request-changes, never blocking). The auto-draft mechanic itself I am fine with — it is the subjective input that has to go.

Not closing this; it is a rebase and a config change away from something I want. Please say if you would rather I push those changes onto your branch than hand them back.

@lidge-jun

Copy link
Copy Markdown
Owner

Closing this one, with the reasoning already on the record above.

The mechanism is not the problem — an auto-draft reconciler that moves a PR out of the review queue when it needs author action is something I want. Two things make this version unmergeable as written:

The input is subjective. .coderabbit.yaml configures request-changes on "technically credible" reasons, unrelated scope, quality of validation evidence, and issue assessment, and only requested reviewers can override it. A contributor blocked by a model's opinion has no deterministic appeal. That is the property I was unwilling to accept, and it is why #903's gate went in first instead — every check there produces a failure a contributor can read and act on.

The cost scales the wrong way. It triggers per PR event, per status event, and per check_run created/completed, plus four sweeps an hour. #899 raised the job count to 9–12, so this reaches 18–24 invocations per update before concurrency coalescing — against a change whose entire purpose was cutting the lane from 17m41s to 4m30s. It also waits on individual checks despite #899 introducing exactly the stable aggregate ci a reconciler should consume.

What I would merge: the same reconciler reading the single ci check plus the narrowly named policy checks, with every CodeRabbit judgment demoted to advisory. That is a config change and a rebase away, and I would review it promptly. Reopen or send a fresh PR when you want to take it up — the idea is welcome, the blocking judgment is not.

@lidge-jun lidge-jun closed this Aug 3, 2026
Wibias added a commit to n3wr1ch/opencodex that referenced this pull request Aug 3, 2026
…n the docs

Consolidates the parts of @Wibias's five-PR governance stack (lidge-jun#900, lidge-jun#902, lidge-jun#905)
that reduce risk without adding friction, and drops the parts that do the
opposite. The measurement behind the ordering: Windows was the last job to
finish in 23 of 23 recent CI runs at a 17m41s median, so lidge-jun#899 was the actual
bottleneck and everything here is judged by whether it makes the lane worse.

Kept, from lidge-jun#902's trust lane: authentication, credential handling, GitHub
Actions workflows, release automation, and dependency installation need a
maintainer to sponsor the change before it merges. MAINTAINERS.md already
requires security review for exactly these; this makes the requirement visible
on the pull request instead of relying on a reviewer noticing. It runs inside
the existing hygiene job rather than adding a workflow, and it applies to every
contributor — blast radius does not depend on how many PRs someone has merged,
which is why the upstream first-timer exemption is gone.

Dropped, from the same PR: the 500-line cap and the one-open-PR limit. A
provider preset with its registry rows, adapter wiring, tests, and five locales
clears 500 lines by itself, and several good first contributions here have.
Telling a newcomer their fix is too big is a worse failure than reviewing a
large diff.

Dropped, from lidge-jun#900: the admission gate requiring a pre-approved issue, and the
five-day auto-close. The DeepSeek reasoning replay, the Cursor Grok parameters,
the AgentRouter EOF tolerance, the tool-result image forwarding — every one
arrived as an unplanned PR from someone who hit the bug. A gate that required a
planning discussion first would have lost all of them.

Kept, from lidge-jun#905: CODEOWNERS entries for the high-impact runtime directories, and
the contributor documentation — rewritten to describe what is actually enforced.
The submitted version documented the approved-for-work gate, the size caps, and
the automatic closure timers, none of which exist here, and publishing rules the
repository does not enforce is worse than publishing none.

Not included: lidge-jun#901's readiness gate. It makes CodeRabbit's judgment blocking and
triggers per check_run, which scales with the job count lidge-jun#899 just raised.

Co-authored-by: Wibias <37517432+Wibias@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Maintenance, CI, tests, refactors, or build changes (not a user-facing bug or feature).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants