open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-jcr1/docs/JCR-1-v1.1-intent.md
Transaction Science 2dc5b0c4cb jouleclaw-jcr1: JCR-1 reference + 10 vectors + Rust producer + Python/Go verifiers
JCR-1 — the **JouleClaw Receipt Specification** — is the propagation
wedge the 5-lens cross-talk audit named as the single highest-leverage
positioning move. A signed, per-resolution thermodynamic receipt
third-party-verifiable with the spec, the public key, and standard
CBOR + COSE tooling. No JouleClaw dependency required to *check* a
receipt; this crate is needed only to *mint* one.

This commit lands the complete propagation package — spec, suite,
producer, two independent verifiers — as a single workspace crate.

## What's in the crate

```
crates/jouleclaw-jcr1/
├── README.md
├── run.sh                       ← Rust mints, Python + Go each verify
├── docs/
│   ├── JCR-1-spec.md            ← the receipt standard (CC-BY-4.0)
│   └── JCR-1-v1.1-intent.md     ← additive proposal: intent in signed core
├── conformance/
│   ├── README.md
│   ├── vectors.json             ← 10 signed canonical vectors
│   ├── test-keys.json           ← published TEST keys
│   ├── verify.py                ← reference verifier (cbor2 + cryptography)
│   └── verify.go                ← independent Go verifier (stdlib only)
├── examples/
│   └── jcr1_mint.rs             ← Rust producer: read energy → sign → emit
└── src/lib.rs                   ← det-CBOR encoder, COSE_Sign1, HMAC, projection
```

## Surface (Rust)

- `canonical_cbor(&Value)` — RFC 8949 §4.2.1 length-first-then-bytewise
  key ordering, no floats/bools/nulls. Matches the Python
  `cbor2.dumps(canonical=True)` and the Go from-scratch encoder
  byte-for-byte across all 10 conformance vectors.
- `to_jcr1_wire(&Receipt) -> Value` — projects a Rust `Receipt` to
  the JCR-1 wire form: `CascadeTier::L0Cache` becomes wire tag `"L0"`,
  `Provenance` renders PascalCase, wave-8 fields `model_id` + `intent`
  are intentionally dropped (deferred to JCR-1 v1.1 — see
  `docs/JCR-1-v1.1-intent.md`).
- `signed_core_bytes(&Value)` — drops `token_attributions` + null
  fields per JCR-1 §3.1, then canonically encodes.
- `canonical_signed_core(&Receipt)` — convenience wrapping both.
- `cose_sign1_envelope` / `verify_cose_sign1` — COSE_Sign1 (RFC 9052)
  envelope with EdDSA / Ed25519 only. Build the protected header,
  Sig_structure, signature; verify by parsing the tagged CBOR,
  re-building Sig_structure, checking kid == expected_steward, and
  Ed25519-verifying.
- `hmac_tool_hex` — gateway tool-receipt HMAC per JCR-1 §5
  (NUL-delimited canonical payload, HMAC-SHA-256, lowercase hex).
  HMAC implemented inline (30 LOC) since `hmac = 0.12` doesn't
  satisfy the workspace's `sha2 = 0.11` CoreProxy bound.
- `check_honesty(&Value)` — JCR-1 §6 step 5: worst-counter rule,
  joule sum vs tools_touched, mJ resolution floor for non-HwShunt.
- `VECTORS_JSON` / `TEST_KEYS_JSON` — the 10 vectors and test keys
  embedded via `include_str!` so `cargo test` re-verifies the suite
  on every workspace run.

## The decisive test: byte-equal re-encode across three encoders

`vector_signed_cores_re_encode_byte_identical` re-encodes each of
the 10 vectors' receipts via the Rust canonical encoder and asserts
the bytes equal the suite's `signed_core_cbor_hex` — which was
produced by Python's `cbor2.dumps(canonical=True)` and independently
re-produced by the Go verifier's hand-written encoder.

Three encoders, three languages, byte-for-byte identical output on
all 10 vectors — that is what makes the spec the source of truth.

## V5 ↔ V10 the promotion-collapse demo

`vector_5_and_10_share_input_hash_promotion_collapse` asserts the
two vectors share the same `input_hash` and that V5 (L3, 2,500,000
µJ) → V10 (L1, 9 µJ) is a ≥100,000× collapse. The "yang→yin"
turnover claim made auditable in a single diff.

## Tamper detection

