Skip to content

fix(release): release from master only, split jobs, ship LICENSE - #272

Merged
allen0099 merged 1 commit into
masterfrom
fix/release-hardening-231-232
Sep 26, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/release-hardening-231-232

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Summary

Closes #231, closes #232.

#231 — release.yml

  • Master only. A non-dry-run dispatch on any ref other than refs/heads/master fails at the first step. The release and publish jobs check the ref again in their job-level if:. The release commit is pushed to HEAD:refs/heads/master explicitly instead of $BRANCH.
    • Dry runs may still be dispatched on any branch, since they write nothing. That is also how this PR's workflow can be rehearsed before merging.
  • Split into three jobs:
    • build: runs the gate, the version bump, the tag check, the changelog promotion and uv build. It has contents: read, and checks out with persist-credentials: false, so the dev dependencies run with no write token and no PyPI token. It uploads one artifact, release-v<version>, containing the bumped pyproject.toml, uv.lock, CHANGELOG.md, release-notes.md and dist/. The same artifact is the dry-run output, replacing dry-run-v<version>.
    • release (needs: build, contents: write): installs nothing. It checks out the same github.sha, downloads the artifact, then commits, pushes, tags and creates the GitHub release. If master has moved since the dispatch, the push is rejected as non-fast-forward before anything is tagged, same as before.
    • publish (needs: [build, release], environment: pypi, the only id-token: write): downloads dist/ and runs pypa/gh-action-pypi-publish, then writes the summary.
  • Workflow-level permissions are now contents: read only.
  • The order of commit → tag → GitHub release → PyPI is unchanged, and the artifact that gets published is the one built from the bumped version.
  • docs/DEVELOPMENT.md "Releasing" and "Rehearsing a release" describe the jobs and the master-only rule.

Not done: pinning third-party actions by digest. The issue marks it optional, and Renovate would need configuring for it.

#232 — LICENSE

  • license-files = ["LICENSE"].
  • Added the Typing :: Typed classifier.

For the maintainer (repo/PyPI settings, not changed here)

  • pypi environment: GitHub creates it on the first run that references it. Add protection rules (required reviewer, branch restriction to master) if you want them.
  • PyPI trusted publisher: if it is configured with no environment, it keeps working as is. Setting its environment to pypi ties publishing to this job.
  • Rehearsal: I did not dispatch the workflow. A dry_run dispatch on this branch will exercise the build job, including the new artifact upload. The release/publish jobs only run for a real release.

Verification

  • uv build into a temp dir, before and after:
    • before: no LICENSE in either artifact;
    • after: the wheel contains fastapi_cachex-0.3.7.dist-info/licenses/LICENSE, METADATA has License-File: LICENSE and Classifier: Typing :: Typed, and the sdist contains fastapi_cachex-0.3.7/LICENSE.
  • actionlint (with shellcheck) on .github/workflows/release.yml only: clean.
  • pre-commit run --all-files: passed.
  • uv run pytest -q without live servers: 800 passed, 190 skipped.
  • zensical build --strict (English docs): clean.

CHANGELOG

Under Security:

- **Releases run from master only, and only the publish step can reach
  PyPI.** `release.yml` could be dispatched on any branch, pushing the
  release commit there and publishing unmerged code, and the one job that
  installed every dev dependency also held `contents: write` and
  `id-token: write`. A non-dry-run dispatch off `master` now fails at once.
  The workflow is split into a read-only `build` job, a `release` job that
  commits, tags and creates the GitHub release, and a `publish` job in the
  `pypi` environment that alone can mint the PyPI token.
  ([#231](https://github.com/allen0099/FastAPI-CacheX/issues/231))

Under Fixed:

- **The wheel and sdist ship the LICENSE file.** `pyproject.toml` declared
  `Apache-2.0` but no `license-files`, so neither artifact contained the
  licence text the Apache-2.0 licence requires recipients to receive. The
  package also carries the `Typing :: Typed` classifier now.
  ([#232](https://github.com/allen0099/FastAPI-CacheX/issues/232))

…ish jobs

release.yml could be dispatched on any branch, pushing the release commit
there and publishing unmerged code, and the job that ran every dev
dependency also held contents: write and id-token: write.

Refuse a non-dry-run dispatch off master. Split the workflow into a build
job (gate, bump, changelog, uv build; read-only, no persisted git
credentials), a release job (commit, tag, GitHub release; contents: write,
installs nothing) and a publish job (the only id-token: write, in the pypi
environment). The later jobs download the build artifact instead of
rebuilding.

Ship LICENSE in the wheel and sdist via license-files, and add the
Typing :: Typed classifier.

Closes #231
Closes #232
@allen0099 allen0099 added this to the 0.3.8 milestone Sep 26, 2026
@allen0099 allen0099 added bug Something isn't working dependencies Dependency update PRs (applied by Renovate) github-actions Legacy Dependabot label for Actions update PRs; Renovate uses 'dependencies'. Use 'ci' for CI issues security Security vulnerability or hardening labels Sep 26, 2026
@allen0099
allen0099 merged commit f5aed3e into master Sep 26, 2026
11 checks passed
@allen0099
allen0099 deleted the fix/release-hardening-231-232 branch September 26, 2026 22:58
allen0099 added a commit that referenced this pull request Sep 27, 2026
Copies the CHANGELOG sections of #207, #208, #209, #211, #212, #272,
#273, #274, #275, #276, #279 and #284 into Unreleased. The #273 entry
drops expire_if_equals from its list of methods that changed, since that
primitive is new in 0.3.8.
allen0099 added a commit that referenced this pull request Sep 27, 2026
Copies the CHANGELOG sections of #207, #208, #209, #211, #212, #272,
#273, #274, #275, #276, #279 and #284 into Unreleased. The #273 entry
drops expire_if_equals from its list of methods that changed, since that
primitive is new in 0.3.8.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dependencies Dependency update PRs (applied by Renovate) github-actions Legacy Dependabot label for Actions update PRs; Renovate uses 'dependencies'. Use 'ci' for CI issues security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ship the LICENSE file in the wheel and sdist release.yml: refuse to release from a branch other than master, and isolate the publish step

1 participant