[Vincent] #28: executable replay/forgery gate for path-proof receipts
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
This commit is contained in:
1 parent
fdb454414d
commit
f61ef22f48
6 files changed
+932
No files matched your search
@@ -0,0 +1,66 @@
|
||||
# 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:
|
||||
|
||||
1. **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`).
|
||||
2. **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.
|
||||
3. **Freshness.** The receipt is inside its `[issued_at, expires_at]` window.
|
||||
4. **Single-use.** The receipt's `nonce` and `action_id` have 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_sha256` is 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 to `result_sha256`). Until that happens the effect is *attested*,
|
||||
not *confirmed*.
|
||||
- **It does not prove the provider ran the advertised weights.** `path.model.id`
|
||||
is 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 **`unverified`** with 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).
|
||||
Reference in new issue
Block a user