0081 — A PR runs the repo-wide tests its diff names when it opens; unit tests and guard pins still wait for the bot's green

The problem

Of the 150 propflowai PRs merged Nov 1–2, 134 review rounds came after the first one, and about 45% of them were a red CI check found after the PR opened. The check was a repo-wide test the author never runs locally: a caller-count pin (portfolio-derivation-call-sites.drift.test.ts, "expected 41 keys to deeply equal 40"), a classification inventory (clara-prompt-adoption.drift.test.ts), or a pinned call list (held-email-senders.drift.test.ts, review-card-hold-families.drift.test.ts). Under Fede's design these tests run in the Unit Tests lane, and that lane starts at the bot's green or at the arm. So the red arrives after a review round has been paid. The bot then reports it as a 🔴, the author pushes a fix, and the fix push costs another round of about 35 minutes.

Authors cannot run these tests by habit. ADR-0040 keeps the pre-push hook light, and the local guard refuses a sweep. Of the 150 PRs, 99 came from Fede's sessions, and those use different tooling. A local-only fix misses them. ADR-0030 says coverage must follow the artifact, not the producer. That rules out option B.

The decision

  1. One early job, early-repo-wide, on a never-armed PR's code push. It runs when preflight reads armed == 'false'. It skips a draft, a Dependabot PR, an express run, a docs-only run, a superseded push, a label-only run, a carried run and a run-tests PR. It runs only on pushes the deferred test lane skips. That is a subset of them, not their exact complement: drafts and Dependabot PRs, which the lane defers for good, are left out. So on any one head, this job and the unit-test lane never both run. On the bot-green rerun, preflight answers armed=true reason=bot-green, and this job skips.

  2. What it runs: the repo-wide tests the diff names, and nothing else. The candidates are the always-run repo-wide list (always-run-tests.mjs --unfiltered): drift, ratchet, inventory, decide-*, and every test that reads a workflow or a JSON ratchet. A candidate is picked when the PR edits it, or when its source text names a changed file's path, distinctive basename or directory of depth 3 or more. That is the same needle rule guard-select.mjs already uses for guard pins. The run goes through pr-selection-run.sh, so a file must fail twice to be red.

  3. What it does NOT run, by construction:

    • The affected unit-test selection. vitest --changed, the declared reads and the test-read map are not used. These are what #23845 took off PR open.
    • The Guard pins job's selection. It stays where Fede put it, at the bot's green or the arm.
    • The mutation-control sweep, the F01 voice fingerprint and the operator-rename freeze fingerprint. These already run at PR open inside CI Checks (the required Lint context), since #24354 and before it. The early job does not run them a second time.

    Every test the early job runs still runs again in full in the normal lane at the bot's green or the arm, so nothing moves out of that lane. The early job is advisory: it is not a required context and no verdict reads it. GitHub shows its red on the PR, and the review bot reads the failing check, so the author learns about it before the round instead of after.

  4. A selection cap. Above it, the job falls through to the normal path. A diff that names more than 200 candidates runs nothing early. It leaves a ::notice with the count and lets the bot-green run cover it. Measured on the 121 merged PRs before this ADR: median 67 files, p75 118, p90 185, p95 216, max 366. 112 of the 121 PRs (93%) are at or under 200. So the cap drops only the hub-sized diffs (the scripts/lib/-shaped PRs, the 940-control universe of #24354) that could bring back the Oct 30 16:00Z overload. Recall on the sample of reds is in Why 200. The job gets one 8-core spot machine, the same shape as a Unit Tests (selection) shard, and a 20-minute timeout.

  5. One week, then keep it or delete it. The check is due 7 days after the propflowai PR merges, comparing that week with the 7 days before the merge:

    • Keep only if review rounds per merged PR fall by a bigger percentage than CI dollars per day rise.
    • And only if peak concurrent runners (the 16:00Z shape) are no higher than in the week before, and ci.yml runs per PR are no higher (2.1 vs 1.3 was the #23845 trigger).
    • Also read: the early job's own job-minutes per day, the share of PRs that run it versus fall through above the cap, and how many early reds came before the first bot verdict.

    Any one of the gates failing deletes the job.

  6. Rollback is deleting the job, plus the selector and its test. Nothing else reads its output, and no required context or verdict depends on it.

Why 200

The 25 test files named in the red verdicts of the review-rounds sample were replayed against the selector. For each, the test source came from main as it was just before the PR merged, and the diff was the PR's non-test files.

What this cannot catch, stated

Rejected

Build