pytest-gpu-proof 0.2.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.
- pytest_gpu_proof-0.4.0/.github/workflows/ci.yml +68 -0
- pytest_gpu_proof-0.4.0/AGENTS.md +29 -0
- pytest_gpu_proof-0.4.0/CHANGELOG.md +129 -0
- pytest_gpu_proof-0.4.0/CLAUDE.md +14 -0
- pytest_gpu_proof-0.4.0/CONTRIBUTING.md +41 -0
- pytest_gpu_proof-0.4.0/PKG-INFO +331 -0
- pytest_gpu_proof-0.4.0/README.md +293 -0
- pytest_gpu_proof-0.4.0/ROADMAP.md +15 -0
- pytest_gpu_proof-0.4.0/SECURITY.md +33 -0
- pytest_gpu_proof-0.4.0/docs/architecture.md +99 -0
- pytest_gpu_proof-0.4.0/docs/ci_gpu_mode.md +61 -0
- pytest_gpu_proof-0.4.0/docs/index.md +37 -0
- pytest_gpu_proof-0.4.0/docs/local_mode.md +87 -0
- pytest_gpu_proof-0.4.0/docs/policy.md +91 -0
- pytest_gpu_proof-0.4.0/docs/quickstart.md +101 -0
- pytest_gpu_proof-0.4.0/docs/security_model.md +111 -0
- pytest_gpu_proof-0.4.0/docs/sharding.md +77 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/mkdocs.yml +1 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/pyproject.toml +25 -7
- pytest_gpu_proof-0.4.0/requirements-dev.txt +8 -0
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/__init__.py +1 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/cli.py +12 -1
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/compare.py +68 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/config.py +58 -4
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/fingerprint.py +166 -0
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/gitutils.py +167 -0
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/merge.py +292 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/plugin.py +159 -78
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/receipt.py +237 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/signers/ed25519.py +47 -18
- pytest_gpu_proof-0.4.0/src/pytest_gpu_proof/verify.py +548 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/tests/conftest.py +27 -0
- pytest_gpu_proof-0.4.0/tests/test_cli.py +82 -0
- pytest_gpu_proof-0.4.0/tests/test_compare.py +118 -0
- pytest_gpu_proof-0.4.0/tests/test_config.py +106 -0
- pytest_gpu_proof-0.4.0/tests/test_fingerprint.py +209 -0
- pytest_gpu_proof-0.4.0/tests/test_gitutils.py +161 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/tests/test_merge.py +83 -3
- pytest_gpu_proof-0.4.0/tests/test_plugin_capture.py +653 -0
- pytest_gpu_proof-0.4.0/tests/test_receipt.py +300 -0
- pytest_gpu_proof-0.4.0/tests/test_sharding.py +468 -0
- pytest_gpu_proof-0.4.0/tests/test_signing.py +272 -0
- pytest_gpu_proof-0.4.0/tests/test_verifier.py +865 -0
- pytest_gpu_proof-0.2.0/.github/workflows/ci.yml +0 -27
- pytest_gpu_proof-0.2.0/CHANGELOG.md +0 -51
- pytest_gpu_proof-0.2.0/CLAUDE.md +0 -73
- pytest_gpu_proof-0.2.0/PKG-INFO +0 -400
- pytest_gpu_proof-0.2.0/README.md +0 -367
- pytest_gpu_proof-0.2.0/ROADMAP.md +0 -11
- pytest_gpu_proof-0.2.0/docs/architecture.md +0 -92
- pytest_gpu_proof-0.2.0/docs/ci_gpu_mode.md +0 -100
- pytest_gpu_proof-0.2.0/docs/index.md +0 -33
- pytest_gpu_proof-0.2.0/docs/local_mode.md +0 -136
- pytest_gpu_proof-0.2.0/docs/quickstart.md +0 -377
- pytest_gpu_proof-0.2.0/docs/security_model.md +0 -75
- pytest_gpu_proof-0.2.0/docs/sharding.md +0 -62
- pytest_gpu_proof-0.2.0/requirements-dev.txt +0 -3
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/__init__.py +0 -1
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/compare.py +0 -49
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/fingerprint.py +0 -38
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/gitutils.py +0 -112
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/merge.py +0 -161
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/receipt.py +0 -143
- pytest_gpu_proof-0.2.0/src/pytest_gpu_proof/verify.py +0 -339
- pytest_gpu_proof-0.2.0/tests/test_compare.py +0 -64
- pytest_gpu_proof-0.2.0/tests/test_fingerprint.py +0 -40
- pytest_gpu_proof-0.2.0/tests/test_gitutils.py +0 -69
- pytest_gpu_proof-0.2.0/tests/test_plugin_capture.py +0 -307
- pytest_gpu_proof-0.2.0/tests/test_receipt.py +0 -142
- pytest_gpu_proof-0.2.0/tests/test_signing.py +0 -96
- pytest_gpu_proof-0.2.0/tests/test_verifier.py +0 -363
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/.github/workflows/docs.yml +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/.github/workflows/publish-testpypi.yml +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/.github/workflows/publish.yml +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/.gitignore +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/LICENSE +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/RELEASING.md +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/docs/landscape.md +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/Makefile +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/README.md +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_ctypes_matmul/test_cuda_ctypes_matmul.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/README.md +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_nanobind_matmul/test_cuda_nanobind_matmul.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/README.md +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/src/bindings.cpp +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/src/matmul_cuda.h +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/src/matmul_kernel.cu +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/cuda_pybind11_matmul/test_cuda_pybind11_matmul.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/github_gpu_runner/.github/workflows/gpu-test.yml +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/CMakeLists.txt +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/README.md +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/jax_ffi_matmul.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/src/matmul_ffi.cu +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/jax_ffi_cuda_matmul/test_jax_ffi_cuda_matmul.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/local_receipt_verify/.github/workflows/verify.yml +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/minimal_python_only/conftest.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/minimal_python_only/test_minimal.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/wrapped_cuda_ctypes/fake_cuda_lib.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/examples/wrapped_cuda_ctypes/test_cuda_wrapper.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/install.sh +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/requirements.txt +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/__main__.py +0 -0
- {pytest_gpu_proof-0.2.0 → pytest_gpu_proof-0.4.0}/src/pytest_gpu_proof/signers/__init__.py +0 -0
- {pytest_gpu_proof-0.2.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
|
+
[](https://github.com/A2R-Lab/pytest-gpu-proof/actions/workflows/ci.yml)
|
|
42
|
+
[](https://a2r-lab.github.io/pytest-gpu-proof/)
|
|
43
|
+
[](https://pypi.org/project/pytest-gpu-proof/)
|
|
44
|
+
[](https://pypi.org/project/pytest-gpu-proof/)
|
|
45
|
+
[](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.
|