ci: pin global.json and CI to the same SDK band - #379
Merged
Conversation
global.json named 10.0.204 with latestFeature, while every actions/setup-dotnet step asked for 10.0.x and floated to whatever band was newest at tag time -- 10.0.400 as of this change. With TreatWarningsAsErrors and AnalysisLevel latest, an analyzer diagnostic new to a later band fails CI without a local repro on the older SDK. Point global.json at 10.0.400 with latestPatch, and have all five setup-dotnet steps resolve global-json-file: global.json instead of repeating the version inline, so the two can no longer drift apart. Closes #231
Security review on #379 flagged that the SDK band change removed the automatic band uptake the old 10.0.x float gave developers, with no human-facing note of the new constraint or the release-time risk. - CONTRIBUTING.md: name the pinned feature band in the prerequisite and require any global.json bump to be merged and CI-validated before the next release tag, since release.yml reads global.json from the tag's tree. - global.json: comment on rollForward explaining it is load-bearing here even though latestPatch is the host default -- without it, setup-dotnet would freeze CI to exactly 10.0.400. - ci.yml, release.yml: log dotnet --version right after each setup-dotnet step so the resolved SDK lands in the build log.
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
The dependency floor on xunit.v3.extensibility.core is [3.2.2, ), an open lower bound already satisfied by the xunit.v3 3.2.2 pin, so this is independent of the xunit v4 migration tracked separately (#200). The only transitives that actually moved are DiffEngine (19.3.3 to 20.0.0, a major) and Microsoft.Bcl.AsyncInterfaces (10.0.10 to 10.0.11); Argon and SimpleInfoName were already in 31.28.0's closure. DiffEngine 20.0.0 bundles native viewer executables and stamps a runtimeconfig path by default, so CI now disables it explicitly and the test projects opt out of the bundled viewer. Adds .github/dependabot.yml, missing until now: nuget, github-actions, and dotnet-sdk, weekly. Minor/patch bumps group per ecosystem; majors stay individual so a snapshot-risk bump like this one is attributable to its own PR. The nuget group excludes the one shipped runtime dependency, whose patches change emitted CMS/PAdES bytes, from that batch. The xunit majors are ignored pending #200's deliberate hold, and dotnet-sdk gives the global.json feature-band pin from #379 the automated update signal it was otherwise missing.
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
The dependency floor on xunit.v3.extensibility.core is [3.2.2, ), an open lower bound already satisfied by the xunit.v3 3.2.2 pin, so this is independent of the xunit v4 migration tracked separately (#200). The transitives that actually moved: Verify itself (31.28.0 to 32.0.0, in lockstep with Verify.XunitV3), DiffEngine (19.3.3 to 20.0.0, a major), and Microsoft.Bcl.AsyncInterfaces (10.0.10 to 10.0.11). Argon and SimpleInfoName were already in 31.28.0's closure. DiffEngine 20.0.0 reads its disable flag lazily instead of capturing it once at type-init (VerifyTests/DiffEngine#825) and now installs a bundled viewer as an always-available last-resort tool, so a detection miss that was a harmless no-op in 19.x could launch a GUI. CI sets DiffEngine_Disabled explicitly against that, and the test projects opt out of the bundled viewer, which also keeps a username-bearing absolute path out of runtimeconfig.json. Neither setting reaches a developer's own installed diff tool; only DiffEngine_Disabled does that. Adds .github/dependabot.yml, missing until now: nuget, github-actions, and dotnet-sdk, weekly. Minor/patch bumps group per ecosystem; majors stay individual so a snapshot-risk major like this one is attributable to its own PR. The nuget group excludes the one shipped runtime dependency, whose patches change emitted CMS/PAdES bytes, from that batch, and ignores xunit.v3* and xunit.runner.visualstudio majors pending #200's hold. dotnet-sdk does not consider rollForward, so it will propose SDKs outside the pinned 10.0.4xx band by design: each such PR is the deliberate band-move signal #379's CONTRIBUTING rule calls for.
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
The dependency floor on xunit.v3.extensibility.core is [3.2.2, ), an open lower bound already satisfied by the xunit.v3 3.2.2 pin, so this is independent of the xunit v4 migration tracked separately (#200). The transitives that actually moved: Verify itself (31.28.0 to 32.0.0, in lockstep with Verify.XunitV3), DiffEngine (19.3.3 to 20.0.0, a major), and Microsoft.Bcl.AsyncInterfaces (10.0.10 to 10.0.11). Argon and SimpleInfoName were already in 31.28.0's closure. DiffEngine 20.0.0 reads its disable flag lazily instead of capturing it once at type-init (VerifyTests/DiffEngine#825) and now installs a bundled viewer as an always-available last-resort tool, so a detection miss that was a harmless no-op in 19.x could launch a GUI. CI sets DiffEngine_Disabled explicitly against that, and the test projects opt out of the bundled viewer, which also keeps a username-bearing absolute path out of runtimeconfig.json. Neither setting reaches a developer's own installed diff tool; only DiffEngine_Disabled does that. Adds .github/dependabot.yml, missing until now: nuget, github-actions, and dotnet-sdk, weekly. Minor/patch bumps group per ecosystem; majors stay individual so a snapshot-risk major like this one is attributable to its own PR. The nuget group excludes the one shipped runtime dependency, whose patches change emitted CMS/PAdES bytes, from that batch, and ignores xunit.v3* and xunit.runner.visualstudio majors pending #200's hold. dotnet-sdk does not consider rollForward, so it will propose SDKs outside the pinned 10.0.4xx band by design: each such PR is the deliberate band-move signal #379's CONTRIBUTING rule calls for.
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
apt-get update exits 0 even when the -o APT::Snapshot fetch itself fails outright, and silently carries on with whatever else is already cached or configured. Reproduced both failure shapes in a container (a TLS trust failure from missing ca-certificates, and a blackholed host) and confirmed apt gives no other signal in either case, so the poppler/fonts install step now greps /var/lib/apt/lists/ for the snapshot's own index files right after the update and fails loudly if they never landed. The mechanism comment, CONTRIBUTING's paragraph, and the qpdf-1ubuntu9.1 verification claim all overstated or misdescribed how -o APT::Snapshot works: it adds the dated snapshot alongside the runner's existing sources rather than rewriting them, ubuntu-24.04 resolves its own sources through mirror+file:/etc/apt/apt-mirrors.txt rather than a literal archive.ubuntu.com host, and the earlier "verified against the live archive already past this revision" claim was false since 9.9 is still current there today. Re-verified instead against poppler-utils 24.02.0-1ubuntu9.1, a revision already absent from the live archive: a snapshot dated to when 9.1 was current resolves and installs it correctly with the live sources still present. The bump-procedure guidance is corrected too: apt-cache policy cannot produce an APT_SNAPSHOT date on its own, so it now documents picking a UTC day at or after the new revision's publication and confirming the stamp resolves before using it. Also: setup-python's 3.14 pin was justified by a wheel-availability cliff at 3.15 that does not exist (zxing-cpp 3.1.1's wheel is cp312-abi3 with no upper Python bound) — reworded to interpreter determinism, the same reason the pip versions beside it are pinned. Restored a pdftotext -v identity check alongside the dpkg-query asserts, since the package record proves the installed version but not which binary the tests actually invoke. Fixed a stale ci.yml line-number citation in OracleGate.cs, a stale ubuntu-latest reference in an unrelated CHANGELOG entry sitting in the same Unreleased section, and noted the --allow-weak-crypto flag the RC4 recipe in Fixtures/Encrypted/README.md needs against qpdf 12.4.1. aot-smoke's own ubuntu-latest is noted as deliberate, since it carries no apt pins. Merged main (PR #379: global.json pins the SDK band, all setup-dotnet steps read global-json-file) — no conflicts. Refs #230
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
#381) * Pin CI's qpdf and poppler oracles instead of floating on ubuntu-latest apt's qpdf on the runner is 11.9.0, two majors behind upstream, and --check's output has changed across qpdf majors while #186's acceptance criterion is exactly that output. Install qpdf 12.4.1 from the official release artifact (checksum-verified) instead; pin poppler-utils, fonts-dejavu-core and fonts-texgyre to the exact versions the runner's apt archive serves today, since an unpinned font update silently changes fixture rendering for every downstream oracle. Also deduplicate the veraPDF Docker tag into one job-level env var, bump zxing-cpp to 3.1.1 with pillow pinned alongside it (the barcode oracle suite passes unchanged), bump setup-dotnet/setup-python to their current majors, and add a step that asserts each pinned tool's own version report so drift fails loudly instead of quietly changing what CI validates against. Closes #230 * Address review: pin the runner, survive apt's revision rotation runs-on moves from the floating ubuntu-latest to ubuntu-24.04, since every apt pin in this job is noble-only and would all break at once the day GitHub retargets the label. The poppler/font =version pins alone were a scheduled outage: noble's -updates/-security pockets keep only the newest revision, so the exact revisions here would 404 the moment Ubuntu ships another one. apt-get now passes -o APT::Snapshot for the date those versions were measured, which rewrites the runner's existing sources to the dated Ubuntu snapshot and keeps the pinned revisions resolvable indefinitely; verified in a container with the live archive already past the pinned revision. setup-python's interpreter is pinned to 3.14 (zxing-cpp 3.1.1 has no wheel past it) instead of floating, and docs-inventory.yml's own setup-python, missed by the previous sweep, moves to v7 to match. The version-assert step now reads every pinned version from job-level env instead of re-hardcoding them, asserts the apt packages' full Ubuntu revision via dpkg-query (pdftotext -v can't see it and fonts can't self-report at all), and compares full lines/values instead of substrings so a future point release can't slip past a careless grep. Restored the zxing-cpp pin's inline rationale in ci.yml, which the previous edit dropped, and updated the CHANGELOG/CONTRIBUTING/CLAUDE.md prose to match the runner label, the snapshot mechanism and the full apt version. tests/VellumPdf.Reader.Tests/Fixtures/Encrypted/README.md's CI/local qpdf version claim was stale in both directions; both are 12.4.1 now. Reran its two qpdf-12.3.2-specific behavioral claims (catalog-in-object-stream under encryption, and /V4 /CFM /V2 ignoring /Length) against 12.4.1 and recorded that both still hold. Refs #230 * Make the snapshot pin's own success observable, not assumed apt-get update exits 0 even when the -o APT::Snapshot fetch itself fails outright, and silently carries on with whatever else is already cached or configured. Reproduced both failure shapes in a container (a TLS trust failure from missing ca-certificates, and a blackholed host) and confirmed apt gives no other signal in either case, so the poppler/fonts install step now greps /var/lib/apt/lists/ for the snapshot's own index files right after the update and fails loudly if they never landed. The mechanism comment, CONTRIBUTING's paragraph, and the qpdf-1ubuntu9.1 verification claim all overstated or misdescribed how -o APT::Snapshot works: it adds the dated snapshot alongside the runner's existing sources rather than rewriting them, ubuntu-24.04 resolves its own sources through mirror+file:/etc/apt/apt-mirrors.txt rather than a literal archive.ubuntu.com host, and the earlier "verified against the live archive already past this revision" claim was false since 9.9 is still current there today. Re-verified instead against poppler-utils 24.02.0-1ubuntu9.1, a revision already absent from the live archive: a snapshot dated to when 9.1 was current resolves and installs it correctly with the live sources still present. The bump-procedure guidance is corrected too: apt-cache policy cannot produce an APT_SNAPSHOT date on its own, so it now documents picking a UTC day at or after the new revision's publication and confirming the stamp resolves before using it. Also: setup-python's 3.14 pin was justified by a wheel-availability cliff at 3.15 that does not exist (zxing-cpp 3.1.1's wheel is cp312-abi3 with no upper Python bound) — reworded to interpreter determinism, the same reason the pip versions beside it are pinned. Restored a pdftotext -v identity check alongside the dpkg-query asserts, since the package record proves the installed version but not which binary the tests actually invoke. Fixed a stale ci.yml line-number citation in OracleGate.cs, a stale ubuntu-latest reference in an unrelated CHANGELOG entry sitting in the same Unreleased section, and noted the --allow-weak-crypto flag the RC4 recipe in Fixtures/Encrypted/README.md needs against qpdf 12.4.1. aot-smoke's own ubuntu-latest is noted as deliberate, since it carries no apt pins. Merged main (PR #379: global.json pins the SDK band, all setup-dotnet steps read global-json-file) — no conflicts. Refs #230 * Stop piping oracle version banners into an early-exiting head The real runner failed the assert step at exit 141 (128+SIGPIPE) right after the dpkg-query lines: `<tool> ... | head -n1` under `set -euo pipefail` lets head exit the moment it has its one line, and the producer (writing a multi-line banner) can still be mid-write when that happens, so the write hits a closed pipe. Local containers never showed it because the timing has to line up, which the runner's own buffering apparently does more often than a clean container does. Fixed by capturing the full output first and taking the first line with a bash parameter expansion instead of a second process in the pipeline: `out=$(cmd); line=${out%%$'\n'*}`. Applied to all three version-banner reads (qpdf, pdftotext, veraPDF). The snapshot-verification guard had the same shape with `grep -q` instead of `head` (`ls ... | grep -q ...`) and got the same treatment: capture the listing, then grep it through a here-string, which reads a fixed string rather than a live pipe and so cannot race a still-writing producer. Re-verified in containers: qpdf/poppler assert logic against a real install (exit 0), the veraPDF line extraction against real multi-line `verapdf --version` output, and the guard in both its failing and passing shapes. Refs #230
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
The dependency floor on xunit.v3.extensibility.core is [3.2.2, ), an open lower bound already satisfied by the xunit.v3 3.2.2 pin, so this is independent of the xunit v4 migration tracked separately (#200). The transitives that actually moved: Verify itself (31.28.0 to 32.0.0, in lockstep with Verify.XunitV3), DiffEngine (19.3.3 to 20.0.0, a major), and Microsoft.Bcl.AsyncInterfaces (10.0.10 to 10.0.11). Argon and SimpleInfoName were already in 31.28.0's closure. DiffEngine 20.0.0 reads its disable flag lazily instead of capturing it once at type-init (VerifyTests/DiffEngine#825) and now installs a bundled viewer as an always-available last-resort tool, so a detection miss that was a harmless no-op in 19.x could launch a GUI. CI sets DiffEngine_Disabled explicitly against that, and the test projects opt out of the bundled viewer, which also keeps a username-bearing absolute path out of runtimeconfig.json. Neither setting reaches a developer's own installed diff tool; only DiffEngine_Disabled does that. Adds .github/dependabot.yml, missing until now: nuget, github-actions, and dotnet-sdk, weekly. Minor/patch bumps group per ecosystem; majors stay individual so a snapshot-risk major like this one is attributable to its own PR. The nuget group excludes the one shipped runtime dependency, whose patches change emitted CMS/PAdES bytes, from that batch, and ignores xunit.v3* and xunit.runner.visualstudio majors pending #200's hold. dotnet-sdk does not consider rollForward, so it will propose SDKs outside the pinned 10.0.4xx band by design: each such PR is the deliberate band-move signal #379's CONTRIBUTING rule calls for.
Tim81
added a commit
that referenced
this pull request
Aug 31, 2026
The dependency floor on xunit.v3.extensibility.core is [3.2.2, ), an open lower bound already satisfied by the xunit.v3 3.2.2 pin, so this is independent of the xunit v4 migration tracked separately (#200). The transitives that actually moved: Verify itself (31.28.0 to 32.0.0, in lockstep with Verify.XunitV3), DiffEngine (19.3.3 to 20.0.0, a major), and Microsoft.Bcl.AsyncInterfaces (10.0.10 to 10.0.11). Argon and SimpleInfoName were already in 31.28.0's closure. DiffEngine 20.0.0 reads its disable flag lazily instead of capturing it once at type-init (VerifyTests/DiffEngine#825) and now installs a bundled viewer as an always-available last-resort tool, so a detection miss that was a harmless no-op in 19.x could launch a GUI. CI sets DiffEngine_Disabled explicitly against that, and the test projects opt out of the bundled viewer, which also keeps a username-bearing absolute path out of runtimeconfig.json. Neither setting reaches a developer's own installed diff tool; only DiffEngine_Disabled does that. Adds .github/dependabot.yml, missing until now: nuget, github-actions, and dotnet-sdk, weekly. Minor/patch bumps group per ecosystem; majors stay individual so a snapshot-risk major like this one is attributable to its own PR. The nuget group excludes the one shipped runtime dependency, whose patches change emitted CMS/PAdES bytes, from that batch, and ignores xunit.v3* and xunit.runner.visualstudio majors pending #200's hold. dotnet-sdk does not consider rollForward, so it will propose SDKs outside the pinned 10.0.4xx band by design: each such PR is the deliberate band-move signal #379's CONTRIBUTING rule calls for.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #231.
global.jsonnamed10.0.204withlatestFeature; everyactions/setup-dotnetstep asked for
10.0.x. Neither points at a fixed SDK, so each resolvesindependently and the two only agreed by accident:
global.json10.0.204,latestFeature10.0.400,latestPatchlatestFeatureroll-forwardactions/setup-dotnet,dotnet-version: '10.0.x', five places)global-json-file: global.jsonDirectory.Build.propssetsTreatWarningsAsErrorsandAnalysisLevel latest, so ananalyzer diagnostic new to a later feature band was previously a CI failure with no
local repro on an older SDK. Pinning both to the same version removes that gap.
Changes:
global.json:10.0.400/latestPatch, with a comment onrollForwardexplaining why it stays even though
latestPatchis the host default —deleting it would freeze CI to exactly
10.0.400and drop patch servicing.actions/setup-dotnet@v5steps (ci.ymlbuild and AOT-smoke jobs,release.ymllibrary and tool-publish jobs,docs.yml) now passglobal-json-file: global.jsoninstead of a hardcodeddotnet-version, so the twocan't drift apart again. Action version tags (
@v5) are untouched, matching aseparate in-flight PR on that axis.
ci.ymlandrelease.ymlalso log theresolved SDK (
dotnet --version) right after eachsetup-dotnetstep.CONTRIBUTING.md: the prerequisite now names the feature band and points atglobal.jsonfor the exact pin, and a new note requires anyglobal.jsonfeature-band bump to be merged and CI-validated before the next release tag,
since
release.ymlreadsglobal.jsonfrom the tag's tree. The automatedhalf of that signal (a
dotnet-sdkDependabot entry) is chore(deps): bump Verify.XunitV3 to 32.0.0, add dependabot #378.## [Unreleased]/### Changed.Rebuilt and retested the full solution under the resolved 10.0.400 SDK before
opening this: no new analyzer diagnostics surfaced in the 4xx band, so there was
nothing to fix.
Test plan
dotnet --versioninside the worktree prints10.0.400dotnet build VellumPdf.slnx— 0 warnings, 0 errorsdotnet test VellumPdf.slnx— 5231 passed, 0 faileddotnet format VellumPdf.slnx --verify-no-changes— cleanpwsh ./eng/clean-room-check.ps1— cleanpwsh ./eng/aot/run-aot-smoke.ps1— PASSED