ci: put a timeout on every job, cancel superseded PR runs, lint the protocol

Lane 3a of the testing arc --- the three cheap, deterministic items of
`TEST_IMPROVEMENT.md` §5-6. The larger ones (nextest, the
serial/parallel split, a parallel canary leg, the nightly cron, the
macOS matrix trim) are deliberately NOT here: each changes what CI
certifies or how it runs, and each deserves its own decision rather
than riding in on a timeout patch.

`timeout-minutes` on every job (§5.2). Measured before changing rather
than assumed: SEVEN of eight jobs had none and inherited GitHub's
360-minute default; only `m6-perf-gates` had one, at 15. So a single
hung test burnt six hours --- times four on the test matrix --- and
reported nothing useful at the end of it. Set to 25 against a measured
~14.6 min critical path (macOS/luajit), which leaves ample headroom for
a slow runner while catching a hang in under half an hour.

This is the gate that has to exist before `PMACS_REQUIRE_PYRIGHT` can
ever be set. Lane 2 left basedpyright unarmed *because* this did not
exist; the two decisions are the same decision, half a lane apart.

`concurrency` with `cancel-in-progress` (§6.1), scoped to pull
requests. This project rebases heavily --- the ledger re-conflicts on
nearly every merge --- so branches take several pushes while earlier
runs are still going, and macOS minutes are both the expensive ones and
the critical path. Pushes to `main` are deliberately exempt:
`github.event.pull_request.number` is empty there, so the fallback keys
those runs by SHA and none can cancel another. Cancelling a `main` run
would leave the branch-protection record ambiguous about a commit that
has already landed, which is the one place the saving is not worth
having.

`-p pmacs-protocol` clippy (§5.7). The root-package clippy never
covered it --- the workspace default member is only `pmacs` --- so a
warning introduced through a protocol-only change would reach `main`
unseen. Verified passing locally BEFORE proposing it, so it cannot turn
CI red on arrival.

The timeout rationale is stated once above the job list rather than
copied onto each job: the first draft duplicated a seven-line comment
across seven jobs, which is the same degraded-copy shape this arc keeps
removing elsewhere.

Verified: YAML parses; all eight jobs carry a timeout (seven at 25,
m6-perf-gates keeping its tighter 15); `cargo fmt --all --check`,
`clippy -p pmacs-protocol` and `clippy -p pmacs-gpu` all exit 0;
`git diff --check` clean. The diff touches `ci.yml` and the ledger and
nothing else, so no code gate is affected.
This commit is contained in:
Levi Neuwirth 2026-07-29 13:05:56 -04:00
parent b7bf2c6644
commit 708054e8d5
2 changed files with 80 additions and 0 deletions

View File

@ -9,10 +9,39 @@ env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
# Cancel a pull request's superseded runs instead of letting them burn
# to completion. This project rebases heavily — the ledger re-conflicts
# on nearly every merge — so a branch routinely takes several pushes
# while an earlier run is still going, and each of those runs is
# obsolete the moment the next push lands. macOS minutes are the
# expensive ones and the macOS leg is the critical path, so superseded
# runs are exactly where the waste concentrates.
#
# Scoped to pull requests deliberately. `github.event.pull_request.number`
# is empty for a push to `main`, so the fallback keys those runs by SHA:
# every `main` commit gets its own group and none can cancel another.
# Cancelling a `main` run would leave the branch-protection record
# ambiguous about a commit that has already landed — the one place this
# saving is not worth having.
concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
# Every job carries `timeout-minutes`. Without one a job inherits
# GitHub's 360-minute default, so a single hung test burns six hours —
# times four on the test matrix — and reports nothing useful at the end
# of it. The measured critical path is ~14.6 min (macOS/luajit), so 25
# leaves ample headroom for a slow runner while catching a hang in
# under half an hour. `m6-perf-gates` keeps its tighter 15.
#
# This is also the gate that has to exist before the basedpyright-class
# hang can ever be armed — see `PMACS_REQUIRE_PYRIGHT`, deliberately
# never set, in the test job below.
jobs:
fmt:
name: Format
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@ -23,6 +52,7 @@ jobs:
clippy:
name: Lint (${{ matrix.lua }})
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
@ -40,10 +70,17 @@ jobs:
# not pmacs), so the root-package clippy above never lints it. Lint
# it explicitly or its warnings slip through CI (audit F-001).
- run: cargo clippy -p pmacs-gpu --all-targets -- -D warnings
# pmacs-protocol is likewise never linted by the root-package
# clippy above: the workspace default member is only `pmacs`. The
# local `--workspace` gate covers it, so it passes today — CI has
# simply never checked, and a warning introduced through a
# protocol-only PR would reach `main` unseen.
- run: cargo clippy -p pmacs-protocol --all-targets -- -D warnings
gpu-render:
name: GPU Render (headless)
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@ -76,6 +113,7 @@ jobs:
test:
name: Test (${{ matrix.os }} / ${{ matrix.lua }})
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
@ -160,6 +198,7 @@ jobs:
acceptance:
name: M1 Acceptance Gates
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@ -170,6 +209,7 @@ jobs:
m4-perf-gates:
name: M4 Perf Gates
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
@ -188,6 +228,7 @@ jobs:
m5-perf-gates:
name: M5 Perf Gates
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable

View File

@ -820,6 +820,45 @@ has **no branch and no framing yet**.
current documentation. The section said "whoever confirms the branch
carries nothing unique removes the section"; this is that.
## Test-improvement arc, lane 3a — CI timeouts and concurrency
- Portable branch: `githubsucks/ci-timeouts-concurrency`, worktree
`../pmacs-ci3`. Workflow only — **no product code, no tests changed.**
- **Base, measured at write time:**
```
$ git log --oneline -1 githubsucks/main
b7bf2c6 Merge pull request #194 from levineuwirth/silent-skip-arming
```
- Ships the three cheap, deterministic items of `TEST_IMPROVEMENT.md`
§5-6. The larger ones — nextest (§6.3), the serial/parallel split
(§6.2), the parallel canary leg (§5.6), the nightly cron (§5.5), and
the macOS matrix trim (§6.4) — are **deliberately not here**: each
changes what CI certifies or how it runs, and each wants its own
decision rather than riding a timeout patch.
- **`timeout-minutes` on every job (§5.2).** Measured before changing:
**7 of 8 jobs had none** and inherited GitHub's 360-minute default;
only `m6-perf-gates` had one (15). A single hung test therefore burnt
six hours, times four on the test matrix. Set to 25 against a
measured ~14.6 min critical path (macOS/luajit).
**This is the gate that must land before `PMACS_REQUIRE_PYRIGHT` can
ever be set** — lane 2 left basedpyright unarmed precisely because
this did not exist.
- **`concurrency` with `cancel-in-progress` (§6.1)**, scoped to pull
requests. `github.event.pull_request.number` is empty on a push to
`main`, so the fallback keys those by SHA and no `main` run can
cancel another — cancelling one would leave the branch-protection
record ambiguous about a commit that already landed.
- **`-p pmacs-protocol` clippy (§5.7).** Verified passing locally
*before* proposing it, so adding it cannot turn CI red on arrival.
The root-package clippy never covered it: the workspace default
member is only `pmacs`.
- Recovery from a clean checkout:
`git fetch githubsucks && git worktree add ../pmacs-ci3
-b ci-timeouts-concurrency githubsucks/ci-timeouts-concurrency`.
## Parked lane: kill-ring browser + persistence
- Portable branch: `githubsucks/kill-ring-browser`