Skip to content

Keep pinned PowerShell Gallery module versions current without Dependabot #136

Description

Context and request

.github/workflows/Docs.yml runs the Pester suites through .github/scripts/Invoke-PesterSuite.ps1, which pins Pester to an exact version and verifies its module GUID — the identity-plus-exact posture Dependencies prescribes for a CI pipeline. That standard is equally explicit that the posture only works when the updates are automated: "Tight pinning is safe because the updates are automated." Ours are not. .github/dependabot.yml covers github-actions and pip; Dependabot has no PowerShell Gallery ecosystem, so nothing will ever open a pull request bumping the Pester pin.

The outcome is the failure mode the standard names as "too tight": the pin stops moving, Pester keeps shipping fixes the repository never takes, and the exact pin becomes the thing preventing a fast patch rather than the thing making the build reproducible.

This is not specific to this repository. Every MSX repository that runs PowerShell in CI has the same gap, so the durable answer may belong in the Dependency Updates capability rather than in one workflow.

Acceptance criteria

  • A pinned PowerShell Gallery module version in this repository is raised by an automated, reviewable pull request rather than by someone noticing.
  • The mechanism is written down where a second repository can adopt it, not only configured here.
  • A bump that breaks the suite fails the Test check and is therefore visible before merge.

Non-goals

  • Changing how tightly the pin is set. Exact plus GUID stays; only the movement is in scope.
  • Introducing a general PowerShell dependency manifest for this repository.

Technical decisions

Decided: the first candidate — a scheduled workflow in this repository — with the logic isolated in a parameterised script so promoting it later is a move rather than a rewrite, and the mechanism documented in the Dependency Updates capability so a second repository can adopt it.

Why the other two lost

Candidate Verdict Evidence
Declaring the dependency so an existing updater handles it Ruled out dependabot/dependabot-core has no PowerShell or PowerShell Gallery ecosystem directory — its ecosystems are bundler, cargo, composer, docker, go_modules, gradle, hex, maven, npm_and_yarn, nuget, pub, python, swift, terraform, uv, vcpkg and friends, with nothing for PowerShell. Renovate's lib/modules/datasource likewise has no PowerShell Gallery datasource. Renovate could be coerced — the Gallery is a NuGet v2 OData feed and Renovate's nuget datasource supports v2 with a custom registry — but only through a customManagers regex to find a pin inside a .ps1 parameter default. That is bespoke automation with extra steps, and it adds a second updater alongside the Dependabot this capability's design names as the platform-native one, plus an org-level app install. The cheapest-sounding option is not the cheapest.
A reusable workflow or action in the ecosystem's action library Premature A code search across MSXOrg for RequiredVersion returns exactly one pin: this repository's. PSModule's Invoke-Pester action already solves its own case with NuGet ranges ([6.0.0,7.0.0)) rather than exact pins, so it is not a waiting consumer. GitHub Actions is explicit: "Default to a local action… Do not reach for a separate repo preemptively — the cost of a shared release surface is only worth paying once there is a second consumer." The duplication objection is answered by parameterising the script instead of hardcoding Pester.

Two decisions this issue did not anticipate

The pin stays in Invoke-PesterSuite.ps1; it does not move to a data file. A pin in a script parameter default is no harder for automation to rewrite than one in a data file — both are an anchored regex — and a single-key data file is a dependency manifest in all but name, which the non-goals above forbid. One source of truth also avoids the drift a second file invites. A -PinPattern parameter is what makes the script indifferent to where a pin lives, so a repository that does keep pins in a data file adopts the same script unchanged.

The updater authenticates with the default GITHUB_TOKEN. A pull request opened with it gets its pull_request checks in an approval-required state until someone with write access selects "Approve workflows to run". That is acceptable here precisely because identity-plus-exact is the one track Dependencies says is never auto-merged: a human opens the pull request anyway, and the same visit starts CI. The actions/create-github-app-token upgrade removes the click and is recorded as the upgrade path — this repository has no Actions secrets or variables today, and installing an App needs organization admin.

A risk worth naming: the schedule can lapse silently

GitHub disables scheduled workflows automatically: "In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days" (docs). This repository is public, so the window applies, and a scheduled workflow that has stopped running looks identical to one that ran and found nothing to do.

This is deliberately not answered with a watchdog, whose own liveness would then need watching. Instead it is made legible and its recovery automated:

  • The design section states the lapse, links the warning, and names the evidence that separates the two states — the workflow's run history, which is empty-since rather than succeeded-with-nothing-to-do. Re-enabling is a one-click repository action.
  • A push to main trigger sits alongside the schedule. It does not cover the quiet window — during inactivity there are no pushes either — but it guarantees the first push after a quiet period re-checks the pin, which is exactly when a stale pin starts to matter again, without anyone remembering that the schedule died.

Implementation plan

  • Choose one of the candidates above and record the decision in this issue.
  • Add .github/scripts/Update-GalleryModulePin.ps1 — parameterised by module name, target file, pin pattern, allowed version range, and Gallery URI.
  • Write the test that proves the mechanism opens a pull request when a newer version exists and does nothing when it does not — an in-process HTTP stub, so the proof is deterministic and offline.
  • Add .github/workflows/Update-ModulePin.yml — schedule, manual dispatch, and push to main; opens one labelled, idempotent pull request; no auto-merge.
  • Document it in src/docs/Capabilities/dependency-updates/design.md, stating plainly that the gap is Dependabot's missing ecosystem rather than a choice this organization made.
  • Prove it fires and stays quiet against the real Gallery, and close the demonstration pull request in the same session it is opened.
  • Prove a breaking bump turns the Test check red, rather than assuming it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    MaintenanceInternal maintenance change

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions