Skip to content

fix(ci): stop a green pack-validation proof failing the step - #152

Merged
kieronlanning merged 2 commits into
mainfrom
fix/pack-validation-gate
Oct 29, 2026
Merged

kieronlanning merged 2 commits into
mainfrom
fix/pack-validation-gate

Conversation

@kieronlanning

Copy link
Copy Markdown
Contributor

Why

The packed-consumers job was red on #151 while the gate it ran passed every check:

29 validation run(s) in 123.5s.
Every ForbiddenContent rule (27) rejected its file, in every package (1).
##[error]Process completed with exit code 1.

Two independent faults, plus one latent one:

  1. The step could never pass. GitHub's pwsh shell appends
    if ((Test-Path -LiteralPath variable:\LASTEXITCODE)) { exit $LASTEXITCODE } to every step script, and
    scripts/test-pack-validation-rules.ps1 deliberately runs 29 dotnet purview-build invocations that must exit
    non-zero. It ended on a Write-Host, so a green proof exited 1. just verify-pack-rules runs the same script
    through pwsh -File, which exits 0, so it never showed locally.
  2. dotnet tool restore failed with The command "purview-build" ... is not contained in the package with Package Id purview.build. The commands contained in the package are dotnet-inspect. A transient NuGet
    tool-restore race: the shared build's LintModule hit it too, in the same run, and retried past it two seconds
    later. The published purview.build 0.8.0 declares purview-build correctly.
  3. Latent: inside the harness, the per-scratch-repo dotnet tool restore was | Out-Null. A failed restore
    would leave the runs using whatever purview-build the tool store already held — it only worked in that run
    because 0.8.0 happened to be there. A stale tool would report rules this repository no longer has.

What

  • scripts/test-pack-validation-rules.ps1 — ends with an explicit exit 0; the tool restore is fatal and its output
    is part of the message.
  • scripts/test-packed-consumers.ps1 — same explicit exit 0, so neither gate inherits $LASTEXITCODE.
  • .github/workflows/pr.yml — the rule proof moves from the last step of packed-consumers to its own
    pack-validation job. It packs the generator and proves the rules, in parallel with the Native AOT / WebAssembly
    AOT gate rather than after its seven minutes. Its dotnet tool restore raises instead of leaving $LASTEXITCODE.
  • .github/workflows/pr.yml — drops TELEMETRY_PRERELEASE_FEED, which was never set. The shared build workflow
    already registers the organisation's GitHub Packages source when a pinned version is not on nuget.org.
  • Justfile — just pipeline-pack-validate now ends with the proof, so packing locally proves the rules.
  • ReleaseGateTests — fails when a gate script stops stating its exit code, when the proof sits behind the consumer
    gate, or when a failed tool restore is not raised. Each new guard was checked by breaking it.
  • AGENTS.md, docs/wiki/Release-Flow.md — gate wiring and the prerelease-feed story.

No version bump: this changes the gates, not the package.

Verification

  • The failing step reproduced and fixed: the proof run under GitHub's exact wrapper
    ($ErrorActionPreference = 'stop' plus a trailing exit $LASTEXITCODE) exits 0; before the fix it exited 1.
  • The new guard bites: deleting exit 0 fails
    GateScript_GivenCommandsThatFailByDesign_EndsWithAnExplicitExit; restoring it is green.
  • just verify-pack-rules — 29 runs, 27/27 rules rejected, exit 0.
  • The new job's exact pack sequence locally — PackValidation: Valid packages: 1/1.
  • Repository.UnitTests 51/51, SourceGenerator.IntegrationTests net10.0 567 passed / 0 failed, just build
    0 warnings, dotnet csharpier check . clean.

The rule proof passed all 29 validations and the step still reported exit 1. Every
validation run has to fail, so $LASTEXITCODE was non-zero when the script fell
through, and GitHub's pwsh shell appends `exit $LASTEXITCODE` to every step. The
script now states exit 0, and the tool restore it depends on is fatal instead of
piped to Out-Null: an ignored failure there leaves a stale purview-build proving
rules this repository no longer has.

The proof moves out of packed-consumers into its own job. It needs nothing the
Native AOT and WebAssembly gates do, and as the last step of that job it could only
fail after seven minutes of them. packed-consumers states its own exit code
explicitly for the same reason.

Drops the TELEMETRY_PRERELEASE_FEED variable. It was never set, and the shared
build workflow already registers the organisation's GitHub Packages source when a
pinned Purview.SourceGeneratorFramework or Purview.BuildSdk version is not on
nuget.org. `-PrereleaseFeed` and LOCAL_NUGET_FEED_PATH remain, for local runs
against a feed that holds an unpublished version.

just pipeline-pack-validate now ends with the proof, so packing locally proves the
rules without a second command. ReleaseGateTests fails when a gate script stops
stating its exit code, when the proof sits behind the consumer gate, or when a
failed tool restore is not raised.
…ores

A cold `dotnet tool restore` fails whenever dotnet-inspect is in the manifest,
on a hosted runner and on a clean machine alike:

    The command "purview-build" specified in the tool manifest file is not
    contained in the package with Package Id purview.build. The commands
    contained in the package are dotnet-inspect.

dotnet-inspect 0.26.0 is a RID-specific tool (DotNetCliTool Version="2" with
RuntimeIdentifierPackages, laid out under tools/any/any). Installing it beside
another tool leaves NuGet validating the wrong package's commands. Reversing the
manifest order changes nothing and --disable-parallel does not help, so it is not
a race: it is every cold restore.

Nothing here ever ran the tool. It was added in 75bf81e and is referenced
nowhere.

Three cold-cache manifests prove the trigger: purview.build alone restores,
csharpier plus purview.build restores, and all three fail. Without the entry the
repository's own manifest restores cold in 7s - which is what every hosted run
and every fresh clone does.

This is why the gate in the parent commit could not pass: the shared build's
LintModule hits the same failure and retries past it.
@kieronlanning

Copy link
Copy Markdown
Contributor Author

The second commit finds the root cause of the dotnet tool restore failure that the new job hit on its first run — it is not transient.

dotnet-inspect 0.26.0 is a RID-specific tool (DotNetCliTool Version="2" with RuntimeIdentifierPackages, laid out under tools/any/any). While it is in the manifest, every cold-cache restore fails:

The command "purview-build" specified in the tool manifest file is not contained in the package
with Package Id purview.build. The commands contained in the package are dotnet-inspect.
Restore partially failed.

Reproduced on three cold manifests (fresh DOTNET_CLI_HOME + fresh NUGET_PACKAGES):

manifest cold restore
purview.build only restored
csharpier + purview.build restored
csharpier + dotnet-inspect + purview.build failed

--disable-parallel changes nothing, so it is not a race, and the tool is referenced nowhere in the repository (added in 75bf81e). Removed. The repository's manifest now restores cold in 7s, and the shared build's LintModule — which hit this and retried past it in the same run as the original failure — should stop needing its retry.

@kieronlanning
kieronlanning merged commit 9da45c0 into main Oct 29, 2026
4 checks passed
@kieronlanning
kieronlanning deleted the fix/pack-validation-gate branch October 29, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant