|
| 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