pytest-gpu-proof 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. pytest_gpu_proof-0.1.0/.github/workflows/ci.yml +27 -0
  2. pytest_gpu_proof-0.1.0/.github/workflows/docs.yml +29 -0
  3. pytest_gpu_proof-0.1.0/.github/workflows/publish-testpypi.yml +49 -0
  4. pytest_gpu_proof-0.1.0/.github/workflows/publish.yml +48 -0
  5. pytest_gpu_proof-0.1.0/.gitignore +302 -0
  6. pytest_gpu_proof-0.1.0/CHANGELOG.md +34 -0
  7. pytest_gpu_proof-0.1.0/CLAUDE.md +73 -0
  8. pytest_gpu_proof-0.1.0/LICENSE +21 -0
  9. pytest_gpu_proof-0.1.0/PKG-INFO +400 -0
  10. pytest_gpu_proof-0.1.0/README.md +367 -0
  11. pytest_gpu_proof-0.1.0/RELEASING.md +57 -0
  12. pytest_gpu_proof-0.1.0/ROADMAP.md +11 -0
  13. pytest_gpu_proof-0.1.0/docs/architecture.md +92 -0
  14. pytest_gpu_proof-0.1.0/docs/ci_gpu_mode.md +100 -0
  15. pytest_gpu_proof-0.1.0/docs/index.md +33 -0
  16. pytest_gpu_proof-0.1.0/docs/landscape.md +76 -0
  17. pytest_gpu_proof-0.1.0/docs/local_mode.md +136 -0
  18. pytest_gpu_proof-0.1.0/docs/quickstart.md +377 -0
  19. pytest_gpu_proof-0.1.0/docs/security_model.md +75 -0
  20. pytest_gpu_proof-0.1.0/examples/cuda_ctypes_matmul/Makefile +16 -0
  21. pytest_gpu_proof-0.1.0/examples/cuda_ctypes_matmul/README.md +40 -0
  22. pytest_gpu_proof-0.1.0/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +43 -0
  23. pytest_gpu_proof-0.1.0/examples/cuda_ctypes_matmul/matmul_kernel.cu +68 -0
  24. pytest_gpu_proof-0.1.0/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +37 -0
  25. pytest_gpu_proof-0.1.0/examples/cuda_nanobind_matmul/CMakeLists.txt +28 -0
  26. pytest_gpu_proof-0.1.0/examples/cuda_nanobind_matmul/README.md +46 -0
  27. pytest_gpu_proof-0.1.0/examples/cuda_nanobind_matmul/src/bindings.cpp +32 -0
  28. pytest_gpu_proof-0.1.0/examples/cuda_nanobind_matmul/src/matmul_cuda.h +4 -0
  29. pytest_gpu_proof-0.1.0/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +64 -0
  30. pytest_gpu_proof-0.1.0/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +42 -0
  31. pytest_gpu_proof-0.1.0/examples/cuda_pybind11_matmul/CMakeLists.txt +28 -0
  32. pytest_gpu_proof-0.1.0/examples/cuda_pybind11_matmul/README.md +46 -0
  33. pytest_gpu_proof-0.1.0/examples/cuda_pybind11_matmul/src/bindings.cpp +32 -0
  34. pytest_gpu_proof-0.1.0/examples/cuda_pybind11_matmul/src/matmul_cuda.h +4 -0
  35. pytest_gpu_proof-0.1.0/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +64 -0
  36. pytest_gpu_proof-0.1.0/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +42 -0
  37. pytest_gpu_proof-0.1.0/examples/github_gpu_runner/.github/workflows/gpu-test.yml +60 -0
  38. pytest_gpu_proof-0.1.0/examples/jax_ffi_cuda_matmul/CMakeLists.txt +27 -0
  39. pytest_gpu_proof-0.1.0/examples/jax_ffi_cuda_matmul/README.md +47 -0
  40. pytest_gpu_proof-0.1.0/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +49 -0
  41. pytest_gpu_proof-0.1.0/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +56 -0
  42. pytest_gpu_proof-0.1.0/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +44 -0
  43. pytest_gpu_proof-0.1.0/examples/local_receipt_verify/.github/workflows/verify.yml +30 -0
  44. pytest_gpu_proof-0.1.0/examples/minimal_python_only/conftest.py +3 -0
  45. pytest_gpu_proof-0.1.0/examples/minimal_python_only/test_minimal.py +89 -0
  46. pytest_gpu_proof-0.1.0/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +33 -0
  47. pytest_gpu_proof-0.1.0/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +133 -0
  48. pytest_gpu_proof-0.1.0/install.sh +19 -0
  49. pytest_gpu_proof-0.1.0/mkdocs.yml +38 -0
  50. pytest_gpu_proof-0.1.0/pyproject.toml +69 -0
  51. pytest_gpu_proof-0.1.0/requirements-dev.txt +3 -0
  52. pytest_gpu_proof-0.1.0/requirements.txt +2 -0
  53. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/__init__.py +1 -0
  54. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/__main__.py +3 -0
  55. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/cli.py +95 -0
  56. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/compare.py +49 -0
  57. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/config.py +82 -0
  58. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/fingerprint.py +38 -0
  59. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/gitutils.py +112 -0
  60. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/plugin.py +331 -0
  61. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/receipt.py +143 -0
  62. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/signers/__init__.py +0 -0
  63. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/signers/base.py +20 -0
  64. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/signers/ed25519.py +178 -0
  65. pytest_gpu_proof-0.1.0/src/pytest_gpu_proof/verify.py +339 -0
  66. pytest_gpu_proof-0.1.0/tests/conftest.py +64 -0
  67. pytest_gpu_proof-0.1.0/tests/test_compare.py +64 -0
  68. pytest_gpu_proof-0.1.0/tests/test_fingerprint.py +40 -0
  69. pytest_gpu_proof-0.1.0/tests/test_gitutils.py +69 -0
  70. pytest_gpu_proof-0.1.0/tests/test_plugin_capture.py +276 -0
  71. pytest_gpu_proof-0.1.0/tests/test_receipt.py +142 -0
  72. pytest_gpu_proof-0.1.0/tests/test_signing.py +96 -0
  73. pytest_gpu_proof-0.1.0/tests/test_verifier.py +363 -0
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+
23
+ - name: Install package with dev extras
24
+ run: pip install -e ".[dev]"
25
+
26
+ - name: Run test suite (CPU-only)
27
+ run: python -m pytest tests/ -q
@@ -0,0 +1,29 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ deploy:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install mkdocs
21
+ run: pip install mkdocs-material
22
+
23
+ - name: Configure git author
24
+ run: |
25
+ git config user.name "github-actions[bot]"
26
+ git config user.email "github-actions[bot]@users.noreply.github.com"
27
+
28
+ - name: Deploy to gh-pages
29
+ run: mkdocs gh-deploy --force
@@ -0,0 +1,49 @@
1
+ name: Publish (TestPyPI)
2
+
3
+ # Dry-run lane ONLY — manual dispatch, publishes to test.pypi.org via OIDC
4
+ # trusted publishing. Cannot touch real PyPI: this workflow contains no pypi
5
+ # environment and no pypi publisher matches its filename. The real-PyPI lane
6
+ # is publish.yml (GitHub release). One-time setup: RELEASING.md.
7
+
8
+ on:
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+ - name: Build sdist + wheel
20
+ run: |
21
+ python -m pip install --upgrade build twine
22
+ python -m build
23
+ python -m twine check --strict dist/*
24
+ - name: Smoke-test the wheel
25
+ run: |
26
+ python -m venv /tmp/wheeltest
27
+ /tmp/wheeltest/bin/pip install -q dist/*.whl
28
+ /tmp/wheeltest/bin/gpu-proof --help
29
+ /tmp/wheeltest/bin/python -c "import pytest_gpu_proof"
30
+ /tmp/wheeltest/bin/python -m pytest -p pytest_gpu_proof --version
31
+ - uses: actions/upload-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+
36
+ publish-testpypi:
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ environment: testpypi
40
+ permissions:
41
+ id-token: write # OIDC trusted publishing
42
+ steps:
43
+ - uses: actions/download-artifact@v4
44
+ with:
45
+ name: dist
46
+ path: dist/
47
+ - uses: pypa/gh-action-pypi-publish@release/v1
48
+ with:
49
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,48 @@
1
+ name: Publish
2
+
3
+ # Real-PyPI lane ONLY — runs exclusively on a published GitHub release, via
4
+ # OIDC trusted publishing (no tokens). The TestPyPI dry-run lane is the
5
+ # separate publish-testpypi.yml workflow (manual dispatch), so a dispatch run
6
+ # never shows a PyPI job. One-time setup: RELEASING.md.
7
+
8
+ on:
9
+ release:
10
+ types: [published]
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+ - name: Build sdist + wheel
21
+ run: |
22
+ python -m pip install --upgrade build twine
23
+ python -m build
24
+ python -m twine check --strict dist/*
25
+ - name: Smoke-test the wheel
26
+ run: |
27
+ python -m venv /tmp/wheeltest
28
+ /tmp/wheeltest/bin/pip install -q dist/*.whl
29
+ /tmp/wheeltest/bin/gpu-proof --help
30
+ /tmp/wheeltest/bin/python -c "import pytest_gpu_proof"
31
+ /tmp/wheeltest/bin/python -m pytest -p pytest_gpu_proof --version
32
+ - uses: actions/upload-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ publish-pypi:
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment: pypi
41
+ permissions:
42
+ id-token: write # OIDC trusted publishing
43
+ steps:
44
+ - uses: actions/download-artifact@v4
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,302 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ *.lcov
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ # Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ # uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ # poetry.lock
110
+ # poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
115
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
116
+ # pdm.lock
117
+ # pdm.toml
118
+ .pdm-python
119
+ .pdm-build/
120
+
121
+ # pixi
122
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
123
+ # pixi.lock
124
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
125
+ # in the .venv directory. It is recommended not to include this directory in version control.
126
+ .pixi/*
127
+ !.pixi/config.toml
128
+
129
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
130
+ __pypackages__/
131
+
132
+ # Celery stuff
133
+ celerybeat-schedule*
134
+ celerybeat.pid
135
+
136
+ # Redis
137
+ *.rdb
138
+ *.aof
139
+ *.pid
140
+
141
+ # RabbitMQ
142
+ mnesia/
143
+ rabbitmq/
144
+ rabbitmq-data/
145
+
146
+ # ActiveMQ
147
+ activemq-data/
148
+
149
+ # SageMath parsed files
150
+ *.sage.py
151
+
152
+ # Environments
153
+ .env
154
+ .envrc
155
+ .venv
156
+ env/
157
+ venv/
158
+ ENV/
159
+ env.bak/
160
+ venv.bak/
161
+
162
+ # Spyder project settings
163
+ .spyderproject
164
+ .spyproject
165
+
166
+ # Rope project settings
167
+ .ropeproject
168
+
169
+ # mkdocs documentation
170
+ /site
171
+
172
+ # mypy
173
+ .mypy_cache/
174
+ .dmypy.json
175
+ dmypy.json
176
+
177
+ # Pyre type checker
178
+ .pyre/
179
+
180
+ # pytype static type analyzer
181
+ .pytype/
182
+
183
+ # Cython debug symbols
184
+ cython_debug/
185
+
186
+ # PyCharm
187
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
188
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
189
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
190
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
191
+ # .idea/
192
+
193
+ # Abstra
194
+ # Abstra is an AI-powered process automation framework.
195
+ # Ignore directories containing user credentials, local state, and settings.
196
+ # Learn more at https://abstra.io/docs
197
+ .abstra/
198
+
199
+ # Visual Studio Code
200
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
201
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
202
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
203
+ # you could uncomment the following to ignore the entire vscode folder
204
+ # .vscode/
205
+ # Temporary file for partial code execution
206
+ tempCodeRunnerFile.py
207
+
208
+ # Ruff stuff:
209
+ .ruff_cache/
210
+
211
+ # PyPI configuration file
212
+ .pypirc
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
218
+
219
+ # Streamlit
220
+ .streamlit/secrets.toml
221
+
222
+ # Prerequisites
223
+ *.d
224
+
225
+ # Compiled Object files
226
+ *.slo
227
+ *.lo
228
+ *.o
229
+ *.obj
230
+
231
+ # Precompiled Headers
232
+ *.gch
233
+ *.pch
234
+
235
+ # Linker files
236
+ *.ilk
237
+
238
+ # Debugger Files
239
+ *.pdb
240
+
241
+ # Compiled Dynamic libraries
242
+ *.so
243
+ *.dylib
244
+ *.dll
245
+ *.so.*
246
+
247
+
248
+ # Fortran module files
249
+ *.mod
250
+ *.smod
251
+
252
+ # Compiled Static libraries
253
+ *.lai
254
+ *.la
255
+ *.a
256
+ *.lib
257
+
258
+ # Executables
259
+ *.exe
260
+ *.out
261
+ *.app
262
+
263
+ # Build directories
264
+ build/
265
+ Build/
266
+ build-*/
267
+
268
+ # CMake generated files
269
+ CMakeFiles/
270
+ CMakeCache.txt
271
+ cmake_install.cmake
272
+ Makefile
273
+ !examples/cuda_ctypes_matmul/Makefile
274
+ install_manifest.txt
275
+ compile_commands.json
276
+
277
+ # Temporary files
278
+ *.tmp
279
+ *.log
280
+ *.bak
281
+ *.swp
282
+
283
+ # vcpkg
284
+ vcpkg_installed/
285
+
286
+ # debug information files
287
+ *.dwo
288
+
289
+ # test output & cache
290
+ Testing/
291
+ .cache/
292
+
293
+ # CUDA
294
+ *.i
295
+ *.ii
296
+ *.gpu
297
+ *.ptx
298
+ *.cubin
299
+ *.fatbin
300
+
301
+ # Specifics
302
+ gpu-proof.json
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ All notable changes to pytest-gpu-proof are documented here. The format is
4
+ based on [Keep a Changelog](https://keepachangelog.com/); versions follow
5
+ [SemVer](https://semver.org/) (pre-1.0: minor bumps may break).
6
+
7
+ ## [0.1.0] — 2026-07-07
8
+
9
+ First public release.
10
+
11
+ ### Added
12
+ - pytest plugin (`-p` auto-loaded via the `pytest11` entry point): captures
13
+ per-test outcomes and skips, git SHA, a SHA-256 source fingerprint over
14
+ configured paths, and self-reported GPU info; emits a signed JSON receipt at
15
+ session end (`--gpu-proof-enable`).
16
+ - Ed25519 SSH-key signing; signature covers the canonical (compact,
17
+ sorted-key) JSON without the signature block. `none` backend for unsigned
18
+ receipts.
19
+ - CPU-only verifier `gpu-proof verify`: signature against the signer's public
20
+ `github.com/<user>.keys`, fingerprint match, commit SHA, outcome + skip
21
+ policy, `--require-gpu`, freshness (`--max-age-days`, `0` = today only),
22
+ dirty policy.
23
+ - Skip policies: `--allow-skipped` (any) or `--expected-skips FILE` (exact-set
24
+ baseline — unexpected AND stale entries both fail); mutually exclusive.
25
+ Also `[tool.gpu_proof] expected_skips`.
26
+ - Signer resolution: explicit flag/config → `gh` CLI login (keyholder) →
27
+ origin-remote owner with a printed warning (org remotes have no SSH keys).
28
+ - `is_dirty` ignores untracked content inside submodules (pin moves and
29
+ tracked edits still count).
30
+ - Receipt diffing (`compare.py`), `[tool.gpu_proof]` pyproject configuration,
31
+ docs site (mkdocs), honest trust-model statement in
32
+ `docs/security_model.md`.
33
+
34
+ [0.1.0]: https://github.com/A2R-Lab/pytest-gpu-proof/releases/tag/v0.1.0
@@ -0,0 +1,73 @@
1
+ # CLAUDE.md — orientation for AI agents (and humans) working on pytest-gpu-proof
2
+
3
+ pytest-gpu-proof is a **pytest plugin that emits a signed JSON receipt of a GPU
4
+ test run** (git SHA + source fingerprint + per-test outcomes + GPU info), plus a
5
+ **CPU-only verifier** (`gpu-proof verify`) that checks the signature against the
6
+ signer's public `github.com/<user>.keys`. The point: run GPU tests on your own
7
+ hardware, let CI prove who attested them — no cloud-GPU fees, no secrets in CI.
8
+
9
+ **Trust model — never oversell it:** a receipt is a **signed attestation by a
10
+ keyholder, NOT cryptographic proof of GPU execution**. `gpu_info` is
11
+ self-reported; `--require-gpu` is modest hardening against accidents, not
12
+ against a dishonest signer. `docs/security_model.md` is the honest statement of
13
+ what is and isn't proven — keep every README/docs claim consistent with it.
14
+
15
+ ## Source layout (`src/pytest_gpu_proof/`)
16
+
17
+ | Module | Role |
18
+ |---|---|
19
+ | `plugin.py` | pytest hooks: options, `gpu_proof` marker, `gpu_proof_check` fixture, outcome+skip capture, receipt emission at session end |
20
+ | `receipt.py` | payload build (repo/fingerprint/tests/env), canonical JSON, sign+write. Signer resolution: flag/config → `gh` CLI login (keyholder) → origin-remote owner (warned — orgs have no SSH keys) |
21
+ | `verify.py` | the 7 verification checks (signature, fingerprint, commit SHA, outcomes+skip policy, gpu_info, freshness, dirty policy). Expected-skips baseline = EXACT set match |
22
+ | `cli.py` | `gpu-proof verify` argument surface |
23
+ | `config.py` | `GpuProofConfig`; CLI flags override `[tool.gpu_proof]` in pyproject.toml |
24
+ | `fingerprint.py` | SHA-256 digest over configured paths |
25
+ | `gitutils.py` | git/gh shell-outs, all failure-tolerant (return `None`) |
26
+ | `signers/` | `base.py` protocol + `ed25519.py` SSH-key signing / GitHub-keys verification; backend `none` emits unsigned receipts |
27
+ | `compare.py` | receipt diffing |
28
+
29
+ Signing covers the canonical (compact, sorted-key) JSON **without** the
30
+ `signature` field; the sig block carries `signer`, key fingerprint, algorithm
31
+ (derived from actual key type — don't hardcode ed25519).
32
+
33
+ ## Behavioral invariants (test-enforced — don't regress)
34
+
35
+ - **Skips prove nothing.** Verifier rejects receipts with skips unless
36
+ `--allow-skipped` (any skips) or `--expected-skips` (EXACT baseline: a new
37
+ skip fails, a stale baseline entry fails). The two are mutually exclusive.
38
+ - **Unsigned receipts** verify only with `--allow-unsigned`, loudly.
39
+ - **`--max-age-days 0`** means "today only", not "disabled".
40
+ - The signer recorded at signing time must be the **keyholder**, never
41
+ silently the repo owner.
42
+
43
+ ## Dev workflow
44
+
45
+ ```bash
46
+ .venv/bin/python -m pytest tests/ -q # full suite, ~2s, no GPU needed
47
+ .venv/bin/mkdocs build --strict # docs must stay warning-clean
48
+ ```
49
+
50
+ - Tests are **hermetic**: `tests/conftest.py` has an autouse fixture nulling
51
+ `get_gh_cli_login` (a dev box with authenticated `gh` would otherwise hit the
52
+ network). Signer tests re-patch explicitly. Keep new shell-outs mockable and
53
+ wrapped in try/except like `gitutils._git`.
54
+ - `tests/conftest.py` uses `pytest_plugins = ["pytester"]`; keypairs are
55
+ generated in-memory (no real SSH keys touched).
56
+ - requires-python ≥ 3.11 (`datetime.UTC`).
57
+ - CI (`.github/workflows/`): tests on 3.11/3.12 + mkdocs gh-pages deploy.
58
+
59
+ ## Conventions & state
60
+
61
+ - Short single-line commit messages; no Co-Authored-By footer.
62
+ - Flow: feature branch → PR → CI green → merge to `main`. Consumers install
63
+ from git (`pip install -e` on a submodule) or PyPI. **Releases**: OIDC
64
+ trusted publishing via `.github/workflows/publish.yml` (TestPyPI on manual
65
+ dispatch, PyPI on GitHub release) — process in `RELEASING.md`; keep
66
+ `CHANGELOG.md` current and bump `pyproject.toml` version in the same PR.
67
+ - Reference integration: **GLASS** (github.com/A2R-Lab/GLASS) —
68
+ `test/run_gpu_proof.sh`, `test/expected_skips.txt`,
69
+ `.github/workflows/verify-gpu-proof.yml`. If you change plugin/verifier
70
+ flags, check GLASS's usage still works and note it in the PR.
71
+ - Consumer-side gotcha worth remembering: a repo that submodules this project
72
+ under its pytest rootdir must `collect_ignore = ["pytest-gpu-proof"]` in its
73
+ conftest, or this repo's `tests/conftest.py` will shadow theirs.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A2R Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.