jcp: JCR-1 receipts bind their payload — jcp/2 + version dispatch (SPEC §8, C23) #23

Merged
dcharlot merged 1 commit from jcp/receipt-payload-hash into main 2026-07-18 03:16:43 -04:00

2026-07-18

Transaction Science
jcp: JCR-1 receipts bind their payload — jcp/2 + version dispatch (SPEC §8, C23)
A jcp/1 receipt proved that a decision happened over a grant for an action at a
cost — but NOT what it was over. Two decisions differing only in payload sealed
to identical bytes, so a tool-call receipt did not commit its arguments and a
payload substitution was undetectable in the audit. This adds `payload_hash` to
the signed core and bumps the profile to `jcp/2`.

The bump is honest rather than silent. Until now the `jcp_receipt` tag was
WRITTEN BUT NEVER READ — decorative. It is now load-bearing: `verify` and
`verify_chained` read the profile out of the signed core and recompute the
preimage under THAT profile, so receipts sealed as `jcp/1` keep verifying, and an
unrecognised tag is refused rather than assumed. `jcp/1` is no longer minted.

Scope, verified rather than assumed. JCR-1 is a FAMILY OF SIBLING PROFILES that
share the envelope (canonical det-CBOR + COSE_Sign1/EdDSA) and vocabulary, each
signing its own core. jouleclaw-jcr1 is an independent implementation but signs
its own receipt kinds (coding/stream windows, ui output) — zero grant_id/verdict
— and sense/map/sandbox vectors carry their own cores (map tags its own "map/1").
So this changes ONE profile among siblings and does not touch theirs. SPEC §8 now
says exactly that, instead of implying a single family-wide core.

- to_jcr1_wire gains `payload_hash`; `_chained` and `_logged` inherit it (both
  already funnel through it — one insertion point, not three).
- to_jcr1_wire_v1 retains the byte-exact legacy projection, for verification only.
- ReceiptCore exposes `payload_hash: Option<String>` — Some on jcp/2, None on a
  legacy receipt, so callers can tell a payload-bound receipt from one that isn't.
- New ReceiptError::UnknownProfile.

Migration proven with REAL bytes, not a reconstruction: the allow receipt
published in conformance/v1/vectors.json before this change is embedded in a test
and MUST still verify. The v1 receipts are also preserved as
conformance/v1/legacy_jcp1.json so cross-language verifiers can prove they honour
the dispatch. Vectors regenerated to jcp/2 (both duplicate paths kept in sync).

SPEC §8 rewritten (field list, the REQUIRED version-dispatch rule, the
sibling-profile clarification) + conformance class C23, executable in the harness:
payload committed, payload-swap forgery caught, jcp/1 still verifies.

40 workspace suites green, clippy clean, all conformance checks pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-18 03:04:19 -04:00