pytest-gpu-proof 0.4.0__tar.gz → 0.5.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 (99) hide show
  1. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/.github/workflows/ci.yml +1 -1
  2. pytest_gpu_proof-0.5.0/.github/workflows/docs.yml +69 -0
  3. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/.gitignore +3 -1
  4. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/CHANGELOG.md +61 -0
  5. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/CONTRIBUTING.md +1 -1
  6. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/PKG-INFO +34 -10
  7. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/README.md +28 -6
  8. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/architecture.md +9 -0
  9. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/ci_gpu_mode.md +4 -1
  10. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/index.md +4 -1
  11. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/policy.md +2 -2
  12. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/security_model.md +7 -3
  13. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/sharding.md +18 -4
  14. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/mkdocs.yml +2 -0
  15. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/pyproject.toml +6 -4
  16. pytest_gpu_proof-0.5.0/requirements.txt +3 -0
  17. pytest_gpu_proof-0.5.0/src/pytest_gpu_proof/__init__.py +1 -0
  18. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/cli.py +108 -0
  19. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/config.py +13 -3
  20. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/gitutils.py +9 -0
  21. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/merge.py +131 -3
  22. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/plugin.py +23 -1
  23. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/receipt.py +55 -8
  24. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/verify.py +58 -15
  25. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/conftest.py +13 -0
  26. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_cli.py +23 -4
  27. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_config.py +37 -0
  28. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_merge.py +24 -3
  29. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_receipt.py +76 -0
  30. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_sharding.py +230 -1
  31. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_verifier.py +94 -3
  32. pytest_gpu_proof-0.5.0/website/README.md +85 -0
  33. pytest_gpu_proof-0.5.0/website/build_site.py +82 -0
  34. pytest_gpu_proof-0.5.0/website/check_site.py +90 -0
  35. pytest_gpu_proof-0.5.0/website/favicon.svg +1 -0
  36. pytest_gpu_proof-0.5.0/website/index.html +127 -0
  37. pytest_gpu_proof-0.5.0/website/main.js +17 -0
  38. pytest_gpu_proof-0.5.0/website/overrides/main.html +4 -0
  39. pytest_gpu_proof-0.5.0/website/provenance.json +10 -0
  40. pytest_gpu_proof-0.5.0/website/requirements.txt +2 -0
  41. pytest_gpu_proof-0.5.0/website/style.css +67 -0
  42. pytest_gpu_proof-0.5.0/website/test_site.py +89 -0
  43. pytest_gpu_proof-0.5.0/website/workflow-mobile.svg +32 -0
  44. pytest_gpu_proof-0.5.0/website/workflow.svg +35 -0
  45. pytest_gpu_proof-0.4.0/.github/workflows/docs.yml +0 -29
  46. pytest_gpu_proof-0.4.0/requirements.txt +0 -2
  47. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/__init__.py +0 -1
  48. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/.github/workflows/publish-testpypi.yml +0 -0
  49. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/.github/workflows/publish.yml +0 -0
  50. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/AGENTS.md +0 -0
  51. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/CLAUDE.md +0 -0
  52. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/LICENSE +0 -0
  53. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/RELEASING.md +0 -0
  54. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/ROADMAP.md +0 -0
  55. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/SECURITY.md +0 -0
  56. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/landscape.md +0 -0
  57. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/local_mode.md +0 -0
  58. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/docs/quickstart.md +0 -0
  59. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/Makefile +0 -0
  60. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/README.md +0 -0
  61. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +0 -0
  62. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu +0 -0
  63. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +0 -0
  64. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/CMakeLists.txt +0 -0
  65. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/README.md +0 -0
  66. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/bindings.cpp +0 -0
  67. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h +0 -0
  68. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +0 -0
  69. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +0 -0
  70. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/CMakeLists.txt +0 -0
  71. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/README.md +0 -0
  72. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/bindings.cpp +0 -0
  73. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h +0 -0
  74. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +0 -0
  75. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +0 -0
  76. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/github_gpu_runner/.github/workflows/gpu-test.yml +0 -0
  77. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt +0 -0
  78. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/README.md +0 -0
  79. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +0 -0
  80. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +0 -0
  81. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +0 -0
  82. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/local_receipt_verify/.github/workflows/verify.yml +0 -0
  83. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/minimal_python_only/conftest.py +0 -0
  84. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/minimal_python_only/test_minimal.py +0 -0
  85. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +0 -0
  86. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +0 -0
  87. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/install.sh +0 -0
  88. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/requirements-dev.txt +0 -0
  89. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/__main__.py +0 -0
  90. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/compare.py +0 -0
  91. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/fingerprint.py +0 -0
  92. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/signers/__init__.py +0 -0
  93. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/signers/base.py +0 -0
  94. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/src/pytest_gpu_proof/signers/ed25519.py +0 -0
  95. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_compare.py +0 -0
  96. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_fingerprint.py +0 -0
  97. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_gitutils.py +0 -0
  98. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_plugin_capture.py +0 -0
  99. {pytest_gpu_proof-0.4.0 → pytest_gpu_proof-0.5.0}/tests/test_signing.py +0 -0
