Contracts

github-release-workflow.yml

Contract: GitHub Actions Workflow for PyPI Publish

name: Publish Release

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - name: Check out repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install tooling
        run: |
          python -m pip install --upgrade pip
          pip install build==1.* twine==5.* tomli==2.* pytest

      - name: Run tests
        run: python -m pytest

      - name: Validate release metadata
        run: python scripts/release/validate_release.py --tag "${GITHUB_REF_NAME}"

      - name: Build distributions
        run: python -m build

      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          password: ${{ secrets.PYPI_API_TOKEN }}
          verbose: true

      - name: Record artifact checksums
        if: success()
        run: |
          sha256sum dist/* > dist/SHA256SUMS.txt
          cat dist/SHA256SUMS.txt

Acceptance Criteria

  • Workflow MUST exit prior to upload when tests fail, metadata validation fails, or version mismatches are detected.
  • Tag triggers MUST be limited to semantic versions prefixed with v.
  • PYPI_API_TOKEN secret MUST be required; run fails with explicit error if missing.
  • Build artifacts MUST include both .whl and .tar.gz files.

release-validation-cli.md

Contract: Release Validation Script

Command

python scripts/release/validate_release.py [--tag vX.Y.Z]

Inputs

  • --tag (optional): Explicit semantic tag to validate against; defaults to the current GITHUB_REF_NAME in CI or derives from git history locally.
  • Repository files read:
  • pyproject.toml
  • CHANGELOG.md
  • .git/refs/tags/* (for latest annotated tag when --tag omitted)

Outputs

  • Exit code 0 when:
  • pyproject.toml version matches the semantic tag (including v prefix).
  • CHANGELOG.md contains a heading for the version and non-empty notes.
  • pyproject.toml version is greater than the most recent published tag.
  • Exit code 1 when any validation fails. Script prints actionable errors:
  • Missing or malformed tag.
  • Version mismatch between files.
  • Changelog entry absent.
  • Version regression relative to the latest git tag.

Side Effects

  • No files are mutated.
  • Standard output lists validation summary; standard error includes failure rationale.

Acceptance Criteria

  • Script MUST run identically in local environments and CI.
  • Error messages MUST guide the maintainer to fix version/changelog/tag alignment.
  • All failures MUST prevent the publish job from advancing to upload.