feat(ci): reusable .NET CI and NuGet release workflows #3

Merged
KofTwentyTwo merged 2 commits from feat/dotnet-workflows into main 2026-10-09 19:36:50 +00:00
KofTwentyTwo commented 2026-10-09 14:57:12 +00:00 (Migrated from github.com)

What

  • dotnet.yml (reusable): locked restore, zero-warning build, tests with a per-package line-coverage gate, the Kingsrook format check (dotnet format --verify-no-changes --severity warn), optional FlaUI UI tests and pack. Callers named ci get ci / build-test, ci / format, ci / ui-tests.
  • release-nuget.yml (reusable, tag-triggered by the caller): strict SemVer tag, Release build/test/coverage gates, dotnet pack with the tag version, a CycloneDX 1.6 SBOM per package from its packages.lock.json (Syft, digest-pinned), SHA256SUMS, SLSA build provenance and SBOM attestations, then a draft GitHub release that is published last. Builds run here, so provenance names KofTwentyTwo/standards as the builder (SLSA Build L3).
  • nuget.org push stays in the caller. nuget.org trusted publishing validates the token's repository and job_workflow_ref claims against the package's own repository (source), so a push inside a reusable workflow from this repo would always be rejected. The release-nuget.yml template's publish job downloads the packages, runs gh attestation verify --signer-repo KofTwentyTwo/standards, then pushes via NuGet/login. Policy fields documented in standards/releases.md.
  • Caller templates dotnet-ci.yml and release-nuget.yml; docs (ci-cd required checks and K22-CI-30, releases, README, configs/dotnet adoption steps).
  • Conformance checker: K22-CI-30 now requires a SHA-pinned KofTwentyTwo/standards/.github/workflows/release-*.yml call in every tag workflow; K22-CI-01 expects ci / build-test and ci / format when the repo has a solution.

Why

AppKit and the Windows app template need shared .NET gates and a release path that meets K22-CI-30/31 and K22-REL-01..07 without each repository re-implementing them.

How tested

  • actionlint: clean. zizmor 1.30.1 (default and pedantic) on workflows and templates: no findings.
  • Syft 1.54.1 locally against an AppKit project with a lock file: CycloneDX 1.6 SBOM listing Velopack 1.2.161 and the project reference.
  • Conformance -SelfTest: passed (new release-call and context assertions). Static run against a scratch repo built from the new templates: K22-CI-10/11/13/14/15/30 PASS; with the release call pinned to @main instead of a SHA, K22-CI-30 FAILs as intended.
  • markdownlint 0.23.3: 0 issues; internal links: 0 problems; gitleaks: no leaks.
  • Not yet run in GitHub Actions: the integration test is AppKit's compliance PR calling this branch's head SHA.

Stacked on #2 (which is stacked on #1).

Generated with Claude Code

## What - **`dotnet.yml`** (reusable): locked restore, zero-warning build, tests with a per-package line-coverage gate, the Kingsrook format check (`dotnet format --verify-no-changes --severity warn`), optional FlaUI UI tests and pack. Callers named `ci` get `ci / build-test`, `ci / format`, `ci / ui-tests`. - **`release-nuget.yml`** (reusable, tag-triggered by the caller): strict SemVer tag, Release build/test/coverage gates, `dotnet pack` with the tag version, a CycloneDX 1.6 SBOM per package from its `packages.lock.json` (Syft, digest-pinned), `SHA256SUMS`, SLSA build provenance and SBOM attestations, then a draft GitHub release that is published last. Builds run here, so provenance names `KofTwentyTwo/standards` as the builder (SLSA Build L3). - **nuget.org push stays in the caller.** nuget.org trusted publishing validates the token's `repository` and `job_workflow_ref` claims against the package's own repository ([source](https://github.com/NuGet/NuGetGallery/blob/main/src/NuGetGallery.Services/Authentication/Federated/GitHubTokenPolicyValidator.cs)), so a push inside a reusable workflow from this repo would always be rejected. The `release-nuget.yml` template's `publish` job downloads the packages, runs `gh attestation verify --signer-repo KofTwentyTwo/standards`, then pushes via `NuGet/login`. Policy fields documented in `standards/releases.md`. - Caller templates `dotnet-ci.yml` and `release-nuget.yml`; docs (ci-cd required checks and K22-CI-30, releases, README, configs/dotnet adoption steps). - Conformance checker: K22-CI-30 now requires a **SHA-pinned** `KofTwentyTwo/standards/.github/workflows/release-*.yml` call in every tag workflow; K22-CI-01 expects `ci / build-test` and `ci / format` when the repo has a solution. ## Why AppKit and the Windows app template need shared .NET gates and a release path that meets K22-CI-30/31 and K22-REL-01..07 without each repository re-implementing them. ## How tested - actionlint: clean. zizmor 1.30.1 (default and pedantic) on workflows and templates: no findings. - Syft 1.54.1 locally against an AppKit project with a lock file: CycloneDX 1.6 SBOM listing `Velopack 1.2.161` and the project reference. - Conformance `-SelfTest`: passed (new release-call and context assertions). Static run against a scratch repo built from the new templates: K22-CI-10/11/13/14/15/30 PASS; with the release call pinned to `@main` instead of a SHA, K22-CI-30 FAILs as intended. - markdownlint 0.23.3: 0 issues; internal links: 0 problems; gitleaks: no leaks. - Not yet run in GitHub Actions: the integration test is AppKit's compliance PR calling this branch's head SHA. Stacked on #2 (which is stacked on #1). Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.