Skip to content

ci: fleet review lanes show up as commit statuses on the PR head - #114

Merged
askalf merged 14 commits into
masterfrom
ci/fleet-status
Sep 25, 2026
Merged

askalf merged 14 commits into
masterfrom
ci/fleet-status

Conversation

@askalf

@askalf askalf commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

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.

status pending green red
fleet/verify waiting on required CI (or the Breaker) at the head verified at the head, or not required (docs, assets, .github config, bot branch) a required check failed at the head
fleet/review waiting on Redline at the head, or on verification Redline approved the head Redline requested changes at the head
fleet/second-read waiting on the Second Read at the head, or on verification READY at the head, or not gating (non-code PR) NOT READY at the head, with its reason

Here the base branch requires docker-build, actionlint, analyze (javascript-typescript), unit-tests. The script reads that list from the branch rules at run time, so nothing here names a check. workflow_run re-runs it when any pull_request workflow 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_target workflows (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 without fleet/verify waiting 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.

  • The status job runs the default branch's copy of scripts/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.
  • It runs on a GitHub-hosted runner with the workflow's own token (checks: read, statuses: write, reads only otherwise). It does not use the fleet's GitHub quota.
  • Fork PRs are skipped; the fleet does not review them.
  • No concurrency group: a cancelled run would roll up as a failed check. Instead each run posts only what differs from the head's newest statuses, then re-reads the PR and corrects what differs (up to three passes), so the run that acts last leaves statuses matching data at least as new as anything posted.
  • The statuses are informational. None is added to the ruleset's required checks.
  • self-test runs the script's 114 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/review and fleet/second-read to the default branch's required status checks, then run backfill, 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: 114 pass, 0 fail.
  • The script and tests are plain ASCII. Tests build the Second Read's own separator character at run time with String.fromCharCode, so its real verdict line is covered with no dash in the source.

@github-actions github-actions Bot added github_actions Pull requests that update GitHub Actions code tests Test suite and CI size/L 200-799 hand-written lines labels Sep 25, 2026
…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 sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated review from the Sprayberry Labs fleet code reviewer.

Reviewed by the GPT gating lane (gating review).

Verdict: request changes — dismissed Second Read verdicts are still treated as active. rule:none

Blocking — incorrect status after a dismissed Second Read review

scripts/fleet-status.mjs:235-243

if (r.login !== SECOND_READ_LOGIN || r.commitId !== facts.head) continue;
...
out = last.startsWith('NOT READY')
? { state: 'NOT READY', reason: last.replace(/^NOT READY\W*/, '').trim() }
: { state: 'READY', reason: '' };

A Second Read can post SECOND READ: NOT READY - x at the current head and then have that review dismissed. The workflow explicitly reruns on pull_request_review dismissed, but this parser does not exclude DISMISSED reviews, so it continues to publish fleet/second-read as failure from the dismissed body. That leaves a PR blocked even after the only negative verdict was dismissed. Filter Second Read reviews to the active state(s) that constitute a verdict and add a regression case for a dismissed NOT READY review.

if (r.login !== SECOND_READ_LOGIN || r.commitId !== facts.head || r.state !== 'COMMENTED') continue;

Required CI is green at the live head. I reviewed the workflow event handling, status computation, and the accompanying unit-test coverage; I did not run the test suite locally.

…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 sprayberry-secondread left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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: The lane logic is correct on every boundary I rebuilt except one. The one test meant to pin "the bot exemption beats the >100-file fail-closed rule" passes with or without the bot exemption, and nothing else pins that ordering.

Read at live head 779cd55 (the ticket named d97817f; five commits have landed since). I read the full diff: workflow, script and tests. I ran node scripts/fleet-status.test.mjs at this head (94 pass, 0 fail) plus two mutants, described below. Required CI is green (docker-build, actionlint, analyze, unit-tests, plus self-test and status).

Finding 1 (medium): test that cannot fail, and an unpinned boundary on the bot exemption vs. the 100-file limit

scripts/fleet-status.mjs:52-55

  if (isBotPr(facts.author, facts.headRef)) return false;
  // More than 100 files: the dispatcher reads the first 100 and fails closed on the rest
  // (readPrFacts in platform's review-events.ts), so the lanes do the same.
  return facts.files.length > 100 || facts.files.some(isCodePath);

scripts/fleet-status.test.mjs:197

  check('a bot PR with 100 files is not code', by(laneStatuses(base({ headRef: 'bot/cc-drift-v2.1.281', files: docs(100) })))[CONTEXTS.verify].state === 'success');

The rows for this predicate:

input head does pinned by
non-bot, 100 docs files not code exactly 100 docs files is not code
non-bot, 101 docs files code more than 100 files is code whatever they are
bot, code files not code bot branch: verify not required, approval counts
bot, 100 docs files not code line 197, but this holds without the bot rule, because 100 docs files are not code either way
bot, 101 files not code (the bot check returns first) nothing

I checked both mutants against this head:

  • Delete line 52 (drop the bot exemption). Line 197 still passes. Only bot branch: verify not required fails, so line 197 tests nothing about bots.
  • Keep the exemption but move it after the size check (files.length > 100 || (!isBotPr(...) && files.some(isCodePath))). All 94 tests pass. For a bot branch with 101 files, fleet/verify flips from success to pending.

This input is reachable. Release, receipts and dependabot branches from askalf/github-actions can touch more than 100 files. After the follow-up in the body makes fleet/verify a required check, that case either merges or waits on a Breaker that the dispatcher never sends for bot PRs. The comment at line 53 says this rule mirrors the dispatcher, but no test fixes which rule wins. The name of line 197 says it covers that case, and it does not.

Suggested fix: test one past the limit, with code paths, so both the ordering and the exemption are pinned. Swap the assertion if the dispatcher actually fails closed before the bot check.

  check('a bot PR with more than 100 files is not code',
    by(laneStatuses(base({ headRef: 'bot/cc-drift-v2.1.281', files: Array.from({ length: 101 }, (_, i) => `src/f${i}.ts`) })))[CONTEXTS.verify].state === 'success');

What I checked and found sound

  • requiredCiState: empty required list, own contexts excluded (so fleet/verify cannot wait on itself), unreported check becomes pending, failed beats pending, last result per name wins for reruns, skipped and neutral pass. Each row has a test, including the three-round self-reference loop.
  • secondReadAtHead: reviews at an older head, dismissed reviews, a line-less follow-up review, READY with trailing text, the last line in a body winning, and the dash-agnostic NOT READY\b.* parse. All are pinned. The em dash in this seat's own verdict line parses (test at line 70 uses \u2014).
  • redlineVerdict: deterministic approval ignored on code and counted on docs, COMMENTED/DISMISSED skipped, a later CHANGES_REQUESTED at the same head wins. All pinned.
  • fit: tested at exactly 140 characters and at 141.
  • Workflow: status/backfill sparse-checkout the default branch's script with persist-credentials: false. self-test runs PR code with contents: read only. The fork guard is in the job if and again in readFacts. permissions: {} is set at the top level. The body's claims (94 tests, no required checks named in code, plain ASCII, no concurrency group, post-then-reverify up to three passes) all match the diff. I found no non-ASCII bytes in any of the three files.
  • Not verified: parity with askalf/dario#1419 and with the dispatcher source (review-events.ts, review-dispatch.sh). I could not read them within budget.

SECOND READ: NOT READY — test at scripts/fleet-status.test.mjs:197 passes without the bot exemption; bot PR with >100 files (bot rule vs fail-closed ordering) is unpinned

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).

