Skip to content

feat(ci): verify checklist claims and reset readiness on head drift - #986

Merged
Wibias merged 9 commits into
lidge-jun:devfrom
Wibias:codex/pr-readiness-gate-recheck
Aug 4, 2026
Merged

feat(ci): verify checklist claims and reset readiness on head drift#986
Wibias merged 9 commits into
lidge-jun:devfrom
Wibias:codex/pr-readiness-gate-recheck

Conversation

@Wibias

@Wibias Wibias commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #969. Three things:

  1. The review-readiness checklist is now bound to the exact commit it was completed on: the gate records the PR head SHA in the readiness state when the fourth box is ticked, and on any later synchronize event where the head has changed, it moves the PR back to draft, unticks all four boxes in the description, clears the maintainer-notification flag, and tells the author to re-test against the latest code and complete the checklist again.
  2. The long enforce-pr-target inline script is split by responsibility into small modules under .github/scripts/ that follow the existing pr-quality*.cjs naming:
    • pr-quality-state.cjs — state markers, defaults, completion-staleness policy
    • pr-quality-messages.cjs — every user-facing comment/notice builder
    • pr-maintainers.cjs — maintainer-list parsing
      Each has its own unit suite. The workflow keeps a thinner orchestrator for the GitHub reads/writes and the draft/ready transitions, so the write-allowlist and argument-shape security pins stay visible in the script.
  3. The gate now verifies two of the four checklist claims itself before accepting a completion (b1a59df6): the head's ci check must be green, and the branch must be on the latest dev commit or at most 10 commits behind it. A disproved claim unchecks the matching box, clears the completion attestation and the maintainer ping, and keeps the PR a draft with an explanatory notice. Unknown CI or compare state fails closed. Heads with no ci check (docs-only changes) keep the CI box; exactly 10 commits behind still passes. The same revalidation notice path now carries either the head-drift or the claim-check reset message.

Why: previously a completed checklist stayed valid no matter what was pushed afterwards, so an author could tick the boxes, be marked ready and ping maintainers, then push questionable changes that nobody re-attested. The completion claim now expires automatically when the code it attested changes, and the two attestations the gate can verify itself (CI green, current dev) are checked against the live repository instead of trusted blindly.

Review rounds (bc9147fd9, 387da1005, ccc109886, dfaa8fd7f, bbb20a272, c9c63094b, cc48263c5, b1a59df6): the binding is race-proofed. A completion whose ticks predate the live head (a push raced the edited job) is rejected and reset instead of silently binding the newer head; a completion recorded while another quality gate keeps the draft still binds the head; a partial reset (body updated, comment update failed) is recovered on the next run; the reset preserves the author's surrounding description byte-for-byte; and a stale event on a never-completed checklist neither posts a reset notice nor wipes the bot's draft-ownership state. State written before this feature has no recorded head, so the binding starts at the next completion instead of retroactively drafting already-ready PRs; migrated states are rewritten at version 2. Module names describe ownership (pr-quality-*, pr-maintainers), not the workflow filename. CodeRabbit follow-ups (bbb20a272): maintainer parsing fails closed without the section, code spans use safe delimiters, readiness absence no longer claims ready, and synchronize events cannot inherit an unrecorded completion. (c9c63094b): the maintainer heading must be an exact ## Current maintainers H2 at line start. (cc48263c5): positive CRLF/trailing-whitespace heading coverage.

