Implements §5 step 1 of the Action Receipt blueprint: the envelope spec plus a
signed conformance vector pack.
## The gap this closes
Three receipt families ship today, each holding one quarter of what an audit
asks for, and none binds to the others:
jcp-receipt authority grant chain, verdict, budget drawn
sandbox-proof environment attestation, isolation tier, substrate
JCR-1 work input hash, measured joules, provenance
(nothing) effect what changed, and whether it can be undone
So no single artifact answers "under what authority did this agent do that,
where did it run, what did it cost, and what did it change?" The encoding is
implemented six independent times across the tree — evidence the *encoding* is
portable, and that the *composition* was never specified. Two families already
gesture at the chain (the proof cell carries a receipt_id, the authority receipt
a payload_hash). The intent was there; the binding was not.
## Envelope, not replacement
AR-1 binds the three by content-id and supplies the fourth. Each family keeps
its own canonical encoding and signer; the envelope reaches into them and never
the reverse, so every existing verifier keeps working unchanged.
No new cryptography: canonical det-CBOR and the tagged COSE_Sign1 envelope are
reused verbatim from jcr1. The only primitive added is a canonical *decoder* —
jcr1 ships an encoder and none was needed downstream until now, and a verifier
needs to read a signed core back. It is the exact inverse of the encoder's
subset (maps, arrays, text, unsigned ints; no floats, bools, nulls, tags),
refuses non-canonical length encodings and trailing bytes, and is pinned to the
encoder by a round-trip property test. Taking a CBOR dependency instead would
have contradicted the crate's own claim.
## Refusal over record-keeping
A format that merely records claims is a log. Three shapes are refused at
verification, re-derived from the wire projection rather than a caller-supplied
struct, so a hand-forged receipt that signs correctly is still rejected:
1. effect `irreversible` REQUIRES a plan receipt — binding the cascade's
existing plan/commit split into the evidence.
2. egress `none` REQUIRES zero bytes — the claim is falsified by its own
byte count.
3. egress `attested_enclave` REQUIRES a peer attestation. An unattested
destination is `third_party` even when the operator believes it
trustworthy. Trust is not an egress class.
Two further invariants are normative: `grounding` is carried verbatim and never
upgraded from software to hardware, and the three energy provenances never
collapse into one number.
## Conformance pack
7 positive and 3 negative vectors under jcp/conformance/v1/, signed with a
published fixture key so third parties can verify. The negatives are the
substance: each carries a *genuine* signature and must still be refused, so an
implementation that checks signatures and skips the structural rules passes the
positives and fails the pack. That is precisely the failure mode the pack
exists to catch. The emitter asserts both properties before writing, so an
unverifiable vector cannot be published.
tests/conformance.rs consumes the published JSON from disk — the same bytes a
third party downloads — and checks that re-derived receipt ids and canonical
cores match byte-for-byte.
AR-1 does not solve effect revocation; nothing does in general. It makes the
distinction machine-readable at the moment of action, which is the precondition
for reasoning about it afterwards.
20 tests in the crate; jcp-rs workspace green at 258.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
(cherry picked from commit d3e1c2adda)
54 lines
1.3 KiB
TOML
54 lines
1.3 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
members = [
|
|
"crates/jcp-schema",
|
|
"crates/jcp-grant",
|
|
"crates/jcp-runtime",
|
|
"crates/jcp-receipt",
|
|
"crates/jcp-action-receipt",
|
|
"crates/jcp-bridge",
|
|
"crates/jcp-energy",
|
|
"crates/jcp-wire",
|
|
"crates/jcp-threshold",
|
|
"crates/jcp-channel",
|
|
"crates/jcp-conformance",
|
|
"crates/jcp-cli",
|
|
"crates/jcp-gateway",
|
|
"crates/jcp-trace",
|
|
"crates/jcp-a2a",
|
|
"crates/jcp-x402",
|
|
"crates/jcp-cite",
|
|
"crates/jcp-policy",
|
|
"crates/jcp-prompt",
|
|
"crates/jcp-orchestrate",
|
|
"crates/jcp-intercept",
|
|
"crates/jcp-efficiency",
|
|
"crates/jcp-flow",
|
|
]
|
|
|
|
[workspace.package]
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
license = "Apache-2.0"
|
|
repository = "https://github.com/transaction-science/open-standards"
|
|
rust-version = "1.97"
|
|
|
|
[workspace.dependencies]
|
|
jcp-schema = { path = "crates/jcp-schema" }
|
|
jcp-grant = { path = "crates/jcp-grant" }
|
|
jcp-runtime = { path = "crates/jcp-runtime" }
|
|
jcp-receipt = { path = "crates/jcp-receipt" }
|
|
jcp-action-receipt = { path = "crates/jcp-action-receipt" }
|
|
jcp-bridge = { path = "crates/jcp-bridge" }
|
|
jcp-energy = { path = "crates/jcp-energy" }
|
|
|
|
blake3 = "1"
|
|
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
|
bs58 = "0.5"
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
hex = "0.4"
|
|
thiserror = "2"
|
|
|
|
[workspace.lints.clippy]
|
|
all = "warn"
|