From c64b7875d842e4fabf991b20c0db8d80d3d375eb Mon Sep 17 00:00:00 2001 From: plancher Date: Tue, 7 Jul 2026 12:10:37 -0400 Subject: [PATCH] PyPI packaging: publish workflow (OIDC trusted publishing), CHANGELOG, RELEASING, metadata --- .github/workflows/publish.yml | 67 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 34 ++++++++++++++++++ CLAUDE.md | 6 ++-- README.md | 16 +++++---- RELEASING.md | 48 +++++++++++++++++++++++++ ROADMAP.md | 11 +++--- pyproject.toml | 15 ++++++++ 7 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 CHANGELOG.md create mode 100644 RELEASING.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e664c0d --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,67 @@ +name: Publish + +# PyPI publishing via OIDC trusted publishing — no tokens stored in the repo. +# One-time setup on pypi.org (and test.pypi.org) is documented in RELEASING.md. +# +# - GitHub release published -> build + publish to PyPI +# - manual workflow_dispatch -> build + publish to TestPyPI (dry-run lane) + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Build sdist + wheel + run: | + python -m pip install --upgrade build twine + python -m build + python -m twine check --strict dist/* + - name: Smoke-test the wheel + run: | + python -m venv /tmp/wheeltest + /tmp/wheeltest/bin/pip install -q dist/*.whl + /tmp/wheeltest/bin/gpu-proof --help + /tmp/wheeltest/bin/python -c "import pytest_gpu_proof" + /tmp/wheeltest/bin/python -m pytest -p pytest_gpu_proof --version + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + + publish-testpypi: + if: github.event_name == 'workflow_dispatch' + needs: build + runs-on: ubuntu-latest + environment: testpypi + permissions: + id-token: write # OIDC trusted publishing + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish-pypi: + if: github.event_name == 'release' + needs: build + runs-on: ubuntu-latest + environment: pypi + permissions: + id-token: write # OIDC trusted publishing + steps: + - uses: actions/download-artifact@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fa27fd9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to pytest-gpu-proof are documented here. The format is +based on [Keep a Changelog](https://keepachangelog.com/); versions follow +[SemVer](https://semver.org/) (pre-1.0: minor bumps may break). + +## [0.1.0] — unreleased + +First public release. + +### Added +- pytest plugin (`-p` auto-loaded via the `pytest11` entry point): captures + per-test outcomes and skips, git SHA, a SHA-256 source fingerprint over + configured paths, and self-reported GPU info; emits a signed JSON receipt at + session end (`--gpu-proof-enable`). +- Ed25519 SSH-key signing; signature covers the canonical (compact, + sorted-key) JSON without the signature block. `none` backend for unsigned + receipts. +- CPU-only verifier `gpu-proof verify`: signature against the signer's public + `github.com/.keys`, fingerprint match, commit SHA, outcome + skip + policy, `--require-gpu`, freshness (`--max-age-days`, `0` = today only), + dirty policy. +- Skip policies: `--allow-skipped` (any) or `--expected-skips FILE` (exact-set + baseline — unexpected AND stale entries both fail); mutually exclusive. + Also `[tool.gpu_proof] expected_skips`. +- Signer resolution: explicit flag/config → `gh` CLI login (keyholder) → + origin-remote owner with a printed warning (org remotes have no SSH keys). +- `is_dirty` ignores untracked content inside submodules (pin moves and + tracked edits still count). +- Receipt diffing (`compare.py`), `[tool.gpu_proof]` pyproject configuration, + docs site (mkdocs), honest trust-model statement in + `docs/security_model.md`. + +[0.1.0]: https://github.com/A2R-Lab/pytest-gpu-proof/releases/tag/v0.1.0 diff --git a/CLAUDE.md b/CLAUDE.md index 674076d..381672a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,8 +60,10 @@ Signing covers the canonical (compact, sorted-key) JSON **without** the - Short single-line commit messages; no Co-Authored-By footer. - Flow: feature branch → PR → CI green → merge to `main`. Consumers install - from git (`pip install -e` on a submodule); **PyPI is deferred** — see - `ROADMAP.md`, it's its own carefully-planned session. + from git (`pip install -e` on a submodule) or PyPI. **Releases**: OIDC + trusted publishing via `.github/workflows/publish.yml` (TestPyPI on manual + dispatch, PyPI on GitHub release) — process in `RELEASING.md`; keep + `CHANGELOG.md` current and bump `pyproject.toml` version in the same PR. - Reference integration: **GLASS** (github.com/A2R-Lab/GLASS) — `test/run_gpu_proof.sh`, `test/expected_skips.txt`, `.github/workflows/verify-gpu-proof.yml`. If you change plugin/verifier diff --git a/README.md b/README.md index 8d3b973..650652a 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,16 @@ pytest --gpu-proof-enable → → gpu-proof verify --receipt gpu-proof.json ## Installation -The package is not yet published to PyPI. Install from a local clone: +From PyPI (v0.1.0+): ```bash -git clone +pip install pytest-gpu-proof +``` + +Or from a clone / git submodule (how the A2R-Lab consumers pin exact versions): + +```bash +git clone https://github.com/A2R-Lab/pytest-gpu-proof cd pytest-gpu-proof pip install -e . # base install (pytest + cryptography) pip install -e ".[dev]" # also installs numpy, pytest-cov @@ -55,11 +61,7 @@ bash install.sh # base install bash install.sh dev # dev install ``` -Once the package is registered on PyPI, you will be able to install it with: - -```bash -pip install pytest-gpu-proof # not yet available -``` +Maintainers: release process in [RELEASING.md](RELEASING.md). --- diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..68233dd --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,48 @@ +# Releasing pytest-gpu-proof + +Publishing runs through GitHub Actions **OIDC trusted publishing** — no PyPI +tokens are stored anywhere. `.github/workflows/publish.yml` has two lanes: + +- **TestPyPI** — manual `workflow_dispatch` of the Publish workflow (dry-run lane). +- **PyPI** — automatic on a published GitHub release. + +Both lanes build sdist + wheel, run `twine check --strict`, and smoke-test the +wheel (CLI entry point, import, pytest plugin registration) before uploading. + +## One-time setup (repo owner, web UI) + +1. **PyPI** (pypi.org → account → Publishing → "Add a new pending publisher"): + - PyPI project name: `pytest-gpu-proof` + - Owner: `A2R-Lab`, Repository: `pytest-gpu-proof` + - Workflow name: `publish.yml` + - Environment: `pypi` +2. **TestPyPI** (test.pypi.org, same form) with environment `testpypi`. +3. **GitHub** (repo → Settings → Environments): create environments `pypi` and + `testpypi`. Optionally add yourself as a required reviewer on `pypi` so + every real publish needs a click of approval. + +The first trusted-publisher upload CREATES the project on (Test)PyPI and +registers the name — no separate name registration step. + +## Per release + +1. Update `version` in `pyproject.toml` and retitle the `unreleased` section + in `CHANGELOG.md` with the date. Commit via the normal branch → PR → CI + green → merge flow. +2. Dry run: Actions → Publish → "Run workflow" (publishes to TestPyPI), then + `pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pytest-gpu-proof` + in a scratch venv and run `gpu-proof --help`. +3. Tag + release: + ```bash + git tag v0.X.Y && git push origin v0.X.Y + gh release create v0.X.Y --title "v0.X.Y" --notes-file <(sed -n '/^## \[0.X.Y\]/,/^## \[/p' CHANGELOG.md | head -n -1) + ``` + The release event publishes to PyPI. +4. Sanity: `pip install pytest-gpu-proof==0.X.Y` in a scratch venv. + +## Versioning + +Pre-1.0 SemVer: patch = fixes, minor = features or breaking changes (called +out in the CHANGELOG). Consumers pinning the git submodule are unaffected by +PyPI releases; keep the two install paths (git submodule, PyPI) documented in +the README. diff --git a/ROADMAP.md b/ROADMAP.md index 6dbd1c8..303876f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,8 +1,11 @@ # Roadmap -- **PyPI publishing (own session):** register the `pytest-gpu-proof` name, trusted - publishing via GitHub Actions (OIDC), README as long_description, versioning + - release discipline, CHANGELOG. Prereqs (CI, LICENSE, docs site, 3.11 floor) - landed on `fixes-and-ci` 2026-07-02. +- **PyPI publishing: infrastructure DONE 2026-07-07** — `publish.yml` (OIDC + trusted publishing: TestPyPI on manual dispatch, PyPI on GitHub release), + CHANGELOG.md, RELEASING.md, full metadata, wheel smoke-tested; name free on + PyPI. REMAINING (repo-owner web UI, ~5 min, steps in RELEASING.md): add the + pending trusted publishers on pypi.org + test.pypi.org and create the + `pypi`/`testpypi` GitHub environments — then dispatch the TestPyPI lane and + cut v0.1.0. - SSHSIG-compatible signing (`ssh-keygen -Y verify` interop; agent-only + FIDO keys). - CI-issued nonce / challenge mode for stronger replay protection. diff --git a/pyproject.toml b/pyproject.toml index ed4302f..d52ac73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,15 +11,30 @@ requires-python = ">=3.11" license = { text = "MIT" } keywords = ["pytest", "gpu", "cuda", "testing", "signing", "attestation"] classifiers = [ + "Development Status :: 4 - Beta", "Framework :: Pytest", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Testing", + "Topic :: Security :: Cryptography", ] dependencies = [ "pytest>=7.0", "cryptography>=41.0", ] +[project.urls] +Homepage = "https://github.com/A2R-Lab/pytest-gpu-proof" +Documentation = "https://a2r-lab.github.io/pytest-gpu-proof/" +Repository = "https://github.com/A2R-Lab/pytest-gpu-proof" +Issues = "https://github.com/A2R-Lab/pytest-gpu-proof/issues" +Changelog = "https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md" + [project.optional-dependencies] dev = [ "pytest-cov",