ci: matrix over Node 22 and 24, add the bench and packaging tiers (task 0040) #49

Merged
alexion merged 1 commits from task-0040-ci-node-matrix-and-new-tiers into main 2026-07-20 07:30:29 -04:00
Owner

Task: .claude/tasks/0040-ci-node-matrix-and-new-tiers.md

Summary

Continuous integration moves off end-of-life Node 20, matrixes over the two supported long-term-support majors, and picks up the two tiers it never ran.

  • The test job matrixes over Node 22 and 24 (fail-fast: false), with the container image taken from the matrix.
  • The manifest's engine range narrows from >=20 to ^22 || ^24, naming exactly the majors the matrix tests — a bare >=22 would have re-promised the odd-numbered, already end-of-life 23. The packaging tier's engine assertion moves with it.
  • The benchmark harness tier runs on every leg, under its own runner configuration. It is deterministic and needs no network, and that non-default configuration made it an easy tier to believe was running when it was not.
  • The end-to-end and packaging tiers run on the highest leg only.
  • The benchmark smoke tier stays out — it would pass by skipping.

The workflow keeps its container-and-npm shape and its GitHub Actions compatibility; nothing migrates to building via Nix (that is task 0041).

Deviations

One matrix job, not a split. The once-only tiers are gated with a step-level if: rather than moved into a separate single-version job. The criteria are phrased in terms of matrix legs, one job keeps the version list in exactly one place, and the task asked that the workflow keep its shape. The cost is an idle Gitea service container on the Node 22 leg — cheap next to running the end-to-end tier twice, which is what the rationale was actually guarding against.

matrix.highest, not matrix.node == '24'. The flag is attached to the 24 leg via a strategy.matrix.include entry, so the two once-only steps share one named condition. An include entry whose keys match an existing combination augments that leg rather than adding a new one, on both platforms, and an undefined context property is falsy on the other leg.

Two changes beyond the acceptance criteria, both surfaced by review: @types/node moved ^20.19.0^22.20.1 (the last Node 20 reference in the manifest, and the typecheck runs on every leg — it tracks the floor, since typings for 24 would let source compile against APIs Node 22 lacks), and .claude/spec/gitea-axi.md's "Language and Runtime" section no longer reads "TypeScript on Node 20+", which this change falsified.

Verified locally through nix develop: typecheck, the fast tier with coverage thresholds (410 tests), the benchmark harness tier (117), the full packaging tier (7), and nix build .#gitea-axi after the lockfile moved. The workflow was parsed with yq to confirm the matrix, the container expression, and both if: conditions land where intended. The live end-to-end tier was not run locally — it needs a disposable Gitea instance, and this diff only gates it.

Review

Risk

Overall: MEDIUM

  • Blast radius: Medium — CI workflow, published manifest engines, and a packaging assertion move together; the narrowing propagates to every consumer's install.
  • Reversibility: Low — pure config revert; nothing published, migrated, or deleted.
  • Test coverage: Low — test/packaging/tarball.test.ts asserts the new range directly, and the matrix is the guard for the majors named.
  • Sensitive domain: Low — no auth, permissions, concurrency, or data paths touched.
  • Size & complexity: Low — ~40 lines of declarative YAML plus a one-line manifest edit.
  • Runtime criticality: Medium — engines is a runtime-facing contract on the shipped package.

Unaddressed findings

Standards — vitest.bench.config.ts sets passWithNoTests: true. If its include glob ever broke, the new benchmark step would go green while running nothing — precisely the failure mode this task cites as the reason to add the step. Pre-existing and not introduced here; left alone rather than widening the change into a test-configuration one.

Standards — the matrix names "24" twice. The include entry must be kept in sync with the last element of the node array by hand. Inherent to the include-augments-a-leg idiom; a comment on the entry says so, and the alternative (repeating the version at each if: site) is worse.

Spec: no findings — all eight acceptance criteria verified individually.