`tampering_with_joules_uj_is_caught_by_re_encode` proves an
understated-joules forgery produces different canonical bytes —
matching the negative-test surface documented in the Python and Go
README. The 4 failure modes (re-encode mismatch, payload mismatch,
HMAC mismatch, Ed25519 mismatch) all surface different errors.

## Quickstart

`./crates/jouleclaw-jcr1/run.sh` runs the full demo: Rust mints,
Python verifier accepts the 10 canonical vectors, Go verifier
accepts the same 10 vectors, then BOTH verifiers accept the
freshly Rust-minted receipt.

## Honest scope (v0.1)

- JCR-1 v1 wire form only. Wave-8 `model_id` + `intent` are NOT in
  the signed core; the additive v1.1 proposal in
  `docs/JCR-1-v1.1-intent.md` describes the path.
- Ed25519 (`alg = -8`) only. ES256 (`alg = -7`) permitted by the
  spec but not implemented here — `verify_cose_sign1` rejects other
  algs with `UnsupportedAlg`.
- Key transparency / rotation is out of scope; KERI AID rotation is
  recommended by §4 but resolution is the consumer's job.
- The 10 vectors are TOY joule values exercising every spec path.
  Real evidence requires RAPL/NVML/IOReport cross-validated against
  Zeus — see strategy-and-landscape.md from the source drop.

## Lineage

This commit ingests a May 31 conversation drop at
`joule-claw-web/notes-05312026/` into the open-standards workspace,
restructured per the 4-move plan: (1) adopt the 10 vectors as
fixtures, (2) decide on `steward` + adopt det-CBOR, (3) propose
v1.1 with `intent`, (4) move the producer + verifiers into the
workspace. All four closed in two commits (b39fe21 + this one).

Tests: 25 lib + 1 example smoke + 10 Python + 10 Go = the full suite
across three languages with byte-identical canonical CBOR.
Workspace regression: 2,427 lib tests pass, 0 failures.
2026-06-01 03:32:44 -04:00

7.7 KiB
Raw Permalink Blame History

JCR-1 v1.1 — Proposal: query-intent in the signed core

Status: Proposal (draft). Target: Additive bump of JCR-1, jc_receipt: "1" → optional intent field MAY appear in the signed core. v1.0 verifiers ignore it (per §8 additive evolution).


1. Why

Priscilla (Sentry) ran 116 of her own Claude sessions through a post-hoc classifier and found 67% comprehension / 2% generation — the rest split across modification / process / review / other. The finding generalises: every workload that uses an LLM-shaped runtime distributes across the same six categories, and the comprehension slice is consistently dominant.

JouleClaw already classifies queries at runtime (jouleclaw-intent::HeuristicClassifier + the LlmFallbackClassifier seam) and tags Receipt.intent on the way to the ledger (jouleclaw-ledger::CostSummary::by_intent). What's missing for a deployment to reproduce Priscilla's analysis on its OWN signed receipts is: the intent verdict needs to sit inside the signed core so it survives across third-party audit. An external grader can then run

"prove to me that ≥60% of your Receipt records carried intent=comprehension and a tier in {L0, L0.x, L1, L2}, signed by the steward I trust."

without trusting the runtime's separate dashboard.

This proposal pins how the intent verdict is canonicalised so the audit is mechanical, not interpretive.


2. What's added

One optional top-level field on the JCR-1 receipt:

intent: { intent: enum, classifier_id: string }     // OPTIONAL

2.1 intent.intent — taxonomy (frozen for v1.x)

A stable string enum mirroring jouleclaw_intent::QueryIntent:

Wire form Meaning Verifiable-zone?
comprehension Reading / understanding existing code or state yes
generation Producing new artefacts no
modification Editing existing artefacts in place partial
process Operational / workflow tasks partial
review Reviewing a candidate artefact yes
other Unclear / refused n/a

Names are snake_case text strings — not small-int enums — so the det-CBOR form stays human-inspectable and matches the Rust #[serde(rename_all = "snake_case")] already shipping.

2.2 intent.classifier_id — provenance for drift

A stable string identifying the classifier that produced the verdict ("heuristic:v1", "llm:claude-haiku-4-5", "override:operator", …). Required when intent is present.

A receipt grader that sees a dashboard shift from 67% → 50% comprehension over a week needs to know whether the workload moved or the classifier got changed. The classifier_id is that signal. The grader correlates the shift against a classifier_id change in the audit log; if it tracks, the workload didn't change.

2.3 What is NOT added

  • confidence is intentionally excluded. IntentVerdict.confidence is a f64; JCR-1 §3.1 already excludes floats from the signed core because they're not reproducible across encoders, and confidence is classifier-specific (no shared scale). Confidence stays on the full Receipt JSON outside the signed core, alongside token_attributions.
  • No new intent_categories map or sub-types. The six-variant taxonomy is the wire form. Subdivisions are caller-side concerns.

3. Canonical encoding

The intent field, when present, is a 2-entry CBOR map under the top- level receipt map. Both subfields are required when intent is present (omitting either is a structural violation).

Order under RFC 8949 §4.2.1 (length-first then bytewise) places the keys as:

intent (6) → comes between "claims" (6) and "tier" (4)? No — length-first.
  4-byte keys:  tier
  6-byte keys:  claims, intent, ...

"intent" and "claims" both have length 6; bytewise: c (0x63) < i (0x69), so claims comes before intent. The inner map {intent, classifier_id} sorts to intent (6) before classifier_id (13) by length.

Wire example (canonical CBOR, hex):

A2                              # map(2)
   66 69 6E 74 65 6E 74          # text(6) "intent"
   6D 63 6F 6D 70 72 65 68 65    # text(13) "comprehension"
   65 6E 73 69 6F 6E
   6D 63 6C 61 73 73 69 66 69    # text(13) "classifier_id"
   65 72 5F 69 64
   6C 68 65 75 72 69 73 74 69    # text(12) "heuristic:v1"
   63 3A 76 31

(Hand-wrapped above; an encoder MUST sort the inner map's keys length-first → intent before classifier_id.)


4. Verification flow change

JCR-1 §6 gains one optional step between step 4 (tool HMACs) and step 5 (honesty consistency):

4.5 — Intent structural. If intent is present, both intent.intent and intent.classifier_id MUST be present; intent.intent MUST be one of the six taxonomy values. Unknown intent values are rejected (the spec is #[non_exhaustive] at the Rust level but the wire enum is frozen for v1.x).

The drift bands of §7.1 are unchanged — intent doesn't enter joule re-validation.


5. Backward-compatibility

  • v1.0 verifiers: per §8 ("Unknown fields MUST be ignored, never rejected"), v1.0 verifiers see the extra intent field and skip it. The 10 canonical vectors continue to verify under v1.0 verifiers.
  • v1.1 receipts produced without intent are valid v1.0 receipts.
  • The canonical CBOR re-encode test continues to pass byte-for-byte — intent just adds a new (key, value) pair to the map, with its position determined by RFC 8949 §4.2.1.

This is an additive change. jc_receipt stays "1".


6. Conformance vectors

A v1.1-ratifying conformance suite SHOULD add three vectors on top of the existing ten:

  1. L0 cache hit + intent=comprehension — verifies the canonical map order with intent present and no tools_touched.
  2. L3 model + intent=generation + ModelBased — verifies intent coexists with tools_touched and ModelBased honesty floor.
  3. Promoted skill (collapses V5) + intent=comprehension — the Priscilla 67% case: comprehension intent now resolves at L1.

A v1.1 verifier that round-trips vectors 1–13 byte-identically is v1.1 vector-conformant.


7. Why not add model_id too?

model_id (also a wave-8 Receipt field) is a candidate but raises distinct questions JCR-1 v1.1 deliberately defers:

  • Where does model_id resolve? KERI AID? OCI subject digest? A registry the spec has to pin? A stable identifier scheme is a wider call than adding the intent taxonomy.
  • Per-receipt model_id overlaps with the wave-6 jouleclaw-oci + jouleclaw-slsa provenance chain — those already pin the model binary's identity via in-toto Statement subject + ORAS referrer. Carrying it on the cascade receipt may duplicate that surface.

A separate v1.2 proposal (or a sidecar binding) would resolve model_id once the wider model-identity question is settled.


8. Implementation in jouleclaw-jcr1

The Rust reference (this crate) implements v1.1 behind an opt-in constructor. The current to_jcr1_wire projection drops intent (v1.0 behaviour). When v1.1 ratifies:

// proposed v1.1 surface
pub fn to_jcr1_wire_v1_1(receipt: &Receipt) -> Result<Value, JcrError>;
pub fn canonical_signed_core_v1_1(receipt: &Receipt) -> Result<Vec<u8>, JcrError>;

Both paths share the canonical CBOR encoder; v1.1 just stops dropping intent during the projection. Roughly five additional lines of code plus three vectors.