Release
This page describes the release process for maintainers. A release is the point where the project promises an installable artifact to external users, so the process checks more than "tests passed": it also verifies documentation, package metadata, security scanning, and command-line smoke tests.
For the full push-tag and PyPI publication runbook, see Publishing Releases.
All release preparation must happen on a release development branch. Do not
prepare releases by committing directly to main. Use a branch such as
release-v0.4.1, merge completed issue branches into it, keep GitHub Actions
quiet during ordinary branch work, create or update a draft pull request with
scripts/open-release-pr.sh --base main, and merge to main only after
manually dispatched release validation checks and maintainer review are
complete. See Hierarchical Branch Development And Release Workflow.
Versioning
nats-sinks uses semantic versioning. The 0.x line may still adjust APIs,
but public behavior changes should be documented and called out in
CHANGELOG.md so users can make informed upgrade decisions.
Maintain release readiness during normal development. Every user-visible change
should update the relevant Markdown documentation and add an entry under the
Unreleased section in CHANGELOG.md before the work is considered complete.
When a release is prepared, move those entries into the versioned section for
the new tag and confirm that the README, docs site, examples, package metadata,
and CLI behavior describe the same release.
GitHub Issues are the live backlog. The changelog is not a planning backlog; it
is the record of shipped or staged user-visible behavior. Before preparing a
release, review the issues labeled for the release and confirm each feature
request is assigned, has a sanitized implementation note, has checked
Acceptance Criteria, and has public sanitized Test Plan Evidence plus
Close-Out Evidence comments. See Backlog Management.
Build
python -m build
python scripts/update-dependency-manifests.py --check
scripts/sbom.sh
python scripts/generate-checksums.py dist
twine check dist/*.whl dist/*.tar.gz
Post-Release PyPI Artifact Verification
Pre-release checks prove that the source tree and local build are healthy. After a release, maintainers should also verify the artifact that external users actually install from PyPI. This is a separate QA failsafe: it catches packaging, dependency, metadata, and publication issues that can only be seen after the package is available from the public registry.
Run the maintained local container check after publication:
python scripts/run-pypi-release-container-validation.py --version 0.4.1
To validate the newest public package without naming a version:
python scripts/run-pypi-release-container-validation.py --version latest
To include optional extras that can be checked without private infrastructure:
python scripts/run-pypi-release-container-validation.py \
--version 0.4.1 \
--extras crypto,mysql,oci
The script builds a temporary validation image from
container-registry.oracle.com/os/oraclelinux:9-slim-fips, installs nats-sinks
from PyPI inside that container, verifies that nats_sinks imports from the
container virtual environment, and runs artifact-level smoke checks for CLI
startup, version reporting, public Python imports, configuration validation,
FileSink behavior, metrics snapshot inspection, and the observability CLI.
Generated reports are written under the ignored
.local/pypi-release-validation/reports/ directory.
Expected sanitized output:
PyPI artifact validation status: passed
Requested version: 0.4.1
Installed version: 0.4.1
Report: .local/pypi-release-validation/reports/nats-sinks-pypi-artifact-0_4_1-20260525T120000Z-abc123.json
This post-release check is intentionally local-only. Do not add it to GitHub Actions by default, because it validates public registry state after publication and should run under maintainer control. If the check finds a defect, create a sanitized GitHub bug issue first, attach the minimal reproduction and local evidence, and then fix the issue through the normal test-driven bug workflow.
Release Flow
flowchart TD
Branch[Prepare release branch] --> PR[Open release pull request]
PR --> Validate[Manually dispatch release validation]
Validate --> Review[CI, CodeQL, docs, dependency review, approval]
Review --> Main[Merge reviewed PR into main]
Main --> Checks[Run release checks]
Checks --> Build[Build sdist and wheel]
Build --> SBOM[Generate CycloneDX SBOM]
SBOM --> Checksums[Generate SHA256SUMS]
Checksums --> Verify[twine check]
Verify --> GHAuth[Check local gh authentication]
GHAuth --> TestPyPI[TestPyPI publish]
TestPyPI --> Smoke[Install smoke test]
Smoke --> Tag[Create annotated git tag]
Tag --> Push[Push tag to GitHub]
Push --> Workflow[release.yml builds artifacts]
Workflow --> PyPI[Publish to PyPI]
PyPI --> GitHub[Create GitHub Release and attach artifacts]
The release workflow does not create the git tag. Maintainers create and push
an annotated tag such as v0.1.0 from a commit already merged into main.
The tag push starts
.github/workflows/release.yml; after the package is published to PyPI, the
workflow creates the GitHub Release page from that tag and uploads the built
source distribution, wheel, SHA256SUMS, and CycloneDX SBOM files as release
assets. SBOM files and checksums are release evidence and are not uploaded to
PyPI.
The release workflow validates that the tag commit is contained in main.
This prevents accidental publication from an unmerged release branch.
After the GitHub Release exists, release automation closes open managed backlog
issues labeled for the release tag. For example, an open issue labeled
backlog and release-v0.4.0 closes only after the v0.4.0 GitHub Release
has been created or updated. This keeps feature requests open while work is
merely pending push, merged, or waiting for publication. The close-out helper
also checks that all Acceptance Criteria are ticked and that the issue has
sanitized test-plan and close-out evidence comments before it closes anything.
Documentation publication is handled separately by Read the Docs. After the
one-time Read the Docs project import, pushes to main and release tags build
the documentation using .readthedocs.yaml. The GitHub Actions Docs workflow
builds the MkDocs site before changes are merged so Read the Docs publication
should normally be a confirmation step, not the first time documentation is
tested. See Read the Docs.
The repository can also publish a GitHub Pages mirror of the current main
documentation at
projectcuillin.github.io/nats-sinks
through .github/workflows/pages.yml. GitHub Pages requires one repository
setting before the first deployment: Settings -> Pages -> Source: GitHub
Actions. See GitHub Pages.
The release workflow should also be reviewed when GitHub announces changes to the JavaScript runtime used by GitHub Actions. First-party actions such as artifact upload and artifact download should stay on versions that support the current GitHub-hosted runner runtime. This avoids release annotations caused by deprecated Node.js versions and keeps the publication path predictable for external users.
Local GitHub CLI Authentication
Maintainers commonly use the GitHub CLI to inspect release workflows after a tag is pushed:
gh run list --limit 10
gh run view --log
gh release view v0.3.0
Those commands depend on local gh authentication. They are separate from PyPI
Trusted Publishing: the release workflow itself uses GitHub Actions
permissions and PyPI OIDC, not the maintainer's local token. A stale local
GH_TOKEN, GITHUB_TOKEN, or stored gh credential can still make local
workflow inspection fail after a successful push.
Before pushing a release tag, run:
scripts/check-gh-auth.sh
The helper performs a small authenticated GitHub API probe for github.com
without printing response bodies or token values. If authentication is not valid
and an interactive terminal is available, it asks whether it should start
browser-based login with:
gh auth login --hostname github.com --web
For CI or non-interactive checks, use:
scripts/check-gh-auth.sh --check-only
The helper never prints token values. If it reports that GH_TOKEN or
GITHUB_TOKEN is set, verify that the token is still valid or unset it before
relying on the stored GitHub CLI login.
Checklist
- Confirm the release branch has an open pull request into
main. - Confirm
mainbranch protection requires pull request review and CI. - Confirm
scripts/run-release-validation.shhas dispatched release validation for the release branch. - Confirm the release pull request has maintainer approval before merge.
- Confirm all user-visible changes are represented in Markdown documentation.
- Confirm every user-visible feature has a linked GitHub issue or a documented reason why no issue was needed.
- Confirm release-labeled feature requests are assigned, have an implementation note, have all Acceptance Criteria checked, and include sanitized test-plan and close-out evidence comments.
- Confirm
CHANGELOG.mdhas a complete version section for this release and a cleanUnreleasedsection for future work. - Update
CHANGELOG.md. - Confirm
scripts/check-gh-auth.shbefore pushing release tags so local workflow-status inspection works after the push. - Confirm
ruff format --check .. - Confirm
ruff check .. - Confirm
mypy src. - Confirm
python scripts/check-markdown-links.py. - Confirm
scripts/check-docs.sh. - Confirm
scripts/check-sinks.sh. - Confirm
NATS_SINKS_RUN_CONTAINER_E2E=1 scripts/check-sinks.shwhen Docker and the optional Oracle NoSQL Database and Oracle Coherence clients are available locally; otherwise document the skip indocs/test-report.md. - Confirm whether
python scripts/run-oracle-db-version-matrix.py --listand at least one selected Oracle Database version dry-run or live matrix step should be captured for this release; document any live matrix skips indocs/test-report.md. - Confirm
pytest. - Confirm
bandit -q -r src. - Confirm
python scripts/update-dependency-manifests.py --check. - Confirm
python -m build. - Confirm
scripts/sbom.sh. - Confirm
python scripts/generate-checksums.py dist. - Confirm
twine check dist/*.whl dist/*.tar.gz. - Smoke test
nats-sink --help. - Smoke test
nats-sink validate examples/file-basic/config.json. - Smoke test
nats-sink test-sink examples/file-basic/config.json. - Smoke test
nats-sink validate examples/oracle-jetstream/config.json. - Run deterministic file sink e2e:
pytest tests/integration/test_file_sink_e2e.py. - Run Oracle live integration and NATS-to-Oracle e2e when the required ignored
.localenvironment files are available; otherwise document that they were not run indocs/test-report.md. - Merge the reviewed release pull request into
main. - Create and push an annotated
v*tag frommain. - Confirm the GitHub Release exists and includes the built wheel, source
distribution,
SHA256SUMS, anddist/sbom/*.cyclonedx.*assets. - Run the local PyPI artifact validation container against the published
version:
python scripts/run-pypi-release-container-validation.py --version X.Y.Z. - Confirm release-labeled backlog issues were closed only after the GitHub Release exists and only after acceptance criteria plus evidence comments were present.
- Confirm Read the Docs built
latestor the release tag successfully. - Confirm the GitHub Pages mirror deployed successfully when documentation was
changed on
main. - After PyPI publication, run the local post-release PyPI artifact verification container check when available. Until the harness is implemented, run the manual clean-container smoke check above and document the result in the local release notes.
- If the post-release PyPI artifact verification finds an issue, create a sanitized GitHub bug report and start the agreed test-driven bug workflow before changing code.
Do not hardcode PyPI tokens. Prefer trusted publishing or OIDC.