Test plan

  • bun test tests/ci-workflows.test.ts — 99/99 pass, including scenarios: completion records the head SHA; a new head after completion re-drafts, resets the boxes, and clears the ping; a same-head rerun is stable; head drift still enforces quality failures; ticks that predate the live head are rejected; completion while quality gates fail still binds the head; partial-reset state recovery; stale events on open checklists preserve ownership; red ci unchecks the CI box and re-drafts; >10 commits behind unchecks the latest-dev box and re-drafts; both claims fail together; checks-lookup failure fails closed; exactly 10 behind and no-ci heads keep their boxes; pending ci cannot attest green.
  • node --test .github/scripts/*.test.cjs — 381/381 pass, including new suites: readinessClaimViolations partitions (green/red/behind/unknown/threshold), uncheckReviewReadinessBoxes (targeted uncheck, surrounding-content preservation, idempotence, malformed markers), buildClaimCheckNotice lines, plus the existing state/message/maintainer/checklist suites.
  • bun run typecheck — pass. bun run lint:gui — pass. bun run privacy:scan — pass. doctor:gui:if-changed — skip (no gui change).
  • Full suite on this Windows machine: 8068 pass; the 14 failures are the pre-existing symlink-environment tests (EPERM: operation not permitted, symlink — no Developer Mode; same class documented in feat(ci): draft contributor PRs until review-readiness checklist is complete #969), all in files untouched by this diff.

Review notes

  • The drift and claim-check resets fold into the existing draft/failure paths instead of short-circuiting them, so a PR that drifts onto a wrong base still fails closed with the wrong-branch message and title prefix while the checklist resets.
  • resetReviewReadinessSection and uncheckReviewReadinessBoxes in .github/scripts/pr-quality.cjs splice only the marker-bounded section; like the strip helper, they leave malformed marker sets untouched.
  • Readiness state version is bumped to 2 (completedAtHeadSha); parsing stays unversioned, so v1 and future states remain readable, and written states are normalized to the current version.
  • The workflow structure (trigger, permissions, concurrency, base-SHA checkout, write surface) is unchanged; the split only relocates pure logic into modules the same sparse checkout already ships.
  • Codex connector findings (3) and CodeRabbit findings (fixed on-thread; 1 declined as out-of-scope follow-up: maintainer-approval invalidation is a GitHub-native control once branch protection is configured) are addressed on-thread.

@github-actions github-actions Bot added the enhancement New feature or request label Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR extracts shared readiness state, message, and maintainer helpers. The workflow binds completed checklists to the exact PR head SHA, resets stale completion after later commits, and re-drafts the PR. Tests and contributor documentation cover the new behavior.

Changes

Readiness head binding

Layer / File(s) Summary
Readiness state and message helpers
.github/scripts/pr-quality-state.cjs, .github/scripts/pr-quality-messages.cjs, .github/scripts/pr-quality.cjs, .github/scripts/pr-maintainers.cjs
Adds state parsing, serialization, and stale-head detection. Builds readiness messages, failure sections, and stale notices. Resets completed checklist sections. Parses maintainer logins from markdown.
Helper test coverage
.github/scripts/pr-quality-state.test.cjs, .github/scripts/pr-quality-messages.test.cjs, .github/scripts/pr-quality.test.cjs, .github/scripts/pr-maintainers.test.cjs
Tests state handling, message rendering, maintainer parsing, checklist reset, malformed input, and stale-head cases.
Workflow head-drift enforcement
.github/workflows/enforce-pr-target.yml
Uses shared helpers. Compares event and live head SHAs. Detects stale completion. Resets checklist and notification state. Re-drafts the PR. Records the exact completed head SHA.
Workflow test scenarios
tests/ci-workflows.test.ts, tests/helpers/enforce-pr-target-harness.ts
Covers state migration, head drift with reset, idempotency, races, synchronize events, quality failures, partial resets, stale events, and action-specific webhook payloads.
Readiness policy documentation
AGENTS.md, MAINTAINERS.md, docs-site/src/content/docs/contributing/pr-quality.md
Documents exact-head approval binding and reset requirements for later commits.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubAPI
  participant EnforcePRTarget
  participant StateModule
  participant ChecklistModule
  participant MaintainersModule
  GitHubAPI->>EnforcePRTarget: deliver PR event with head SHA
  EnforcePRTarget->>StateModule: parse stored readiness state
  EnforcePRTarget->>StateModule: compare completion head with live head
  alt Completion is stale
    EnforcePRTarget->>ChecklistModule: reset readiness section
    ChecklistModule-->>EnforcePRTarget: return unticked checklist
    EnforcePRTarget->>StateModule: clear completion state
  end
  EnforcePRTarget->>MaintainersModule: parse eligible maintainers
  EnforcePRTarget->>GitHubAPI: update draft status, body, and state
Loading

Possibly related PRs

  • lidge-jun/opencodex#648: Introduced the enforcement workflow and CI test infrastructure extended by this PR.
  • lidge-jun/opencodex#969: Introduced the review-readiness checklist functionality extended and refactored here.
  • lidge-jun/opencodex#960: Extends the same PR-quality script with shared readiness-state and message handling while preserving quality-failure checks.

Suggested reviewers: ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes: verifying checklist claims and resetting readiness when the PR head changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 4, 2026
chatgpt-codex-connector[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/scripts/pr-quality.cjs (1)

330-340: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared marker-validation guard used by both readiness helpers.

Lines 331-340 duplicate the guard in stripReviewReadinessSection at lines 306-317 exactly: the same typeof check, the same indexOf pair, the same end <= start rejection, and the same duplicate-occurrence counting.

The failure mode is divergence. If the marker policy changes later (for example, tolerating a second marker pair, or accepting end === start), a maintainer can update one copy and not the other. stripReviewReadinessSection and resetReviewReadinessSection would then disagree about which bodies are "valid", so a body that reset refuses to touch could still be stripped, or the reverse. Both functions gate PR draft state, so the inconsistency is user-visible.

Extract the bounds lookup once and let both callers use it.

♻️ Proposed refactor to share the bounds lookup
+/**
+ * Bounds of the single well-formed readiness section, or `null` when the body
+ * is not a string, has no markers, has inverted markers, or has duplicate
+ * marker pairs. Malformed marker sets stay untouched by both callers.
+ */
+function findReviewReadinessBounds(body) {
+  if (typeof body !== "string") return null;
+  const start = body.indexOf(REVIEW_READINESS_START);
+  const end = body.indexOf(REVIEW_READINESS_END);
+  if (start === -1 || end === -1 || end <= start) return null;
+  if (
+    body.split(REVIEW_READINESS_START).length - 1 !== 1 ||
+    body.split(REVIEW_READINESS_END).length - 1 !== 1
+  ) {
+    return null;
+  }
+  return { start, endExclusive: end + REVIEW_READINESS_END.length };
+}
+
 function resetReviewReadinessSection(body) {
-  if (typeof body !== "string") return body;
-  const start = body.indexOf(REVIEW_READINESS_START);
-  const end = body.indexOf(REVIEW_READINESS_END);
-  if (start === -1 || end === -1 || end <= start) return body;
-  if (
-    body.split(REVIEW_READINESS_START).length - 1 !== 1 ||
-    body.split(REVIEW_READINESS_END).length - 1 !== 1
-  ) {
-    return body;
-  }
-  const section = buildReviewReadinessSection();
+  const bounds = findReviewReadinessBounds(body);
+  if (!bounds) return body;
+  const section = buildReviewReadinessSection();
   // Splice only the bounded section: the author's surrounding content —
   // including deliberate blank lines and trailing markdown — stays byte for
   // byte identical to what they wrote.
   return (
-    body.slice(0, start) +
+    body.slice(0, bounds.start) +
     section +
-    body.slice(end + REVIEW_READINESS_END.length)
+    body.slice(bounds.endExclusive)
   );
 }

Apply the same substitution inside stripReviewReadinessSection at lines 306-317, keeping its \n{3,} collapse and trimEnd() (that normalization is intentional there, because removing a section leaves an extra blank gap).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/pr-quality.cjs around lines 330 - 340, Extract the shared
marker-validation logic from stripReviewReadinessSection and
resetReviewReadinessSection into a helper that returns the validated start/end
bounds or an invalid result. Update both functions to use this helper,
preserving stripReviewReadinessSection’s existing \n{3,} collapse and trimEnd()
normalization while keeping resetReviewReadinessSection’s behavior unchanged.
♻️ Duplicate comments (1)
.github/workflows/enforce-pr-target.yml (1)

631-636: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

The reset still asks only for re-testing and re-ticking, not for a fresh maintainer approval of the new head.

Lines 635-636 instruct the author to re-test and re-tick. No step in the reset flow invalidates a maintainer approval that was granted against the previous head, and the repository documents that branch protection is not configured, so a stale approval survives the push. This repeats the finding raised on the previous commit at lines 625-628.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/enforce-pr-target.yml around lines 631 - 636, Update the
headDriftNotice reset flow to explicitly invalidate the prior maintainer
approval and require a fresh approval for the current head, in addition to
re-testing and re-ticking all four boxes. Ensure the reset logic clears the
stored approval state so stale approval cannot survive a pushed commit.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/enforce-pr-target.yml:
- Around line 609-636: Update the head-drift logic around ticksPredateLiveHead
and headDrifted so the no-recorded-SHA case requires readiness.complete,
preventing incomplete or migrated v1 checklists from entering the reset path.
Build headDriftNotice only after freshReadiness is obtained, using the fresh
checklist state for the message and preserving maintainer notification state
when no reset occurred. Add a regression test alongside the existing stale-head
case using readinessChecklistBody(0) and maintainersPinged:true, asserting the
state is preserved and no reset notice is emitted.

In `@tests/ci-workflows.test.ts`:
- Around line 1495-1497: Replace the vacuous callsTo(result, "graphql").join("")
assertion near the rejected-completion test with an assertion over each recorded
call’s query string, using the accessor style already present nearby, so
markPullRequestReadyForReview is genuinely absent. Search this file and the
broader tests for other callsTo(...).join(...) assertions and update each
occurrence to inspect GraphQL query text rather than argument objects.

---

Outside diff comments:
In @.github/scripts/pr-quality.cjs:
- Around line 330-340: Extract the shared marker-validation logic from
stripReviewReadinessSection and resetReviewReadinessSection into a helper that
returns the validated start/end bounds or an invalid result. Update both
functions to use this helper, preserving stripReviewReadinessSection’s existing
\n{3,} collapse and trimEnd() normalization while keeping
resetReviewReadinessSection’s behavior unchanged.

---

Duplicate comments:
In @.github/workflows/enforce-pr-target.yml:
- Around line 631-636: Update the headDriftNotice reset flow to explicitly
invalidate the prior maintainer approval and require a fresh approval for the
current head, in addition to re-testing and re-ticking all four boxes. Ensure
the reset logic clears the stored approval state so stale approval cannot
survive a pushed commit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7c216074-d3a1-4acf-9961-25d75d410041

📥 Commits

Reviewing files that changed from the base of the PR and between 476688b and bc9147f.

📒 Files selected for processing (4)
  • .github/scripts/pr-quality.cjs
  • .github/scripts/pr-quality.test.cjs
  • .github/workflows/enforce-pr-target.yml
  • tests/ci-workflows.test.ts

Comment thread .github/workflows/enforce-pr-target.yml Outdated
Comment thread tests/ci-workflows.test.ts Outdated
@Wibias Wibias changed the title feat(ci): reset PR readiness checklist when new commits land after completion feat(ci): reset PR readiness checklist on head drift and split enforce-target into modules Aug 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/enforce-pr-target-maintainers.cjs:
- Around line 13-19: Update the section extraction logic in
enforce-pr-target-maintainers.cjs so a missing “## Current maintainers” heading
produces an empty recipient list rather than using the entire file; preserve the
existing subsection slicing when the heading exists. Update the corresponding
test expectation to assert [] for the absent-section fixture.

In @.github/scripts/enforce-pr-target-messages.cjs:
- Around line 37-49: Update the readiness message in the PR comment generation
flow around readinessChecklistLines so the readiness.present === false branch
only states that the checklist is not required, without claiming the PR is ready
for review. Add or update a test covering an absent checklist with a failure
section supplied through extra, and verify the output does not report the PR as
ready while preserving the failure section.
- Around line 13-15: Update inlineCode to wrap values with a Markdown code-span
delimiter longer than any contiguous backtick run in the value, rather than
attempting to escape backticks with a backslash. Extend the inlineCode tests to
cover values containing both a single embedded backtick and multiple consecutive
backticks, preserving correct rendering in each case.