Verdict: changes requested — generated-writing tells remain in public test text. rule:reads-as-generated

Blocking — generated-writing tell

scripts/fleet-status.test.mjs:70

review(SECOND_READ_LOGIN, 'COMMENTED', OLD, 'text\nSECOND READ: NOT READY — stale stack'),

scripts/fleet-status.test.mjs:96

review(SECOND_READ_LOGIN, 'COMMENTED', HEAD, 'SECOND READ: NOT READY — commit subject has an em dash'),

The added test fixtures retain typographic em dashes. First-party public-repo strict mode treats an em dash anywhere in added public code or tests as a blocking generated-writing tell. These strings are merely fixture prose; replace the punctuation with the file's ordinary ASCII delimiter without changing the verdict cases.

review(SECOND_READ_LOGIN, 'COMMENTED', OLD, 'text\nSECOND READ: NOT READY - stale stack'),
// ...
review(SECOND_READ_LOGIN, 'COMMENTED', HEAD, 'SECOND READ: NOT READY - commit subject has an em dash'),

The required checks are green at 779cd55. I reviewed the workflow, status-state implementation, and its unit-test coverage; I did not run the local suite.

…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.
… 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 sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated review from the Sprayberry Labs fleet code reviewer.

Reviewed by the GPT gating lane (gating review).

