ci: publish Python SDK with trusted publishing#4
Conversation
jwfing
left a comment
There was a problem hiding this comment.
Summary
Adds a tag-driven PyPI Trusted Publishing (OIDC) workflow with a build/publish split, pre-publish validation, and CI coverage for Python 3.14 — a clean, well-scoped release-infra change with no blocking issues.
Requirements context
The repo uses docs/superpowers/{plans,specs}/, but the only documents there (2026-03-28-python-sdk-design.md, 2026-03-28-python-sdk.md) describe the SDK architecture and testing strategy — they contain no release/publishing/CI requirements (grep for publish|pypi|release|twine|workflow returned nothing). No matching spec/plan for release infrastructure — this PR is assessed against its own description and GitHub Actions / PyPA trusted-publishing best practice.
Note: I verified every pinned action version against the live GitHub releases API — checkout@v7 (v7.0.0), setup-python@v6 (v6.3.0), upload-artifact@v7 (v7.0.1), download-artifact@v8 (v8.0.1), and pypa/gh-action-pypi-publish@v1.14.0 are all real and current. The upload-artifact@v7 → download-artifact@v8 pairing is intentional and compatible: download-artifact@v8's release notes document it was built to consume upload-artifact@v7 (direct-upload / Content-Type-aware decompress), and this workflow uses the default zipped path, so the handoff works.
Findings
Critical
(none)
Suggestion
Security — SHA-pin the third-party publish action (.github/workflows/publish.yml:79)
pypa/gh-action-pypi-publish@v1.14.0 is pinned to a mutable version tag. This is the single most sensitive step in the pipeline — it mints the OIDC token and uploads to PyPI. First-party actions/* are lower risk, but for the publish action, supply-chain best practice is to pin to a full commit SHA (with the version in a trailing comment), e.g. uses: pypa/gh-action-pypi-publish@<sha> # v1.14.0. Non-blocking, but recommended for the upload step specifically.
Security — consider a protected GitHub Environment on the publish job (.github/workflows/publish.yml:65-70)
The publish job intentionally runs with no environment: (per the PR description). Trusted Publishing works fine without one, but PyPA recommends binding the publish job to a dedicated environment (e.g. pypi) with required-reviewer / deployment protection rules, and configuring the Trusted Publisher to require that environment. As written, any maintainer who can push a v* tag triggers an unattended publish with no second gate. Adding an environment is a low-cost defense-in-depth improvement. Deliberate choice by the author, so flagging as a suggestion only.
Information
- Functionality — tag/version guard fails closed (
.github/workflows/publish.yml:26-34): the exact string comparison"$RELEASE_TAG" != "v$package_version"correctly rejects mismatches, including PEP 440 edge cases (e.g. tagv1.0.0vs version1.0fails safely).tomllibis stdlib on the pinned Python 3.11 — good. Minor: theopen(...)handle isn't closed, negligible in a one-shot script. - Software engineering — no unit tests apply to workflow YAML; validation is appropriately done through the pipeline itself (
compileall,twine check, isolated wheel smoke-import) and, per the PR body,actionlint. Reasonable coverage for this change type. - Software engineering — duplication between the new
packagejob inci.yml:34-61and thebuildjob inpublish.yml:16-63(build →twine check→ smoke test). This is acceptable — the release build should be self-contained — but the shared steps could later be factored into a reusable/composite workflow if they drift. - Security (positive) — strong least-privilege posture: workflow-level
permissions: contents: read, andid-token: writescoped only to the isolatedpublishjob (job-levelpermissions:replaces the top-level set, so publish gets onlyid-token: write, which is correct since it never checks out source). No stored PyPI token; attestations left enabled by default. This matches PyPA's recommended split-build/publish pattern. - Performance — no concerns; adding the
3.14matrix entry and apackagejob is negligible CI cost,concurrencywithcancel-in-progress: falsecorrectly avoids interrupting an in-flight publish.
Verdict
approved (informational — a human still gives the explicit GitHub approval). Zero Critical findings; the two Suggestions are supply-chain hardening on an already-sound trusted-publishing setup.
What changed
v*tag to exactly matchproject.versionRelease behavior
.github/workflows/publish.ymlonly runs onv*tag pushes. The publish job has job-scopedid-token: write, no GitHub environment, and no username/password/token input. PyPI digital attestations remain enabled by default. Duplicate versions fail instead of being skipped.Validation
twine check, isolated wheel import/version checkPyPI setup after merge
Configure the existing
insforgeproject Trusted Publisher with organizationInsForge, repositoryinsforge-python, workflow filenamepublish.yml, and no environment name.Summary by cubic
Enables PyPI Trusted Publishing via OIDC for the Python SDK on
v*tags. Adds build/validation/smoke tests, Python 3.14 in CI, auto GitHub Releases for stable versions, and pins the PyPI publish action.New Features
.github/workflows/publish.ymlusing OIDC; no stored PyPI token; attestations enabled; auto GitHub Release for stableX.Y.Ztags (prereleases skip).project.version(e.g.,vX.Y.Z; PEP 440 prereleases supported); duplicate versions fail.twine check, and wheel smoke import/version; CI adds Python3.14, apackagejob, and updates toactions/checkout@v7/actions/setup-python@v6.pypa/gh-action-pypi-publishto v1.14.0 (commit SHA); addproject.urls(Source, Issues); update README with tag-based release steps.Migration
insforgeto organizationInsForge, repositoryinsforge-python, workflowpublish.yml, with no environment name.Written for commit 6664980. Summary will update on new commits.