In @.github/workflows/enforce-pr-target.yml:
- Around line 403-414: Prevent the completion path around completionIsStale from
attesting an unrecorded checklist during synchronize events. Require an
unrecorded completion to originate from a checklist-edit event whose event head
still matches the live head; otherwise keep the PR in draft and reset or require
a new checklist edit. Add a regression covering checklist completion on head A,
delayed persistence, and synchronize on head B.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b355acde-ac4c-4dc3-875a-215c192042a4

📥 Commits

Reviewing files that changed from the base of the PR and between bc9147f and ccc1098.

📒 Files selected for processing (8)
  • .github/scripts/enforce-pr-target-maintainers.cjs
  • .github/scripts/enforce-pr-target-maintainers.test.cjs
  • .github/scripts/enforce-pr-target-messages.cjs
  • .github/scripts/enforce-pr-target-messages.test.cjs
  • .github/scripts/enforce-pr-target-state.cjs
  • .github/scripts/enforce-pr-target-state.test.cjs
  • .github/workflows/enforce-pr-target.yml
  • tests/ci-workflows.test.ts

Comment thread .github/scripts/pr-maintainers.cjs Outdated
Comment thread .github/scripts/pr-quality-messages.cjs
Comment thread .github/scripts/pr-quality-messages.cjs
Comment thread .github/workflows/enforce-pr-target.yml
@Wibias
Wibias marked this pull request as draft August 4, 2026 07:30
Drop the enforce-pr-target-* prefix from the split helpers so they match
the existing pr-quality*.cjs naming and describe what they own.
@Wibias Wibias changed the title feat(ci): reset PR readiness checklist on head drift and split enforce-target into modules feat(ci): reset PR readiness checklist on head drift and split quality helpers by responsibility Aug 4, 2026
Fail closed when MAINTAINERS.md lacks the current-maintainers section,
render code spans with a safe delimiter, stop claiming readiness from
checklist absence, and reject unrecorded complete checklists on
synchronize so a push cannot inherit a still-queued attestation.
@Wibias
Wibias marked this pull request as ready for review August 4, 2026 08:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/pr-maintainers.cjs:
- Around line 10-22: Update the section-start lookup in the maintainer parser to
match only an exact “## Current maintainers” H2 at the beginning of a line,
excluding ### subsections and prose occurrences. Preserve extraction of the
authoritative section through the existing next-heading logic, and add tests
covering both excluded cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b9bc2d18-648b-4fb5-b864-54aa0a090118

📥 Commits

Reviewing files that changed from the base of the PR and between ccc1098 and bbb20a2.

📒 Files selected for processing (9)
  • .github/scripts/pr-maintainers.cjs
  • .github/scripts/pr-maintainers.test.cjs
  • .github/scripts/pr-quality-messages.cjs
  • .github/scripts/pr-quality-messages.test.cjs
  • .github/scripts/pr-quality-state.cjs
  • .github/scripts/pr-quality-state.test.cjs
  • .github/workflows/enforce-pr-target.yml
  • tests/ci-workflows.test.ts
  • tests/helpers/enforce-pr-target-harness.ts

Comment thread .github/scripts/pr-maintainers.cjs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/pr-maintainers.test.cjs:
- Around line 37-49: The tests around parseMaintainerLogins should add
regression coverage for a valid maintainer section using CRLF line endings,
trailing spaces or tabs on the heading, and a following H2 heading. Assert that
the fixture returns the expected maintainer login while preserving the existing
subsection and prose rejection cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2309287f-e640-4ee7-87bc-aab60f5b29f8

📥 Commits

Reviewing files that changed from the base of the PR and between bbb20a2 and c9c6309.

📒 Files selected for processing (2)
  • .github/scripts/pr-maintainers.cjs
  • .github/scripts/pr-maintainers.test.cjs

