Release evidence and package promotion¶
The release pipeline is package-first and fail-closed. A release candidate and every final or post release is built reproducibly from a protected tag, recorded in the Gitea Package Registry, validated through TestPyPI, and only then eligible for public PyPI and Docker Hub promotion.
Required sequence¶
- Record requirements, design impact, verification scope, coverage, known defects, operational impact, and approvals in the private lifecycle record.
- Create a PEP 440 release-candidate version such as
X.Y.Zrc1and push the matching protectedvX.Y.Zrc1tag. The Gitea package workflow builds the wheel and sdist twice under oneSOURCE_DATE_EPOCH, requires identical SHA256 values, runs the full test suite, and publishes the exact pair to the Gitea Package Registry. - Verify the Gitea package evidence artifact and the package listing through
nms git packages. Do not promote a tag whose package record is absent or whose served bytes differ from the build manifest. - Promote the RC tag to GitHub. The
v*rc*trigger publishes the same pair to TestPyPI and validates the bytes served by TestPyPI across Python 3.11, 3.12, and 3.13 and all supported schema fixtures. Iterate withrcNuntil clean. - Create the final protected tag on Gitea and require its final package record to pass the same byte-level verification.
- Copy
.github/RELEASE_EVIDENCE_TEMPLATE.mdinto the public GitHub Release body, set the exact version, copy the Gitea evidence artifact'sdistribution_manifest_sha256, complete every evidence item, and remove all private tracker references. The public workflow rebuilds the manifest and rejects a digest mismatch, missing/unchecked evidence, a version mismatch, or internal evidence. - Publish the non-prerelease GitHub Release. PyPI publication runs in a protected, artifact-only job. A later job downloads the project wheel back from PyPI, hashes the served bytes, and makes that exact wheel the only project payload accepted by service-image builds.
- Core and service images build without registry credentials, run on amd64 and
arm64 under a digest-pinned QEMU helper where needed, and emit CycloneDX
inventories. BuildKit is likewise selected by a reviewed multi-architecture
digest. Protected jobs load the tested Docker archives, verify the requested
platform plus source/version/wheel labels, bind each archive and SBOM hash to
its registry manifest digest, and stage candidate manifests. Every candidate
is pulled by digest and smoked on both platforms before one final fan-in job
verifies all per-architecture evidence and writes version,
latest, and commit-traceability aliases. - Retain the distribution, served-PyPI, candidate-manifest, SBOM, and final promotion evidence artifacts. The post-release workflow rechecks alias digests and runs all seven image identities on both architectures.
RC tags never enter the public PyPI or stable Docker paths. A manual public
workflow dispatch is TestPyPI-only and must execute from protected main.
Manual Docker dispatches build and test only.
Required external controls¶
Repository YAML cannot create environment reviewers, deployment branch rules, protected-tag rules, runner isolation, or environment-scoped secrets. Operators must configure all of the following before enabling publication:
| Environment | Credential | Required deployment policy |
|---|---|---|
testpypi |
TEST_PYPI_TOKEN |
Required reviewer; protected main and protected v*rc*/prerelease tags only |
pypi |
PYPI_TOKEN |
Required reviewer; protected final/post release tags only |
dockerhub-candidate |
DOCKERHUB_USERNAME, DOCKERHUB_TOKEN |
Protected main/testing and protected release tags only |
dockerhub-development |
Docker Hub credentials | Protected main/testing only |
dockerhub-release |
Docker Hub credentials | Required reviewer; protected final/post release tags only |
gitea-package-registry |
GITEA_PACKAGE_USERNAME, GITEA_PACKAGE_TOKEN |
Required reviewer; protected v* tags only |
Remove the corresponding repository-scoped publisher secrets after the
environment secrets are configured. Protect tag creation, restrict environment
administrators, and require the pypi/dockerhub-release reviewer to confirm
the Gitea package-of-record and RC evidence. Provision isolated Gitea runner
labels release-builder (no publisher secret) and release-publisher
(environment access only); do not place either label on a general PR runner.
Reproducibility and digest terminology¶
The Python wheel and sdist are required to reproduce byte for byte under the tag commit timestamp. Container inputs are constrained by the reviewed Python/Alpine multi-architecture index digest, exact direct APK versions, the locked Python graph, checksummed mkcert downloads, source revision labels, and per-image CycloneDX inventories.
Container binary reproducibility is not claimed: Alpine repositories and
transitive build behavior can change independently of this repository. The OCI
manifest digest is the immutable image identity. Tags named sha-<commit> are
commit traceability tags, not immutable objects; all promotion and verification
therefore reads and compares manifest digests.
Within a workflow run, distribution, Docker archive, SBOM, and provenance artifact names include the validated source SHA, GitHub run ID, and run attempt. Downloads are restricted to those exact names, so a rerun cannot consume an earlier attempt's artifact. Candidate registry tags carry the same run token to prevent a same-commit CI/release race. Candidate evidence records the local image ID, requested platform, OCI revision/version/wheel labels, archive SHA256, SBOM SHA256, and the matching digest returned by both the push and registry inspection. The final promotion evidence hashes the candidate-evidence documents themselves so a later artifact substitution cannot silently change the approved identity chain.
Docker Hub does not provide a transaction that atomically updates all aliases. The workflow gates all candidates before the first stable write and promotes only from validated digests, but an external registry failure can still stop a multi-image alias update partway through. A rerun revalidates the candidates and idempotently re-points every alias to the recorded digests; never rebuild or delete a released artifact to repair a partial alias update.
Stable Docker promotion is serialized across releases and rechecks that the
triggering GitHub Release is still the repository's current latest release
immediately before alias writes. An older rerun may verify its immutable version
artifacts, but it cannot move latest* aliases backward.
Lifecycle evidence retained¶
The evidence set covers NASA NPR 7150.2D Chapter 4 lifecycle expectations:
- requirements and acceptance-criteria traceability;
- architecture/design impact and reused-component decisions;
- coding standards, static analysis, tool and dependency versions;
- unit, integration, package, schema-matrix, container, and post-release tests;
- measured coverage or an approved rationale for gaps;
- security checks, known-defect disposition, and reviewer approval;
- delivery manifests containing source, distribution, served-wheel, and OCI digest identities;
- operator documentation, rollback, maintenance, archive, and retirement assessment.
The workflows retain release evidence artifacts for 90 days. The private issue and pull-request record remains the long-term lifecycle archive. Public Release notes contain only the product-facing checklist and must not include private URLs, issue/PR numbers, branch names, or internal commit references.