Skip to content

release: qualify NuGet packages before GitHub Packages publication #227

Description

@smiggleworth

Summary

Turn .github/workflows/publish.yml into a release qualification gate, not just a pack-and-push job. Keep assembly identity fixed at 1.0.0 while proving the computed 1.0.x package is buildable, installable, reopen-compatible, and traceable before anything is published or tagged.

Evidence

Audited Pants baseline: 28585a10551d9c636f2c945ec39d4f21aab6f1f3 (origin/main).

The current workflow correctly delegates version computation to version.yml, packs Cntryl.Pants and Cntryl.Pants.DependencyInjection, pushes them to GitHub Packages, and then creates a tag. It does not currently establish a complete pre-publication gate:

  • formatting and the full Release test/provider/compatibility lanes are not run in the publish workflow;
  • package contents and NuGet metadata are not inspected;
  • a fresh consumer does not install both packages from the produced artifacts;
  • no persistence reopen smoke proves the packed bits work;
  • package version versus fixed assembly/file identity is not asserted;
  • the two-package partial-publication/retry contract is not documented or verified.

Requirements

Immutable version and identity

  • Invoke the reusable version.yml once and use its exact output for every package, artifact, readback, and tag.
  • Keep AssemblyVersion constant at 1.0.0.0 (the intended 1.0.0 assembly identity) while allowing PackageVersion to advance only within 1.0.x.
  • Define and test the intended FileVersion and InformationalVersion, including commit provenance.
  • Fail before publishing if the computed version is malformed, already maps to a conflicting tag, changes major/minor, or differs between projects.

Pre-publication qualification

Before the first package push, either run or require successful checks for the exact commit being published:

Do not treat a green workflow on a different SHA as release evidence.

Package quality

  • Add complete NuGet metadata for both packages: description, repository URL/commit, license expression or packaged license, README, tags, authors/ownership, and package project URL as appropriate.
  • Inspect each .nupkg and fail on missing metadata, wrong target framework, unexpected dependencies, duplicate assemblies, test/build output, bin/obj, secrets, or internal qualification artifacts.
  • Produce symbols/source-link artifacts if that is the repository's selected package policy, and validate them consistently.
  • Verify the core and DI packages declare the exact intended dependency relationship and version.

Fresh-consumer smoke

  • Create a temporary consumer outside the repository and configure a temporary local NuGet source containing only the just-built artifacts plus normal public dependencies.
  • Disable reliance on already-built project references and clear/isolate package caches enough to prove the package artifacts are consumed.
  • Install both packages, compile with warnings as errors, configure Pants through the idiomatic DI/options API, open a local database, write/commit/read, dispose, reopen, and read again.
  • Assert the loaded assembly identity remains 1.0.0.0 while the installed NuGet package version equals the computed 1.0.x version.

Publication and recovery

  • Grant only the workflow permissions required for package publication and tag creation.
  • Publish only after all qualification completes.
  • Make reruns safe when one of the two package pushes succeeded: read back exact package/version state, accept only byte-identical already-published artifacts, and fail on a conflict.
  • Create/push the release tag only after both package versions are confirmed present and associated with the exact commit.
  • Perform post-publish readback from GitHub Packages and a clean consumer restore before declaring success.
  • Document how to retry a partial publication and how to handle the immutable bad-package case; do not imply a package can be overwritten.

Acceptance criteria

  • A deliberately failing format/test/compatibility/consumer check prevents both package pushes and tag creation.
  • The workflow proves package version 1.0.x and assembly identity 1.0.0.0 for both assemblies.
  • Both packages contain complete, correct, inspectable NuGet metadata and no unintended files.
  • A fresh external consumer installs core plus DI from the built packages and passes write/read/dispose/reopen/read.
  • All release evidence is tied to the exact published commit SHA and computed version.
  • A simulated second-package push failure can be rerun safely without overwriting or silently accepting different bytes.
  • The tag is created only after readback confirms both packages.
  • Post-publish clean restore/install succeeds from GitHub Packages using least-privilege credentials.
  • No secret appears in logs or uploaded artifacts.
  • The workflow remains GitHub Packages-only; no NuGet.org publication is introduced.
  • Workflow syntax, local package validation, formatting, Release build/tests, Sqrzl, and compatibility qualification pass.

Suggested TDD/implementation sequence

  1. Extract local scripts/tests that validate computed version and assembly identity.
  2. Add deterministic package-content/metadata assertions.
  3. Add the temporary fresh-consumer persistence smoke.
  4. Make the publish job depend on exact-SHA qualification.
  5. Implement idempotent two-package publication/readback and tag ordering.
  6. Exercise failure paths with workflow tests or a non-publishing dry run.

Dependencies and non-goals

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:governanceRepository policy, review, and merge controlsenhancementNew feature or requestpriority:P2Normal: bounded hardening, coverage, maintainability, or non-critical behavior gap

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions