Skip to content

Pipelines | Validate every package the OneBranch build produces - #4655

Open
paulmedynski wants to merge 7 commits into
dev/paul/onebranch-version-cleanupfrom
dev/paul/onebranch-validate-packages
Open

Pipelines | Validate every package the OneBranch build produces#4655
paulmedynski wants to merge 7 commits into
dev/paul/onebranch-version-cleanupfrom
dev/paul/onebranch-validate-packages

Conversation

@paulmedynski

@paulmedynski paulmedynski commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Re-enables the OneBranch package validation stage and widens it from one package to all six.

The stage has been commented out for some time behind this @TODO:

Update validate-signed-package-job to compute expected versions from Versions.props (same as build jobs) instead of receiving them as parameters.

#4652 resolved that by publishing package and file versions from the compute_versions stage, so validation can now assert them directly.

What it validates

tools/PackageValidator runs once over all packages together, not once per package. That matters: its most valuable rules are cross-package — the SqlClient family must share one version, and their inter-package dependency ranges must agree. Validating one package at a time silently skips every one of those findings.

Expectations come from compute_versions rather than being re-derived. The family version is applied as a wildcard, so a mismatch in any single package is caught and so is the case where every package is consistently wrong; Microsoft.SqlServer.Server overrides it by id.

When SqlServer isn't built its expectations are omitted entirely rather than passed empty — verified that the validator exits 1 with missing value otherwise, so blanking them would fail every buildSqlServer: false run.

Gating

Run Gate
Non-official error, missing-symbols, dependency-inconsistency, delay-signed, unsigned
Official the above plus package-unsigned

The error token matches only error-severity findings, so every warning/info category the stage relies on has to be named explicitly. Confirmed against Validator.cs:

Category Severity Why it must be named
missing-symbols Warning not covered by error
dependency-inconsistency Warning not covered by error
delay-signed Warning not covered by error
unsigned Info not covered by error
package-unsigned Info not covered by error

dependency-inconsistency gates on every run. It is signing-independent, and mismatched inter-package dependency ranges are precisely what validating the whole drop in one invocation exists to catch.

The two strong-name categories also gate on every run, because strong-name signing is the one signing type the pipeline applies unconditionally: build-buildproj-step.yml downloads netfxKeypair.snk and passes -p:SigningKeyPath= with no shouldSignPackage guard, unlike the two ESRP steps. Run 26251.2 confirms it — 59 of 59 implementation assemblies reported Signing status: Signed on a non-official run, with zero delay-signed and zero unsigned findings. Leaving these official-only would have left the always-applied signing type unvalidated on PR builds, where a broken key download would silently flip PublicKeyToken from 23ec7fc2d6eaa4a5 to null — a breaking identity change — and not surface until an official run.

package-unsigned stays official-only. NuGet package signing is an ESRP step genuinely conditioned on shouldSignPackage, so non-official packages carry no .signature.p7s by design and gating it everywhere would fail every PR build.

Verified by isolating each token against a signed package and a stripped-signature copy:

Package Gate Exit
unsigned error only 0 — info isn't caught, so non-official runs don't fail for being unsigned
unsigned package-unsigned 2
signed package-unsigned 0 — no false positive on official runs

Also confirmed via --fail-on help output that all five category tokens are accepted, and that gate matching is exact set membership (Program.Matches), so unsigned cannot collide with package-unsigned.

Gating on missing-symbols is safe: I confirmed against non-official run 172931 that all six packages emit a .snupkg, that each is copied into its artifact, and that it lands beside its .nupkg — which is what the symbol resolver requires. That includes Microsoft.Data.SqlClient, which packs via NuspecFile.

Release gating

release-stages.yml now depends on package_validation, so a package that fails validation is never published.

Replaces validate-signed-package-job

Deleted. It validated only Microsoft.Data.SqlClient, and its checks had real gaps: folder and TFM checks could only detect unexpected entries and never missing ones, the "expected hierarchy" check indexed extracted content positionally (so it could pass on wrong content), and strong-name verification depended on a hardcoded sn.exe path under a wildcard VS directory. Authenticode and NuGet signature verification now cover every produced package, and strong-name state comes from PackageValidator cross-platform.

Scripts, not inline YAML

Step and job logic lives in scripts/validate-packages.ps1, verify-package-signatures.ps1 and verify-assembly-signatures.ps1, matching compute-versions.ps1 and publish-symbols.ps1.

The validator is invoked twice over the same inputs. The first run writes the JSON report and never gates, so the artifact survives a failing run; the second renders the human-readable report and applies the gate, so findings appear in the build's own log rather than only in an artifact. Because the reporting run is ungated, any non-zero exit from it means the validator itself failed, and the script fails there rather than writing a misleading report and letting the gated run obscure the cause.

-FailOn is normalized by splitting on commas, so it behaves identically whether it arrives as a PowerShell array or as the single comma-joined token an Azure Pipelines arguments: line produces.

27 new Pester tests across three new test files (77 total in the suite, all passing) cover: wildcard vs per-id expectations, SqlServer omitted when unbuilt, a guard rejecting a half-supplied SqlServer version pair, comma-joined -FailOn normalization, report-written-before-gating, the reporting run being ungated, the reporting run failing before the gate is reached, exit-code handling (0 / 2 / other), all failures collected before throwing, native binaries under runtimes/ included, and stale expansions being replaced.

The assembly test builds real .nupkg files and lets the script genuinely expand them; only Get-AuthenticodeSignature is substituted, since it's Windows-only.

Testing

  • 77/77 Pester tests pass
  • PackageValidator CLI behaviour verified end-to-end against a real published package: matching expectations exit 0, wrong versions exit 2 with per-assembly findings, empty expectation exits 1, --json well-formed
  • Run 26251.3 — non-official dry run on the current head (66d6267e), verifying the widened strong-name gate. In progress; result to be posted when it completes. Run 26251.2 below already shows the findings this gate acts on: zero delay-signed and zero unsigned across all six packages, so it is expected to pass.
  • Run 26251.2 — succeeded. Non-official dry run on d34d615d, covering the dependency-inconsistency gate and the script hardening. All six packages validated together against the versions published by compute_versions: family wildcard 7.1.0-preview3.26251.2 plus the Microsoft.SqlServer.Server=1.1.0-preview1.26251.2 per-id override, gate error + missing-symbols + dependency-inconsistency, JSON report written before gating. Summary: 6 inspected, 0 errors, 0 warnings, 6 info — gate passed. The six info findings are all package-unsigned, correctly non-gating on a non-official run. Zero dependency-inconsistency findings, so the family's inter-package ranges agree with the versions actually produced; every implementation assembly resolved a checksum-verified symbol-package PDB.
  • That run also exercises both review fixes end to end: the task passed -FailOn "error,missing-symbols,dependency-inconsistency" as a single quoted token and the script split it correctly into --fail-on error --fail-on missing-symbols --fail-on dependency-inconsistency. No Guardian Roslyn task was injected into the validation job.
  • Earlier dry run on the non-official pipeline: run 26250.4 — succeeded. All six packages were validated together against the versions published by compute_versions: family wildcard 7.1.0-preview3.26250.4 plus the Microsoft.SqlServer.Server=1.1.0-preview1.26250.4 per-id override, JSON report written before gating. Summary: 6 inspected, 0 errors, 0 warnings, 6 info — all package-unsigned, correctly non-gating on a non-official run — gate passed. That run predates the dependency-inconsistency gate, which is what 26251.2 re-verifies.
  • The preceding run 26250.3 produced identical validation output but failed its job on an unrelated Guardian Roslyn task that 1ES auto-injects into any job containing a DotNetCoreCLI build task. Here that is only the PackageValidator tool build, which never ships, so Roslyn is disabled for this job; shipping-code Roslyn coverage in the six build jobs is unchanged.

Follow-ups

Missing validations found while doing this are captured in ADO #47876 (16 items: TFM completeness, content manifest, nuspec metadata, deterministic build/SourceLink, API-compat baseline, restore smoke test, and more).

  • Tests added or updated
  • Public API changes documented — n/a
  • Ensure no breaking changes introduced

Copilot AI balanced review requested due to automatic review settings September 6, 2026 18:51
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Sep 6, 2026
@paulmedynski paulmedynski added the Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems. label Sep 6, 2026
@paulmedynski paulmedynski added this to the 7.1.0 milestone Sep 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The JSON-report PackageValidator run in validate-packages.ps1 doesn’t check its exit code, which can produce misleading/invalid report artifacts if the tool fails.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Re-enables and expands OneBranch package validation so that all produced NuGet packages are validated together (exercising cross-package rules), and gates releasing on that validation.

Changes:

  • Adds a new package_validation stage that downloads all produced package artifacts into one tree and runs tools/PackageValidator once across the full set.
  • Adds official-only signature verification scripts for NuGet package signatures and Authenticode signatures of extracted binaries, with new Pester coverage.
  • Makes release stages depend on package_validation and removes the legacy single-package validate-signed-package-job.yml.