Comment thread .github/scripts/pr-maintainers.test.cjs
Wibias added 2 commits August 4, 2026 11:32
The gate now checks two of the four readiness boxes itself instead of
trusting the author's ticks: the head's `ci` check must be green, and the
branch must be on the latest dev or at most 10 commits behind it. A
disproved claim unchecks the matching box, clears the completion
attestation and maintainer ping, and keeps the PR a draft with an
explanatory notice. Unknown CI/compare state fails closed.
@Wibias Wibias changed the title feat(ci): reset PR readiness checklist on head drift and split quality helpers by responsibility feat(ci): verify checklist claims and reset readiness on head drift Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR quality gates passed

This pull request now targets dev with acceptable ancestry and description.

The title was left unchanged. The pull request has been marked ready for review again.

@github-actions
github-actions Bot marked this pull request as draft August 4, 2026 09:54
@github-actions
github-actions Bot marked this pull request as ready for review August 4, 2026 09:55
@Wibias

Wibias commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

[GD] Verdict: approve-comment

TLDR

  • PR: #986 — verify checklist claims and reset readiness on head drift
  • Head: b1a59df6 on dev (mergeStateStatus: CLEAN)
  • Decision: useful and ready — the review-readiness checklist is now bound to the exact head it attested, and the gate verifies the CI and latest-dev claims itself instead of trusting the ticks.
  • Usefulness: fixes the gap raised in review on feat(ci): draft contributor PRs until review-readiness checklist is complete #969 — a completed checklist no longer survives un-attested pushes, and the two attestations the bot can check are verified against the live repository.
  • Bugs: none blocking. Three race/robustness fixes landed this session (unrecorded-completion inheritance on synchronize, exact-H2 maintainer parsing, code-span rendering, readiness-absence wording).
  • Security: none. The workflow surface (trigger, permissions, base-SHA checkout, write allowlist) is unchanged; the new checks.listForRef read is read-only and unknown state fails closed.
  • Spec / standards: clean — spec source is the feat(ci): draft contributor PRs until review-readiness checklist is complete #969 review comment; contributor/maintainer docs and docs-site/.../pr-quality.md are updated.
  • Reviews: all review threads resolved (CodeRabbit findings fixed on-thread, one declined with rationale as a GitHub-native control once branch protection exists); no open human threads.
  • Base / CI: green on b1a59df6 — all required checks pass, including enforce-target, ci, shards, keyring, macOS, React Doctor, label, and CodeRabbit.
  • Gate: none — draft cleared; ship-gate.mjs returned ready on the unchanged head after the thin settle.
  • Simplification: the requested split of the inline enforce-pr-target script into responsibility-named modules (pr-quality-state, pr-quality-messages, pr-maintainers) plus the user-requested claim-verification feature; behavior-preserving split verified by the same harness scenarios plus new claim-check coverage.
  • Bottom line: ship. PR is merge-ready per the authoritative gate; verdict is approve-comment.
Full verdict

Semantic propagation

  • Concepts audited: readiness-completion attestation (completedAtHeadSha, state version 2), staleness policy (completionIsStale incl. the synchronize unrecorded-completion race), body surgery inside the marker-bounded checklist (resetReviewReadinessSection, uncheckReviewReadinessBoxes), bot-verifiable claims (CI green via the ci check; latest-dev within 10 commits), maintainer recipient parsing, and every user-facing notice builder.
  • Authoritative sources: .github/scripts/pr-quality.cjs (checklist items, marker splice helpers), .github/scripts/pr-quality-state.cjs (state + claim policy), .github/scripts/pr-quality-messages.cjs (comment/notice builders), .github/workflows/enforce-pr-target.yml (orchestration).
  • Producers and consumers checked: the workflow's edited/synchronize paths, the readiness comment marker, the PR-body checklist markers, MAINTAINERS.md current-maintainers table, and the head ci check-run (the repo's documented "CI passed" signal) via checks.listForRef.
  • Public/derived representations checked: the injected checklist mirrors the author-ticked section; the readiness comment carries the serialized state marker; the stale/claim notices describe exactly what the reset produced (unticked boxes, cleared ping, null completion head).
  • Material variant partitions checked: maintainer vs contributor; dev base vs stacked child vs wrong base; head drift vs claim violation vs quality-failure paths; v1/v2/unversioned states; heads with a green/red/pending/missing ci check; heads exactly 10 vs more than 10 commits behind.
  • Positive and negative assertions checked: no-ci heads (docs-only) keep the CI box; exactly 10 behind passes; pending CI cannot attest green; checks/compare lookup failure fails closed; malformed marker sets stay untouched; never-completed stale events preserve bot ownership; already-unchecked boxes are idempotent.
  • Unmapped surfaces: none.
  • Unproven equivalence assumptions: none — the claim policy is a single pure function with partitioned unit tests, and the workflow passes the exact same harness scenarios as the pre-split heads plus new ones.
  • Representation mismatches: none.
  • Variant coverage gaps: none.
  • Axis verdict: pass.

