Skip to content

Cut the verification pipeline to what a single-owner fleet actually needs #236

Description

@chrisdpurcell

Summary

Measured on the 2026-09-01 v5.28.0 train: hosted Check ran 15 times for 1,299.8 minutes and gated
nothing — 923.5 of those minutes (71%) executed after their own PR had already merged (11 of 13 PRs
were open ≤1 minute; branches are unprotected), and 202.8 minutes were red by design from
pre-R4 reconcile lag. Locally, one --full battery ran ~87.5 minutes and was discarded because an
R6c pin-site edit moved a tests/issue_regressions/ledger.toml proof digest that R6c does not check.

The audience is ~24 repositories owned by one person, most tracking @v5, with no third-party consumers
and no support obligation to historical pins. Every documented install path takes the immutable Git
tag
(README.md:159-163, .pre-commit-hooks.yaml:4, docs/mcp-server.md:47) — no documented consumer
path downloads a release asset.

Restructure the pipeline so each proof runs once, on the final bytes: legs run only what they changed;
one integrated gate on the final integration tree; a delta-only re-check after release prep; hosted CI on
main only. Full analysis, per-gate minutes, and the cut/collapse list: E5-pipeline-min.md (session
scratch). Cross-links #227.

Acceptance criteria

  • check.yml no longer triggers on pull_request; it triggers on push: [main] and
    workflow_dispatch. A release train produces exactly one Check run.
  • Coherence is deleted, with evidence that tests/coherence is inside Check's ordinary selector.
  • Validate standards graph no longer produces by-design reds during a train — either via a
    check-release --staged classification of {PC-RELEASE-LEVEL, PC-RELEASE-PROJECTION, PC-RELEASE-PROJECT-VERSION}, or by restricting the workflow to push: [main].
  • VERIFY_FULL_COMPAT_WORKERS defaults to 16 at scripts/verify.sh:70, with the 21-core-workstation
    rationale removed from the header comment.
  • scripts/verify.sh supports --fail-fast (default on for --full), with --keep-going preserving
    today's run-every-lane behavior; a test proves a red ordinary lane skips the compatibility lane.
  • The R6c fast gate set includes pytest tests/issue_regressions, and the leg brief template requires
    it whenever a proof-bearing test module is edited.
  • The release runbook states the layer that owns each proof, and no proof appears in two layers.
  • A recorded before/after: pre-tag battery wall clock and total train wall clock, measured the same way
    as docs/research/2026-09-01-release-train-wall-clock.md §1a.

Out of scope

Evidence (from the session analysis, preserved here because the scratch file is session-local)

Today's redundancy — 2026-09-01, one release train

Sources: gh run list --limit 400 filtered to createdAt >= 2026-09-01 (118 runs, 01:35Z–13:35Z);
gh pr list (13 PRs); the checkpoint log in .workflow/orchestration-state.md; per-lane seconds from
docs/research/2026-09-01-release-train-wall-clock.md §1a/§1d; per-command seconds from
/tmp/ps528-session/efficiency/E1-cli.md and E2-tests.md.

Gate Times it ran today on identical or near-identical bytes Minutes spent Minutes actually needed Evidence
Hosted Check (statics + ordinary 5,405 + compatibility 103 rows + performance + coverage + pip-audit) 15 runs, every one on a pull_request event; zero on main 1,299.8 0 today — the release-CI run on main happens at R12 and has not run yet gh run list; check.yml:3-6
— of which executed after its own PR had already merged 14 of 15 923.5 (71%) 0 run updatedAt vs PR mergedAt (derived)
— of which were red by design (reconcile lag, pre-R4) 6 (mt116 34.7, pt118 25.7, hyg2 36.9, eff1 36.5, gw19 35.6, docs-528 33.4) 202.8 0 state log 08:51: "FAILED with exactly the 6 reconcile-lag tests … by design until R4"
Compatibility matrix (103 rows), hosted ~9 executions (only the 9 green Check runs reach it; the 6 red ones die at 25–37 min) ~823 (75% of the 1,097.2 green-run minutes) 0 research §1d "1 h 35 m 16 s (75%)"; green-run sum derived
Compatibility matrix, local ≥2 (battery #2 11:13:31–12:41; battery #4 12:42–) ~26–53 each at -n 16 1 execution, on the release commit state log 11:13/12:41/12:42; research §3 lever A
Local scripts/verify.sh --full battery 4 launches, 3 false starts #1 died in 1 s (stale wheel stamp); #2 ran ~87.5 min and was thrown away (ledger digest red); #3 killed after ~1 min; #4 is the real one 1 state log 11:13, 12:41, 12:42
Serial ordinary lane inside --full (5,405 tests, one process) 2 (batteries #2 and #4) 3,031 s = 50.5 min each 0 by the record — no case in 14 trains where serial caught what parallel missed research §1a, §2; verify.sh:312
Ordinary pytest selection (identical 5,405-test selection in fast, full, and hosted modes) ≥18 executions (gate-int 1 + hosted Check 15 + battery ordinary lanes 2) 28 m 28 s hosted per run + 50.5 min serial per battery 1, on the final tree state log 10:56 "pytest 7 failed/5611 passed"; check.yml:104 vs verify.sh:300-321 — same -m selector
Five standards validators + check-release ≥16 local (12 mutation legs per briefs/COMMON.md "standard fast gate set", + orchestrator re-verification at merge, + gate-int, + R6c, + release_prep.py verify_chain twice) and 48 hosted (Validate standards graph 33, Validate project standards 15) local ≈ 36 s × 16 ≈ 10; hosted 37.6 2 — once on the final integration tree, once after the R2–R5 edits COMMON.md; scripts/release_prep.py:695-770; validate-standards-graph.yml:36-48,80; per-command seconds from E1-cli.md (validate 16.98 s, sync-payload-projection 7.11 s, render-catalog 3.22 s, validate-packages 3.14 s, validate-graph 3.07 s)
Validate standards graph, hosted 33 runs, 12 red — it fires on push: [main, testing] and every PR, and is red by construction from the first payload cut until R4 27.8 1 (post-release, on main) gh run list; validate-standards-graph.yml:6
tests/coherence Runs inside every hosted Check (the ordinary selector includes it) and again as its own Coherence workflow, 15 runs 7.0 duplicated 0 extra check.yml:104; coherence.yml:35
Markdown gate (prettier + markdownlint) Per-leg (12) + R6c + every battery statics lane + hosted Format 15 + Lint Markdown 11 20.4 hosted 1–2 gh run list; verify.sh:282-283
tests/package_contract (1,852 tests, 104 modules, ~24 min at -n auto) ≥5 (baseline R2 "1763 passed", R6c "1810 passed remote", gate-int, both battery ordinary lanes, every hosted Check) ~24 min per standalone execution 1–2 state log 07:25, 11:11, 10:56; E2-tests.md collection counts
make go-check (incl. 3 full go build reproducibility rebuilds, unconditional) gate-int + R6c + gw19 sealing + R11 + hosted Go 2 runs 4.1 hosted; local unmeasured 1 state log 09:32, 11:11; E3-build.md item 8 (Makefile go-check:, go-reproducible-build.sh:110-120)
Candidate-wheel build + 137 MiB extraction 12 leg worktrees + R6a + 2 re-stamps after the release-commit amendments 3.1 s compute each (E3 item 9) — but each rebuild re-mtimes 2,879 files, so rsync resends all 137 MiB on the next rexec call 1 per tree that runs a gate E3-build.md items 4 and 9
Hosted CI, all 8 workflows 118 runs 1,373.6 ~40 (one post-release pass on main) gh run list aggregate

Two numbers to carry into the decision.

  1. 1,299.8 hosted Check minutes ran today and none of them gated anything. 923.5 of those minutes
    (71%) executed after the code had already merged — 11 of today's 13 PRs were open for ≤1 minute
    (gh pr list: docs(research): cost out release-train wall-clock levers (#207) #221 test: route every exec_module under tests/ through one guarded loader (#215) #222 chore: hygiene fixes from the security, drift, and review sweeps #223 feat(standards): cut python-tooling 1.18 for the three adoption reports #225 chore: exclude stale build artifacts from rexec sync; reuse the projection plan; align a pin comment #226 test: assert the schema-payload invariant once and share one candidate wheel across pristine-root tests #231 feat(standards): cut github-workflow 1.9 with the ADR 0031 admission classifier #232 at 0 min; fix(command-guard): grant the exact --root . --check invocation form #219 docs(adr): ADR 0031 integration-branch admission model and enforcement #220 fix(markdown-tooling): cut 1.16 to repair the Prettier gate and repin the lint action #224 docs(upgrading): add the 5.28.0 refresh sections for python-tooling 1.18 and markdown-tooling 1.16 #233 at 1 min). The
    orchestrator already knew this and wrote it down: "Merge does not wait for the 32-min check job
    (branches unprotected) — hosted CI is proven on main at release" (state log, admission recipe).
  2. ~87.5 minutes of battery were thrown away for a defect a 20-second suite would have caught. The R6c
    pin-site edit moved the GH-47 proof digest in tests/issue_regressions/ledger.toml; R6c runs
    pytest tests/package_contract (runbook R6c) and never touches tests/issue_regressions, so the red
    first appeared in the battery's ordinary lane and was found at 12:41 — 87.5 minutes after the
    11:13:31 launch (state log). Derived: the ordinary lane finishes ~52 min in (77 s statics +
    3,031 s serial ordinary, research §1a), and serial_lane (verify.sh:257-260, 396-400) records a
    lane's status but never aborts the sequence — so roughly 35 minutes of compatibility matrix ran after
    the battery was already known-red.

Cut / collapse / run once

Saving is per release train unless the row says otherwise. Size is implementation effort.

ID Action Saving per release Size Risk
C1 Stop running hosted Check on pull_request. Change check.yml:3-6 to push: branches: [main] plus workflow_dispatch 1,299.8 min of hosted wall-clock measured today, of which 923.5 min ran post-merge and 202.8 min were red by design. Frees the queue for the seven cheap workflows XS Low. Nothing waits for it — branches are unprotected and 11 of 13 PRs merged in ≤1 min. Residual: a defect that only a fresh-clone runner sees now surfaces on main instead of on a branch. Given a single owner who fast-forwards main from a gated testing, that is the same commit either way
C2 Delete the Coherence workflow; tests/coherence already runs inside Check's ordinary selection 7.0 min hosted; removes one duplicate red surface XS None — verify the selector covers it before deleting
C3 Stop Validate standards graph firing on push: testing and on PRs during a train — it is red by construction from the first payload cut until R4 (12 of 33 runs red today). Either adopt E3-item-6's check-release --staged classification, or restrict the workflow to push: [main] 27.8 min hosted and the agent-attention cost of triaging 12 by-design reds S Low. The staged flag is the better fix: it keeps the signal and removes the noise, instead of removing both
C4 Retire the serial ordinary lane (verify.sh:312), replacing it with a trace-core run of the same selection at -n 16 so the coverage-core cross-check survives ~45 min (3,031 s → ~260 s at -n 16, research §1c) M Medium, and honestly stated: research §2 found no recorded case in 14 trains where serial caught what parallel missed, but the record is written, not instrumented. Replacing rather than deleting keeps the coverage-core claim
C5 Make VERIFY_FULL_COMPAT_WORKERS=16 the default at verify.sh:70 (the literal 4 is tuning for the retired 21-core workstation; the worker has 40 cores / 64 GiB) 25–35 min XS None — research §3 lever A; already proven on this train's battery
C6 Add --fail-fast to verify.sh (default on for release-prep). serial_lane records a red and continues, so today ~35 min of compatibility matrix ran after the battery was already known-red (derived) up to ~50 min on any red battery — and today's battery was red XS Low. You lose "which other lanes were also red" on a red run; keep the current behavior behind --keep-going
C7 Add tests/issue_regressions to the R6c fast gate set and to every leg that edits a proof-bearing test the ~87.5-minute false start that happened today XS None
C8 Compatibility matrix: run the 57 pair rows source-only instead of dual-distribution, and drop the 21 legacy pair rows (E2 items 1–2) ~15 min + ~10 min per battery; ~26 min off any hosted Check M Moderate. Item 1 drops "every pair also converges from a wheel"; item 2 drops legacy pairwise ownership. E2 flags that no one has grepped the battery record for a red only the wheel arm produced — do that first
C9 Mark the historical per-version modules in tests/package_contract (104 modules / 1,852 tests, most asserting frozen bytes already digest-pinned in standards/<family>/standard.toml and re-verified catalog-wide at repository.py:287) with a historical marker; deselect in the fast lane, keep in the battery proportional to the historical fraction of the ~24-min lane M Low in this audience — the owner has stated no support obligation to historical pins. It is still an explicit coverage-tier decision, not a cleanup. E2 asks for a --durations=50 capture first, because the 24 min may be one long-tail test rather than breadth
C10 Demote R10's download-and-sha256sum loop and R11's worktree reproducibility rebuild from per-release to periodic. Keep R10's --verify-tag and the isLatest/isDraft assertion 10–15 min XS Low, and conditional: every documented consumer path installs from the Git tag (README.md:159-163, .pre-commit-hooks.yaml:4, docs/mcp-server.md:47), so nothing consumers use depends on asset bytes. If the owner later documents an asset-download install path, this reverses
C11 Keep the issue-regression ledger, but move its cost to the front (C7) and add a writer — standards ledger amend --issue GH-N --reason … — so the amendment record is generated from symbol_digest, not hand-transcribed the recurring amendment false start (one today; the protocol is in the memory index because it has bitten before) M None to the claim. The ledger is the only mechanism that makes an agent's silent weakening of a regression proof visible; do not drop it in an agent-driven workflow
C12 cut-successor command (E3 item 1) + --apply-pins (E3 item 5) + make release-reconcile / make release-golden (E3 item 7) 20–40 min per payload cut; 10–15 min for R3's 12 line-targeted sed -i edits; 5 min for R4/R5 sequencing L Low–medium. Writers over a surface every existing validator still gates. This is where the 755-line runbook shrinks
C13 One gh-workflow land --pr N transaction (set Workflow=In progress → ready → merge → landing-diff proof) instead of four calls plus a hand-written body per leg ~2–3 min × 13 PRs, and one class of ordering bug — today's mt116 incident reaped a worktree while the PR was still blocked S–M Low. Keep the PR itself: it is the squash boundary and the landing proof. The ceremony worth cutting is the sequencing, not the record

Not recommended. Retiring the compatibility matrix, the five validators, check-release, the golden
re-render, or the per-leg targeted tests. Each of those maps directly onto a way one of the 24 repos gets
a broken reconcile, and none has a cheaper substitute.


Decisions the owner must make

# Decision Recommendation
D1 Does hosted Check run on pull requests at all? No. push: [main] + workflow_dispatch. Measured: 15 runs / 1,299.8 min today, 71% of it post-merge, zero runs gated a merge, six red by design. Keep the seven cheap workflows on PRs — they cost 73.8 min combined and give a signal in under a minute
D2 Is a version no repository pins still a release blocker? (historical package_contract modules, legacy compatibility pair rows) No — mark them historical, keep them in the pre-tag battery, drop them from the fast lane and from PR CI. Take E2's --durations=50 capture first so the cut targets the real cost
D3 Retire the serial ordinary lane? Yes, but replace it, not delete it: same selection, -n 16, trace core. Saves ~45 min and keeps the coverage-core claim that is the lane's second, undocumented-in-the-issue purpose
D4 Are the GitHub release assets a supported install path? Declare "no — install from the tag." Then R10's byte-verify and R11's reproducibility rebuild become periodic instead of per-release. If the answer is yes, say so in README.md and keep both
D5 Where does the integrated proof live — one gate on the final tree, or per-leg gates plus orchestrator re-verification? One gate on the final tree. Legs run only what they changed (Layer 0); the orchestrator stops re-verifying each merge; gate-int on the final integration commit is the single authority. This is the layering change that makes C1–C9 coherent rather than a pile of individual cuts

Uncertainty

Stated so the owner can discount the right rows.

  1. gh run list durations are createdAt→updatedAt wall clock, not billed job time. For Check
    (a single job) they are within seconds of each other; for multi-job workflows they are an upper bound.
    Hosted minutes are free here regardless — the currency is latency and attention.
  2. The ~35 minutes of post-red compatibility matrix in battery build(deps): bump actions/checkout from 6 to 7 #2 is derived, not logged:
    verify-full.log was overwritten by relaunch spec-validate (v4.0.0): ID checker rejects ADR & external-ID references, misparses license tokens; Markdown formatter divergence #3 (it now shows relaunch spec-validate (v4.0.0): ID checker rejects ADR & external-ID references, misparses license tokens; Markdown formatter divergence #3's VERIFY_FULL_EXIT=21,
    a rexec transport code, not a gate result). The derivation is 87.5 min elapsed vs a ~52-minute point
    where the ordinary lane ends, plus the fact that serial_lane does not abort. If the owner wants it
    confirmed, verify.sh would need to timestamp lane starts.
  3. The per-leg validator count (≥16 local invocations) is a floor, read from briefs/COMMON.md's
    mandated gate set and the checkpoint log's merge entries. I did not have per-leg command transcripts,
    so the local minutes column is an estimate built from E1-cli.md's measured per-command seconds.
  4. C4 (serial lane) rests on an absence of evidence. Research §2 searched three sources and found no
    recorded case; a divergence noticed and quietly fixed inside a session would not appear. Replacing the
    lane rather than deleting it is what makes that acceptable.
  5. C8's risk is not fully assessed. E2 explicitly did not grep the battery record for a red that only
    the wheel arm of the compatibility matrix produced. Do that before cutting distribution parity.
  6. C10 depends on a policy statement that does not exist yet. I read the install paths; I did not find
    a document that says the release assets are unsupported. D4 is the decision that makes C10 safe.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions