Skip to content

Commit 208974a

Browse files
authored
Merge pull request #9 from A2R-Lab/agent/harden-receipt-trust-and-coverage
Harden receipt trust, coverage, and documentation
2 parents d3e01e8 + 1fffcb4 commit 208974a

42 files changed

Lines changed: 4172 additions & 1798 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
python-version: ["3.11", "3.12"]
14+
python-version: ["3.11", "3.12", "3.13"]
1515

1616
steps:
1717
- uses: actions/checkout@v4
@@ -25,3 +25,44 @@ jobs:
2525

2626
- name: Run test suite (CPU-only)
2727
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

‎AGENTS.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

‎CHANGELOG.md‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,69 @@ All notable changes to pytest-gpu-proof are documented here. The format is
44
based on [Keep a Changelog](https://keepachangelog.com/); versions follow
55
[SemVer](https://semver.org/) (pre-1.0: minor bumps may break).
66

7+
## [Unreleased] — 0.4.0
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+
770
## [0.3.0] — 2026-08-08
871

972
### Added

‎CLAUDE.md‎

Lines changed: 11 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,14 @@
1-
# CLAUDE.md — orientation for AI agents (and humans) working on pytest-gpu-proof
1+
# Claude orientation
22

3-
pytest-gpu-proof is a **pytest plugin that emits a signed JSON receipt of a GPU
4-
test run** (git SHA + source fingerprint + per-test outcomes + GPU info), plus a
5-
**CPU-only verifier** (`gpu-proof verify`) that checks the signature against the
6-
signer's public `github.com/<user>.keys`. The point: run GPU tests on your own
7-
hardware, let CI prove who attested them — no cloud-GPU fees, no secrets in CI.
3+
Use [AGENTS.md](AGENTS.md) as the canonical agent guide. In particular:
84

9-
**Trust model — never oversell it:** a receipt is a **signed attestation by a
10-
keyholder, NOT cryptographic proof of GPU execution**. `gpu_info` is
11-
self-reported; `--require-gpu` is modest hardening against accidents, not
12-
against a dishonest signer. `docs/security_model.md` is the honest statement of
13-
what is and isn't proven — keep every README/docs claim consistent with it.
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.
1412

15-
## Source layout (`src/pytest_gpu_proof/`)
16-
17-
| Module | Role |
18-
|---|---|
19-
| `plugin.py` | pytest hooks: options, `gpu_proof` marker, `gpu_proof_check` fixture, outcome+skip capture, receipt emission at session end |
20-
| `receipt.py` | payload build (repo/fingerprint/tests/env), canonical JSON, sign+write. Signer resolution: flag/config → `gh` CLI login (keyholder) → origin-remote owner (warned — orgs have no SSH keys) |
21-
| `verify.py` | the 7 verification checks (signature, fingerprint, commit SHA, outcomes+skip policy, gpu_info, freshness, dirty policy). Expected-skips baseline = EXACT set match |
22-
| `cli.py` | `gpu-proof verify` argument surface |
23-
| `config.py` | `GpuProofConfig`; CLI flags override `[tool.gpu_proof]` in pyproject.toml |
24-
| `fingerprint.py` | SHA-256 digest over configured paths |
25-
| `gitutils.py` | git/gh shell-outs, all failure-tolerant (return `None`) |
26-
| `signers/` | `base.py` protocol + `ed25519.py` SSH-key signing / GitHub-keys verification; backend `none` emits unsigned receipts |
27-
| `compare.py` | receipt diffing |
28-
29-
Signing covers the canonical (compact, sorted-key) JSON **without** the
30-
`signature` field; the sig block carries `signer`, key fingerprint, algorithm
31-
(derived from actual key type — don't hardcode ed25519).
32-
33-
## Behavioral invariants (test-enforced — don't regress)
34-
35-
- **Skips prove nothing.** Verifier rejects receipts with skips unless
36-
`--allow-skipped` (any skips) or `--expected-skips` (EXACT baseline: a new
37-
skip fails, a stale baseline entry fails). The two are mutually exclusive.
38-
- **Unsigned receipts** verify only with `--allow-unsigned`, loudly.
39-
- **`--max-age-days 0`** means "today only", not "disabled".
40-
- The signer recorded at signing time must be the **keyholder**, never
41-
silently the repo owner.
42-
43-
## Dev workflow
44-
45-
```bash
46-
.venv/bin/python -m pytest tests/ -q # full suite, ~2s, no GPU needed
47-
.venv/bin/mkdocs build --strict # docs must stay warning-clean
48-
```
49-
50-
- Tests are **hermetic**: `tests/conftest.py` has an autouse fixture nulling
51-
`get_gh_cli_login` (a dev box with authenticated `gh` would otherwise hit the
52-
network). Signer tests re-patch explicitly. Keep new shell-outs mockable and
53-
wrapped in try/except like `gitutils._git`.
54-
- `tests/conftest.py` uses `pytest_plugins = ["pytester"]`; keypairs are
55-
generated in-memory (no real SSH keys touched).
56-
- requires-python ≥ 3.11 (`datetime.UTC`).
57-
- CI (`.github/workflows/`): tests on 3.11/3.12 + mkdocs gh-pages deploy.
58-
59-
## Conventions & state
60-
61-
- Short single-line commit messages; no Co-Authored-By footer.
62-
- Flow: feature branch → PR → CI green → merge to `main`. Consumers install
63-
from git (`pip install -e` on a submodule) or PyPI. **Releases**: OIDC
64-
trusted publishing via `.github/workflows/publish.yml` (TestPyPI on manual
65-
dispatch, PyPI on GitHub release) — process in `RELEASING.md`; keep
66-
`CHANGELOG.md` current and bump `pyproject.toml` version in the same PR.
67-
- Reference integration: **GLASS** (github.com/A2R-Lab/GLASS) —
68-
`test/run_gpu_proof.sh`, `test/expected_skips.txt`,
69-
`.github/workflows/verify-gpu-proof.yml`. If you change plugin/verifier
70-
flags, check GLASS's usage still works and note it in the PR.
71-
- Consumer-side gotcha worth remembering: a repo that submodules this project
72-
under its pytest rootdir must `collect_ignore = ["pytest-gpu-proof"]` in its
73-
conftest, or this repo's `tests/conftest.py` will shadow theirs.
13+
Before implementation work, read `docs/architecture.md`,
14+
`docs/security_model.md`, `docs/policy.md`, and `CONTRIBUTING.md`.

‎CONTRIBUTING.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)