ci (release): Refactor release workflow and address readiness assessment topics - #371
ci (release): Refactor release workflow and address readiness assessment topics#371turbobobbytraykov wants to merge 13 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR strengthens the IgniteUI.Blazor.Lite release pipeline by splitting the release workflow into least-privilege jobs, adding supply-chain evidence generation (SBOM + attestations), introducing enforced bundle-size budgets, and publishing readiness documents (accessibility, performance, nullable plan). It also improves NuGet package provenance metadata and pins signing identities in-repo.
Changes:
- Refactors the GitHub release workflow into isolated build/sign/pack/evidence/SBOM/publish/attach jobs with digest-verified handoffs.
- Adds enforced static web asset bundle budgets plus reporting (
eng/Check-BundleBudget.ps1,eng/bundle-budgets.json) and publishes related docs. - Improves NuGet provenance and metadata (
Authors, repository URL publishing, embed sources), and documents verification steps in README/CHANGELOG.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/IgniteUI.Blazor.Lite.csproj |
Adds NuGet authors + repository/source metadata; clarifies nullable opt-out tracking. |
README.md |
Documents release verification and links to new readiness/perf/nullable/accessibility docs. |
eng/IG.publickey.hex |
Pins the strong-name public key used for assembly identity validation. |
eng/IG.authenticode-certificates.sha256 |
Adds an allowlist of approved Authenticode signing cert fingerprints. |
eng/Check-BundleBudget.ps1 |
Implements bundle measurement + budget enforcement + release evidence reporting. |
eng/bundle-budgets.json |
Defines bundle groups/totals and budget thresholds used by the checker. |
docs/performance.md |
Publishes performance budget policy and local reproduction steps. |
docs/nullable-migration-plan.md |
Documents staged plan to re-enable nullable analysis for the shipped library. |
docs/accessibility-conformance.md |
Publishes WCAG conformance claim, scope, verification approach, and known failures. |
CHANGELOG.md |
Records new release evidence, signing/provenance changes, and breaking strong-name signing. |
.gitignore |
Ignores artifacts/ produced by release evidence jobs/scripts. |
.github/workflows/igniteui-blazor-lite-release.yml |
New multi-job release workflow with signing, provenance checks, SBOM + attestations, and release attachments. |
.github/scripts/verify-strong-name.ps1 |
Validates strong-name signing against a pinned public key (not just sn -vf). |
.github/scripts/Assert-NuspecRepository.ps1 |
Fails release if nuspec provenance metadata is missing/incorrect. |
.config/sbom-tool/dotnet-tools.json |
Pins sbom-tool via a dedicated tool manifest for the SBOM job. |
Suppressed comments (3)
.github/workflows/igniteui-blazor-lite-release.yml:244
actions/download-artifactis extracting thesigned-assembliesartifact intosrc/, but the artifact paths already start withsrc/...(src/bin/**,src/obj/**,src/wwwroot/**). This will typically createsrc/src/..., causingdotnet pack --no-buildto use the unsigned/unbuilt checkout outputs instead of the downloaded signed ones.
- name: Download signed assemblies
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: signed-assemblies
path: src
digest-mismatch: error
.github/workflows/igniteui-blazor-lite-release.yml:358
- The
evidencejob downloadsbuild-outputintosrc/, but the artifact itself containssrc/wwwroot/**. This will typically extract tosrc/src/wwwroot, whileeng/Check-BundleBudget.ps1expects assets undersrc/wwwroot(fromeng/bundle-budgets.json). That mismatch will make the budget check fail even when the build produced assets.
- name: Download build output
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: build-output
path: src
digest-mismatch: error
eng/Check-BundleBudget.ps1:151
- Same rounding issue for totals: comparing
result.RawKiB/result.GzipKiB(rounded) can let a total exceed its budget without failing the build. Use$raw/$gzipbyte totals for the enforcement condition.
if ($result.RawKiB -gt $total.maxRawKiB) {
$problems += "Total '$($total.id)' is $($result.RawKiB) KiB raw, over its $($total.maxRawKiB) KiB budget."
}
if ($null -ne $total.maxGzipKiB -and $result.GzipKiB -gt $total.maxGzipKiB) {
$problems += "Total '$($total.id)' is $($result.GzipKiB) KiB gzipped, over its $($total.maxGzipKiB) KiB budget."
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…d refactor comments
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 15 changed files in this pull request and generated 3 comments.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
docs/accessibility-conformance.md:34
- This states that the axe and keyboard suites currently run and gate releases, but the status section below says neither suite exists yet. Describe these layers in future tense so consumers do not mistake planned verification for completed evidence.
1. **Automated scanning.** An axe-core scan runs over every component in the Playwright integration suite, asserting the `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`, and `wcag22aa` rule sets. It gates pull requests and the release, and the resulting report is attached to the GitHub release as evidence.
2. **Keyboard operation.** Covered by the same suite: tab order, roving tab stops, arrow-key navigation, activation, and focus restoration.
3. **Screen reader smoke testing.** Manual, once per major release, against the matrix below.
docs/performance.md:32
- The generated files do not match exactly one pattern: for example, an
app.<hash>.bundle.jsmatches bothapp.*.bundle.jsand the later*.bundle.jscatch-all. The checker intentionally assigns the first match, so document that ordering rule instead of claiming uniqueness.
Bundle filenames are content-hashed, so budgets are expressed as patterns rather than filenames. Every produced file must match exactly one group — an asset that matches none fails the check, so a new bundle cannot enter the package without someone budgeting for it.
.github/workflows/igniteui-blazor-lite-release.yml:226
- The pack job is also placed in the NuGet publishing environment while holding
id-token: write. NuGet's OIDC policy matches repository/workflow/ref/environment claims rather than the job name, so this job can mint the same short-lived publish credential as the nominal publish-only job. Move package signing to a separate environment and keepnuget-org-publishexclusive to the final job.
environment: nuget-org-publish
permissions:
contents: read
id-token: write
| - name: Restore strong-name key | ||
| shell: pwsh | ||
| env: | ||
| STRONG_NAME_KEY_BASE64: ${{ secrets.IG_STRONG_NAME_KEY }} |
#365 Pull requests are gated by dependency-review (fails on High and above). The release scans what it ships and records the report as a release asset, but stays advisory so a finding never holds up a publish. PR #365 enables nullable analysis outright and makes the staged migration plan moot, so the doc and its references are removed and the csproj nullable block is left exactly as master has it to keep that PR merging cleanly.
…ng the release evidence
There was a problem hiding this comment.
🟡 Changes recommended
Scanner failures can pass as advisory results, and parts of the dependency-review gate are misconfigured.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 14/15 changed files
- Comments generated: 4
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
The dependency scan can report false success after command failures, and a write-capable CI job uses a mutable action tag.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
.github/workflows/igniteui-blazor-lite-release.yml:446
- The NuGet scan discards every
dotnet listfailure and then initializesnuget_statusto success. If restore or the advisory query fails (for example, because a feed is unavailable), the job reaches the “No vulnerable shipped dependencies reported” branch even though no successful scan occurred. Capture the restore/query exit status and report a scan error separately from a clean result; whether that error warns or blocks can remain consistent with the intended advisory policy.
dotnet list ./src/IgniteUI.Blazor.Lite.csproj package --vulnerable --include-transitive \
> artifacts/dependency-scan/nuget-vulnerable.txt 2>&1 || true
- Files reviewed: 14/15 changed files
- Comments generated: 1
- Review effort level: Balanced
There was a problem hiding this comment.
🔵 Needs a closer look
Shared OIDC environment scope defeats the intended credential isolation, with additional security and documentation issues requiring correction.
Review details
Suppressed comments (3)
Previously missed (1) — in code that hasn't changed since the last review.
README.md:205
- This README is also packed as the NuGet package README (
IgniteUI.Blazor.Lite.csproj:40,86-88), but these relative links resolve within NuGet.org rather than back to the repository, and thedocs/files are not packed. Use absolute repository URLs so package consumers can open both documents.
- [Accessibility conformance](docs/accessibility-conformance.md) — the WCAG 2.2 AA claim, its scope, how it is verified, and the known unfixed failures.
- [Performance targets and measurements](docs/performance.md) — the enforced bundle size budgets and the runtime targets.
.github/workflows/ci.yml:28
- This new job has a
pull-requests: writetoken but runs checkout through a mutable tag. The release workflow already pins the same v7.0.1 action to an immutable commit; use that pin here so a retargeted tag cannot execute with the write-enabled job token.
uses: actions/checkout@v7.0.1
.github/workflows/igniteui-blazor-lite-release.yml:151
- The signing, packing, and publishing jobs all use the same GitHub environment with
id-token: write. Environment-based GitHub OIDC subjects do not identify the job, and NuGet Trusted Publishing authorizes the repository/workflow/environment combination, so the signing jobs can also request a NuGet publishing credential (and the publish job matches the Azure federation). This defeats the intended credential isolation. Use separate environments and federated identities for Azure signing and NuGet publishing, and restrict each provider's trust policy accordingly.
environment: nuget-org-publish
permissions:
contents: read
id-token: write
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…SHAs for action versions
There was a problem hiding this comment.
🔵 Needs a closer look
The release-critical security workflow requires human review, particularly the remaining OIDC exposure during MSBuild packing.
Review details
Suppressed comments (2)
.github/workflows/igniteui-blazor-lite-release.yml:290
- This
dotnet packstill runs MSBuild project/imported package targets inside a job that hasid-token: writeand thenuget-org-publishenvironment.--no-build --no-restoredoes not prevent Pack targets from executing, so repository or dependency build logic can request the OIDC token intended for Key Vault signing. To preserve the stated least-privilege boundary, create and validate the unsigned nupkg in a credential-free job, then pass only that immutable artifact to a checkout-free package-signing job with Key Vault OIDC access.
- name: Pack NuGet package
run: >
dotnet pack ./src/IgniteUI.Blazor.Lite.csproj
--configuration ${{ env.BUILD_CONFIGURATION }}
--no-build
--no-restore
.github/workflows/ci.yml:15
- The configured action does not currently block any licenses: no
allow-licensesordeny-licensespolicy is supplied, and an undetected license is only reported rather than failed. This comment therefore promises a license gate that the job does not implement; either add an explicit policy or describe only the High/Critical vulnerability gate.
# Blocks a pull request that would introduce a High or Critical advisory, or a
# dependency under a license the package cannot ship. Pushes to master skip it —
# the action needs the two-commit range a pull request gives it.
- Files reviewed: 14/15 changed files
- Comments generated: 0 new
- Review effort level: Balanced
…t of this PR The accessibility conformance and performance documents move to their own pull requests against master, so they can be reviewed as documents rather than as an appendix to a workflow refactor. The README section keeps only the supply chain prose it actually still owns. The 'Verify SBOM output' step and the two SBOM_* budget variables that only it read move to a separate branch: the check needs reworking, and leaving it here would hold the rest of the workflow behind that rework.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Description
Rewrites the
IgniteUI.Blazor.Literelease workflow from a single monolithic job into nine least-privilege, digest-chained jobs, and closes the specific readiness gaps that require a release-pipeline change:actions/attest), bound to the SHA-256 digest of the exact signed package that gets published. Verifiable withgh attestation verify. See Scope changes below — the check that guards SBOM quality has moved out of this PR.RepositoryUrlin nuspec (LP-06/LP-07):RepositoryUrl/RepositoryCommitare now passed explicitly at pack time and asserted against the release tag before the package is signed.0.1.1shipped a<repository>element with a commit but no URL; that class of omission now fails the release instead of shipping.eng/IG.publickey.hex), verified inside the packed nupkg before it's signed. This changesPublicKeyTokenfrom null to7dd5c3163f2cd0cb— see Breaking Changes.eng/bundle-budgets.json) are measured and enforced against exact raw and gzip byte totals every release; rounded KiB values are reporting-only. An over-budget or unbudgeted asset fails the release, the report is attached as release evidence, and the same measured-versus-budget tables are written to the run summary. The document that publishes those budgets and the runtime targets has moved to docs: Publish the performance targets and measurements document #382.dependency-review-action, which fails on a new High or Critical advisory before it can reachmaster. The release additionally scans what it actually ships (dotnet list package --vulnerable --include-transitiveon the library project,npm audit --omit=dev) and attaches the report to the GitHub release. Scanner execution, restore, and malformed-report failures block publishing; only confirmed vulnerability findings are advisory, annotated and recorded without holding up the release.eng/IG.authenticode-certificates.sha256) rather than only "is the signature valid".SignAssembly/build-config/publish credentials no longer share a job — the strong-name key, the Key Vault credential, and the NuGet publish credential are each scoped to the job that needs them. The webpack/npm build is a separate credential-free job, so no JavaScript dependency code ever executes on the runner the strong-name key is written to.--skip-duplicate. A full rerun produces newly signed bytes, so skipping a duplicate would attach this run's SBOM, attestations and checksum to a release whose published package is a different build. A push NuGet.org accepted but whose response was lost is reported as exactly that — the existence probe retries with a short backoff, because the flat container lags a push by seconds to minutes — and the evidence is then attached manually from the retained artifacts.Scope changes
Three things that were previously in this PR have been split out, so each can be reviewed on its own terms rather than as an appendix to a workflow refactor. Commit
4c748a7removes them here.docs/accessibility-conformance.mddocs/performance.mdeng/bundle-budgets.jsonandeng/Check-BundleBudget.ps1, which arrive here — so #382 should merge after this PR, or those two files should move into it.Verify SBOM outputstep and the twoSBOM_*variables only it readImportant
#383 leaves a real gap in this PR, and it should be weighed rather than waved through. The dev-dependency leakage that produced a 589-component SBOM in
0.1.2-alpha.0is fixed here — the scopeddotnet restore,npm ci --omit=dev, and thepackage-lock.jsondeletion that defeatssbom-tool's lockfile detector are all in this PR. What is no longer here is the guard: nothing now fails the release if that fix regresses. The regression would ship a signed, attested SBOM describing the wrong dependency graph, and the run would be green.The reason the step is not simply kept as-is: it asserts SBOM quality through a hand-maintained blocklist of build-only package names, a component ceiling of 150, and a declared-license floor set below the measured value. Each proxy fails in a different direction — the blocklist only catches names somebody thought to add, the ceiling is one constant spanning two ecosystems, and a floor below the measurement cannot detect a drop toward it. Keeping it would mean shipping a gate whose green result does not mean what it appears to. #383 carries the reasoning and the candidate replacements.
If reviewers would rather have the imperfect gate than none, the correct action is to merge #383 into this branch before this PR merges, not to leave it deferred.
Explicitly out of scope, tracked separately, not touched here: automated accessibility scanning (axe-core) and its CI wiring — deferred to a separate branch per prior agreement;
IsTrimmable/AOT matrix (#348, being closed by #359); nullable analysis (#347, being closed by #365); threat model (#344, being closed by #328); callback awaiting and disposal (#338/#334, being closed by #340); interop surface refactor (#346); release-checklist governance (#349); the upstreamigniteui-webcomponentsradio-group defect (#336).Motivation / Context
Triggered by an external readiness assessment of the
IgbRadioGroup/IgbRadiocomponent and the0.1.1package, which flagged (among others) missing SBOM/provenance, an incomplete nuspec, no strong-name signing, no performance budget, and no release-time dependency scanning as release-blocking gaps. Reference issues: #341 (strong-name), #342 (SBOM), #343 (provenance), #344 (threat model), #346 (interop), #347 (nullable), #348 (trimming), #349 (release checklist), #336 (radio-group defect, unrelated to this PR).The initial implementation was validated end-to-end against three real prerelease tags (
0.1.2-alpha.0,0.1.2-alpha.1, and a re-run ofalpha.1) rather than assumed correct from a local build — see How Has This Been Tested, which documents two real defects the live runs caught and how each was fixed.Type of Change (check all that apply):
Component(s) / Area(s) Affected:
Release pipeline (
.github/workflows/igniteui-blazor-lite-release.yml), pull-request CI (.github/workflows/ci.yml), package metadata (src/IgniteUI.Blazor.Lite.csproj), supply-chain tooling (eng/,.config/sbom-tool/,.github/scripts/), documentation (README.md,CHANGELOG.md). No component (IgbButton,IgbGrid, etc.) source changed.How Has This Been Tested?
Not unit tests in the conventional sense — this is release-pipeline infrastructure, verified by driving it end to end against real prerelease tags and by reproducing the exact prior defects locally before trusting the fix.
Local, before any tag was cut:
dotnet packreproduced the0.1.1LP-06 defect on purpose (-p:PublishRepositoryUrl=false -p:RepositoryUrl=), confirmed.github/scripts/Assert-NuspecRepository.ps1catches it, then confirmed the correct invocation produces a nuspec with bothurlandcommit.eng/Check-BundleBudget.ps1run against a realnpm run buildoutput; budgets ineng/bundle-budgets.jsonseeded from measured sizes (not estimated), then re-verified at the exact boundary: 460 KiB passes, while 460 KiB + 40 bytes fails even though both values round to 460.0 KiB in the report. An unbudgeted asset also fails closed.sbom-toolrun locally against the repo to confirm the production-only npm/NuGet component set before trusting it in CI.Three real releases, inspected via
gh run view/gh release view/ downloaded artifacts, not just "workflow went green":0.1.2-alpha.0npm ciinstalled devDependencies (webpack, typescript, tslint, etc.);-bcscanned the whole repo.NET restoreto the library project only;npm ci --omit=dev0.1.2-alpha.1(1st run)--omit=devsbom-tool'sNpmWithRootsdetector readspackage-lock.jsondirectly, which records the entire dependency graph (includingdev: trueentries) regardless of what's installedpackage-lock.jsonafternpm ci --omit=dev, so thenode_moduleswalk is the only npm source0.1.2-alpha.1(re-run)gh attestation verify, validdotnet nuget verify, and a nuspec carrying bothurlandcommitThose three runs were executed with the
Verify SBOM outputstep present, so the pipeline evidence above describes a workflow that had it. The current branch does not. The generation-side fixes the runs proved are unchanged; the assertion that would have failed the release is what moved to #383. The YAML was re-parsed after the removal to confirm thesbomjob is still valid and thatGenerate SBOMsnow runs straight intoReverify package before attestation.The external ClearlyDefined license-lookup (
-li true) was removed after measuring it: it resolved 0 components across all three runs while costing ~4 minutes per run, and the declared-license percentage it was meant to improve is unaffected (comes entirely from local NuGet metadata via-pm true). Can be reinstated if a mandatory requirement for npm license attribution appears.Test Configuration:
global.jsonpinned SDK; library multi-targets net8.0/net9.0/net10.0)windows-latestGitHub Actions runners (signing requires Authenticode/strong-name tooling);attach-to-releaseruns onubuntu-latestScreenshots / Recordings
N/A — no UI change.
Checklist:
README.MDCHANGELOG.MDupdates for newly added functionalityBreaking change detail
Shipped assemblies are now strong-name signed.
PublicKeyTokenmoves fromnullto7dd5c3163f2cd0cb. Any consumer with an explicit fully-qualified assembly reference or a binding redirect toIgniteUI.Blazor.Liteneeds updating. Documented inCHANGELOG.md; first shipped in0.1.2-alpha.1and confirmed present in the published assemblies (sn -Tp/AssemblyName.GetPublicKeyToken()match the pinned key on all three TFMs).Files changed
13 files, +1392/-36 vs
origin/master(excludes prior.NET/npm dependency updates already onmaster):.github/workflows/igniteui-blazor-lite-release.yml— full rewrite:build-assets → build → sign-assemblies → pack → {evidence, sbom, dependency-scan} → publish → attach-to-release..github/workflows/ci.yml—dependency-reviewjob on pull requests, failing on High and above..github/scripts/{verify-strong-name.ps1, Assert-NuspecRepository.ps1}— new, pack-time gates..config/sbom-tool/dotnet-tools.json— nested tool manifest, kept out of the rootdotnet tool restoreused by signing.eng/{IG.publickey.hex, IG.authenticode-certificates.sha256}— pinned identities.eng/{Check-BundleBudget.ps1, bundle-budgets.json}— performance evidence and enforced budgets. The document describing them is docs: Publish the performance targets and measurements document #382.src/IgniteUI.Blazor.Lite.csproj—Authors,PublishRepositoryUrl,EmbedUntrackedSources.README.md,CHANGELOG.md,.gitignore— a "Supply chain" section covering SBOM, attestations and how to verify a downloaded package; release notes;artifacts/ignored.Closes #341, #342, #343