Task: [`.claude/tasks/0040-ci-node-matrix-and-new-tiers.md`](.claude/tasks/0040-ci-node-matrix-and-new-tiers.md) ## Summary Continuous integration moves off end-of-life Node 20, matrixes over the two supported long-term-support majors, and picks up the two tiers it never ran. - The `test` job matrixes over Node **22** and **24** (`fail-fast: false`), with the container image taken from the matrix. - The manifest's engine range narrows from `>=20` to `^22 || ^24`, naming exactly the majors the matrix tests — a bare `>=22` would have re-promised the odd-numbered, already end-of-life 23. The packaging tier's engine assertion moves with it. - The **benchmark harness tier** runs on every leg, under its own runner configuration. It is deterministic and needs no network, and that non-default configuration made it an easy tier to believe was running when it was not. - The **end-to-end** and **packaging** tiers run on the highest leg only. - The **benchmark smoke tier** stays out — it would pass by skipping. The workflow keeps its container-and-npm shape and its GitHub Actions compatibility; nothing migrates to building via Nix (that is task 0041). ## Deviations **One matrix job, not a split.** The once-only tiers are gated with a step-level `if:` rather than moved into a separate single-version job. The criteria are phrased in terms of matrix legs, one job keeps the version list in exactly one place, and the task asked that the workflow keep its shape. The cost is an idle Gitea service container on the Node 22 leg — cheap next to running the end-to-end tier twice, which is what the rationale was actually guarding against. **`matrix.highest`, not `matrix.node == '24'`.** The flag is attached to the `24` leg via a `strategy.matrix.include` entry, so the two once-only steps share one named condition. An `include` entry whose keys match an existing combination augments that leg rather than adding a new one, on both platforms, and an undefined context property is falsy on the other leg. **Two changes beyond the acceptance criteria**, both surfaced by review: `@types/node` moved `^20.19.0` → `^22.20.1` (the last Node 20 reference in the manifest, and the typecheck runs on every leg — it tracks the *floor*, since typings for 24 would let source compile against APIs Node 22 lacks), and `.claude/spec/gitea-axi.md`'s "Language and Runtime" section no longer reads "TypeScript on Node 20+", which this change falsified. Verified locally through `nix develop`: typecheck, the fast tier with coverage thresholds (410 tests), the benchmark harness tier (117), the full packaging tier (7), and `nix build .#gitea-axi` after the lockfile moved. The workflow was parsed with `yq` to confirm the matrix, the container expression, and both `if:` conditions land where intended. The live end-to-end tier was not run locally — it needs a disposable Gitea instance, and this diff only gates it. ## Review ### Risk **Overall: MEDIUM** - Blast radius: Medium — CI workflow, published manifest `engines`, and a packaging assertion move together; the narrowing propagates to every consumer's install. - Reversibility: Low — pure config revert; nothing published, migrated, or deleted. - Test coverage: Low — `test/packaging/tarball.test.ts` asserts the new range directly, and the matrix is the guard for the majors named. - Sensitive domain: Low — no auth, permissions, concurrency, or data paths touched. - Size & complexity: Low — ~40 lines of declarative YAML plus a one-line manifest edit. - Runtime criticality: Medium — `engines` is a runtime-facing contract on the shipped package. ### Unaddressed findings **Standards — `vitest.bench.config.ts` sets `passWithNoTests: true`.** If its include glob ever broke, the new benchmark step would go green while running nothing — precisely the failure mode this task cites as the reason to add the step. Pre-existing and not introduced here; left alone rather than widening the change into a test-configuration one. **Standards — the matrix names `"24"` twice.** The `include` entry must be kept in sync with the last element of the `node` array by hand. Inherent to the `include`-augments-a-leg idiom; a comment on the entry says so, and the alternative (repeating the version at each `if:` site) is worse. Spec: no findings — all eight acceptance criteria verified individually.
alexion added 1 commit 2026-07-19 23:46:25 -04:00
ci: matrix over Node 22 and 24, add the bench and packaging tiers (task 0040)
All checks were successful
CI / test (22) (pull_request) Successful in 1m8s
CI / test (true, 24) (pull_request) Successful in 1m12s
CI / test (22) (push) Successful in 47s
CI / test (true, 24) (push) Successful in 1m4s
ccc8dbe998
The workflow pinned Node 20, which reached end-of-life in April 2026, while
the manifest promised support down to it — so the entire claimed range below
the single tested version went unverified and its floor was unsupported.

Matrix over the two supported long-term-support majors and narrow the engine
range to `^22 || ^24`, naming exactly what is tested. Narrowing is free now
because nothing has been published and no tags exist.

The benchmark harness tier joins every leg: it is deterministic and needs no
network, and its non-default runner configuration made it easy to believe it
was running when it was not. The end-to-end and packaging tiers run on the
highest leg only, conditioned on a flag attached to that leg through a matrix
`include` entry rather than on a version number restated at each site. The
benchmark smoke tier stays out, since it would pass by skipping.

`@types/node` follows the new floor; it was the last Node 20 reference in the
manifest, and the typecheck runs on every leg.
alexion merged commit ccc8dbe998 into main 2026-07-20 07:30:29 -04:00
alexion deleted branch task-0040-ci-node-matrix-and-new-tiers 2026-07-20 07:30:29 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/gitea-axi#49