Versions and upgrades¶
This site documents engine v6.4.0.
Every version number below is the one the platform pins today. When an upgrade lands, update this page first.
Five families of pins carry versions in a customer repository: the engine, the toolchain, the AVM modules, the provider lock files, and the ALZ policy libraries. Each one upgrades the same way, through a pull request that plans before it applies. One more pin lives outside the repository, in the bootstrap: the self-hosted runner image.
Engine pins¶
The engine is consumed through four caller workflows in
.github/workflows/.
| File | Pin | engine_ref input |
|---|---|---|
tf-pr-ops-pr.yml |
uses: nrit-solutions/tf-pr-ops/.github/actions/dispatch@v6.4.0 |
none (the action checks the core out at the commit its ref was published from and installs the release's prebuilt binary, or builds from that checkout) |
tf-pr-ops-unlock.yml |
uses: nrit-solutions/tf-pr-ops/.github/actions/dispatch@v6.4.0 |
none (same action; the closed-event unlock caller) |
tf-pr-ops.yml |
uses: nrit-solutions/tf-pr-ops/.github/workflows/terraform-pr-ops.yml@v6.4.0 |
v6.4.0 |
drift.yml |
uses: nrit-solutions/tf-pr-ops/.github/workflows/drift.yml@v6.4.0 |
v6.4.0 |
In the two reusable-workflow callers the pins do different jobs: uses:
picks the workflow YAML that GitHub runs, engine_ref picks the engine code
the workflow checks out at run time. They must hold the same exact tag and
move together in the same commit, or new YAML runs old code; the resolve
job logs the requested engine_ref and the commit it checked out, so a skew
is visible at the top of every run. The PR and unlock callers carry one pin
each: the dispatch composite action knows its own ref. Move every pin
together.
Since v1.12.0, a growing part of the engine runs as a binary (tfpr) built
inside the run from that same pinned checkout: project discovery (v1.12.0),
the run report renderer (v1.13.0), the pull-request comment surface with
the moved-head verification (v1.14.0), the per-project check runs on
comment-triggered plans and applies (v1.15.0), the merge-gate and
approval commit statuses (v1.16.0), and command authorization, the apply
approval gate, and drift-issue reporting (v1.17.0), at which point every
GitHub call the engine makes is compiled. v2.0.0 closes the port: the
cross-PR unit lock runs compiled, a pull request plan runs as a thin
dispatcher so every per-unit check on the PR is engine-named
(<command> / <label> #<run> on all triggers, the form of that era), and the
last shell fallbacks are retired. The major carries two caller edits, both in the canonical caller:
the permissions ceiling gains actions: write, and workflow_dispatch
declares and forwards four engine-set dispatch inputs. The merge-gate and
approval status contexts are unchanged. v2.1.0 adds the dedicated
checks App: with TFPR_CHECKS_APP_CLIENT_ID and
TFPR_CHECKS_APP_PRIVATE_KEY configured, the engine's checks are created
with that App's token and render under its heading (tf-pr-ops). v3.0.x
is the v3 PR surface: the pull_request path becomes the dispatch composite
action in its own caller (tf-pr-ops-pr.yml, with the repository's
pre-commit hooks as the gating first job), the merge gate and approval move
from commit statuses to App check runs with unchanged names, per-unit
checks read tf-pr-ops / plan (<label>) #<run>, the five TF_PR_OPS_*
consumer variables rename to TFPR_*, and the checks App graduates from
optional to part of onboarding. v4.0.0 renames the engine App credentials
to ENGINE_APP_CLIENT_ID and ENGINE_APP_PRIVATE_KEY (the previous names
are in the v4.0.0 release notes) and drops the git credential rewrite for
private module sources, which no unit uses; the bootstrap sets the new
names. v5.0.0 moves every uses: line to the public entrypoint
repository nrit-solutions/tf-pr-ops, which the engine's release job writes
from the private core under the same tag; the core stays private and is
checked out by the jobs with the engine App token, so a landing-zone
repository in any organization can run the engine. The variables and secrets
do not change. v5.1.0 counts imports as changes, so /apply lands an
import-only plan and the per-unit check titles carry the import count
(Plan: 1 to import, 0 to add, 0 to change, 0 to destroy, Resources: 1
imported, 0 added, 0 changed, 0 destroyed), and the apply job receives
INFRACOST_API_KEY, so the cost gate runs on apply. No caller change. v6.0.0
is the pipeline speed release. The steps: block in projects.yml gains a
plan stage and an apply stage, and apply runs no hooks unless its own stage
lists them: the plan run already gated the plan, and the reviewed-plan guard
refuses an apply whose plan differs, so the apply report carries a one-line
note instead of the gate sections (the flat steps: form keeps working and
means the plan stage only). A push starts the plan directly at the plan job
instead of through a resolve job, the runner restores its tool installs and
Terraform providers from the Actions cache, and the merge-gate job runs
without checkouts. Two caller edits, both in the canonical callers: the ops
caller declares and forwards a context workflow_dispatch input, and its
actions: read permission goes. v6.0.1 fixes the provider cache on
self-hosted runners, whose temp path carries the runner instance name; the
cache now lives under a path that is the same on every instance. v6.1.0
links each per-unit check to the job that ran the unit, so its "View more
details" opens the live log instead of the run overview. v6.2.0 fences a
pull request's plan behind its running apply: the plan and apply jobs of
one PR share a concurrency group, so a push mid-apply queues its plan until
the apply finishes, and the new head's plan checks open as "Waiting for
apply #N on
Exact versions are the contract¶
vX.Y.Z tags are immutable and callers pin one exactly. There is no moving v1
tag. An earlier policy offered one and it is retired, because it meant a customer
could not tell from the file which engine they were running, and a repository
could change behavior with no commit. Nothing arrives automatically, so an
upgrade is always a change you can see, review, and revert.
How to upgrade the engine¶
- Read the release notes for the target tag. They call out anything a consumer must change.
- Bump all six pins in the four caller files, in one commit:
uses: ...@vX.Y.Zeverywhere, andengine_ref: vX.Y.Zin the two reusable-workflow callers. - Open a pull request.
-
Smoke test the new engine. A pin bump on its own changes no Terraform, so the pull request plans nothing and proves nothing. Since v2.0.0, no pre-merge path plans on the new pin at all: the dispatch action hands the plan work to the ops caller that is already on main, and the
/plancomment path runs from main too, so both silently test the old engine. So:- Merge the bump first, then dispatch
drift.yml, which plans every unit on the new pin. - Or, after merging, open a throwaway pull request with a change under a unit and let it plan; close it unmerged.
- Merge the bump first, then dispatch
-
Get the required reviews, comment
/applyif the change needs one, and merge.
Roll back by reverting the pin commit.
See The PR-ops engine for the full caller files.
Toolchain pins¶
The whole toolchain is pinned in mise.toml at the repository root: the two
tools the engine runs, and the three the commit hooks and the PR caller's
pre-commit job run. The setup-tools action detects the version manager and installs those
versions, so a bump is a one-line change with no workflow edit.
[tools]
terragrunt = "1.0.7"
terraform = "1.15.5"
pre-commit = "4.6.1"
tflint = "0.64.0"
checkov = "3.3.8"
live/root.hcl also sets floor constraints: Terraform >= 1.12 and Terragrunt
>= 1.0. Keep the mise.toml versions at or above those floors.
Runner image pin¶
The self-hosted runner image is ghcr.io/nrit-solutions/alz-runner at
1.0.0, digest
sha256:e04c94f28972ce94742ba9402b3fcf250d22f377018b463e5a5ef9069762329a.
It is pinned per customer in the bootstrap tfvars (runner_image_version
plus runner_image_digest, always from the same release) and imported into
an in-tenant registry by digest, so the pin states exactly which signed
artifact a customer's runners execute. The image carries the gate tools
(checkov 3.3.8, conftest 0.68.2) and the GitHub Actions runner
(2.336.0); it deliberately carries no Terraform, Terragrunt, or engine
binary, because the mise.toml and engine pins above own those. Upgrading
is a two-value tfvars change and a targeted bootstrap apply; see
Bootstrap.
Provider lock files¶
Provider versions are settled by the committed .terraform.lock.hcl in each
unit, not by the ~> constraints in the generated providers.tf. Terragrunt
copies the lock file into its run directory and back out again, so it is the
file that decides what plan and apply use, and both HashiCorp and Terragrunt say
to commit it. Without one, every run re-resolves and /apply can run a
different provider version than the plan that was reviewed.
A new unit ships its own lock file, from
terragrunt --working-dir live/<unit> init -backend=false followed by
TG_NO_AUTO_INIT=true terragrunt --working-dir live/<unit> run -- providers lock -platform=darwin_arm64 -platform=linux_amd64.
The second command records the linux_amd64 hashes the runner needs: since
v6.0.0 the engine verifies the cached provider packages against the lock
file, and a lock file without the runner's platform makes the cache miss on
every job. To move a provider version, run init again with -upgrade, then
the same providers lock command, and commit the diff as its own pull
request. Never delete a lock file to make an error go away.
A repository freshly stamped from the template starts with none, on purpose: a lock file records the versions resolved at the moment it is written, so pre-generated ones would start every customer on whatever resolved the day the template was last touched. Onboarding generates them once, against that customer's own tree, and the pre-commit invariants check warns, without blocking, until it has.
AVM module versions¶
Each foundation unit's main.tf sources an Azure Verified Module with an explicit
version argument.
| Module | Version | Unit |
|---|---|---|
Azure/avm-ptn-alz-management/azurerm |
0.9.0 |
management-resources |
Azure/avm-ptn-alz/azurerm |
0.21.0 |
landing-zones |
Azure/avm-ptn-monitoring-amba-alz/azurerm |
0.4.0 |
amba |
Azure/avm-ptn-alz/azurerm |
0.21.0 |
amba |
To upgrade, change the version argument, open a pull request, and review the
plan. The Azure/avm-ptn-alz/azurerm version appears in two units. Keep both on
the same version so the two ALZ pattern deployments agree.
Provider constraints live alongside them: hashicorp/azurerm at ~> 4.0,
Azure/azapi at ~> 2.4, and Azure/alz at ~> 0.21. A major AVM bump usually
moves the alz provider constraint too. See
The foundation units.
ALZ and AMBA library refs¶
Two units set library_references on the alz provider.
| Library | Ref | Set in |
|---|---|---|
platform/alz |
2026.04.2 |
landing-zones, amba |
platform/amba |
2026.06.2 |
amba |
Keep the platform/alz ref identical in both units. In landing-zones the
vendored NRIT library is the second and last entry, a custom_url with no
ref; the amba unit has no custom_url entry. It lives in the repository,
so you edit it in place rather than bumping it.
Re-sync the vendored architecture on every platform/alz bump
live/_foundation/landing-zones/lib/architecture_definitions/nrit.alz_architecture_definition.json
is a full copy of the stock alz architecture plus one line: the nrit_tags
archetype on the intermediate root. The module needs a complete architecture,
so the whole hierarchy is duplicated there.
When you bump the platform/alz ref, fetch the stock architecture at the new
version, diff it against that file, apply any hierarchy changes upstream made,
and keep nrit_tags on the root. Nothing tracks this. No test fails and no
plan warns if the hierarchy has moved on. Put the re-sync in the same pull
request as the ref bump.
See The vendored policy library.
What bumps the engine major¶
The caller contract is what a consumer repository has to provide. A breaking change to any of it bumps the major version:
- workflow inputs,
- secrets the workflow expects,
- hook conventions, including the meaning of
$TFPR_ENGINE_DIRand the step seams, - required caller-side repository variables,
- required GitHub environments.
Everything else is a minor or a patch. See projects.yml schema and Hook and step model for the surfaces this covers.
Checking for updates¶
Engine releases live on the GitHub releases page of nrit-tf-pr-ops. Since
v1.9.1 the repository also carries a generated CHANGELOG.md; it and the
release notes are produced from the same commits, so either is the record of
what changed and what a consumer must do about it.
AVM module versions come from the Terraform registry. The ALZ and AMBA library refs come from the upstream Azure Landing Zones library releases.
Upgrade one thing at a time¶
Bump one module, one library, or the engine per pull request. The plan diff stays readable, and a revert is one commit. Upgrading everything at once turns a readable diff into a wall of noise.
The process is the same in each case:
- Change the
version,ref, or the caller pins on a branch. - Open a pull request. The engine plans the impacted units and posts the diff.
- Review the plan and the gate output.
- Comment
/applyafter approval. - Roll back by reverting the change and applying again.
See Plan and apply.