File summaries
File Description
eng/pipelines/onebranch/steps/validate-packages-step.yml New step template to build and run PackageValidator with version expectations and gating tokens.
eng/pipelines/onebranch/jobs/validate-packages-job.yml New Windows job that downloads all package artifacts and runs validation + (official-only) signature checks.
eng/pipelines/onebranch/stages/build-stages.yml Replaces commented-out validation with active package_validation stage wired to compute_versions outputs.
eng/pipelines/onebranch/stages/release-stages.yml Adds package_validation to release-stage dependencies to prevent publishing unvalidated packages.
eng/pipelines/onebranch/scripts/validate-packages.ps1 New script that runs PackageValidator twice (JSON report then gated human output).
eng/pipelines/onebranch/scripts/verify-package-signatures.ps1 New script to run dotnet nuget verify --all across all .nupkg/.snupkg (official builds only).
eng/pipelines/onebranch/scripts/verify-assembly-signatures.ps1 New script to expand .nupkg and verify Authenticode signatures for all .dll under extracted content (official builds only).
eng/pipelines/onebranch/scripts/tests/validate-packages.Tests.ps1 New Pester tests covering expectation shaping, gating args, and exit-code handling.
eng/pipelines/onebranch/scripts/tests/verify-package-signatures.Tests.ps1 New Pester tests covering per-package verification and failure aggregation.
eng/pipelines/onebranch/scripts/tests/verify-assembly-signatures.Tests.ps1 New Pester tests covering expansion behavior, native binaries, stale extraction replacement, and failure aggregation.
eng/pipelines/onebranch/scripts/tests/README.md Documents the newly added script test coverage areas.
eng/pipelines/onebranch/jobs/validate-signed-package-job.yml Deleted legacy job that only validated the SqlClient package and had known gaps.
.github/instructions/onebranch-pipeline-design.instructions.md Updates design doc to reflect the new stage/job/template structure and gating rules.
Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread eng/pipelines/onebranch/scripts/validate-packages.ps1
@paulmedynski
paulmedynski force-pushed the dev/paul/onebranch-validate-packages branch from 87262e6 to e48f26a Compare September 6, 2026 19:12
Copilot AI review requested due to automatic review settings September 6, 2026 19:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The FailOn gate tokens are currently passed/consumed in a way that collapses multiple tokens into a single comma-delimited value, which will break PackageValidator argument parsing and cause validation to fail.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread eng/pipelines/onebranch/scripts/validate-packages.ps1
Copilot AI review requested due to automatic review settings September 7, 2026 12:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Gate arguments are currently ineffective, and dependency-range inconsistencies are not configured to block releases.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

eng/pipelines/onebranch/jobs/validate-packages-job.yml:173

  • The configured gates omit dependency-inconsistency, even though that cross-package check is a Warning (tools/PackageValidator/src/Validator.cs:165-166) and therefore is not covered by error. A package whose dependency range excludes the sibling version will only be reported and can still proceed to release, contrary to this stage's dependency-agreement purpose. Gate this category in both official and non-official runs.

eng/pipelines/onebranch/scripts/validate-packages.ps1:166

  • PowerShell@2 passes the comma-joined value to this [string[]] parameter as one element ("error,missing-symbols"), so the loop emits a single unknown --fail-on token. As a result, even Error findings do not trip the validation gate and the stage can release invalid packages. Split each incoming element before constructing the repeated validator arguments.
foreach ($token in $FailOn) {
    $gate += @("--fail-on", $token)
  • Files reviewed: 13/13 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@paulmedynski paulmedynski moved this from To triage to In progress in SqlClient Board Sep 8, 2026
@paulmedynski
paulmedynski marked this pull request as ready for review September 8, 2026 10:24
@paulmedynski
paulmedynski requested a review from a team September 8, 2026 10:25
Make the compute-versions stage the single source of every version the
OneBranch build jobs consume, so nothing is re-derived downstream.

- Remove the addRevision mode entirely.  Package versions now have a single
  shape driven by the pipeline build number, and the 16-bit revision wrapping,
  the four-part package base handling, and the Build.BuildId plumbing are gone.

- Move package version stamping out of PowerShell and into Versions.props.
  BuildSuffix now does what it always documented: it turns a stable base into a
  prerelease.  Any version carrying a prerelease tag, from either source, is
  stamped with the build number; released versions are left untouched.

- Publish SqlClient and SqlServer file versions from the compute-versions stage
  and pass them into the build jobs, which previously received a raw build
  number and re-derived the file version through MSBuild.  build.proj gains
  opt-in FileVersion* arguments, so PR/CI and local builds are unchanged.

- Fix SBOM metadata, which reported the pipeline run number as the version of a
  single hardcoded package name.  Each build job now supplies the name and
  computed version of the package it produces, and jobs that publish no
  packages disable SBOM generation instead.
Re-enable the package validation stage, which had been commented out pending the
version pre-computation that landed in the previous change, and widen it from one
package to all six.

- Add a package_validation stage that depends on all four build stages.  Every
  package is downloaded into one tree and validated together so PackageValidator
  can apply its cross-package rules: the SqlClient family must share a single
  version, and their inter-package dependency ranges must agree.  Validating one
  package at a time would silently skip all of those findings.

- Assert the versions the compute-versions stage already published, rather than
  re-deriving them.  The family version is applied as a wildcard expectation, so
  a mismatch in any one package is caught along with the case where every package
  is consistently wrong; Microsoft.SqlServer.Server overrides it by id.  When
  SqlServer is not built its expectations are omitted entirely, because the
  validator rejects an expectation whose value is empty.

- Gate on error and missing-symbols always, plus package-unsigned on official
  runs.  missing-symbols is a warning and package-unsigned is info, so neither is
  covered by the error severity and both must be named explicitly.  Non-official
  runs are deliberately unsigned, so gating them on package-unsigned would always
  fail.

- Make the release stage depend on package validation, so a package that fails
  validation is never published.

- Replace the SqlClient-only validate-signed-package-job, which checked one
  package, could not detect missing files, indexed extracted content positionally,
  and depended on a hardcoded sn.exe path.  Authenticode and NuGet signature
  verification now cover every produced package.

Step and job logic lives in scripts with Pester coverage rather than inline YAML,
matching compute-versions and publish-symbols.
1ES auto-injects the RoslynAnalyzers task into any job containing a DotNetCoreCLI build task, and drives the build itself. The validation job's only compile is PackageValidator, a build-time tool that never ships, and the injected run is launched from the host where the container's dotnet does not exist, so it failed with exit code 17 and failed the job even though package validation passed.
Addresses review feedback on PR #4655.

The JSON-report invocation of PackageValidator ignored its exit code, so a
validator crash produced a misleading report artifact and let the gated run
obscure the real cause.  Capture and check the code before writing the
success message or reaching the gate.

FailOn arrives from the pipeline as a single comma-joined token, which
PowerShell -File argument mode does not split into an array.  Normalize the
parameter by splitting, trimming, and dropping empties, and quote the YAML
argument so the value is one token in every invocation mode.
Copilot AI review requested due to automatic review settings September 8, 2026 11:41
@paulmedynski
paulmedynski force-pushed the dev/paul/onebranch-validate-packages branch from 644228f to e87b80a Compare September 8, 2026 11:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The gate currently allows dependency-range and strong-name regressions to pass validation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread eng/pipelines/onebranch/jobs/validate-packages-job.yml Outdated
…dings

Addresses review feedback on PR #4655.

The error severity covers only error-severity findings, so the warning and
info categories this job exists to catch were slipping through the gate.
dependency-inconsistency is a warning, and mismatched family dependency
ranges are precisely what validating the whole drop at once is meant to
find, so gate it on every run.

delay-signed is a warning and unsigned is info.  Non-official builds have no
access to the real strong-name key and are delay-signed by design, so gate
those two on official runs only, alongside package-unsigned.
Copilot AI review requested due to automatic review settings September 8, 2026 11:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes official signing and release gates, while only the non-official path has end-to-end validation evidence.

Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread eng/pipelines/onebranch/jobs/validate-packages-job.yml
Addresses review feedback on PR #4655.

The previous split assumed non-official builds cannot strong-name sign, but
build-buildproj-step.yml downloads netfxKeypair.snk and passes SigningKeyPath
unconditionally, so every OneBranch build signs with the real key.  Run
26251.2 confirms it: 59 of 59 implementation assemblies reported Signed on a
non-official run, with no delay-signed or unsigned findings.

Gating delay-signed and unsigned only on official runs therefore left the one
signing type the pipeline always applies unvalidated on PR builds, where a
regression that drops the key would go unnoticed.  Gate both everywhere.

package-unsigned stays official-only.  NuGet package signing is an ESRP step
condition on shouldSignPackage, so non-official packages genuinely carry no
.signature.p7s.
Copilot AI review requested due to automatic review settings September 8, 2026 13:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Failed report generation can publish an untrusted artifact, and the documented non-official gate differs from the implementation.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

eng/pipelines/onebranch/scripts/validate-packages.ps1:195

  • When the reporting invocation exits nonzero, Set-Content has already created an empty or partial file under $(JOB_OUTPUT). Throwing here does not remove it, so OneBranch may still publish the report even though this block explicitly considers it untrustworthy. Remove the file before throwing, and update the Pester failure test to assert it is absent.

eng/pipelines/onebranch/jobs/validate-packages-job.yml:183

  • The PR description still says delay-signed and unsigned are official-only, but this non-official branch gates both. The implementation and repository design document now agree that OneBranch always strong-name signs, so update the PR description's gating table and prose to reflect these two non-official gates; otherwise release operators are given the wrong policy.
              - delay-signed
              - unsigned
  • Files reviewed: 13/13 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Engineering Use this for issues that are targeted for changes in the 'eng' folder or build systems.

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

4 participants