Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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/<user>.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
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <this-repo>
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
Expand All @@ -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).

---

Expand Down
48 changes: 48 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 7 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading