Dependency-free path_proof package under a dedicated module. Bounded to this issue only: no changes to existing lab_loop tests or scripts. Deliverables: 1. path_proof/ package (receipt.py, keys.py, __init__.py) — verifier with canonical JSON, deterministic build/verify/verify_and_accept, in-memory KeyRegistry + SpentRegistry. No I/O, no wall clock, no randomness. 2. Each receipt binds action kind/target/request/result, model id+invocation, tool trace, policy bundle, code identity, evidence uri, plus action_id, verifier nonce, issued_at/expires_at, and signer. 3. Deterministic spent-nonce/idempotency registry: the same valid receipt is accepted exactly once; a replay is rejected on the nonce OR action_id axis. 4. Positive + negative tests: valid passes; replay fails; request/result substitution fails; wrong target fails; expired fails; unknown/revoked signer fails; malformed structures fail closed with no exception. Every negative case asserts rejection AND a specific reason. 5. docs/path-proof-threat-boundary.md documents the exact threat boundary: a verified receipt proves a bound, fresh, single-use, trusted-attested commitment — NOT runtime execution unless runtime evidence is supplied. Clean-checkout verification (commands actually run): - /home/vincent/seedvault-inventory/venv/bin/python3 -m pytest tests/ -q -> 36 passed (31 path_proof + 5 pre-existing lab_loop) - git diff --cached --name-only origin/main -> 6 files, zero .pyc - __pycache__ gitignored; clean checkout stays clean after tests Closes #28 Refs: #3 (Vincent path-proof critique), #19/PR #42 (canon validator r2) [HANDOFF] from=vincent to=timmy
3.4 KiB
3.4 KiB
Path Proof Receipt — Threat Boundary
This document states, precisely, what the path_proof verification gate proves
and does not prove. It is the contract the verifier enforces and the limit a
consumer must respect. The field rationale lives in
docs/path-proof-receipt.md (branch timmy/3-path-proof-receipt); this file
is the executable-side boundary statement required by issue #28.
What a verified, freshly-accepted receipt proves
A receipt that passes verify_receipt and is accepted by verify_and_accept
proves exactly the following, and nothing more:
- Binding. The receipt commits, as a single signed unit, to:
- the consequential action (
action.kind,action.target), - the exact request and observed result it claims
(
action.request_sha256,action.result_sha256), - the model invocation identity (
path.model.id,path.model.invocation), - the ordered tool path and effective policy
(
path.tools.trace_sha256,path.policy.bundle_sha256), - the immutable code identity (
path.code.repo,path.code.commit,path.code.entrypoint), and - the retrievable evidence bundle (
evidence.uri).
- the consequential action (
- Trust. The attestation resolves to a signer that is present in the caller's trusted key registry, is not revoked, and is within its validity window at the time of verification.
- Freshness. The receipt is inside its
[issued_at, expires_at]window. - Single-use. The receipt's
nonceandaction_idhave not been previously consumed, so an identical receipt cannot be accepted a second time (idempotency / replay protection).
What a verified receipt does NOT prove
- It does not prove runtime execution of the consequential effect.
result_sha256is the hash of an observed result the recorder claims it saw. Proving the side effect actually took place requires the verifier to independently re-observe it (for example, read the Gitea label state back and compare it toresult_sha256). Until that happens the effect is attested, not confirmed. - It does not prove the provider ran the advertised weights.
path.model.idis an identity string, not a cryptographic attestation of model provenance. Only a separately trusted provider model attestation could close that gap. - It does not survive compromise of both the recorder and its signing key. The trust anchor is the key registry. If both are compromised, a forged receipt with a valid signature is indistinguishable from a genuine one.
- It does not prove the evidence bundle was not selectively redacted ambiguously. The redaction manifest is part of the bundle; verifying the bundle digest proves integrity of what is present, not that nothing material was omitted.
Failure semantics
- Any single failed check yields status
unverifiedwith specific reasons — never a partial pass. - A structurally unreadable receipt yields status
malformed. - A rejected receipt has no side effects: it does not consume a nonce or action_id. Only a verified acceptance marks identities as spent.
Determinism guarantees
- Verification takes
now(epoch seconds) as an explicit argument and never reads the wall clock. - The key registry and spent-identity registry are in-memory objects the caller controls; the module performs no I/O, no randomness, and no network.
- Therefore the gate is fully reproducible from a clean checkout and leaves no generated artifacts (test caches are gitignored).