@@ -11,7 +11,7 @@ jobs:
11
11
  strategy:
12
12
  fail-fast: false
13
13
  matrix:
14
- python-version: ["3.11", "3.12", "3.13"]
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v4
@@ -0,0 +1,69 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ paths:
8
+ - 'docs/**'
9
+ - 'website/**'
10
+ - 'mkdocs.yml'
11
+ - '.github/workflows/docs.yml'
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: docs-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: "3.12"
29
+
30
+ - name: Install website dependencies
31
+ run: pip install -r website/requirements.txt
32
+
33
+ - name: Build and check the complete website
34
+ run: python website/build_site.py
35
+
36
+ - name: Test website checks
37
+ run: python -m unittest discover -s website -p 'test_*.py'
38
+
39
+ - uses: actions/upload-artifact@v4
40
+ with:
41
+ name: website-preview
42
+ path: build/website
43
+ include-hidden-files: true
44
+
45
+ deploy:
46
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
47
+ needs: build
48
+ runs-on: ubuntu-latest
49
+ permissions:
50
+ contents: write
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ with:
54
+ fetch-depth: 0
55
+ - uses: actions/setup-python@v5
56
+ with:
57
+ python-version: "3.12"
58
+ - run: pip install -r website/requirements.txt
59
+ - uses: actions/download-artifact@v4
60
+ with:
61
+ name: website-preview
62
+ path: build/website
63
+ - name: Configure git author
64
+ run: |
65
+ git config user.name "github-actions[bot]"
66
+ git config user.email "github-actions[bot]@users.noreply.github.com"
67
+
68
+ - name: Deploy to gh-pages
69
+ run: ghp-import --no-jekyll --push --branch gh-pages build/website
@@ -299,4 +299,6 @@ Testing/
299
299
  *.fatbin
300
300
 
301
301
  # Specifics
302
- gpu-proof.json
302
+ gpu-proof.json
303
+ # Local working notes (never committed)
304
+ docs/open-tasks/
@@ -4,6 +4,67 @@ All notable changes to pytest-gpu-proof are documented here. The format is
4
4
  based on [Keep a Changelog](https://keepachangelog.com/); versions follow
5
5
  [SemVer](https://semver.org/) (pre-1.0: minor bumps may break).
6
6
 
7
+ ## [0.5.0] — 2026-10-28
8
+
9
+ ### Added
10
+
11
+ - Python 3.10 support with a conditional `tomli` dependency and a CI test lane.
12
+ - Signed `environment.nvcc_version` records the stripped output of
13
+ `nvcc --version` from PATH, or null when unavailable. Fresh shard merges
14
+ require matching compiler metadata; existing receipts without the field
15
+ remain supported.
16
+
17
+ ## [0.4.1] — 2026-10-12
18
+
19
+ ### Fixed
20
+
21
+ - Shard entries record `dirty` (the recording tree's state); merge ORs shard
22
+ flags into the merged `repo.dirty`, carry-forward refuses a dirty shard, and
23
+ verification refuses any dirty shard unless `allow_dirty` is set.
24
+ - `gpu-proof merge --carry-from` refuses a source receipt recorded with a dirty
25
+ working tree. The merged `repo.dirty` is OR-ed over the fresh inputs only, so a
26
+ carried shard from a dirty recording was laundered into a clean-looking merged
27
+ receipt (found by GRiD's split driver, 2026-10-10). Carried shards now record
28
+ `carried.original_dirty`, and verification refuses a carried shard recorded
29
+ dirty unless `allow_dirty` is set.
30
+
31
+ - The expected-skips mismatch names the delta (unexpected skips and stale
32
+ baseline entries) instead of only reporting that the sets differ.
33
+ - The receipt's `environment.gpu_info` falls back to the same torch probe the
34
+ `gpu_required` marker uses when nvidia-smi is absent or fails (`source`
35
+ records which probe answered), so a torch-only box no longer records
36
+ `gpu_info: null` and then fails a `require_gpu` policy.
37
+
38
+ - Verification refuses a receipt whose session started more than five
39
+ minutes before its commit existed (committer time): a cheap replay and
40
+ forgery guard that needs no nonce.
41
+
42
+ - `mode: ci-gpu` is now bound to the signing key: a ci-gpu receipt verifies
43
+ only when the policy lists `allowed_key_fingerprints` and the receipt was
44
+ signed by one of them (previously the mode was an unverified label).
45
+ - Without an explicit `carried_max_age_days`, a carried shard's original run
46
+ may not be older than the receipt's `max_age_days` (was a fixed 30 days).
47
+
48
+ ### Added
49
+
50
+ - `gpu-proof merge --verify-inputs`: authenticate every input shard against
51
+ its signer's current GitHub keys before merging (unsigned or tampered inputs
52
+ are refused).
53
+ - `gpu-proof status PATH...`: one-line summaries of receipt files or
54
+ directories (commit, dirty, signed, outcome, counts, shards, carried).
55
+ - `gpu-proof durations RECEIPT... --out map.json`: fold per-test durations
56
+ into a JSON map for shard bin-packing.
57
+ - Command-line equivalents for the toml-only options:
58
+ `--gpu-proof-no-gpu-uuid`, `--gpu-proof-require-gpu`,
59
+ `--gpu-proof-max-age-days N`.
60
+ - `gpu-proof plan --receipt OLD [--repo .] [--json]`: a dry run of the
61
+ carry-forward conditions against the current tree, reporting per shard
62
+ whether it could be carried or must be re-run (and why). Drivers that plan
63
+ partial re-runs no longer reimplement the checks.
64
+ - `record_gpu_uuid` (`[tool.gpu_proof]`, default `true`): set `false` to omit each GPU's
65
+ nvidia-smi UUID from the receipt environment while keeping name, driver, memory and
66
+ compute capability. Verification does not read the UUID.
67
+
7
68
  ## [0.4.0] — 2026-08-17
8
69
 
9
70
  ### Added
@@ -19,7 +19,7 @@ python -m build
19
19
  python -m twine check --strict dist/*
20
20
  ```
21
21
 
22
- CI runs the test suite on Python 3.11, 3.12, and 3.13, independently enforces
22
+ CI runs the test suite on Python 3.10, 3.11, 3.12, and 3.13, independently enforces
23
23
  100% line and branch coverage, builds docs strictly, and smoke-tests the wheel.
24
24
 
25
25
  ## Change expectations
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-gpu-proof
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys
5
- Project-URL: Homepage, https://github.com/A2R-Lab/pytest-gpu-proof
6
- Project-URL: Documentation, https://a2r-lab.github.io/pytest-gpu-proof/
5
+ Project-URL: Homepage, https://a2r-lab.org/pytest-gpu-proof/
6
+ Project-URL: Documentation, https://a2r-lab.org/pytest-gpu-proof/docs/
7
7
  Project-URL: Repository, https://github.com/A2R-Lab/pytest-gpu-proof
8
8
  Project-URL: Issues, https://github.com/A2R-Lab/pytest-gpu-proof/issues
9
9
  Project-URL: Changelog, https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md
@@ -16,14 +16,16 @@ Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
19
20
  Classifier: Programming Language :: Python :: 3.11
20
21
  Classifier: Programming Language :: Python :: 3.12
21
22
  Classifier: Programming Language :: Python :: 3.13
22
23
  Classifier: Topic :: Security :: Cryptography
23
24
  Classifier: Topic :: Software Development :: Testing
24
- Requires-Python: >=3.11
25
+ Requires-Python: >=3.10
25
26
  Requires-Dist: cryptography>=41.0
26
27
  Requires-Dist: pytest>=7.0
28
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
27
29
  Provides-Extra: dev
28
30
  Requires-Dist: build>=1.2; extra == 'dev'
29
31
  Requires-Dist: coverage[toml]>=7.6; extra == 'dev'
@@ -39,13 +41,17 @@ Description-Content-Type: text/markdown
39
41
  # pytest-gpu-proof
40
42
 
41
43
  [![CI](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml/badge.svg)](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
42
- [![Docs](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/docs.yml/badge.svg)](https://a2r-lab.github.io/pytest-gpu-proof/)
44
+ [![Docs](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/docs.yml/badge.svg)](https://a2r-lab.org/pytest-gpu-proof/docs/)
43
45
  [![PyPI](https://img.shields.io/pypi/v/pytest-gpu-proof.svg)](https://pypi.org/project/pytest-gpu-proof/)
44
46
  [![Python](https://img.shields.io/pypi/pyversions/pytest-gpu-proof.svg)](https://pypi.org/project/pytest-gpu-proof/)
45
47
  [![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
46
48
 
47
49
  Signed pytest receipts for local GPU runs, verified in CPU-only CI.
48
50
 
51
+ [Project overview](https://a2r-lab.org/pytest-gpu-proof/) ·
52
+ [Documentation](https://a2r-lab.org/pytest-gpu-proof/docs/) ·
53
+ [Website preview instructions](website/README.md)
54
+
49
55
  `pytest-gpu-proof` records which marked tests ran, their outcomes, the Git
50
56
  commit and source fingerprint, the environment, and the run time. It signs
51
57
  that payload with an existing SSH key. A normal CPU runner can then verify the
@@ -86,7 +92,7 @@ cd pytest-gpu-proof
86
92
  python -m pip install -e ".[dev]"
87
93
  ```
88
94
 
89
- Requirements: Python 3.11+, pytest 7+, and `cryptography` 41+.
95
+ Requirements: Python 3.10+, pytest 7+, and `cryptography` 41+.
90
96
 
91
97
  ## Quick start
92
98
 
@@ -126,15 +132,23 @@ fingerprint scope, no selected tests, or write failure makes pytest fail and
126
132
  leaves no stale receipt behind. `--gpu-proof-best-effort` is an explicit
127
133
  development-only opt-out.
128
134
 
129
- ## Four interfaces
135
+ ## Five interfaces
130
136
 
131
- The project exposes four small interfaces:
137
+ The project exposes five small interfaces:
132
138
 
133
139
  1. **Markers** select receipt tests.
134
- 2. **`gpu_proof_check`** compares a reference callable with a candidate.
140
+ 2. **`gpu_proof_check`** compares a reference callable with a candidate
141
+ (optional; every current user selects tests with the marker alone).
135
142
  3. **`gpu-proof verify`** validates a receipt and repository policy.
136
143
  4. **`gpu-proof merge`** combines separately executed shards and optionally
137
144
  carries unchanged shards forward.
145
+ 5. **`gpu-proof plan`** reports, before a partial re-run, which shards of an
146
+ old receipt could still be carried and which must be re-run.
147
+
148
+ Two maintenance helpers round it out: `gpu-proof status PATH...` summarizes
149
+ receipt files (commit, dirty, signed, outcome, counts, shards), and
150
+ `gpu-proof durations RECEIPT... --out map.json` folds per-test durations into
151
+ a map that shard bin-packers can calibrate from.
138
152
 
139
153
  ### Markers
140
154
 
@@ -197,6 +211,7 @@ fingerprint_excluded_paths = ["gpu-proof.json"]
197
211
  required_marker = "gpu_proof"
198
212
  max_age_days = 30
199
213
  require_gpu = true
214
+ record_gpu_uuid = false # keep device name/driver/memory, omit the card's UUID
200
215
  ```
201
216
 
202
217
  By default, the fingerprint covers every tracked file in the repository,
@@ -304,6 +319,15 @@ runtime environments, duplicate tests, and duplicate shard names. It records
304
319
  input provenance but does not verify each input signature; the merger signs
305
320
  for the union. See [sharding and carry-forward](docs/sharding.md).
306
321
 
322
+ Carry-forward (`gpu-proof merge ... --carry-from last-green.json`) grafts an
323
+ absent old shard only when its receipt commit is an ancestor of the current
324
+ one, both the receipt and the shard were recorded with a clean tree, its
325
+ narrow fingerprint recomputes identically, and it introduces no duplicate
326
+ test. `gpu-proof plan --receipt last-green.json` prints the same verdicts
327
+ without merging. Generated inputs named through `fingerprint_extra_paths`
328
+ should be gitignored: an untracked generated file makes the recording tree
329
+ dirty, and dirty recordings are never carried.
330
+
307
331
  ## Receipt schema
308
332
 
309
333
  New receipts use schema `"3"`. Signer identity is inside the signed payload,
@@ -322,7 +346,7 @@ python -m build
322
346
  python -m twine check --strict dist/*
323
347
  ```
324
348
 
325
- CI tests Python 3.11–3.13 and enforces 100% line and branch coverage. See
349
+ CI tests Python 3.10–3.13 and enforces 100% line and branch coverage. See
326
350
  [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
327
351
  [RELEASING.md](RELEASING.md).
328
352
 
@@ -1,13 +1,17 @@
1
1
  # pytest-gpu-proof
2
2
 
3
3
  [![CI](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml/badge.svg)](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
4
- [![Docs](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/docs.yml/badge.svg)](https://a2r-lab.github.io/pytest-gpu-proof/)
4
+ [![Docs](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/docs.yml/badge.svg)](https://a2r-lab.org/pytest-gpu-proof/docs/)
5
5
  [![PyPI](https://img.shields.io/pypi/v/pytest-gpu-proof.svg)](https://pypi.org/project/pytest-gpu-proof/)
6
6
  [![Python](https://img.shields.io/pypi/pyversions/pytest-gpu-proof.svg)](https://pypi.org/project/pytest-gpu-proof/)
7
7
  [![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
8
8
 
9
9
  Signed pytest receipts for local GPU runs, verified in CPU-only CI.
10
10
 
11
+ [Project overview](https://a2r-lab.org/pytest-gpu-proof/) ·
12
+ [Documentation](https://a2r-lab.org/pytest-gpu-proof/docs/) ·
13
+ [Website preview instructions](website/README.md)
14
+
11
15
  `pytest-gpu-proof` records which marked tests ran, their outcomes, the Git
12
16
  commit and source fingerprint, the environment, and the run time. It signs
13
17
  that payload with an existing SSH key. A normal CPU runner can then verify the
@@ -48,7 +52,7 @@ cd pytest-gpu-proof
48
52
  python -m pip install -e ".[dev]"
49
53
  ```
50
54
 
51
- Requirements: Python 3.11+, pytest 7+, and `cryptography` 41+.
55
+ Requirements: Python 3.10+, pytest 7+, and `cryptography` 41+.
52
56
 
53
57
  ## Quick start
54
58
 
@@ -88,15 +92,23 @@ fingerprint scope, no selected tests, or write failure makes pytest fail and
88
92
  leaves no stale receipt behind. `--gpu-proof-best-effort` is an explicit
89
93
  development-only opt-out.
90
94
 
91
- ## Four interfaces
95
+ ## Five interfaces
92
96
 
93
- The project exposes four small interfaces:
97
+ The project exposes five small interfaces:
94
98
 
95
99
  1. **Markers** select receipt tests.
96
- 2. **`gpu_proof_check`** compares a reference callable with a candidate.
100
+ 2. **`gpu_proof_check`** compares a reference callable with a candidate
101
+ (optional; every current user selects tests with the marker alone).
97
102
  3. **`gpu-proof verify`** validates a receipt and repository policy.
98
103
  4. **`gpu-proof merge`** combines separately executed shards and optionally
99
104
  carries unchanged shards forward.
105
+ 5. **`gpu-proof plan`** reports, before a partial re-run, which shards of an
106
+ old receipt could still be carried and which must be re-run.
107
+
108
+ Two maintenance helpers round it out: `gpu-proof status PATH...` summarizes
109
+ receipt files (commit, dirty, signed, outcome, counts, shards), and
110
+ `gpu-proof durations RECEIPT... --out map.json` folds per-test durations into
111
+ a map that shard bin-packers can calibrate from.
100
112
 
101
113
  ### Markers
102
114
 
@@ -159,6 +171,7 @@ fingerprint_excluded_paths = ["gpu-proof.json"]
159
171
  required_marker = "gpu_proof"
160
172
  max_age_days = 30
161
173
  require_gpu = true
174
+ record_gpu_uuid = false # keep device name/driver/memory, omit the card's UUID
162
175
  ```
163
176
 
164
177
  By default, the fingerprint covers every tracked file in the repository,
@@ -266,6 +279,15 @@ runtime environments, duplicate tests, and duplicate shard names. It records
266
279
  input provenance but does not verify each input signature; the merger signs
267
280
  for the union. See [sharding and carry-forward](docs/sharding.md).
268
281
 
282
+ Carry-forward (`gpu-proof merge ... --carry-from last-green.json`) grafts an
283
+ absent old shard only when its receipt commit is an ancestor of the current
284
+ one, both the receipt and the shard were recorded with a clean tree, its
285
+ narrow fingerprint recomputes identically, and it introduces no duplicate
286
+ test. `gpu-proof plan --receipt last-green.json` prints the same verdicts
287
+ without merging. Generated inputs named through `fingerprint_extra_paths`
288
+ should be gitignored: an untracked generated file makes the recording tree
289
+ dirty, and dirty recordings are never carried.
290
+
269
291
  ## Receipt schema
270
292
 
271
293
  New receipts use schema `"3"`. Signer identity is inside the signed payload,
@@ -284,7 +306,7 @@ python -m build
284
306
  python -m twine check --strict dist/*
285
307
  ```
286
308
 
287
- CI tests Python 3.11–3.13 and enforces 100% line and branch coverage. See
309
+ CI tests Python 3.10–3.13 and enforces 100% line and branch coverage. See
288
310
  [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
289
311
  [RELEASING.md](RELEASING.md).
290
312
 
@@ -69,6 +69,15 @@ canonical = json.dumps(
69
69
  Putting signer metadata inside this payload prevents identity or algorithm
70
70
  substitution after signing.
71
71
 
72
+ The optional `environment.nvcc_version` field records the stripped stdout of
73
+ `nvcc --version` resolved through PATH. It is null if the executable is missing,
74
+ cannot run, times out after ten seconds, fails, or returns empty/undecodable output.
75
+ This is self-reported compiler availability, not evidence that this compiler built
76
+ the tested code. It is independent of GPU detection and requires no GPU.
77
+ Existing schema-3 receipts without the field still verify. Fresh shard merges
78
+ require matching values (missing and null are equivalent); carried shards retain
79
+ their original environment metadata.
80
+
72
81
  ## Fingerprint manifest
73
82
 
74
83
  `sha256-manifest-v2` enumerates stage-0 Git index entries. Regular files bind
@@ -2,7 +2,10 @@
2
2
 
3
3
  CI-GPU mode uses the same receipt and verifier but records `mode: ci-gpu`.
4
4
  Choose it when GPU tests must run on a controlled runner rather than a
5
- developer machine.
5
+ developer machine. The verifier binds the mode to the runner's signing key:
6
+ a `ci-gpu` receipt verifies only when the repository policy lists
7
+ `allowed_key_fingerprints` and the receipt was signed by one of them, so the
8
+ label cannot be claimed by an arbitrary developer key.
6
9
 
7
10
  ## Dedicated signing identity
8
11
 
@@ -1,5 +1,8 @@
1
1
  # pytest-gpu-proof
2
2
 
3
+ This is the code documentation. Use the project-overview link above for the
4
+ workflow figure, paper, and introduction to the tool.
5
+
3
6
  **Signed pytest receipts for local GPU runs, verified in CPU-only CI.**
4
7
 
5
8
  The plugin turns an ordinary marked pytest run into a signed, reviewable
@@ -33,5 +36,5 @@ or exact SSH key fingerprints.
33
36
  - [Architecture](architecture.md): schema and data flow.
34
37
  - [Security model](security_model.md): exact guarantees and limits.
35
38
 
36
- Requirements: Python 3.11+, pytest 7+, and an SSH private-key file whose public
39
+ Requirements: Python 3.10+, pytest 7+, and an SSH private-key file whose public
37
40
  key is registered on GitHub.
@@ -53,7 +53,7 @@ schema 3. Repositories that have finished migrating should pin
53
53
  | `allowed_key_fingerprints` | SSH SHA-256 fingerprints accepted in restricted mode |
54
54
  | `min_schema` | Minimum acceptable receipt schema version (1, 2, or 3) |
55
55
  | `max_age_days` | Maximum age of the merged/session receipt |
56
- | `require_mode` | Required receipt mode: `local` or `ci-gpu` |
56
+ | `require_mode` | Required receipt mode: `local` or `ci-gpu` (a `ci-gpu` receipt additionally requires `allowed_key_fingerprints` and must be signed by a listed key) |
57
57
  | `allow_dirty` | Permit dirty recording or verification trees |
58
58
  | `required_fingerprint_paths` | Exact tracked path list |
59
59
  | `required_fingerprint_extra_paths` | Exact generated/ignored path list |
@@ -61,7 +61,7 @@ schema 3. Repositories that have finished migrating should pin
61
61
  | `required_test_manifest` | Repository-relative file containing exact node IDs |
62
62
  | `required_shard_fingerprints` | Exact paths/extras for every named shard |
63
63
  | `allow_carried` | Permit carry-forward shards |
64
- | `carried_max_age_days` | Maximum age of each carried shard's original run |
64
+ | `carried_max_age_days` | Maximum age of each carried shard's original run (default: `max_age_days`, capped at 30) |
65
65
 
66
66
  Example shard scope:
67
67
 
@@ -75,7 +75,9 @@ escaping, or unmerged inputs fail closed.
75
75
  Schema 3 rejects dirty recording and verification trees by default. A receipt
76
76
  may verify at its exact commit or a descendant only when the source manifest
77
77
  still matches. Freshness limits replay duration but no server-issued nonce is
78
- currently used.
78
+ currently used; a session that started more than five minutes before its
79
+ commit's committer time is refused, so a receipt cannot be back-dated onto a
80
+ commit that did not yet exist.
79
81
 
80
82
  `allow_dirty: true`, long age limits, `--allow-skipped`, and
81
83
  `--allow-unsigned` weaken guarantees. Unsigned mode authenticates no signer,
@@ -98,8 +100,10 @@ each input signature. The merger's signature attests to the union. A review
98
100
  workflow that needs independent shard authentication should verify every
99
101
  input before merging.
100
102
 
101
- Carry-forward means a test passed at an ancestor commit and its declared shard
102
- inputs are byte-identical now. It does not establish that omitted dependencies
103
+ Carry-forward means a test passed at an ancestor commit, with a clean working
104
+ tree, and its declared shard inputs are byte-identical now. A receipt recorded
105
+ dirty is never a carry source: the merged `repo.dirty` is computed from fresh
106
+ inputs only, so carrying from it would launder the dirty flag. It does not establish that omitted dependencies
103
107
  or the old environment remain equivalent. Carried shards are rejected unless
104
108
  policy opts in and bounds their original age.
105
109
 
@@ -38,8 +38,10 @@ session failed.
38
38
 
39
39
  The merged receipt records input source names, counts, times, and recorded
40
40
  signers, then signs the result with the merger's key. Input signatures are
41
- provenance, not independently verified during offline merge. The merger
42
- attests to the union.
41
+ provenance, not verified during an offline merge, unless `--verify-inputs` is
42
+ passed: then every input must verify against its signer's current GitHub keys
43
+ (unsigned inputs are refused) before the union is formed. The merger attests
44
+ to the union either way.
43
45
 
44
46
  ## Carry forward unchanged shards
45
47
 
@@ -55,10 +57,22 @@ An absent old shard is carried only when:
55
57
  1. its receipt commit is the current commit or an ancestor;
56
58
  2. its stored narrow manifest—including explicit extra paths—recomputes
57
59
  identically at the current tree;
58
- 3. it does not duplicate a fresh node ID;
59
- 4. every claimed node ID exists in the old tests list.
60
+ 3. its receipt was recorded with a clean working tree (`repo.dirty` false): a
61
+ dirty recording may have exercised uncommitted bytes outside the shard's
62
+ narrow manifest, and the merged `repo.dirty` covers fresh inputs only;
63
+ 4. it does not duplicate a fresh node ID;
64
+ 5. every claimed node ID exists in the old tests list.
65
+
66
+ Every shard entry records `dirty` (the recording tree's state), merge ORs
67
+ shard flags into the merged `repo.dirty`, carry-forward refuses a dirty shard,
68
+ each carried shard records `carried.original_dirty`, and verification refuses
69
+ any dirty shard unless `allow_dirty` is set.
60
70
 
61
71
  Freshly rerun shards always win. Changed or malformed scopes require a rerun.
72
+ `gpu-proof plan --receipt old.json --repo .` applies the same conditions as a
73
+ dry run and reports, per shard, `carryable` or `stale` with the reason (add
74
+ `--json` for drivers), so a partial re-run can be planned without reimplementing
75
+ the checks.
62
76
 
63
77
  Verification rejects carried shards by default:
64
78
 
@@ -1,4 +1,5 @@
1
1
  site_name: pytest-gpu-proof
2
+ site_url: https://a2r-lab.org/pytest-gpu-proof/docs/
2
3
  site_description: >-
3
4
  pytest plugin for GPU equivalence testing with signed receipts verified via
4
5
  GitHub SSH keys
@@ -7,6 +8,7 @@ repo_name: A2R-Lab/pytest-gpu-proof
7
8
 
8
9
  theme:
9
10
  name: material
11
+ custom_dir: website/overrides
10
12
  palette:
11
13
  - scheme: default
12
14
  primary: indigo
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pytest-gpu-proof"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "pytest plugin for GPU equivalence testing with signed receipts verified via GitHub SSH keys"
9
9
  readme = "README.md"
10
- requires-python = ">=3.11"
10
+ requires-python = ">=3.10"
11
11
  license = { text = "MIT" }
12
12
  keywords = ["pytest", "gpu", "cuda", "testing", "signing", "attestation"]
13
13
  classifiers = [
@@ -17,6 +17,7 @@ classifiers = [
17
17
  "License :: OSI Approved :: MIT License",
18
18
  "Operating System :: OS Independent",
19
19
  "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
20
21
  "Programming Language :: Python :: 3.11",
21
22
  "Programming Language :: Python :: 3.12",
22
23
  "Programming Language :: Python :: 3.13",
@@ -26,11 +27,12 @@ classifiers = [
26
27
  dependencies = [
27
28
  "pytest>=7.0",
28
29
  "cryptography>=41.0",
30
+ "tomli>=2.0; python_version < '3.11'",
29
31
  ]
30
32
 
31
33
  [project.urls]
32
- Homepage = "https://github.com/A2R-Lab/pytest-gpu-proof"
33
- Documentation = "https://a2r-lab.github.io/pytest-gpu-proof/"
34
+ Homepage = "https://a2r-lab.org/pytest-gpu-proof/"
35
+ Documentation = "https://a2r-lab.org/pytest-gpu-proof/docs/"
34
36
  Repository = "https://github.com/A2R-Lab/pytest-gpu-proof"
35
37
  Issues = "https://github.com/A2R-Lab/pytest-gpu-proof/issues"
36
38
  Changelog = "https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md"
@@ -0,0 +1,3 @@
1
+ pytest>=7.0
2
+ cryptography>=41.0
3
+ tomli>=2.0; python_version < '3.11'
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0"