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
Owner

A jcp/1 receipt proved a decision happened — over this grant, for this action, at this cost — but not what it was over. Two decisions differing only in payload sealed to identical bytes, so a tool-call receipt never committed 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, not silent

Until now the jcp_receipt tag was written but never read — decorative. It is now load-bearing: verify/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.

Migration proven with real bytes

The allow receipt published in conformance/v1/vectors.json before this change is embedded in a test and must still verify — not a reconstruction, the actual bytes. Both v1 receipts are preserved as conformance/v1/legacy_jcp1.json so cross-language verifiers can prove they honour the dispatch.

Scope — verified, not assumed

JCR-1 is a family of sibling profiles sharing 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.
  • 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 states that explicitly, replacing a sentence that implied a single family-wide core.

Also

  • One insertion point: _chained and _logged both funnel through to_jcr1_wire.
  • ReceiptCore.payload_hash: Option<String> — Some on jcp/2, None on legacy, so callers can tell a payload-bound receipt from one that isn't.
  • New conformance class C23, executable in the harness: payload committed, payload-swap forgery caught, jcp/1 still verifies.

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

A `jcp/1` receipt proved a decision *happened* — over this grant, for this action, at this cost — but **not what it was over**. Two decisions differing only in payload sealed to **identical bytes**, so a tool-call receipt never committed 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, not silent Until now the `jcp_receipt` tag was **written but never read** — decorative. It is now load-bearing: `verify`/`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. ### Migration proven with real bytes The allow receipt **published in `conformance/v1/vectors.json` before this change** is embedded in a test and must still verify — not a reconstruction, the actual bytes. Both v1 receipts are preserved as `conformance/v1/legacy_jcp1.json` so cross-language verifiers can prove they honour the dispatch. ### Scope — verified, not assumed JCR-1 is a **family of sibling profiles** sharing 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`. - `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 states that explicitly, replacing a sentence that implied a single family-wide core. ### Also - One insertion point: `_chained` and `_logged` both funnel through `to_jcr1_wire`. - `ReceiptCore.payload_hash: Option<String>` — `Some` on jcp/2, `None` on legacy, so callers can tell a payload-bound receipt from one that isn't. - New conformance class **C23**, executable in the harness: payload committed, payload-swap forgery caught, `jcp/1` still verifies. 40 suites green, clippy clean, all conformance checks pass.
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]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!23
No description provided.