Usefulness

Real, user-visible gap closure: the #969 review comment asked for the checklist completion to be tied to the exact commit so "someone could get it checked and then make questionable changes afterward" without re-attestation. The gate now (1) records the attested head and re-drafts/resets on any later synchronize where the head changed, and (2) verifies the two claims it can check itself — the head's ci check is green and the branch is on the latest dev or at most 10 commits behind — unticking a disproved box and keeping the PR a draft. Contributors no longer need a human to notice that CI went red after they ticked the boxes.

Bugs / correctness

  • Method: references/bug-review.md — bug-scope deep on the changed domain (state-consistency, boundary-conditions, api-compatibility, retry-idempotency lenses) plus the harness, which executes the actual workflow script against a recording client.
  • Findings: none blocking.
  • Fixed this session (in this PR's history): bc9147fd9 (race-proof the head binding and recover partial resets), 387da1005 (only reject completions when the checklist is actually ticked), bbb20a272 (fail closed without the maintainers section, safe code-span delimiters, no ready claim from checklist absence, no unrecorded-completion inheritance on synchronize), c9c63094b (exact ## Current maintainers H2), cc48263c5 (CRLF/trailing-whitespace heading coverage), b1a59df6 (claim verification: ci green + ≤10 commits behind, with uncheck/re-draft + fail-closed lookups).
  • Local verification on b1a59df6: bun test tests/ci-workflows.test.ts 99/99 (includes red-CI, >10-behind, both-claims, lookup-failure, exactly-10, no-ci, pending-ci scenarios), node --test .github/scripts/*.test.cjs 381/381, bun x tsc --noEmit pass, bun run privacy:scan pass. Full local suite on this Windows machine: 8068 pass / 14 pre-existing symlink EPERM failures in untouched files (no Developer Mode; same class documented in feat(ci): draft contributor PRs until review-readiness checklist is complete #969).

Security

  • Scope reviewed: workflow trigger/permissions/checkout/write surface, module require boundary, checks.listForRef read, state marker parsing, comment builders.
  • Findings: none. The workflow still uses pull_request_target with a base-SHA sparse checkout of .github/scripts + MAINTAINERS.md, contents: write + pull-requests: write for the draft/ready GraphQL mutations, and no event payload interpolation into commands. The new claim check is a read; unknown CI/compare state fails closed (a box is unticked and the PR stays a draft rather than attesting on missing evidence).
  • Fixed this session: none outstanding.

Spec / standards

  • Spec source: the feat(ci): draft contributor PRs until review-readiness checklist is complete #969 review comment (bind completion to PR head SHA; on a new commit, return to draft, reset completion and admin-notification status, ask to re-test on latest code) plus the user-requested verification of the CI and latest-dev boxes and the split of the oversized inline script.
  • Gaps: none. Behavior is documented in AGENTS.md, MAINTAINERS.md, and docs-site/src/content/docs/contributing/pr-quality.md, all synced in this PR.

Reviews

  • Owners/maintainers: none open.
  • Bots: CodeRabbit threads all resolved — findings fixed on-thread in bbb20a272, c9c63094b, cc48263c5; one declined with rationale (maintainer-approval invalidation is a GitHub-native control once branch protection is configured; CodeRabbit withdrew). Codex connector findings addressed on-thread earlier. Unresolved thread count: 0.

Base / CI

  • Behind/conflicts: clean (mergeStateStatus: CLEAN).
  • Required checks on b1a59df6: all green — enforce-target (latest run after the body fix), ci, test 1-4/4, macos, keyring ubuntu/windows/macos, gates, react-doctor, changes, label, issue-quality test, CodeRabbit. (Two earlier enforce-target failures on this head were the base-branch script correctly rejecting a transiently flattened PR body during a body rewrite; the corrected body passes.)
  • Local tip compile/tests: bun test tests/ci-workflows.test.ts 99/99, node --test .github/scripts/*.test.cjs 381/381, bun x tsc --noEmit pass, bun run privacy:scan pass.

Simplification

The user-approved split relocated the 970-line inline script's pure logic into responsibility-named modules under .github/scripts/ (pr-quality-state.cjs — state markers/defaults/staleness + claim policy; pr-quality-messages.cjs — every user-facing builder; pr-maintainers.cjs — maintainer-table parsing), each with its own unit suite, leaving a thinner orchestrator for GitHub reads/writes and draft/ready transitions so the write-allowlist and argument-shape pins stay visible. The workflow also gained the requested claim verification in b1a59df6. The split is behavior-preserving: the full harness suite (now 99 scenarios including the new claim-check ones) passes on the final head, and SCRIPT_LOAD/write-surface assertions are unchanged apart from the documented read.

Gate

None. The PR is no longer a draft; ship-gate.mjs returned ready (required checks, base health, review policy, review threads, wake, CODEOWNERS all clear) on the unchanged head after the thin settle. Verdict: approve-comment.

Bottom line

Ship. The checklist completion is bound to the attested head, the gate verifies the CI and latest-dev claims itself and unchecks/redrafts on violation, the oversized script is split by responsibility, all review threads are resolved, required CI is green on the final head, and the authoritative ship gate is ready.

@Wibias

Wibias commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

Merging #986: the review-readiness checklist is now bound to the exact commit it attested, and the gate verifies the CI and latest-dev claims itself, so a completed checklist can no longer survive un-attested pushes. The oversized enforce-pr-target script is also split into responsibility-named modules. Required CI, review threads, and the authoritative ship gate are all clear.

@Wibias
Wibias merged commit de7f5c8 into lidge-jun:dev Aug 4, 2026
21 of 25 checks passed
@Wibias
Wibias deleted the codex/pr-readiness-gate-recheck branch August 4, 2026 10:09

@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: b1a59df612

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +500 to +504
claimViolations = readinessClaimViolations({
ciGreen,
behindBase,
behindUnknown: ancestryLookupFailed
});

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 Compare stacked readiness claims against dev

In the stacked-base path, behindBase is never populated because the compare calls run only when baseAllowed is true, but stacked PRs are accepted with baseAllowed=false. This feeds the initialized 0 into the latest-dev claim check here, so a contributor can target an open PR head that is far behind dev, tick all four boxes, and be marked ready without the promised freshness verification; compare DEFAULT_BASE...head separately for this checklist claim.

Useful? React with 👍 / 👎.

let ciGreen = true;
try {
const { data: checksData } =
await github.rest.checks.listForRef({

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 Grant checks read before querying check runs

I checked this workflow's permissions block and it grants only contents: write and pull-requests: write, while GitHub documents this endpoint as requiring Checks repository read permission. In any environment where the public unauthenticated fallback is unavailable, this call throws, the catch sets ciGreen=false, and every completed contributor checklist has its CI box unticked instead of being accepted; add checks: read to the workflow permissions.

Useful? React with 👍 / 👎.

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

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 Wire the new helper tests into CI

These new PR-quality helper tests are not picked up by the existing workflows: I searched .github/workflows for pr-quality-state, pr-quality-messages, and pr-maintainers, and only enforce-pr-target.yml imports the modules, while issue-quality-tests.yml still lists and runs only the older script tests. A future PR touching only these privileged gate helpers would not trigger this test workflow or run the new unit tests, so regressions in the readiness gate can merge untested; add the new files/tests to the workflow paths and test command list, or switch that workflow to a safe glob.

Useful? React with 👍 / 👎.

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). enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant