# JCR-1 v1.1 — Proposal: query-intent in the signed core **Status:** Proposal (draft). **Target:** Additive bump of [JCR-1](./JCR-1-spec.md), `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: 11. **L0 cache hit + intent=comprehension** — verifies the canonical map order with `intent` present and no `tools_touched`. 12. **L3 model + intent=generation + ModelBased** — verifies intent coexists with `tools_touched` and ModelBased honesty floor. 13. **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: ```rust // proposed v1.1 surface pub fn to_jcr1_wire_v1_1(receipt: &Receipt) -> Result; pub fn canonical_signed_core_v1_1(receipt: &Receipt) -> Result, 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.