pytest-gpu-proof 0.3.0__tar.gz → 0.4.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 (110) hide show
  1. pytest_gpu_proof-0.4.0/.github/workflows/ci.yml +68 -0
  2. pytest_gpu_proof-0.4.0/AGENTS.md +29 -0
  3. pytest_gpu_proof-0.4.0/CHANGELOG.md +129 -0
  4. pytest_gpu_proof-0.4.0/CLAUDE.md +14 -0
  5. pytest_gpu_proof-0.4.0/CONTRIBUTING.md +41 -0
  6. pytest_gpu_proof-0.4.0/PKG-INFO +331 -0
  7. pytest_gpu_proof-0.4.0/README.md +293 -0
  8. pytest_gpu_proof-0.4.0/ROADMAP.md +15 -0
  9. pytest_gpu_proof-0.4.0/SECURITY.md +33 -0
  10. pytest_gpu_proof-0.4.0/docs/architecture.md +99 -0
  11. pytest_gpu_proof-0.4.0/docs/ci_gpu_mode.md +61 -0
  12. pytest_gpu_proof-0.4.0/docs/index.md +37 -0
  13. pytest_gpu_proof-0.4.0/docs/local_mode.md +87 -0
  14. pytest_gpu_proof-0.4.0/docs/policy.md +91 -0
  15. pytest_gpu_proof-0.4.0/docs/quickstart.md +101 -0
  16. pytest_gpu_proof-0.4.0/docs/security_model.md +111 -0
  17. pytest_gpu_proof-0.4.0/docs/sharding.md +77 -0
  18. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/mkdocs.yml +1 -0
  19. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/pyproject.toml +25 -7
  20. pytest_gpu_proof-0.4.0/requirements-dev.txt +8 -0
  21. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/__init__.py +1 -0
  22. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/cli.py +2 -2
  23. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/compare.py +68 -0
  24. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/config.py +44 -5
  25. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/fingerprint.py +166 -0
  26. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/gitutils.py +167 -0
  27. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/merge.py +43 -22
  28. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/plugin.py +146 -80
  29. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/receipt.py +237 -0
  30. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/signers/ed25519.py +47 -18
  31. pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/verify.py +548 -0
  32. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/tests/conftest.py +27 -0
  33. pytest_gpu_proof-0.4.0/tests/test_cli.py +82 -0
  34. pytest_gpu_proof-0.4.0/tests/test_compare.py +118 -0
  35. pytest_gpu_proof-0.4.0/tests/test_config.py +106 -0
  36. pytest_gpu_proof-0.4.0/tests/test_fingerprint.py +209 -0
  37. pytest_gpu_proof-0.4.0/tests/test_gitutils.py +161 -0
  38. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/tests/test_merge.py +83 -3
  39. pytest_gpu_proof-0.4.0/tests/test_plugin_capture.py +653 -0
  40. pytest_gpu_proof-0.4.0/tests/test_receipt.py +300 -0
  41. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/tests/test_sharding.py +199 -13
  42. pytest_gpu_proof-0.4.0/tests/test_signing.py +272 -0
  43. pytest_gpu_proof-0.4.0/tests/test_verifier.py +865 -0
  44. pytest_gpu_proof-0.3.0/.github/workflows/ci.yml +0 -27
  45. pytest_gpu_proof-0.3.0/CHANGELOG.md +0 -66
  46. pytest_gpu_proof-0.3.0/CLAUDE.md +0 -73
  47. pytest_gpu_proof-0.3.0/PKG-INFO +0 -400
  48. pytest_gpu_proof-0.3.0/README.md +0 -367
  49. pytest_gpu_proof-0.3.0/ROADMAP.md +0 -11
  50. pytest_gpu_proof-0.3.0/docs/architecture.md +0 -92
  51. pytest_gpu_proof-0.3.0/docs/ci_gpu_mode.md +0 -100
  52. pytest_gpu_proof-0.3.0/docs/index.md +0 -33
  53. pytest_gpu_proof-0.3.0/docs/local_mode.md +0 -136
  54. pytest_gpu_proof-0.3.0/docs/quickstart.md +0 -377
  55. pytest_gpu_proof-0.3.0/docs/security_model.md +0 -87
  56. pytest_gpu_proof-0.3.0/docs/sharding.md +0 -109
  57. pytest_gpu_proof-0.3.0/requirements-dev.txt +0 -3
  58. pytest_gpu_proof-0.3.0/src/pytest_gpu_proof/__init__.py +0 -1
  59. pytest_gpu_proof-0.3.0/src/pytest_gpu_proof/compare.py +0 -49
  60. pytest_gpu_proof-0.3.0/src/pytest_gpu_proof/fingerprint.py +0 -38
  61. pytest_gpu_proof-0.3.0/src/pytest_gpu_proof/gitutils.py +0 -112
  62. pytest_gpu_proof-0.3.0/src/pytest_gpu_proof/receipt.py +0 -166
  63. pytest_gpu_proof-0.3.0/src/pytest_gpu_proof/verify.py +0 -412
  64. pytest_gpu_proof-0.3.0/tests/test_compare.py +0 -64
  65. pytest_gpu_proof-0.3.0/tests/test_fingerprint.py +0 -40
  66. pytest_gpu_proof-0.3.0/tests/test_gitutils.py +0 -69
  67. pytest_gpu_proof-0.3.0/tests/test_plugin_capture.py +0 -307
  68. pytest_gpu_proof-0.3.0/tests/test_receipt.py +0 -142
  69. pytest_gpu_proof-0.3.0/tests/test_signing.py +0 -96
  70. pytest_gpu_proof-0.3.0/tests/test_verifier.py +0 -363
  71. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/.github/workflows/docs.yml +0 -0
  72. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/.github/workflows/publish-testpypi.yml +0 -0
  73. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/.github/workflows/publish.yml +0 -0
  74. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/.gitignore +0 -0
  75. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/LICENSE +0 -0
  76. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/RELEASING.md +0 -0
  77. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/docs/landscape.md +0 -0
  78. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/Makefile +0 -0
  79. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/README.md +0 -0
  80. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +0 -0
  81. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu +0 -0
  82. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +0 -0
  83. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/CMakeLists.txt +0 -0
  84. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/README.md +0 -0
  85. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/src/bindings.cpp +0 -0
  86. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h +0 -0
  87. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +0 -0
  88. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +0 -0
  89. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/CMakeLists.txt +0 -0
  90. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/README.md +0 -0
  91. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/src/bindings.cpp +0 -0
  92. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h +0 -0
  93. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +0 -0
  94. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +0 -0
  95. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/github_gpu_runner/.github/workflows/gpu-test.yml +0 -0
  96. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt +0 -0
  97. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/README.md +0 -0
  98. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +0 -0
  99. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +0 -0
  100. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +0 -0
  101. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/local_receipt_verify/.github/workflows/verify.yml +0 -0
  102. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/minimal_python_only/conftest.py +0 -0
  103. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/minimal_python_only/test_minimal.py +0 -0
  104. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +0 -0
  105. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +0 -0
  106. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/install.sh +0 -0
  107. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/requirements.txt +0 -0
  108. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/__main__.py +0 -0
  109. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/signers/__init__.py +0 -0
  110. {pytest_gpu_proof-0.3.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/signers/base.py +0 -0
@@ -0,0 +1,68 @@
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", "3.13"]
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
28
+
29
+ coverage:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.12"
36
+ - name: Install package with dev extras
37
+ run: pip install -e ".[dev]"
38
+ - name: Enforce 100% line and branch coverage
39
+ run: |
40
+ coverage run -m pytest tests/ -q
41
+ coverage report --fail-under=100
42
+ coverage xml
43
+ - uses: actions/upload-artifact@v4
44
+ with:
45
+ name: coverage-xml
46
+ path: coverage.xml
47
+
48
+ docs-and-package:
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - uses: actions/setup-python@v5
53
+ with:
54
+ python-version: "3.12"
55
+ - name: Install package with dev extras
56
+ run: pip install -e ".[dev]"
57
+ - name: Build documentation strictly
58
+ run: mkdocs build --strict
59
+ - name: Build and inspect distributions
60
+ run: |
61
+ python -m build
62
+ python -m twine check --strict dist/*
63
+ - name: Smoke-test the wheel
64
+ run: |
65
+ python -m venv /tmp/wheeltest
66
+ /tmp/wheeltest/bin/pip install -q dist/*.whl
67
+ /tmp/wheeltest/bin/gpu-proof --help
68
+ /tmp/wheeltest/bin/python -m pytest -p pytest_gpu_proof --version
@@ -0,0 +1,29 @@
1
+ # Agent guide
2
+
3
+ This repository is a security-sensitive pytest plugin. Preserve fail-closed
4
+ behavior and treat receipt, fingerprint, signature, Git, merge, and policy code
5
+ as trust boundaries.
6
+
7
+ Before changing behavior, read:
8
+
9
+ - `README.md`
10
+ - `docs/architecture.md`
11
+ - `docs/security_model.md`
12
+ - `docs/policy.md`
13
+ - `CONTRIBUTING.md`
14
+
15
+ Implementation rules:
16
+
17
+ - New receipts are schema 3; keep explicit legacy verification isolated.
18
+ - Signer metadata belongs inside the signed payload.
19
+ - Default fingerprint scope is the full tracked repository; generated inputs
20
+ require explicit extra paths.
21
+ - Never turn receipt-emission failures into success unless the caller selected
22
+ `best_effort`.
23
+ - Never accept unknown policy fields or malformed receipt structure.
24
+ - Keep tests hermetic: mock GitHub key lookup and local identity.
25
+ - Maintain 100% line and branch coverage. Add adversarial cases for new trust
26
+ branches.
27
+ - Do not edit generated `site/` or release artifacts in `dist/`.
28
+
29
+ Run the complete check list from `CONTRIBUTING.md` before proposing a PR.
@@ -0,0 +1,129 @@
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.4.0] — 2026-08-17
8
+
9
+ ### Added
10
+
11
+ - Schema `"3"`: signer username, key fingerprint, key algorithm, exact test
12
+ collection, session outcome, pytest arguments, and per-shard environment/time
13
+ are included in the signed payload.
14
+ - Open contributor and restricted signer policies. Restricted mode supports
15
+ username and exact SSH-key-fingerprint allowlists.
16
+ - Policy pinning for mode, global tracked/extra fingerprint scope, exact test
17
+ manifest, shard names, and per-shard tracked/extra scope.
18
+ - Explicit fingerprint paths for ignored/generated inputs and manifest support
19
+ for symlinks and submodule gitlinks.
20
+ - Explicit receipt-artifact exclusions avoid self-referential whole-tree
21
+ fingerprints and can be pinned by verification policy.
22
+ - `--gpu-proof-best-effort` as an explicit development escape hatch.
23
+ - `min_schema` policy field to refuse legacy schema-1/2 receipts.
24
+ - Python 3.13 CI, strict docs/package jobs, and enforced 100% line and branch
25
+ coverage.
26
+
27
+ ### Changed
28
+
29
+ - The default fingerprint scope is the entire Git-tracked repository instead
30
+ of `src,tests`.
31
+ - Receipt creation now fails pytest on Git, fingerprint, key, serialization, or
32
+ write errors; it also removes stale output at session start.
33
+ - Schema-3 verification rejects dirty recording and verification trees by
34
+ default and accepts receipt commits only at the current commit or an ancestor.
35
+ - Setup/teardown failures, missing terminal reports, comparison exceptions,
36
+ skipped tests, and overall session failure are represented and verified.
37
+ - GPU metadata records all devices reported by `nvidia-smi`.
38
+ - Array comparison is shape-safe, uses tolerance only for float/complex data,
39
+ and uses exact equality for other dtypes.
40
+ - Receipt writes are atomic and strict JSON forbids NaN/non-JSON values.
41
+ - Receipt generation rejects xdist workers; use separate shard processes.
42
+ - Legacy schema-1/2 verification derives the policy-checked key fingerprint
43
+ from the key that actually verified the signature; an asserted
44
+ `signature.key_fingerprint` that disagrees is rejected.
45
+ - `signer_mode: restricted` policies reject unsigned receipts even with
46
+ `--allow-unsigned`, and `--github-user` must match the signed schema-3
47
+ identity.
48
+ - The receipt under verification is excluded from the verification-tree dirty
49
+ check, so an untracked just-generated receipt verifies without gitignoring.
50
+ - Passphrase-protected SSH keys prompt correctly on current cryptography
51
+ releases (`ValueError` as well as `TypeError`) and fail closed with an
52
+ actionable message when no terminal is available.
53
+ - Uninitialized submodule checkouts fingerprint their index gitlink commit
54
+ instead of accidentally recording the parent repository's HEAD.
55
+ - Receipt/shard age limits are enforced to the exact day boundary.
56
+ - GitHub usernames are validated before key fetches and key responses are
57
+ size-capped.
58
+ - A stale receipt that cannot be cleared at session start raises a pytest
59
+ usage error (an exit-status write that early would be silently overwritten).
60
+ - Merging receipts without session timestamps is refused with a clear error.
61
+
62
+ ### Fixed
63
+
64
+ - Carry-forward now recomputes the stored fingerprint algorithm and preserves
65
+ explicit generated/ignored shard inputs.
66
+ - Signed identity and algorithm substitution are rejected.
67
+ - Empty source scope, unmerged index entries, malformed policies, incomplete
68
+ collections, and stale success artifacts fail closed.
69
+
70
+ ## [0.3.0] — 2026-08-08
71
+
72
+ ### Added
73
+ - Schema `"2"` sharded receipts: `--gpu-proof-shard NAME` +
74
+ `--gpu-proof-shard-fingerprint-paths` emit a per-shard narrow fingerprint;
75
+ `gpu-proof merge` unions schema-2 shards (unique names enforced).
76
+ - Verifiable carry-forward: `gpu-proof merge --carry-from OLD` grafts shards
77
+ absent from the fresh inputs iff the old commit is an ancestor AND the
78
+ shard's narrow fingerprint recomputes clean; grafted shards are marked
79
+ `carried` and the verifier rejects them unless the policy sets
80
+ `allow_carried: true` (bounded by `carried_max_age_days`, default 30).
81
+ - Verifier: schema-2 checks (per-shard fingerprint recompute, membership
82
+ partition of `tests[]`, carried-shard policy gate). Schema-1 receipts are
83
+ unchanged and a schema-1 receipt carrying a `shards` block is rejected.
84
+
85
+ ## [0.2.0] — 2026-08-07
86
+
87
+ ### Added
88
+ - `gpu-proof merge`: union N shard receipts from one commit into a single
89
+ re-signed receipt (per-module crash isolation / machine sharding). Refuses
90
+ shards that disagree on schema, commit SHA, fingerprint, mode, or
91
+ environment; duplicate node IDs across shards are a hard error. Records
92
+ per-shard provenance under `session.shards` (additive — the verifier is
93
+ unchanged). `repo.dirty` is OR-ed; `gpu_info` survives CPU-only shards.
94
+ Docs: `docs/sharding.md`.
95
+
96
+ ### Fixed
97
+ - Explicit CLI values equal to their built-in defaults are no longer silently
98
+ ignored in favor of `[tool.gpu_proof]` (value-taking options now register a
99
+ `None` sentinel; `--gpu-proof-fail-on-skip` ORs with the toml value since a
100
+ store_true flag can only turn it on).
101
+
102
+ ## [0.1.0] — 2026-07-07
103
+
104
+ First public release.
105
+
106
+ ### Added
107
+ - pytest plugin (`-p` auto-loaded via the `pytest11` entry point): captures
108
+ per-test outcomes and skips, git SHA, a SHA-256 source fingerprint over
109
+ configured paths, and self-reported GPU info; emits a signed JSON receipt at
110
+ session end (`--gpu-proof-enable`).
111
+ - Ed25519 SSH-key signing; signature covers the canonical (compact,
112
+ sorted-key) JSON without the signature block. `none` backend for unsigned
113
+ receipts.
114
+ - CPU-only verifier `gpu-proof verify`: signature against the signer's public
115
+ `github.com/<user>.keys`, fingerprint match, commit SHA, outcome + skip
116
+ policy, `--require-gpu`, freshness (`--max-age-days`, `0` = today only),
117
+ dirty policy.
118
+ - Skip policies: `--allow-skipped` (any) or `--expected-skips FILE` (exact-set
119
+ baseline — unexpected AND stale entries both fail); mutually exclusive.
120
+ Also `[tool.gpu_proof] expected_skips`.
121
+ - Signer resolution: explicit flag/config → `gh` CLI login (keyholder) →
122
+ origin-remote owner with a printed warning (org remotes have no SSH keys).
123
+ - `is_dirty` ignores untracked content inside submodules (pin moves and
124
+ tracked edits still count).
125
+ - Receipt diffing (`compare.py`), `[tool.gpu_proof]` pyproject configuration,
126
+ docs site (mkdocs), honest trust-model statement in
127
+ `docs/security_model.md`.
128
+
129
+ [0.1.0]: https://github.com/A2R-Lab/pytest-gpu-proof/releases/tag/v0.1.0
@@ -0,0 +1,14 @@
1
+ # Claude orientation
2
+
3
+ Use [AGENTS.md](AGENTS.md) as the canonical agent guide. In particular:
4
+
5
+ - a receipt is signer attestation, not proof of GPU execution;
6
+ - receipt, fingerprint, signature, Git, merge, and policy code fail closed;
7
+ - new recordings use schema 3 and bind signer metadata inside the signature;
8
+ - the default manifest covers the full tracked repository;
9
+ - tests are hermetic and maintain 100% line and branch coverage;
10
+ - generated `site/`, `dist/`, coverage output, real keys, and local receipts are
11
+ not source changes.
12
+
13
+ Before implementation work, read `docs/architecture.md`,
14
+ `docs/security_model.md`, `docs/policy.md`, and `CONTRIBUTING.md`.
@@ -0,0 +1,41 @@
1
+ # Contributing
2
+
3
+ ## Development setup
4
+
5
+ ```bash
6
+ python -m venv .venv
7
+ source .venv/bin/activate
8
+ python -m pip install -e ".[dev]"
9
+ ```
10
+
11
+ ## Required checks
12
+
13
+ ```bash
14
+ python -m pytest tests -q
15
+ coverage run -m pytest tests -q
16
+ coverage report --fail-under=100
17
+ mkdocs build --strict
18
+ python -m build
19
+ python -m twine check --strict dist/*
20
+ ```
21
+
22
+ CI runs the test suite on Python 3.11, 3.12, and 3.13, independently enforces
23
+ 100% line and branch coverage, builds docs strictly, and smoke-tests the wheel.
24
+
25
+ ## Change expectations
26
+
27
+ - Add adversarial tests for trust-boundary changes, not only happy paths.
28
+ - Keep receipt generation and verification fail-closed by default.
29
+ - Treat schema and policy changes as compatibility decisions; document them in
30
+ `CHANGELOG.md` and the relevant guide.
31
+ - Keep Git and network tests hermetic. Never depend on a developer's actual
32
+ GitHub login, SSH keys, GPU, or global Git configuration.
33
+ - Use separate pytest processes for shards. Receipt generation does not support
34
+ xdist workers.
35
+ - Do not commit generated `site/`, coverage files, caches, or local receipts.
36
+
37
+ ## Pull requests
38
+
39
+ Describe the trust claim before and after the change, migration implications,
40
+ and the exact checks run. Small, reviewable commits are preferred. Security
41
+ reports should follow [SECURITY.md](SECURITY.md), not a public issue.
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-gpu-proof
3
+ Version: 0.4.0
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/
7
+ Project-URL: Repository, https://github.com/A2R-Lab/pytest-gpu-proof
8
+ Project-URL: Issues, https://github.com/A2R-Lab/pytest-gpu-proof/issues
9
+ Project-URL: Changelog, https://github.com/A2R-Lab/pytest-gpu-proof/blob/main/CHANGELOG.md
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: attestation,cuda,gpu,pytest,signing,testing
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: cryptography>=41.0
26
+ Requires-Dist: pytest>=7.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.2; extra == 'dev'
29
+ Requires-Dist: coverage[toml]>=7.6; extra == 'dev'
30
+ Requires-Dist: hatchling>=1.25; extra == 'dev'
31
+ Requires-Dist: mkdocs-material>=9.5; extra == 'dev'
32
+ Requires-Dist: numpy>=1.26; extra == 'dev'
33
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
34
+ Requires-Dist: twine>=5.0; extra == 'dev'
35
+ Provides-Extra: yaml
36
+ Requires-Dist: pyyaml>=6.0; extra == 'yaml'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # pytest-gpu-proof
40
+
41
+ [![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/)
43
+ [![PyPI](https://img.shields.io/pypi/v/pytest-gpu-proof.svg)](https://pypi.org/project/pytest-gpu-proof/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/pytest-gpu-proof.svg)](https://pypi.org/project/pytest-gpu-proof/)
45
+ [![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
+
47
+ Signed pytest receipts for local GPU runs, verified in CPU-only CI.
48
+
49
+ `pytest-gpu-proof` records which marked tests ran, their outcomes, the Git
50
+ commit and source fingerprint, the environment, and the run time. It signs
51
+ that payload with an existing SSH key. A normal CPU runner can then verify the
52
+ receipt against the signer's public keys on GitHub without rerunning CUDA.
53
+
54
+ This is a practical bridge for projects with local or lab GPUs but no
55
+ always-on GPU CI. It is signer attestation—not hardware attestation. See the
56
+ [security model](docs/security_model.md) before making stronger claims.
57
+
58
+ ## The workflow
59
+
60
+ ```text
61
+ GPU machine CPU-only CI
62
+ ────────────────────────────────── ───────────────────────────────
63
+ pytest --gpu-proof-enable gpu-proof verify --receipt ...
64
+ run marked tests validate schema and signature
65
+ capture setup/call/teardown outcomes fetch current GitHub SSH keys
66
+ fingerprint the checked-out tree recompute source fingerprint
67
+ sign one schema-3 receipt enforce repository policy
68
+ ```
69
+
70
+ The default signer policy is **open**: a valid receipt from any GitHub user is
71
+ accepted. This supports contributor-signed pull requests. Repositories that
72
+ only trust maintainers or dedicated CI keys can use **restricted** mode with
73
+ username and/or key-fingerprint allowlists.
74
+
75
+ ## Install
76
+
77
+ ```bash
78
+ python -m pip install pytest-gpu-proof
79
+ ```
80
+
81
+ For development:
82
+
83
+ ```bash
84
+ git clone https://github.com/A2R-Lab/pytest-gpu-proof.git
85
+ cd pytest-gpu-proof
86
+ python -m pip install -e ".[dev]"
87
+ ```
88
+
89
+ Requirements: Python 3.11+, pytest 7+, and `cryptography` 41+.
90
+
91
+ ## Quick start
92
+
93
+ Mark a test, or use the comparison fixture:
94
+
95
+ ```python
96
+ import pytest
97
+
98
+
99
+ @pytest.mark.gpu_proof
100
+ def test_rnea(gpu_proof_check):
101
+ gpu_proof_check(
102
+ name="rnea",
103
+ reference=python_rnea,
104
+ candidate=cuda_rnea,
105
+ args=(model, q, qd, qdd),
106
+ metadata={"robot": "go2"},
107
+ )
108
+ ```
109
+
110
+ Run from the root of the project being attested:
111
+
112
+ ```bash
113
+ pytest tests/gpu --gpu-proof-enable --gpu-proof-github-user YOUR_USER
114
+ gpu-proof verify --receipt gpu-proof.json --repo .
115
+ ```
116
+
117
+ Commit `gpu-proof.json` with the code, then add a CPU-only CI step:
118
+
119
+ ```yaml
120
+ - name: Verify local GPU test receipt
121
+ run: gpu-proof verify --receipt gpu-proof.json --repo .
122
+ ```
123
+
124
+ Receipt creation is fail-closed. A missing key, invalid Git state, empty
125
+ fingerprint scope, no selected tests, or write failure makes pytest fail and
126
+ leaves no stale receipt behind. `--gpu-proof-best-effort` is an explicit
127
+ development-only opt-out.
128
+
129
+ ## Four interfaces
130
+
131
+ The project exposes four small interfaces:
132
+
133
+ 1. **Markers** select receipt tests.
134
+ 2. **`gpu_proof_check`** compares a reference callable with a candidate.
135
+ 3. **`gpu-proof verify`** validates a receipt and repository policy.
136
+ 4. **`gpu-proof merge`** combines separately executed shards and optionally
137
+ carries unchanged shards forward.
138
+
139
+ ### Markers
140
+
141
+ | Marker | Meaning |
142
+ |---|---|
143
+ | `@pytest.mark.gpu_proof` | Include the test in the receipt |
144
+ | `@pytest.mark.gpu_equivalence` | Alias for `gpu_proof` |
145
+ | `@pytest.mark.gpu_required` | Skip when neither `nvidia-smi` nor PyTorch reports a GPU |
146
+
147
+ Skipped tests are recorded. Verification rejects them by default. Prefer an
148
+ exact `--expected-skips` baseline over the broad `--allow-skipped` escape
149
+ hatch.
150
+
151
+ ### Comparison fixture
152
+
153
+ ```python
154
+ gpu_proof_check(
155
+ name="operation",
156
+ reference=reference_fn,
157
+ candidate=gpu_fn,
158
+ args=(arg1, arg2),
159
+ kwargs={"option": value},
160
+ compare=custom_compare,
161
+ metadata={"case": "small"},
162
+ )
163
+ ```
164
+
165
+ The default comparator is shape-safe: float/complex NumPy-compatible arrays
166
+ use `numpy.allclose(..., equal_nan=True)` and other arrays use exact equality.
167
+ Provide a comparator for GPU tensors, domain-specific tolerances, or structured
168
+ outputs. A comparator should return normally on success and raise
169
+ `AssertionError` on mismatch.
170
+
171
+ ## Recording options
172
+
173
+ | Option | Default | Purpose |
174
+ |---|---:|---|
175
+ | `--gpu-proof-enable` | off | Enable receipt generation |
176
+ | `--gpu-proof-mode` | `local` | Record `local` or `ci-gpu` provenance |
177
+ | `--gpu-proof-out` | `gpu-proof.json` | Output artifact |
178
+ | `--gpu-proof-key` | discovered | SSH private-key file |
179
+ | `--gpu-proof-github-user` | discovered | GitHub account that owns the public key |
180
+ | `--gpu-proof-signing-backend` | `ed25519` | SSH signing, or explicit `none` |
181
+ | `--gpu-proof-required-marker` | `gpu_proof` | Custom receipt marker |
182
+ | `--gpu-proof-fail-on-skip` | off | Fail and suppress the receipt on selected skips |
183
+ | `--gpu-proof-fingerprint-paths` | `.` | Comma-separated Git-tracked scope |
184
+ | `--gpu-proof-fingerprint-extra-paths` | empty | Explicit ignored/generated inputs |
185
+ | `--gpu-proof-fingerprint-excluded-paths` | `gpu-proof.json` | Receipt artifacts omitted to avoid self-reference |
186
+ | `--gpu-proof-best-effort` | off | Warn instead of failing if emission fails |
187
+
188
+ Most defaults can live in the consumer's `pyproject.toml`:
189
+
190
+ ```toml
191
+ [tool.gpu_proof]
192
+ mode = "local"
193
+ output = "gpu-proof.json"
194
+ fingerprint_paths = ["."]
195
+ fingerprint_extra_paths = ["generated/kernel_table.cuh"]
196
+ fingerprint_excluded_paths = ["gpu-proof.json"]
197
+ required_marker = "gpu_proof"
198
+ max_age_days = 30
199
+ require_gpu = true
200
+ ```
201
+
202
+ By default, the fingerprint covers every tracked file in the repository,
203
+ including symlink targets and submodule gitlinks. Ignored/generated artifacts
204
+ are excluded unless explicitly named as extra paths. An empty or unreadable
205
+ scope is an error. The receipt artifact itself is excluded because a signed
206
+ file cannot hash its own final contents; set the exclusion explicitly if your
207
+ committed receipt uses a different path.
208
+
209
+ ## Verification and policy
210
+
211
+ ```bash
212
+ gpu-proof verify \
213
+ --receipt gpu-proof.json \
214
+ --repo . \
215
+ --policy gpu-proof-policy.yaml
216
+ ```
217
+
218
+ Schema-3 verification checks:
219
+
220
+ - strict receipt structure, complete test collection, and session outcome;
221
+ - signature, signed username, key fingerprint, and key algorithm;
222
+ - the signer's current public SSH keys at `github.com/<user>.keys`;
223
+ - tracked and explicit-extra source fingerprints;
224
+ - current/ancestor Git commit and clean recording/verification trees;
225
+ - test and comparison outcomes, exact skip policy, and optional test manifest;
226
+ - optional shard membership, shard fingerprints, and carry-forward policy;
227
+ - mode, GPU-information requirement, and freshness.
228
+
229
+ Open policy, suitable for contributor-signed PRs:
230
+
231
+ ```yaml
232
+ signer_mode: open
233
+ max_age_days: 30
234
+ require_mode: local
235
+ required_fingerprint_paths: ["."]
236
+ required_fingerprint_excluded_paths: [gpu-proof.json]
237
+ required_test_manifest: gpu-proof-tests.txt
238
+ ```
239
+
240
+ Restricted policy, suitable for a maintainer or CI allowlist:
241
+
242
+ ```yaml
243
+ signer_mode: restricted
244
+ allowed_signers: [alice, release-bot]
245
+ allowed_key_fingerprints:
246
+ - "SHA256:..."
247
+ max_age_days: 14
248
+ require_mode: ci-gpu
249
+ required_fingerprint_paths: ["."]
250
+ required_fingerprint_extra_paths: [generated/kernel_table.cuh]
251
+ required_fingerprint_excluded_paths: [gpu-proof.json]
252
+ allow_dirty: false
253
+ allow_carried: false
254
+ ```
255
+
256
+ If both restricted allowlists are present, both must match. Unknown policy
257
+ fields fail verification so misspellings cannot silently weaken policy.
258
+ YAML support is available through `pytest-gpu-proof[yaml]`; JSON policy files
259
+ need no optional dependency.
260
+
261
+ `--allow-unsigned` accepts `"signature": null` with a loud warning. It removes
262
+ signer authentication and should not be used as a merge gate.
263
+
264
+ ## Signing identity
265
+
266
+ The GitHub username is resolved in this order:
267
+
268
+ 1. `--gpu-proof-github-user` or `github_username` configuration;
269
+ 2. the authenticated `gh` CLI user;
270
+ 3. the origin owner, with a warning because organization owners normally do
271
+ not own an individual's SSH key.
272
+
273
+ The private-key file is resolved in this order:
274
+
275
+ 1. `--gpu-proof-key`;
276
+ 2. `git config user.signingKey`;
277
+ 3. `~/.ssh/id_ed25519`, `id_ecdsa`, then `id_rsa`.
278
+
279
+ Ed25519, ECDSA, and RSA-PSS keys are supported. Agent-only and
280
+ hardware-backed keys are not yet supported because signing currently requires
281
+ a readable private-key file.
282
+
283
+ ## Shards
284
+
285
+ Run shards as separate pytest processes; xdist workers are intentionally
286
+ rejected for receipt generation.
287
+
288
+ ```bash
289
+ pytest tests/gpu/a --gpu-proof-enable \
290
+ --gpu-proof-shard=a \
291
+ --gpu-proof-shard-fingerprint-paths=src/a,tests/gpu/a \
292
+ --gpu-proof-out=receipts/a.json
293
+
294
+ pytest tests/gpu/b --gpu-proof-enable \
295
+ --gpu-proof-shard=b \
296
+ --gpu-proof-shard-fingerprint-paths=src/b,tests/gpu/b \
297
+ --gpu-proof-out=receipts/b.json
298
+
299
+ gpu-proof merge receipts/a.json receipts/b.json --out gpu-proof.json
300
+ ```
301
+
302
+ The merger refuses mixed commits, schemas, global fingerprints, modes,
303
+ runtime environments, duplicate tests, and duplicate shard names. It records
304
+ input provenance but does not verify each input signature; the merger signs
305
+ for the union. See [sharding and carry-forward](docs/sharding.md).
306
+
307
+ ## Receipt schema
308
+
309
+ New receipts use schema `"3"`. Signer identity is inside the signed payload,
310
+ test node IDs exactly match the recorded collection, setup and teardown
311
+ failures are terminal outcomes, and writes are atomic. The verifier retains
312
+ schema-1/2 compatibility, but their legacy defaults are less strict.
313
+
314
+ ## Development
315
+
316
+ ```bash
317
+ python -m pytest tests -q
318
+ coverage run -m pytest tests -q
319
+ coverage report --fail-under=100
320
+ mkdocs build --strict
321
+ python -m build
322
+ python -m twine check --strict dist/*
323
+ ```
324
+
325
+ CI tests Python 3.11–3.13 and enforces 100% line and branch coverage. See
326
+ [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md), and
327
+ [RELEASING.md](RELEASING.md).
328
+
329
+ ## License
330
+
331
+ MIT.