ci: pin Pester by exact version everywhere it is installed or imported

Pester was installed with -MinimumVersion 5.0 and no ceiling in the CI job
image, both install steps in unit-tests.yml, and both Import-Module calls, plus
the suite's own import inside the container. The image is rebuilt on every CI
run and Pester is installed fresh on every unit-test run, so PSGallery chose the
version — a new major could reach the required PR checks with no commit here,
surfacing as unexplained test breakage on whichever PR ran next.

It had already happened. Steps named "Install Pester 5" were resolving 6.1.0 on
both legs, because Pester 6 declares PowerShellVersion 5.1 and so installs on
Windows PowerShell too. Nothing broke — the suite uses only constructs common to
5 and 6, and runs 1566/0 under 6.1.0 with no deprecation warnings — but nobody
chose it. The step names are corrected; they had been describing an install that
stopped happening some time ago.

Pinning the install alone is not enough, in two ways review found:

An unset variable does not fail. -RequiredVersion accepts an empty value and
degrades to "latest" for Install-Module and to "any" for Import-Module, both
exiting 0, so a renamed or dropped env key would silently restore the float this
commit removes. A guard step now fails the job instead.

The point of use was still floored. run-integration.sh imported the suite's
Pester with -MinimumVersion 5.0, so a second Pester reaching PSModulePath would
win regardless of what was installed. The Dockerfile now promotes the ARG to ENV
so the version is discoverable at runtime, and that import is pinned to it.

The pin lives in two files, so shell-selfchecks asserts they agree — split-brain
between the workflow and the image is precisely the unexplained breakage this is
meant to prevent. CONTRIBUTING.md and CLAUDE.md are updated too; the contributor
instructions were a third floating install site.

Recorded as an amendment to D017 — the same principle as the nested PVE package
pin, applied to the lane's own tooling.
This commit is contained in:
goodolclint-claude[bot]
2026-09-01 17:55:54 -05:00
parent 8623118557
commit fc073e7e2a
6 changed files with 74 additions and 12 deletions
+21
View File
@@ -634,6 +634,27 @@ still fails the job. `run-integration.sh` returns 3 for a genuine test failure a
reach or authenticate to a node; only 3 is suppressed. Suppressing both would let a botched reboot
report success while the lane learned nothing.
### Amendment 2026-09-01 — the pin covers the test tooling, not just the nested PVE packages
The gating lane's own tooling is pinned by exact version for the same reason its nested PVE packages
are: `Pester` in `tests/Dockerfile.test` (`ARG PESTER_VERSION`) and in `.github/workflows/unit-tests.yml`
(`env.PESTER_VERSION`), installed and imported with `-RequiredVersion` at every site, including the
suite's own import inside the container. The Dockerfile promotes the ARG to `ENV` so the version is
discoverable at runtime. Both files must name the same version, and `shell-selfchecks` asserts it.
Before this, both sites used `-MinimumVersion 5.0` with no ceiling. The image is rebuilt on every CI
run and Pester is installed fresh on every unit-test run, so PSGallery decided the version — a new
major could reach the merge gate with no commit to this repository, surfacing as unexplained test
breakage on whichever PR happened to run next. That is the same class of moving input the lane split
exists to eliminate; the difference is only that it moves in the test runner rather than in PVE.
It had already happened silently: steps named "Install Pester 5" were resolving 6.1.0 on both the
PowerShell 5.1 and 7.x legs, because Pester 6 declares `PowerShellVersion 5.1` and so installs on
Windows PowerShell too. Nothing broke — the suite uses only constructs common to 5 and 6 — but
nobody chose it.
Bumping is a deliberate commit that changes both files together.
### Anti-pattern (do not reintroduce)
```bash
# NEVER in first-boot.sh — this is the mismatch that left a node unclustered