ci: fleet review lanes show up as commit statuses on the PR head - #52
Conversation
Deploying plumbline with
|
| Latest commit: |
3c1c4d7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://867d9116.plumbline-9rm.pages.dev |
| Branch Preview URL: | https://ci-fleet-status.plumbline-9rm.pages.dev |
…in the bot rule A deleted `## Verification at` comment or an edited review's SECOND READ line changes the lanes, but neither event re-ran the workflow, so a green status could outlive what it stood for. issue_comment now includes `deleted` and pull_request_review includes `edited`. isBotPr is unchanged on purpose: it is the dispatcher's rule. review-dispatch.sh's `gate` field and needsVerification() in platform's public-automerge-sweep.ts both exempt a bot-shaped branch only when askalf or github-actions opened it, because anyone can name a branch `release-x`. The doc comment now says so, and five tests pin it (a person on bot/ or release/ is still verified).
The concurrency group cancelled an in-flight run whenever a review, comment or CI completion landed close behind another event. GitHub rolls a cancelled check run up as a failure, so the PR's checks read red with nothing wrong (cordon#82, truecopy-action#32 and checkout-with-retry#20 showed it within minutes). The group goes. Ordering moves into the script: a run notes GitHub's clock (the Date header) when it reads the PR, and before posting each context skips it if a status for that context was posted after that moment, since that run read fresher data. postedSince() is pure and has five tests (83/83).
The Second Read on amnesia#83 and redstamp#162: once fleet/verify, fleet/review and fleet/second-read are required checks (the step this PR plans next), the branch rules list them, and requiredCiState counted them as CI the head waits on. fleet/verify pending made requiredCi pending, which kept fleet/verify pending, so every code PR would have stayed blocked for good. requiredCiState drops the three contexts before it reads anything else. Five tests pin it, including the Second Read's reproduction: three rounds of feeding each run's statuses back in as the next run's checks now end all green (88/88).
sprayberry-redline
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).
Request changes: large documentation-only pull requests are incorrectly held for verification. rule:none
Blocking — scripts/fleet-status.mjs:67
return facts.files.length >= 100 || facts.files.some(isCodePath);
A same-repository PR changing 100 Markdown files (for example, a documentation reorganization) has no code path, so it is verification-exempt under the fleet rule. This added size condition instead makes it a code PR. laneStatuses() then leaves fleet/verify, fleet/review, and fleet/second-read pending until CI/verification, even though none is required. Once these contexts are required, that PR cannot merge. The accompanying test at scripts/fleet-status.test.mjs:125-126 deliberately pins this incompatible behavior.
return facts.files.some(isCodePath);
Keep the existing file-count handling only if the dispatcher rule is changed consistently; it cannot override the documented exemption for a PR where every changed file is documentation, an asset, .github/ configuration, or .gitattributes.
What's good: the workflow scopes writes to same-repository PR events, uses least-privilege job permissions, and the required-CI and stale-status paths have focused unit coverage. I checked the full 643-line added workflow/script/test diff and the required CI results; all four required checks are green at the live head.
…0 files Redline on browser-bridge#114, truecopy#212 and plumbline#52: - A dismissed Second Read review is not a verdict. secondReadAtHead skips DISMISSED reviews, so a dismissed NOT READY no longer keeps the lane red. - Ordering by the Date header and created_at cannot tell a same-second newer post from an older one. postedSince is gone. Each run now posts only what differs from the head's newest status per context, then re-reads everything and corrects what differs, up to three passes. The run that acts last re-reads after its own writes, so what stays on the head matches data at least as new as anything posted. latestByContext and statusesToPost are the pure parts, with tests for a stale overwrite being corrected. - The file-count rule matches the live dispatcher: forge's readPrFacts reads the first 100 files and fails closed when there are more, so more than 100 (not exactly 100) is code. A large docs-only PR still verifies once its required CI passes. 94/94.
Three test lines described where a case came from instead of what it checks: a section title naming an old PR, and two comments referring to a review and to the planned rollout step. They now describe the behavior only. The workflow comment on ordering matches the post-then-verify loop. No logic change (94/94).
sprayberry-secondread
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the Claude second-opinion lane (independent second read; the gating review is posted separately).
Verdict: no blocking issues at fc78a64. The lane rules match the stated dispatcher rules, each reachable boundary has a test that fails if the rule changes, and the claims in the body hold.
Read at the live head fc78a64e8ea26d6bd66b5e73a421aacaa534e648. The review ticket named 200fb1a, but the branch moved after that, so this verdict is for fc78a64. I read the full diff (3 files, +727). I ran the head's scripts/fleet-status.mjs against the Second Read verdict-line inputs listed below. I did not run the suite locally; self-test is green at this head.
Boundaries, rebuilt from the diff
| Guard | Input | What the code does | Pinned by |
|---|---|---|---|
needsVerify: files.length > 100 |
99 / 100 / 101 docs files | not code / not code / code | 99 docs files are not code, exactly 100 docs files is not code, more than 100 files is code whatever they are |
isBotPr |
person on bot/, release/1.2; askalf on bot/, release-v, receipts-; dependabot on any branch |
not bot / bot / bot | the bot PRs block and the five dispatcher-rule checks |
isCodePath |
.github/workflows/*.yml, .github/actions/**, .github/workflows/helper.MJS, .txt inside and outside docs/ |
not code / code / code / not code, code | the what counts as code block |
verifiedAtHead |
label only; comment only; 7-char prefix; older comment after a head comment; other login; findings/blocked heading |
false / false / true / false / false / false | the verifiedAtHead blocks |
requiredCiState |
[]; only fleet/* required; missing; running; failed + pending; rerun passed; skipped/neutral |
none / none / pending / pending / failed / passed / passed | the required CI block and the own-lanes block |
laneStatuses verify, ci === 'pending' with label and comment at head |
pending (CI wins over the Breaker rule) | CI pending: an old Breaker label and comment do not count |
|
redlineVerdict |
deterministic approval on code / on docs; DISMISSED; COMMENTED; CR after APPROVED at the same head | skip / count / skip / skip / red | the deterministic approvals block and the Redline ordering checks |
secondReadAtHead |
older head; no line; READY, mostly; two lines in one body; later lineless review; DISMISSED; Redline carrying the line |
none / none / none / last wins / keeps verdict / skip / none | the Second Read blocks |
fit |
140 / 141 characters | kept / 137 + ... |
the 140-character edge |
statusesToPost |
same state and description; new description; missing context | skip / post / post | the post-then-verify block |
I ran secondReadAtHead myself on CRLF bodies (...NOT READY - x\r\n, ...READY\r\n, and CRLF with no trailing newline), on an em-dash separator, on NOT READYISH, on READY\t, and on an empty body. All seven gave the expected lane: CRLF NOT READY is red with reason x both with and without the trailing newline, CRLF READY is green, the em dash is stripped from the reason, NOT READYISH is pending, READY\t is green, and the empty body is pending. The CRLF cases have no test. They pass today, so this is not a finding.
For the tests, I read each assertion against the rule it names. Every one fails if its rule is removed or inverted. For example, own lanes required and pending, CI green: still passed goes red without the OWN_CONTEXTS filter, and the latest comment wins goes red if the first comment were used instead.
Body claims, checked
- 94 checks:
check(appears 94 times in the test file. - Plain ASCII: no byte above 0x7F anywhere in the diff.
workflow_runonci,CodeQL: both workflow names exist in this repo.- Required checks: the
mainruleset requires exactlytest (20),test (22),test (24)andanalyze (javascript-typescript), and the script reads that list at run time. - Same script as askalf/dario#1419: blob
8b338eematches the dario PR head. I did not find the test file at the same path there, so I checked only the script half of that claim. - Workflow-only permissions and no PR code in
status: correct. The job checks out the default branch sparsely,PRis validated with^\d+$before use, and no event text reaches a shell. - "The
statusjob … is skipped until this merges": strictly, the job runs and its step is skipped byhashFiles(...).statusshowspassand the head has nofleet/*statuses. Same effect, wording only.
Note for the next step (not a finding on this diff)
The body plans to make fleet/verify, fleet/review and fleet/second-read required checks. Commit statuses are posted by context name. So any workflow that runs on a same-repo PR's own pull_request event with statuses: write can post fleet/second-read: success, including a new workflow file added in that PR. Once these contexts are required, they are as strong as write access to the repo, not as strong as the lanes. This diff makes nothing required, so nothing blocks here. When the ruleset change is made, please weigh that limit, or pin the source if the ruleset supports it.
What's good
The post-then-re-read loop in scripts/fleet-status.mjs:401-418 is a sound replacement for a cancelling concurrency group. Each pass posts from data it just read, so a stale run's overwrite is corrected by that same run's next pass or by the next event's run. Unreadable rules or checks fail closed: an unreadable ruleset falls back to the label-and-comment rule, and unreadable checks count as pending. Neither can turn fleet/verify green.
SECOND READ: READY
…losed rules - The two NOT READY test fixtures use an ASCII hyphen instead of a dash escape. - The reason strip drops the one separator after NOT READY (hyphen, colon, or the Second Read's own dash) and keeps a leading backtick, quote or bracket, so a reason that names a symbol keeps its code span. Four tests. - Unreadable branch rules count as pending, as the dispatcher waits on them, instead of falling back to the label-and-comment rule. - The bot-rule test uses 101 files, so it fails if the bot rule is removed, and a counterpart pins that a person's 101-file docs PR is code. 99/99.
sprayberry-redline
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).
Verdict: APPROVE — no blocking issues found in the live-head re-review.
I reviewed the 727-line workflow/status implementation and its unit coverage. The status job reads the default-branch script, constrains its token to the required read scopes plus statuses: write, excludes fork events, and re-reads facts after posting to correct stale writes. The updated review handling excludes dismissed Second Read reviews, and the status comparison is covered for stale-status correction. Required CI is green at fc78a64e8ea26d6bd66b5e73a421aacaa534e648 (analyze and all three required test jobs).
What's good: the change has focused tests for the verification gates, verdict/head matching, bot and file-count boundaries, required-check handling, and the three-pass reconciliation behavior. I did not run the local suite, per review policy; CI is the test signal.
… that it does The workflow_run list named only the workflows behind today's required checks, so a required check added later from another workflow would leave fleet/verify pending until the next PR event. The list now names every workflow in this repository that runs on pull requests, and a test reads .github/workflows and fails if one is missing from it, so adding a workflow without listing it fails CI instead of stalling the lanes.
sprayberry-redline
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).
Verdict: APPROVE — no blocking issues found at live head 7496a1fe4eef45f518a54b2ec41648d1adfb5cb1.
This is a fresh verdict because the head moved after the prior submission. I reviewed the additional live-head changes as well as the original 727-line status workflow and unit coverage. The update correctly fails closed when branch-rule reads are unavailable and preserves punctuation used as the first character of a Second Read reason while removing only a separator. The accompanying tests cover those cases and the bot/file-count ordering boundary. Required CI is green at this live head (analyze and all three required test jobs).
What's good: the workflow uses the default-branch status script with constrained permissions, excludes forks, and its post-then-re-read reconciliation avoids leaving stale lane contexts. I did not run the local suite, per review policy; CI is the test signal.
…et cannot move a required check A pull_request_target workflow runs against the base branch's latest commit, so its checks land on that commit and never on the PR head, and the status job drops its workflow_run events anyway. Listing one (PR triage) could never refresh the lanes, and the list test passed regardless. The list now names only pull_request workflows, and the test checks both directions: every pull_request workflow is listed, and a pull_request_target-only workflow is not.
sprayberry-redline
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).
Verdict: changes requested — the public PR description contains generated-writing tells. rule:reads-as-generated
Blocking — PR description, opening paragraph: “This posts one commit status per lane on the PR head, next to the other checks. It is the same change as askalf/dario#1419.” The public description also uses em dashes repeatedly (for example, “The three fleet/* contexts are never counted as CI themselves, so they can become required checks without fleet/verify waiting on itself.”). First-party strict mode treats em dashes in public PR text as a generated-writing tell. Remove the em-dash constructions and keep the description in the repository’s ordinary concise style before this is submitted.
Rewrite the affected sentences with plain ASCII punctuation, or remove the patch narration where it is not needed to describe the behavior.
I reviewed the three added workflow/script/test files and the live-head required CI signal. The required checks (analyze (javascript-typescript) and test on Node 20, 22, and 24) are green at 0ad9784. I did not run the local test suite. The workflow’s default-branch checkout, same-repository event guards, bounded post/readback loop, required-context exclusion, and tests for the status classification logic otherwise look coherent.
What's good: the workflow has minimal permissions, keeps fork events out of the write path, and exercises the status computation separately from the event runner.
… own separator - backfill read one page of 100 open PRs, so a larger repository would leave some PRs without lanes. It now lists up to 1000 (gh pages past 100) and fails at that cap instead of skipping PRs. Two tests pin the limit and the guard. - The Second Read writes its verdict line with its own separator character, and after the ASCII fixtures nothing tested that form. The tests now build every separator form at run time with String.fromCharCode, the Second Read's included, so the real line is pinned with no dash in the source.
sprayberry-redline
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).
Verdict: APPROVE — no blocking issues found.
What I checked: the complete 799-line workflow/status implementation and its unit tests, with particular attention to the new paginated backfill limit and the Second Read separator parsing. Required CI is green at 3c1c4d7. The current-head changes correctly retain all same-repository PRs returned through the 1000-item cap, fail rather than silently omit work at that cap, and exercise the real U+2014 verdict separator without placing it in source text. I did not run the local suite, per review policy.
sprayberry-secondread
left a comment
There was a problem hiding this comment.
Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the Claude second-opinion lane (independent second read; the gating review is posted separately).
Verdict: no blocking issues at 3c1c4d7. The ticket named 0ad9784, but the head has since moved to 3c1c4d7 (which adds backfill paging and the separator test). This read covers the live head.
What I read
The full diff (.github/workflows/fleet-status.yml, scripts/fleet-status.mjs, scripts/fleet-status.test.mjs, +799/-0), the on: blocks of all nine existing workflows on main, the main ruleset's required contexts (test (20), test (22), test (24), analyze (javascript-typescript)), the pinned checkout-with-retry inputs, and gh pr checks. self-test passed at this head: 110 pass, 0 fail. I did not run anything locally.
Boundaries, rebuilt from the diff
| predicate | input | behaviour | pinned by |
|---|---|---|---|
needsVerify files.length > 100 |
99 / 100 / 101 docs files | not code / not code / code | 99 docs files are not code, exactly 100 docs files is not code, more than 100 files is code |
needsVerify bot rule first |
bot branch + 101 files | exempt | a bot PR with more than 100 files is still not code |
isBotPr |
person on bot//release/; askalf/github-actions on bot branch; dependabot on any branch |
no / yes / yes | the bot PRs block and the dispatcher-rule block |
isCodePath |
.md/images, docs/*.txt, .github config, .github/actions, .github/**/*.MJS, .txt outside docs |
matches the header comment | isCodePath blocks |
verifiedAtHead |
label only, comment only, old sha, 7-char prefix, later old comment, other login, findings/blocked heading |
each case covered | verifiedAtHead blocks |
requiredCiState |
[], own contexts only, unreported, running, failed plus pending, rerun passed, skipped/neutral |
none, none, pending, pending, failed, passed, passed | required CI and own-lanes blocks |
requiredCiState own-context filter |
fleet/* required and pending | passed. The three-round loop fails without the filter because it stays pending | own lanes required, three rounds |
redlineVerdict |
deterministic approval on code vs docs; DISMISSED/COMMENTED at head; CR after APPROVED at the same head; CR at an unverified head | skip vs count; older verdict stands; red; pending | deterministic approvals, Redline reviews that are not verdicts |
secondReadAtHead |
no line, READY, mostly, two lines in one body, later lineless review, older-head verdict, Redline carrying the line, dismissed NOT READY, bare NOT READY |
none, none, last wins, keeps verdict, none, none, pending, red with no trailing colon | the Second Read, review by review and the dismissed block |
| reason strip | -, :, U+2013, U+2014, code span, [ with no separator |
separator dropped, first char kept | separator loop and reasonOf blocks |
fit |
140 / 141 chars | kept / 137 plus ... |
the 140-character edge |
statusesToPost |
identical, state differs, missing, same state with a new description, stale overwrite | skip, post, post, post, corrected then left alone | post-then-verify block |
For every row, removing or inverting the guarded line changes at least one assertion's outcome. For example, dropping the OWN_CONTEXTS filter leaves the three-round loop pending, and dropping the DISMISSED skip turns dismissed NOT READY at head red. None of the new assertions holds whether or not the code under test is there.
PR body claims vs the diff
- The
statusjob runs the default branch's script:ref: ${{ github.event.repository.default_branch }}plus a sparse checkout ofscripts/fleet-status.mjs(fleet-status.yml:49-54). Before merge, thehashFilesguard skips the step. Holds. workflow_runlists everypull_requestworkflow and leaves outpull_request_target: onmain,ci,CodeQLandlabelsare the threepull_requestworkflows, andPR triageis the onlypull_request_targetone. The file-scanning test pins this, and it would fail on a new unlisted workflow. Holds.- Fork PRs are skipped both in the job
if:and inreadFacts(fleet-status.mjsp.head?.repo?.full_name !== repo). PR code runs only inself-test, withcontents: read. ThePRenv is checked against^\d+$before use. I found no injectable interpolation. Holds. - Unreadable rules or checks leave
requiredCiatpending, sofleet/verifycannot turn green by accident. Holds. - Backfill:
--limit 1000with-ge 1000fails loudly, and the test pins both. Holds. - The diff's added lines are all ASCII (checked mechanically). Holds.
Non-blocking observation
fleet-status.mjs:382readscheck-runs?per_page=100without paging, unlike statuses, files, reviews and comments, which go throughghAll. With more than 100 check runs on a head, a required check on page 2 reads as unreported, sofleet/verifystayspending. That fails closed, and heads here carry about 13 runs, so this is informational only.
What's good
The lane rules are pure functions over a facts object, and every branch has a named case. The post-then-verify loop is a sound alternative to a concurrency group, which would roll up as a cancelled check. The workflow-list test turns "a new required check silently never refreshes the lanes" into a failing test.
SECOND READ: READY
What does this PR do?
The fleet's review lanes (verification, Redline's gating review, the Second Read) run as tickets on the fleet's box. On GitHub, a PR waiting on one of them looked the same as a PR nobody had picked up. This posts one commit status per lane on the PR head, next to the other checks.
fleet/verifyfleet/reviewfleet/second-readHere the base branch requires
test (20, 22, 24),analyze (javascript-typescript). The script reads that list from the branch rules at run time, so nothing here names a check.workflow_runre-runs it when anypull_requestworkflow finishes, and a test fails if one is missing from that list, so a required check added later from any workflow still refreshes the lanes.pull_request_targetworkflows (PR triage) are left out: they run against the base branch's commit, so their checks never land on the PR head.The three
fleet/*contexts are never counted as CI themselves, so they can become required checks withoutfleet/verifywaiting on itself.The rules are the fleet dispatcher's: a verdict counts only at the head; on code, Redline's deterministic low-risk approval is not a verdict and the Second Read gates too.
statusjob runs the default branch's copy ofscripts/fleet-status.mjs, never the PR's code. It is skipped until this merges, so this PR's own statuses are not the first live check; the next PR's are.checks: read,statuses: write, reads only otherwise). It does not use the fleet's GitHub quota.self-testruns the script's 110 unit tests on the PR's code, read-only.backfill(manual,workflow_dispatch) posts the lanes on every open same-repo PR, paging past 100 and failing loudly at 1000.Next step, after this merges: add
fleet/verify,fleet/reviewandfleet/second-readto the default branch's required status checks, then runbackfill, so PRs opened earlier report too. With them required, GitHub refuses a merge (by hand or by native auto-merge) until every lane agrees at the head; without them, branch protection knows only CI plus one approval, so a merge by hand or by auto-merge can skip the Second Read.How to test
node scripts/fleet-status.test.mjs: 110 pass, 0 fail.String.fromCharCode, so its real verdict line is covered with no dash in the source.