Skip to content

Commit f27d6c8

Browse files
authored
Merge pull request #265 from cipherstash/ci/pr-feedback-sharding
perf(ci): shard PR tests with nextest + merge-queue gate
2 parents 4ba2cfc + f5ef641 commit f27d6c8

6 files changed

Lines changed: 519 additions & 135 deletions

File tree

.github/workflows/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# CI: `test-eql.yml`
2+
3+
Fast PR feedback + a thorough pre-merge gate, using a merge queue and a single
4+
aggregated required check.
5+
6+
## Two run shapes
7+
8+
`test-eql.yml` triggers on `pull_request`, `merge_group`, and `workflow_dispatch`.
9+
`setup` derives the matrix from the event:
10+
11+
| Event | Trigger | Matrix | Purpose |
12+
|---|---|---|---|
13+
| `pull_request` | push to a PR | PG17 × 4 shards | fast developer feedback |
14+
| `merge_group` | "Merge when ready" → queued | PG14–17 × 2 shards | full pre-merge gate on the real merged state |
15+
| `workflow_dispatch` | manual run | PG17 × 4 shards (PR shape) | ad-hoc |
16+
17+
The PR run is feedback only. The merge-queue run is the gate.
18+
19+
## Relevance skip applies to PRs only
20+
21+
Each job runs when:
22+
`merge_group || workflow_dispatch || (pull_request && relevant == 'true')`.
23+
24+
So the `changes` relevance filter (`relevant:` paths) **only gates the
25+
`pull_request` event** — a docs-only PR skips the heavy jobs on its PR run. On
26+
`merge_group` (and `workflow_dispatch`) every job runs **unconditionally**: a
27+
queued PR always pays the full gate regardless of which files it touched.
28+
29+
## How the queue works
30+
31+
1. Click **Merge when ready** — the PR is queued, not merged.
32+
2. GitHub builds a temporary branch = `main` + this PR (+ any PRs ahead in the
33+
queue) and fires `merge_group`, so CI tests the **post-merge state**, not the
34+
stale PR branch.
35+
3. The full PG14–17 × 2 matrix (plus the single-run jobs) runs and feeds
36+
`ci-required`.
37+
4. `ci-required` green → the PR is **merged into `main` using the queue's
38+
configured merge method**. Red → the PR is **removed from the queue**; `main`
39+
is untouched.
40+
41+
This catches semantic conflicts — two PRs that each pass alone but break
42+
together — which PR-only checks never test.
43+
44+
## The `ci-required` aggregator
45+
46+
Per-event matrices make leaf job names unstable (a `test` job is displayed as
47+
`Shard PG17 1/4`, but the queue produces `Shard PG14 1/2``Shard PG17 2/2`),
48+
so leaf names can't be named as required checks. Instead, one aggregator job
49+
(id `ci-required`, **display name `CI required`**) `needs:` every job, runs with
50+
`if: always()`, and passes only if each needed result is `success` **or**
51+
`skipped`. Mark **only `CI required`** as the required status check.
52+
53+
- `if: always()` — runs even when dependencies fail/skip, so the check always
54+
reports (a never-reported required check leaves the queue stuck *Pending*).
55+
- `skipped` counts as pass — a docs-only PR skips the heavy jobs on its PR run
56+
but must still report Success so the PR stays eligible to queue.
57+
58+
This is the well-known "aggregate / final gate job" pattern for matrix +
59+
merge-queue workflows.
60+
61+
## Operator setup (one-time, GitHub UI)
62+
63+
Settings → Branches → rule for `main`:
64+
65+
1. **Require merge queue.**
66+
2. **Require status checks to pass** → add **`CI required` only** (the display
67+
name; not the per-shard leaf names).
68+
69+
Then verify (see `docs/plans/2026-06-09-ci-pr-feedback-sharding-rollout.md`):
70+
71+
- **Queue a relevant PR**`merge_group` runs the full gate — 8 `Shard …` jobs
72+
+ 4 `Validate …` jobs + `build-archive`, `schema`, `rust-crates`, `codegen`,
73+
`self-contained-v3`, `matrix-coverage`, `splinter` — all green → `CI required`
74+
green → PR merges.
75+
- **Open a docs-only PR** → on its `pull_request` run the heavy jobs skip and
76+
`CI required` reports **Success** (not stuck *Pending*), so the PR can be
77+
queued.
78+
79+
## References
80+
81+
- Merge queue: <https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue>
82+
- `merge_group` event: <https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#merge_group>
83+
- Required status checks: <https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging>
84+
- `needs` / `always()` / `join()`: <https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions>
85+
- Path filtering (`dorny/paths-filter`): <https://github.com/dorny/paths-filter>
86+
- nextest archive + partitioning: <https://nexte.st/docs/ci-features/archiving/> · <https://nexte.st/docs/ci-features/partitioning/>

0 commit comments

Comments
 (0)