From c6f1237cf1fb6eb5451d285c8e734555b0dccca9 Mon Sep 17 00:00:00 2001 From: Kent Bull Date: Wed, 25 Mar 2026 17:41:26 -0600 Subject: [PATCH 1/3] adds release workflow pipeline and readme Created by Codex with prompting from Kent Bull Signed-off-by: Kent Bull --- .github/workflows/release.yml | 256 +++++++++++++++++++++++++++ .github/workflows/test.yaml | 2 + Makefile | 30 +++- docs/README.rst | 6 +- docs/changelog.md | 25 +++ docs/index.rst | 2 + docs/releasing.md | 129 ++++++++++++++ newsfragments/.gitignore | 1 + pyproject.toml | 40 +++++ scripts/extract_changelog_section.py | 54 ++++++ src/signify/__init__.py | 21 ++- tests/core/test_versioning.py | 13 ++ uv.lock | 27 +++ 13 files changed, 597 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 docs/changelog.md create mode 100644 docs/releasing.md create mode 100644 newsfragments/.gitignore create mode 100644 scripts/extract_changelog_section.py create mode 100644 tests/core/test_versioning.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a4941c6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,256 @@ +name: Release + +on: + push: + tags: + - "*" + workflow_dispatch: + inputs: + release_tag: + description: "Existing release tag to publish, for example 0.5.0" + required: true + type: string + +concurrency: + group: release-${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }} + cancel-in-progress: false + +env: + PYTHON_VERSION: "3.12.6" + +jobs: + resolve_release: + name: Resolve Release + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.release.outputs.release_tag }} + package_version: ${{ steps.release.outputs.package_version }} + release_sha: ${{ steps.release.outputs.release_sha }} + steps: + - name: Check out release ref + uses: actions/checkout@v4.2.2 + with: + ref: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Resolve tag and package version + id: release + shell: bash + run: | + set -euo pipefail + + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + release_tag="${{ inputs.release_tag }}" + else + release_tag="${{ github.ref_name }}" + fi + + if [[ ! "$release_tag" =~ ^([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + echo "Release tags must use canonical X.Y.Z format." >&2 + exit 1 + fi + + # Python script to parse version from pyproject.toml + package_version="$release_tag" + declared_version="$(python - <<'PY' +import tomllib +from pathlib import Path +with Path("pyproject.toml").open("rb") as handle: + print(tomllib.load(handle)["project"]["version"]) +PY +)" + + runtime_version="$(python - <<'PY' +import sys +from pathlib import Path +sys.path.insert(0, str(Path("src").resolve())) +import signify +print(signify.__version__) +PY +)" + + if [ "$declared_version" != "$package_version" ]; then + echo "pyproject.toml version '$declared_version' does not match tag '$release_tag'." >&2 + exit 1 + fi + + if [ "$runtime_version" != "$package_version" ]; then + echo "Runtime version '$runtime_version' does not match tag '$release_tag'." >&2 + exit 1 + fi + + { + echo "release_tag=$release_tag" + echo "package_version=$package_version" + echo "release_sha=$(git rev-parse HEAD)" + } >> "$GITHUB_OUTPUT" + + assert_ci_green: + name: Require Green Tests + needs: resolve_release + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + steps: + - name: Wait for Tests workflow success on release commit + uses: actions/github-script@v7 + env: + TARGET_SHA: ${{ needs.resolve_release.outputs.release_sha }} + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const workflowId = "test.yaml"; + const targetSha = process.env.TARGET_SHA; + const maxAttempts = 60; + const delayMs = 30000; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const response = await github.rest.actions.listWorkflowRuns({ + owner, + repo, + workflow_id: workflowId, + head_sha: targetSha, + per_page: 100, + }); + + const runs = response.data.workflow_runs || []; + const successfulRun = runs.find((run) => run.conclusion === "success"); + if (successfulRun) { + core.info(`Found successful Tests workflow run ${successfulRun.id} for ${targetSha}.`); + return; + } + + const pendingRun = runs.find((run) => run.status !== "completed"); + if (!pendingRun && runs.length > 0) { + const conclusions = runs.map((run) => `${run.id}:${run.conclusion}`).join(", "); + core.setFailed(`No successful Tests workflow run found for ${targetSha}. Seen runs: ${conclusions}`); + return; + } + + if (attempt === maxAttempts) { + core.setFailed(`Timed out waiting for a successful Tests workflow run for ${targetSha}.`); + return; + } + + core.info(`Attempt ${attempt}/${maxAttempts}: waiting for Tests workflow success on ${targetSha}.`); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + + build_dist: + name: Build Release Artifacts + needs: + - resolve_release + - assert_ci_green + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out release tag + uses: actions/checkout@v4.2.2 + with: + ref: ${{ needs.resolve_release.outputs.release_tag }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: pip + cache-dependency-path: | + pyproject.toml + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "0.6.14" + + - name: Build and validate distributions + run: | + make dist-check + + - name: Smoke install wheel + shell: bash + run: | + set -euo pipefail + python -m venv smoke-venv + ./smoke-venv/bin/python -m pip install --upgrade pip + ./smoke-venv/bin/python -m pip install --no-deps dist/*.whl + ./smoke-venv/bin/python -c "import signify; print(signify.__version__)" + + - name: Upload release artifacts + uses: actions/upload-artifact@v4 + with: + name: release-dist-${{ needs.resolve_release.outputs.release_tag }} + path: dist/* + if-no-files-found: error + + publish_pypi: + name: Publish To PyPI + needs: + - resolve_release + - build_dist + runs-on: ubuntu-latest + permissions: + contents: read + env: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + steps: + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + name: release-dist-${{ needs.resolve_release.outputs.release_tag }} + path: dist + + - name: Check PyPI token availability + shell: bash + run: | + set -euo pipefail + if [ -z "${PYPI_API_TOKEN:-}" ]; then + echo "Missing repository secret PYPI_API_TOKEN." >&2 + echo "Add a project-scoped PyPI token to repository secrets before publishing releases." >&2 + exit 1 + fi + + - name: Publish package distributions + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist + password: ${{ secrets.PYPI_API_TOKEN }} + + publish_github_release: + name: Publish GitHub Release + needs: + - resolve_release + - publish_pypi + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check out release tag + uses: actions/checkout@v4.2.2 + with: + ref: ${{ needs.resolve_release.outputs.release_tag }} + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Extract changelog section + run: | + python scripts/extract_changelog_section.py \ + --version "${{ needs.resolve_release.outputs.package_version }}" \ + --input docs/changelog.md \ + --output release-notes.md + + - name: Create or update GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.resolve_release.outputs.release_tag }} + name: ${{ needs.resolve_release.outputs.release_tag }} + body_path: release-notes.md diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d307293..ab91b4a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,6 +4,8 @@ on: branches: - 'main' - 'development' + tags: + - '*' pull_request: workflow_dispatch: diff --git a/Makefile b/Makefile index 5799ff4..95eaa7e 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ INTEGRATION_WORKERS ?= 2 INTEGRATION_DIST ?= loadscope INTEGRATION_TARGETS ?= tests/integration -.PHONY: help sync test test-fast test-ci test-integration test-integration-ci test-integration-parallel test-integration-parallel-ci build release docs clean +.PHONY: help sync test test-fast test-ci test-integration test-integration-ci test-integration-parallel test-integration-parallel-ci build dist-check release-patch release-minor release-major release-bump docs clean guard-clean-worktree help: ## Show available maintainer tasks @awk 'BEGIN {FS = ":.*## "}; /^[a-zA-Z0-9_-]+:.*## / {printf "%-18s %s\n", $$1, $$2}' $(MAKEFILE_LIST) @@ -51,11 +51,37 @@ test-integration-parallel-ci: ## Run integration tests in parallel with CI-frien build: ## Build source and wheel distributions @$(UV_CACHE) $(UV) build -release: clean build ## Build release artifacts and validate them with twine +dist-check: clean build ## Build release artifacts and validate them with twine @$(UV_CACHE) $(UV_ENV) $(UV) run --with twine twine check dist/* +release-patch: ## Prepare and commit a patch release + @$(MAKE) release-bump BUMP=patch + +release-minor: ## Prepare and commit a minor release + @$(MAKE) release-bump BUMP=minor + +release-major: ## Prepare and commit a major release + @$(MAKE) release-bump BUMP=major + +release-bump: guard-clean-worktree + @VERSION_BEFORE=$$($(UV_CACHE) $(UV_ENV) $(UV) run python -c "import tomllib; from pathlib import Path; print(tomllib.load(Path('pyproject.toml').open('rb'))['project']['version'])"); \ + $(UV) version --bump $(BUMP); \ + VERSION_AFTER=$$($(UV_CACHE) $(UV_ENV) $(UV) run python -c "import tomllib; from pathlib import Path; print(tomllib.load(Path('pyproject.toml').open('rb'))['project']['version'])"); \ + echo "Preparing release $$VERSION_AFTER from $$VERSION_BEFORE"; \ + $(UV_CACHE) $(UV) lock; \ + $(UV_CACHE) $(UV_ENV) $(UV) run --group dev towncrier build --yes --version "$$VERSION_AFTER"; \ + git add -A pyproject.toml uv.lock docs/changelog.md newsfragments src/signify/__init__.py; \ + git commit -m "chore(release): $$VERSION_AFTER" + docs: ## Build the Sphinx documentation @./venv/bin/python -m sphinx -b html docs docs/_build/html +guard-clean-worktree: + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "Release preparation requires a clean git worktree."; \ + git status --short; \ + exit 1; \ + fi + clean: ## Remove build, docs, and test artifacts @rm -rf build dist docs/_build .pytest_cache .ruff_cache .uv-cache src/signifypy.egg-info diff --git a/docs/README.rst b/docs/README.rst index 70d876e..f7946c0 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -9,5 +9,7 @@ This documentation set serves two audiences: - contributors who need the generated API reference for the underlying modules Start with :doc:`maintainer_features` for the current request families, route -ownership, workflow notes, and test coverage. Then use the API reference pages -to drill down into the implementation modules. +ownership, workflow notes, and test coverage. Use :doc:`releasing` for the +maintained PyPI release workflow and :doc:`changelog` for versioned release +notes. Then use the API reference pages to drill down into the implementation +modules. diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..db55ed8 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,25 @@ +# Changelog + +This page tracks SignifyPy release notes. New release entries are generated +from Towncrier fragments during release preparation. + + + +## 0.4.0 + +- Added maintained client accessors for config reads, external request signing, + passcode-save and passcode-delete helpers, and the remaining compatibility + wrappers needed to close the current parity stack. +- Expanded test coverage across fast suites and live integration workflows for + provisioning, identifier rename compatibility, multisig choreography, and + credential exchange paths. +- Refreshed maintainer documentation, parity planning, and harness guidance so + the documented client surface better matches the code and tests. + +## 0.1.1 + +- Published a maintenance follow-up to the initial SignifyPy release line. + +## 0.1.0 + +- Published the first public SignifyPy package release. diff --git a/docs/index.rst b/docs/index.rst index a8763f5..b695cb6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,6 +12,8 @@ Welcome to Signifypy's documentation! README maintainer_features + releasing + changelog API Reference ============= diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 0000000..8e9594e --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,129 @@ +# Releasing SignifyPy + +SignifyPy releases use GitHub Actions and a PyPI API token stored in GitHub. +The package version published to PyPI is always bare semantic versioning like +`0.5.0`. Git tags and GitHub Releases use the same bare `X.Y.Z` format like +`0.5.0`. + +## Release Flow + +1. Land all releasable pull requests with Towncrier fragments under + `newsfragments/`. +2. Prepare the release commit locally with one of: + - `make release-patch` + - `make release-minor` + - `make release-major` +3. Review the generated release-prep commit. It should update: + - `pyproject.toml` + - `uv.lock` + - `docs/changelog.md` + - consumed `newsfragments/` +4. Push the release-prep commit. +5. Create and push the matching Git tag: `X.Y.Z`. +6. Wait for the `Tests` workflow to pass for that tagged commit. +7. Let the release workflow publish automatically once the build and publish + jobs pass. +8. Confirm the workflow created: + - a PyPI release `X.Y.Z` + - a GitHub Release `X.Y.Z` + +## Maintainer Commands + +- `make dist-check` + Builds `sdist` and wheel artifacts and validates them with `twine check`. +- `make release-patch` + Bumps the patch version, rebuilds the changelog, removes consumed release + fragments, refreshes `uv.lock`, and creates a release-prep commit. +- `make release-minor` + Same as above for a minor version bump. +- `make release-major` + Same as above for a major version bump. + +The old `make release` target has been removed. Use `make dist-check` for local +artifact validation and `make release-*` for release preparation. + +## Changelog Fragments + +Every releasable change should land with one Towncrier fragment in +`newsfragments/`. + +Use the fragment filename format: + +- `..md` + +Valid fragment types are: + +- `added` +- `changed` +- `fixed` +- `removed` +- `doc` +- `misc` + +Example: + +```text +138.fixed.md +``` + +The fragment body should be a short human-facing sentence or two. The release +prep targets collect those fragments into `docs/changelog.md` and remove the +consumed fragment files. + +## CI Release Entry Points + +SignifyPy supports two release entry points: + +- Push a canonical Git tag `X.Y.Z`. +- Run the `Release` workflow manually with `release_tag=X.Y.Z`. + +The manual workflow is for publishing an existing tag. It does not create tags, +edit versions, or rewrite release notes. + +## CI Release Guards + +The release workflow refuses to publish unless: + +- the tag is canonical `X.Y.Z` +- `pyproject.toml` and `signify.__version__` both resolve to bare `X.Y.Z` +- the normal `Tests` workflow has passed for the same commit +- a changelog section exists for `X.Y.Z` + +## GitHub And PyPI Setup + +PyPI publishing currently depends on a project-scoped API token for the +`signifypy` package: + +- create the token from a PyPI owner or maintainer account that can manage the + `signifypy` project +- scope it to the `signifypy` project if possible +- store it as the `PYPI_API_TOKEN` repository secret on `signifypy` +- let `.github/workflows/release.yml` read it from repository secrets at + publish time + +Do not copy this token into `.pypirc`, workflow YAML, or personal shell +history. Treat it as rotation-managed operational state owned by repository +secrets. + +Trusted Publishing is not the active path right now because package-manager +access is not available for this package. If that changes later, migrating back +to OIDC would be preferable. + +## Troubleshooting Publish Auth + +If the publish step reports invalid authentication: + +- verify the token still exists on PyPI +- verify it is scoped to `signifypy` rather than to an unrelated project +- verify the stored secret includes the full `pypi-...` token value +- verify it is stored as the `PYPI_API_TOKEN` repository secret + +## Recovery Rules + +- Never overwrite an existing PyPI version. +- If PyPI accepted `X.Y.Z`, treat that release number as permanently consumed. +- If the publish workflow fails after PyPI accepts the version, fix forward + with a new version and a new `X.Y.Z` tag. +- If the workflow fails before publish, fix the problem on a new commit, + prepare the correct version again if needed, and push a corrected canonical + tag. diff --git a/newsfragments/.gitignore b/newsfragments/.gitignore new file mode 100644 index 0000000..f935021 --- /dev/null +++ b/newsfragments/.gitignore @@ -0,0 +1 @@ +!.gitignore diff --git a/pyproject.toml b/pyproject.toml index 9c63a50..d1e08d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ dev = [ "myst-parser>=0.16.1", "sphinx>=4.3.2", "sphinx-rtd-theme>=1.2.2", + "towncrier>=24.8.0", ] [project.scripts] @@ -94,3 +95,42 @@ include = [ [tool.uv] default-groups = ["dev"] + +[tool.towncrier] +package = "signify" +package_dir = "src" +directory = "newsfragments" +filename = "docs/changelog.md" +start_string = "\n" +title_format = "## {version}" +issue_format = "[#{issue}](https://github.com/WebOfTrust/signifypy/pull/{issue})" + +[[tool.towncrier.type]] +directory = "added" +name = "Added" +showcontent = true + +[[tool.towncrier.type]] +directory = "changed" +name = "Changed" +showcontent = true + +[[tool.towncrier.type]] +directory = "fixed" +name = "Fixed" +showcontent = true + +[[tool.towncrier.type]] +directory = "removed" +name = "Removed" +showcontent = true + +[[tool.towncrier.type]] +directory = "doc" +name = "Documentation" +showcontent = true + +[[tool.towncrier.type]] +directory = "misc" +name = "Miscellaneous" +showcontent = true diff --git a/scripts/extract_changelog_section.py b/scripts/extract_changelog_section.py new file mode 100644 index 0000000..af21258 --- /dev/null +++ b/scripts/extract_changelog_section.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Extract a version section from docs/changelog.md.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + + +def extract_section(text: str, version: str) -> str: + heading = f"## {version}" + lines = text.splitlines() + start = None + end = None + + for index, line in enumerate(lines): + if line.strip() == heading: + start = index + continue + if start is not None and line.startswith("## "): + end = index + break + + if start is None: + raise ValueError(f"Version section '{heading}' not found.") + + if end is None: + end = len(lines) + + section = "\n".join(lines[start:end]).strip() + if not section: + raise ValueError(f"Version section '{heading}' is empty.") + + return section + "\n" + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--version", required=True, help="Bare package version like 0.5.0") + parser.add_argument("--input", required=True, help="Path to changelog markdown") + parser.add_argument("--output", required=True, help="Path to write the extracted section") + args = parser.parse_args() + + if args.version.startswith("v"): + raise ValueError("Expected a bare package version without the 'v' prefix.") + + input_path = Path(args.input) + output_path = Path(args.output) + output_path.write_text(extract_section(input_path.read_text(), args.version)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/signify/__init__.py b/src/signify/__init__.py index 520831d..fe44a9f 100644 --- a/src/signify/__init__.py +++ b/src/signify/__init__.py @@ -1,7 +1,18 @@ -# -*- encoding: utf-8 -*- -""" -main package -""" +"""Signify package metadata.""" -__version__ = '0.4.0' # also change in setup.py +from importlib.metadata import PackageNotFoundError, version +from pathlib import Path +import tomllib + +def _source_tree_version() -> str: + """Read the package version from pyproject.toml in a source checkout.""" + pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml" + with pyproject.open("rb") as handle: + return tomllib.load(handle)["project"]["version"] + + +try: + __version__ = version("signifypy") +except PackageNotFoundError: + __version__ = _source_tree_version() diff --git a/tests/core/test_versioning.py b/tests/core/test_versioning.py new file mode 100644 index 0000000..42b6aae --- /dev/null +++ b/tests/core/test_versioning.py @@ -0,0 +1,13 @@ +from pathlib import Path +import tomllib + +from signify import __version__ + + +def test_package_version_matches_pyproject(): + pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml" + with pyproject.open("rb") as handle: + declared_version = tomllib.load(handle)["project"]["version"] + + assert __version__ == declared_version + assert not __version__.startswith("v") diff --git a/uv.lock b/uv.lock index b6f45ce..15a2130 100644 --- a/uv.lock +++ b/uv.lock @@ -286,6 +286,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -1361,6 +1373,7 @@ dev = [ { name = "responses" }, { name = "sphinx" }, { name = "sphinx-rtd-theme" }, + { name = "towncrier" }, ] [package.metadata] @@ -1393,6 +1406,7 @@ dev = [ { name = "responses", specifier = ">=0.25.6" }, { name = "sphinx", specifier = ">=4.3.2" }, { name = "sphinx-rtd-theme", specifier = ">=1.2.2" }, + { name = "towncrier", specifier = ">=24.8.0" }, ] [[package]] @@ -1531,6 +1545,19 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cf/6c/fa601216344952be8f9a51a3f49b4274bbbc58bd395f87f67f6131726358/sseclient-0.0.27.tar.gz", hash = "sha256:b2fe534dcb33b1d3faad13d60c5a7c718e28f85987f2a034ecf5ec279918c11c", size = 7465, upload-time = "2020-09-25T02:46:41.997Z" } +[[package]] +name = "towncrier" +version = "25.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/eb/5bf25a34123698d3bbab39c5bc5375f8f8bcbcc5a136964ade66935b8b9d/towncrier-25.8.0.tar.gz", hash = "sha256:eef16d29f831ad57abb3ae32a0565739866219f1ebfbdd297d32894eb9940eb1", size = 76322, upload-time = "2025-08-30T11:41:55.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl", hash = "sha256:b953d133d98f9aeae9084b56a3563fd2519dfc6ec33f61c9cd2c61ff243fb513", size = 65101, upload-time = "2025-08-30T11:41:53.644Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From a87365bca9a9da4aea3fae27d199052af4526e75 Mon Sep 17 00:00:00 2001 From: Kent Bull Date: Wed, 25 Mar 2026 17:48:57 -0600 Subject: [PATCH 2/3] add newsfragments for 0.4.1 --- docs/releasing.md | 55 +++++++++++++++++++++++++++++++++++--- newsfragments/137.fixed.md | 1 + newsfragments/138.fixed.md | 1 + 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 newsfragments/137.fixed.md create mode 100644 newsfragments/138.fixed.md diff --git a/docs/releasing.md b/docs/releasing.md index 8e9594e..5448f2d 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -70,6 +70,57 @@ The fragment body should be a short human-facing sentence or two. The release prep targets collect those fragments into `docs/changelog.md` and remove the consumed fragment files. +## Towncrier Examples + +Create a fragment for PR or issue `100` using the configured SignifyPy types: + +```bash +cd /Users/kbull/code/keri/kentbull/signifypy +./venv/bin/python -m towncrier create --dir newsfragments \ + --content "Documented the repository-secret PyPI publish flow." \ + 100.doc.md +``` + +Another example for a bug fix: + +```bash +cd /Users/kbull/code/keri/kentbull/signifypy +./venv/bin/python -m towncrier create --dir newsfragments \ + --content "Fixed release workflow auth to use the repository secret PYPI_API_TOKEN." \ + 101.fixed.md +``` + +You can also create the fragment file yourself if that is faster: + +```bash +cd /Users/kbull/code/keri/kentbull/signifypy +cat > newsfragments/102.misc.md <<'EOF' +Clarified the maintainer release runbook with concrete Towncrier examples. +EOF +``` + +Preview the unreleased changelog without modifying tracked files: + +```bash +cd /Users/kbull/code/keri/kentbull/signifypy +./venv/bin/python -m towncrier build --draft --version 0.4.1 +``` + +Build the actual `0.4.1` changelog entry during release preparation: + +```bash +cd /Users/kbull/code/keri/kentbull/signifypy +./venv/bin/python -m towncrier build --yes --version 0.4.1 +``` + +Or let the maintained release-prep target do the version bump, lock refresh, +Towncrier build, and release commit together: + +```bash +cd /Users/kbull/code/keri/kentbull/signifypy +make release-patch +``` + ## CI Release Entry Points SignifyPy supports two release entry points: @@ -105,10 +156,6 @@ Do not copy this token into `.pypirc`, workflow YAML, or personal shell history. Treat it as rotation-managed operational state owned by repository secrets. -Trusted Publishing is not the active path right now because package-manager -access is not available for this package. If that changes later, migrating back -to OIDC would be preferable. - ## Troubleshooting Publish Auth If the publish step reports invalid authentication: diff --git a/newsfragments/137.fixed.md b/newsfragments/137.fixed.md new file mode 100644 index 0000000..b0a1e7b --- /dev/null +++ b/newsfragments/137.fixed.md @@ -0,0 +1 @@ +Added boot agent validation on connect. diff --git a/newsfragments/138.fixed.md b/newsfragments/138.fixed.md new file mode 100644 index 0000000..672ef3a --- /dev/null +++ b/newsfragments/138.fixed.md @@ -0,0 +1 @@ +Fixed Randy (random) key manager implementation From 7486ce104b572b4022190a16a51ec4f5f50619a8 Mon Sep 17 00:00:00 2001 From: Kent Bull Date: Wed, 25 Mar 2026 17:49:02 -0600 Subject: [PATCH 3/3] chore(release): 0.4.1 --- docs/changelog.md | 8 ++++++++ newsfragments/137.fixed.md | 1 - newsfragments/138.fixed.md | 1 - pyproject.toml | 2 +- uv.lock | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) delete mode 100644 newsfragments/137.fixed.md delete mode 100644 newsfragments/138.fixed.md diff --git a/docs/changelog.md b/docs/changelog.md index db55ed8..e8deaca 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,14 @@ from Towncrier fragments during release preparation. +## 0.4.1 + +### Fixed + +- Added boot agent validation on connect. ([#137](https://github.com/WebOfTrust/signifypy/pull/137)) +- Fixed Randy (random) key manager implementation ([#138](https://github.com/WebOfTrust/signifypy/pull/138)) + + ## 0.4.0 - Added maintained client accessors for config reads, external request signing, diff --git a/newsfragments/137.fixed.md b/newsfragments/137.fixed.md deleted file mode 100644 index b0a1e7b..0000000 --- a/newsfragments/137.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Added boot agent validation on connect. diff --git a/newsfragments/138.fixed.md b/newsfragments/138.fixed.md deleted file mode 100644 index 672ef3a..0000000 --- a/newsfragments/138.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fixed Randy (random) key manager implementation diff --git a/pyproject.toml b/pyproject.toml index d1e08d8..19fd86d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "signifypy" -version = "0.4.0" +version = "0.4.1" description = "SignifyPy: KERI Signing at the Edge" readme = "README.md" license = { text = "Apache Software License 2.0" } diff --git a/uv.lock b/uv.lock index 15a2130..4eeb734 100644 --- a/uv.lock +++ b/uv.lock @@ -1337,7 +1337,7 @@ wheels = [ [[package]] name = "signifypy" -version = "0.4.0" +version = "0.4.1" source = { editable = "." } dependencies = [ { name = "http-sfv" },