The runner's capacity was raised to 2. The six CI jobs have no needs: between them, so they already schedule concurrently now — this PR makes that safe and tidy rather than enabling it.
Keep the two memory-heavy jobs apart.verify-stack now needs: [mutation] — not a data dependency, but so Stryker and the full-stack-bring-up + Playwright browser never run at once on the one host and re-trigger the e2e OOM (#126, commit d5e5fa2). if: ${{ !cancelled() }} keeps verify-stack running even when the mutation ratchet fails, so we don't lose its signal, while still honouring cancellation.
Light jobs stay dependency-free (lint / build / unit / frontend) → they parallelise up to runner capacity.
Supersede stale runs via a workflow concurrency group, so a new push cancels the previous run and frees the slot instead of piling up.
Net effect at capacity 2: the light jobs pair up (and overlap mutation), then verify-stack runs alone — shorter wall-clock, no heavy-heavy collision.
CI green — this PR is the test: it exercises needs, if: !cancelled(), and the concurrency group on Gitea. Watch that (a) verify-stack starts only after mutation, (b) verify-stack still runs, (c) the workflow parses (concurrency accepted).
No app/docs/ADR impact (CI-only).
Notes for reviewers
One thing to watch on this first run: if this Gitea version doesn't support the top-level concurrency key, drop that hunk — the needs/if guard is the load-bearing part and is plain job-graph syntax.
Cross-run collisions (two different PRs' verify-stack on the 2-capacity runner) aren't controllable via intra-workflow needs. If that becomes a problem, the clean fix is a second runner (or a dedicated capacity-1 label for the stack job) rather than ordering — out of scope here.
## What & why
The runner's `capacity` was raised to 2. The six CI jobs have no `needs:` between them, so they already schedule concurrently now — this PR makes that safe and tidy rather than enabling it.
- **Keep the two memory-heavy jobs apart.** `verify-stack` now `needs: [mutation]` — not a data dependency, but so Stryker and the full-stack-bring-up + Playwright browser never run at once on the one host and re-trigger the e2e OOM (#126, commit d5e5fa2). `if: ${{ !cancelled() }}` keeps verify-stack running even when the mutation ratchet fails, so we don't lose its signal, while still honouring cancellation.
- **Light jobs stay dependency-free** (lint / build / unit / frontend) → they parallelise up to runner capacity.
- **Supersede stale runs** via a workflow `concurrency` group, so a new push cancels the previous run and frees the slot instead of piling up.
Net effect at capacity 2: the light jobs pair up (and overlap `mutation`), then `verify-stack` runs alone — shorter wall-clock, no heavy-heavy collision.
Closes #127
## Definition of Done
- [x] Linked issue (#127).
- [x] Conventional Commit referencing it.
- [x] CI green — this PR **is** the test: it exercises `needs`, `if: !cancelled()`, and the `concurrency` group on Gitea. Watch that (a) verify-stack starts only after mutation, (b) verify-stack still runs, (c) the workflow parses (concurrency accepted).
- [x] No app/docs/ADR impact (CI-only).
## Notes for reviewers
- **One thing to watch on this first run:** if this Gitea version doesn't support the top-level `concurrency` key, drop that hunk — the `needs`/`if` guard is the load-bearing part and is plain job-graph syntax.
- **Cross-run collisions** (two different PRs' `verify-stack` on the 2-capacity runner) aren't controllable via intra-workflow `needs`. If that becomes a problem, the clean fix is a second runner (or a dedicated capacity-1 label for the stack job) rather than ordering — out of scope here.
The six jobs have no data dependencies, so they now schedule concurrently on the
capacity-2 runner. Guard against the two memory-heavy jobs (mutation + verify-stack)
co-scheduling and re-triggering the e2e OOM (#126): order verify-stack after mutation
via needs, with if: !cancelled() so it still runs when the ratchet fails. Add a
concurrency group so a new push supersedes the previous run instead of wasting a slot.
closes#127
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
The runner's
capacitywas raised to 2. The six CI jobs have noneeds:between them, so they already schedule concurrently now — this PR makes that safe and tidy rather than enabling it.verify-stacknowneeds: [mutation]— not a data dependency, but so Stryker and the full-stack-bring-up + Playwright browser never run at once on the one host and re-trigger the e2e OOM (#126, commitd5e5fa2).if: ${{ !cancelled() }}keeps verify-stack running even when the mutation ratchet fails, so we don't lose its signal, while still honouring cancellation.concurrencygroup, so a new push cancels the previous run and frees the slot instead of piling up.Net effect at capacity 2: the light jobs pair up (and overlap
mutation), thenverify-stackruns alone — shorter wall-clock, no heavy-heavy collision.Closes #127
Definition of Done
needs,if: !cancelled(), and theconcurrencygroup on Gitea. Watch that (a) verify-stack starts only after mutation, (b) verify-stack still runs, (c) the workflow parses (concurrency accepted).Notes for reviewers
concurrencykey, drop that hunk — theneeds/ifguard is the load-bearing part and is plain job-graph syntax.verify-stackon the 2-capacity runner) aren't controllable via intra-workflowneeds. If that becomes a problem, the clean fix is a second runner (or a dedicated capacity-1 label for the stack job) rather than ordering — out of scope here.