Verdict: approve — no blocking issues found.

Required CI is green at live head 431eb50ab85827f7bcc77d9cec98ac68722d860a. I reviewed the full workflow, lane-status implementation, and unit-test diff, with particular attention to the new workflow_run coverage list and its test that enumerates pull-request workflows. The status workflow is triggered after each listed PR workflow completes, while the test makes adding an unlisted PR workflow fail CI instead of silently leaving the lane statuses stale. The required-status logic continues to exclude the three fleet contexts, avoiding self-dependency. I did not run the local test suite.

…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.
master gained hygiene.yml (a pull_request workflow) after this branch was cut, and
the list test caught it on the merge. Merged master and listed it.

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated review from the Sprayberry Labs fleet code reviewer.

Reviewed by the GPT gating lane (gating review).

Verdict: approve — no blocking issues found.

Required CI is green at live head 4498951c53c9fc30d0eb7b3fab116f96d024dc5b. I reviewed the workflow, lane-status implementation, and its unit-test coverage. In particular, I checked the current-head fixes for dismissed Second Read reviews, status self-dependency exclusion, bot PRs over the 100-file threshold, and the workflow list assertion that refreshes lanes after every pull-request workflow. The workflow uses the default branch script for status writes, keeps pull-request test execution read-only, and filters pull_request_target workflows whose checks cannot appear on the PR head. I did not run the local suite; required CI is the test signal.

What's good: the bounded post-then-re-read loop avoids leaving a stale lane status after concurrent webhook runs, while the tests pin the relevant head, dismissal, file-count, and required-check boundaries.

… 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-secondread sprayberry-secondread left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 c146493. The lane rules, the fail-closed paths and the workflow wiring all match the diff. Every reachable boundary I rebuilt has a test that can fail.

This ticket named head b9c8b7d, and the head moved twice while I read (4498951, then c146493). This review is of c146493.

What I checked

  • The Second Read's own verdict line. secondReadAtHead (scripts/fleet-status.mjs:114, :118) is new at this head's tests, and they now drive the separator it really posts. scripts/fleet-status.test.mjs:364-374 builds -, :, U+2013 and U+2014 at run time and pins the full laneStatuses result for a U+2014 line. Both can fail. Tightening the separator class to [-:] leaves \u2014 stale stack as the reason, which fails separator U+2014 is dropped. Tightening the line regex to ASCII separators turns the lane pending, which fails the state === 'failure' check. I also ran the code against SECOND READ: NOT READY \u2014 \x` is nulland got{"state":"NOT READY","reason":"`x` is null"}. self-testat this head logs all five checksok, 114 pass, 0 fail`.

  • Boundaries rebuilt from the diff, with the tests that pin them. Required CI:

    • no required checks gives none, and so does only own lanes required;
    • own lanes pending while CI is green gives passed, and the three-round self-feed stays green;
    • a required check that has not reported, or is still running, gives pending;
    • failed while another is pending gives failed; for reruns, the last result wins; skipped and neutral count as passed.

    Files: 99, 100 and 101 docs files; a bot PR with 101 files stays exempt. Descriptions: exactly 140 and 141 characters. Verification SHA:

    • 7-character prefix, older comment, latest comment wins;
    • wrong login, findings and blocked headings.

    Second Read: another login carrying the line, an older head, a lineless follow-up, dismissed, READY, mostly, no separator. Redline: deterministic approval on code (ignored) and on docs (counted); CHANGES_REQUESTED after APPROVED at the same head.

  • Fail-closed paths in the CLI (fleet-status.mjs:236-252).

    • Unreadable rules give required = null, which gives pending.
    • A check read that throws gives pending.
    • Check runs are read one page of 100. A required check past that reads as unreported, so pending, never green.
    • A completed run with a null conclusion maps to '', so pending.
  • Backfill at this head. --limit 1000, and at 1000 or more PRs the job fails with ::error:: rather than dropping PRs silently. jq is on ubuntu-latest, and set -euo pipefail covers the pipe. The new test only matches the workflow text, but a limit of 100 or a missing cap guard would fail it.

  • Workflow.

    • permissions: {} at the top.
    • status and backfill sparse-checkout the default branch's script with persist-credentials: false; hashFiles skips the step until this merges.
    • self-test runs PR code with contents: read only.
    • The fork guard is in the job if and again in readFacts.
    • The workflow_run list covers every pull_request workflow on master (all 12 name: values checked) and leaves out PR triage (pull_request_target). The test at :333-361 enforces this against the directory.
  • Body claims.

    • Master's branch rules require docker-build, actionlint, analyze (javascript-typescript) and unit-tests, as the body says, and no check is named in code.
    • The count of 114 matches the self-test log.
    • The body's line about the separator is built with String.fromCharCode and matches the diff.
    • No non-ASCII bytes in the diff.
    • scripts/fleet-status.mjs is blob-identical to askalf/dario#1419's (aab0a0e).
    • Required CI passes at this head.

What's good: the post-then-reread loop is a sound replacement for a concurrency group, since a cancelled run would otherwise show up red. The test that checks the workflow_run list against the workflow directory stops the lanes from quietly going stale when a later required check is added.

SECOND READ: READY

@askalf askalf added the author-fixes The PR's author answers its reviews; no fleet fix ticket label Sep 25, 2026

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).

Verdict: request changes — the lane classifier treats a documented exempt asset type as code, so docs-only .txt PRs are sent through verification and gating contrary to the stated policy. rule:none

Blocking — correctness: scripts/fleet-status.mjs:33-35

if (/\.(md|svg|png|jpe?g|webp|gif)$/i.test(path)) return false;
if (/^docs\/.*\.txt$/i.test(path)) return false;
if (path === '.gitattributes') return false;

The documented exemption is for .txt assets, not only .txt files below docs/. For example, a same-repository PR changing only NOTICE.txt or RELEASE-NOTES.txt at the repository root reaches isCodePath() with neither condition true. needsVerify() consequently returns true, and laneStatuses() puts it through required-CI verification plus Redline and Second Read gating. That is the wrong lane state for an exempt docs/assets-only PR and differs from the policy this change is meant to publish.

if (/\.(md|txt|svg|png|jpe?g|webp|gif)$/i.test(path)) return false;

Please add a root-level .txt fixture alongside the existing path-classification coverage.

I reviewed the new workflow, lane-state computation, API reads/writes, and regression tests. Required CI is green at c146493129c32ba14ad38f8690fb0442ec533842; I did not run the suite locally.

@askalf
askalf dismissed sprayberry-redline’s stale review September 25, 2026 05:52

Dismissed for a re-read: this verdict came from Redline's pre-398 rule text (.txt exempt everywhere). Migration 398 (live since 04:54Z) states the dispatcher's rule: .txt is exempt only under docs/, which is what this script implements.

@sprayberry-redline sprayberry-redline left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Automated review from the Sprayberry Labs fleet code reviewer.
Reviewed by the GPT gating lane (gating review).

Verdict: APPROVED — no blocking issues found in the current-head implementation.

What's good: Required CI is green at c146493. I reviewed the added workflow, status derivation, API/error paths, and the 114 unit-test cases. The workflow runs the default-branch script with minimally scoped read/status permissions, treats unreadable required-check metadata as pending, excludes its own lane contexts from CI gating, and tests the dispatcher-aligned code/verification and review-head rules. I did not run the suite locally, per review policy; CI is the test signal.

@sprayberry-secondread sprayberry-secondread left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 f252571. The diff reads correctly, and each boundary I rebuilt from it has a test that would fail without the change.

Change since my last read (c146493): one merge commit from master (93f2d30, #115) touches .github/labels.json. That file is not in this PR's diff. .github/workflows/fleet-status.yml, scripts/fleet-status.mjs and scripts/fleet-status.test.mjs are byte-identical to the head I last read. I re-read all three for this gating read anyway.

Boundaries rebuilt from the diff

Predicate (file:line) Input Behaviour Pinned by
needsVerify: isBotPr before files.length > 100 (scripts/fleet-status.mjs:52-56) bot branch + 101 files exempt a bot PR with more than 100 files is still not code (fails if the order is swapped)
same, > 100 person, 100 / 101 / 99 docs files not code / code / not code exactly 100 docs files is not code, a person with more than 100 docs files is code, 99 docs files are not code
isBotPr identity AND branch person on bot/, release/1.2; askalf on feature branch; dependabot on any branch not bot / not bot / bot the isBotPr checks in both blocks
requiredCiState OWN_CONTEXTS filter fleet/* listed as required, pending ignored; only own lanes → none own lanes required, three rounds: all three green (without the filter the lanes wait on themselves forever, so this test fails)
requiredCiState empty / missing / running / failed / rerun / skipped-neutral each none / pending / pending / failed / last wins / passed the eight requiredCiState checks
rules or checks unreadable (:236-242, :251) API error pending; never green fails closed by construction (CLI path, untested; acceptable for I/O)
verifiedAtHead label AND last askalf comment prefix label only, comment only, older comment last, other login, findings/blocked headings, 7-char prefix each handled the verifiedAtHead blocks
ci === 'none' vs pending with Breaker label pending CI + label + comment still pending CI pending: an old Breaker label and comment do not count
redlineVerdict deterministic marker on code vs docs both skipped on code, counted on docs deterministic approvals block
secondReadAtHead commit, login, DISMISSED, last line, READY, mostly, bare NOT READY each handled the Second Read, review by review + dismissed block + no reason ... without a trailing colon
reason separator strip (:118) -, :, U+2013, U+2014, leading backtick / paren / bracket separator dropped, first char kept the separator loop and the reasonOf block
fit at 140 140 / 141 chars kept / 137 + ... the 140-character edge
statusesToPost identical / state differs / description differs / missing skip / post / post / post post-then-verify block

I checked the new tests for assertions that would pass whether or not the change is there, and found none. The ordering test and the three-round own-lanes test are the two that previously could not pin their rule, and both now fail if their rule is removed. The workflow_run list test reads .github/workflows/*.yml at the head, so a later pull_request workflow left out of the list turns it red.

PR body claims vs the diff

  • The status job runs the default branch's script: ref: ${{ github.event.repository.default_branch }} with a sparse checkout, and it is skipped until then by hashFiles(...) != ''. Holds.
  • The token is limited to checks: read and statuses: write, with reads only otherwise: permissions: {} at top level, plus job-level grants. Holds.
  • Fork PRs are skipped: they are dropped both in the job's if: and in readFacts() via the head.repo.full_name check. Holds.
  • There is no concurrency group, and the script re-reads and corrects for up to three passes: for (let pass = 1; pass <= 3; pass++), which breaks when todo is empty. Holds.
  • Backfill pages past 100 PRs and fails at 1000: --limit 1000, -ge 1000, and ::error::, which a test pins. Holds.
  • pull_request_target workflows are left out of workflow_run: the test asserts they are absent. Holds.

Also checked

  • CI at f252571 is green: docker-build, actionlint, analyze (javascript-typescript), unit-tests, self-test, hygiene and CodeQL all pass. I did not run the suite locally.
  • Security: nothing an attacker controls reaches a shell. PR is numeric, validated by /^\d+$/, and it only goes into API paths. Review and comment bodies are only regex-matched and then written into a status description. The backfill loop iterates over jq-extracted numbers.

What's good: the lane rules are pure functions with the I/O kept in the CLI block, so the table above can be pinned without network mocks. Wherever something is ambiguous (unreadable rules, a missing check, no verdict line), the result is pending rather than green.

SECOND READ: READY

@askalf
askalf merged commit bae62d3 into master Sep 25, 2026
16 checks passed
@askalf
askalf deleted the ci/fleet-status branch September 25, 2026 05:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author-fixes The PR's author answers its reviews; no fleet fix ticket github_actions Pull requests that update GitHub Actions code size/L 200-799 hand-written lines tests Test suite and CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants