Both were cases where the NORMATIVE TEXT was wrong or under-specified, which is
worse than a code bug: an implementor reading §4.9 would have diverged from the
reference and believed they were conforming.
1. The spec said caveats "MUST all hold for a decision to be allowed", full stop,
while the implementation deliberately skipped MaxCostUj in the two-phase
`authorize` path (the cost is genuinely unknowable there). So the spec promised
something the reference did not do, and a `MaxCostUj` caveat was in practice
unenforced for any host that authorized, ran, then charged — `charge()` never
re-checked.
§4.9 now states the rule explicitly: a cost-dependent caveat MUST be enforced
at the DEBIT POINT, an implementation MUST NOT report a grant as authorized in
a way implying it passed, and every cost-independent caveat MUST bind in both
phases. `Runtime::charge` enforces it.
The doctrine question that mattered: on a breach the work has ALREADY run, so
the joules are real. `charge` still debits them — energy that was spent is
always accounted — and records the breach in the verdict, so the sealed receipt
is evidence. Suppressing the debit to make the record look clean would be the
dishonest move; the spec now names both failure modes as non-conforming. This
follows the existing budget-overrun precedent ("records the overshoot rather
than hiding it") rather than inventing a new one.
2. The spec asserted that "weakening" an inherited caveat is an amplification, but
never defined weakening — while the code compares by exact structural equality.
So `MaxCostUj(400)` under a parent's `MaxCostUj(500)` was refused as *dropped*
despite being strictly tighter, with nothing saying so.
§4.9 now defines comparison as EXACT IDENTITY and says why: a per-variant
ordering would have to be defined for each predicate, and a verifier that got
it wrong would silently widen authority. A delegator wanting a tighter bound
adds it alongside the inherited one — since all caveats must hold, the tighter
governs. Conservative on purpose: it can never widen, and it can be relaxed
later without breaking existing grants, whereas the reverse is not true.
Also adds `Caveat::is_cost_dependent()` / `cost_breached()` to the schema so the
authorize-skip and the charge-enforce share one definition instead of two
`matches!` arms that could drift.
Tests pin both phases: authorize allows what it cannot evaluate and never debits;
charge denies the breach AND still accounts the 900 µJ. C24 extended to cover the
debit-point rule.
42 suites green, clippy clean, all conformance checks pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
630 lines
35 KiB
Markdown
630 lines
35 KiB
Markdown
# JCP v0.3 — The Joule Context Protocol
|
||
|
||
**Status:** v0.3.0 draft. **Licence:** specification text CC-BY-4.0; reference
|
||
code Apache-2.0.
|
||
|
||
A unified, energy-native, capability-secured protocol for agentic systems. JCP
|
||
subsumes in one spec what today's stack splits across MCP (tool/context
|
||
connection), A2A/ACP (agent-to-agent), an identity layer, an authorization
|
||
layer, and — missing everywhere — an energy layer. Its thesis is one object:
|
||
|
||
> **A capability and an energy budget are the same thing — a signed, bounded,
|
||
> debited grant. A capability you cannot pay for is denied; an expenditure you
|
||
> are not capable of is denied. One check, made deterministically outside the
|
||
> model, sealed in a receipt.**
|
||
|
||
---
|
||
|
||
## 1. Scope and doctrine
|
||
|
||
1. **Enforcement is outside the model.** Authorization and metering are decided
|
||
by the runtime, never by the LLM's instruction-following. The published
|
||
*inverse-scaling-law* result — more capable models are more exploitable
|
||
because they follow injected instructions better — makes model-side control
|
||
unsound by construction.
|
||
2. **Authority is a capability, not ambient.** No actor holds standing
|
||
privilege; every action requires an unforgeable [Grant](#4-the-grant-required).
|
||
There are no long-lived ambient secrets in env vars.
|
||
3. **Energy is first-class.** Every grant carries a joule budget that is debited
|
||
per action and **enforced before the side effect** — not merely observed.
|
||
4. **Data is never control.** Tool and agent *outputs* are untrusted data and can
|
||
never be executed as instructions or used to exercise a capability. This
|
||
closes the asymmetric-trust gap that tool poisoning exploits.
|
||
5. **Every decision is non-repudiable.** Allow and deny alike seal as a signed
|
||
receipt. Forging the audit means forging a signature.
|
||
6. **One substrate.** The same identity, grant, frame, and receipt span the tool
|
||
layer and the agent layer; there is no second protocol to secure.
|
||
|
||
## 2. Actors and identity (REQUIRED)
|
||
|
||
Every actor — model, agent, tool, or server — is an Ed25519 key, expressed as a
|
||
W3C `did:key` (multibase base58btc of the `0xed01` multicodec ‖ the public key).
|
||
There are no anonymous actors. An actor proves itself by signing.
|
||
|
||
### 2.1 Identity continuity: pre-committed rotation (OPTIONAL)
|
||
|
||
For long-lived actors, an identity is its **inception DID** (seq 0), forever; the
|
||
key that controls it rotates through a verified event log:
|
||
|
||
```
|
||
KeyEvent {
|
||
identity: did:key ; the seq-0 (inception) DID, stable for life
|
||
seq: uint ; 0 = inception, N = the Nth rotation
|
||
current: did:key ; the controlling key from this event on
|
||
next_hash: content-id ; blake3 of the NEXT key's 32 public bytes
|
||
parent: content-id? ; the previous event's id; absent at inception
|
||
nonce: bytes(16)
|
||
}
|
||
```
|
||
|
||
Inception is signed by the identity key (`current` MUST equal `identity`).
|
||
Rotation event N is signed by **its new `current` key**, which MUST hash to
|
||
event N−1's `next_hash` — possession of the pre-committed key is the proof of
|
||
control. The consequences are exactly the two that matter: a thief who steals
|
||
the *current* key still cannot rotate the identity away (they lack the
|
||
pre-committed next key), and an owner who *loses* the current key still can
|
||
(they hold it). A verifier MUST reject sequence gaps, parent-link mismatches,
|
||
hash mismatches, and bad signatures.
|
||
|
||
Rotation machinery is the long-lived-identity case. The RECOMMENDED norm is
|
||
cheaper: **short-lived grants re-issued on a heartbeat**, with revocation (§4.3)
|
||
covering the emergency. Rotation pressure scales with credential lifetime.
|
||
|
||
### 2.2 Threshold actors: the two-agent rule (OPTIONAL)
|
||
|
||
A **threshold actor** is a `did:key` whose private key never exists in one
|
||
place: it is split k-of-n (FROST, RFC 9591, Ed25519 ciphersuite), and a
|
||
signature exists only when at least k shareholders cooperate. Because the group
|
||
key is an ordinary Ed25519 verifying key, **nothing else in JCP changes** — a
|
||
threshold actor issues, holds, delegates, revokes, and signs wire requests, and
|
||
no verifier can tell or needs to.
|
||
|
||
Use it where one gated model is still too few: make the subject of an
|
||
irreversible capability a k-of-n team, and no single agent — however capable,
|
||
however poisoned — can exercise it alone. The refusal is arithmetic, not
|
||
policy.
|
||
|
||
Shares MAY be minted by a trusted dealer (one party creates a team identity and
|
||
hands out shares) or by **distributed key generation**, in which the group's
|
||
private key never exists anywhere — not even momentarily at a dealer. The group
|
||
key is the same ordinary Ed25519 key either way.
|
||
|
||
## 3. Canonical serialization and content addressing (REQUIRED)
|
||
|
||
Content addresses and the signed receipt core use **canonical det-CBOR**
|
||
(RFC 8949 §4.2.1): length-first then bytewise key order, shortest integer form,
|
||
**no floats, bools, or nulls** in a signed core. A content id is
|
||
`blake3(canonical_cbor(value))`, lowercase-hex on the wire — the same encoding
|
||
the JCR-1 receipt signs over (§8), so an id and a receipt agree on bytes.
|
||
|
||
## 4. The Grant (REQUIRED)
|
||
|
||
The **metered capability** is JCP's central object:
|
||
|
||
```
|
||
Grant {
|
||
issuer: did:key ; who granted this authority
|
||
subject: did:key ; who holds it
|
||
capability: { resource: content-id, actions: [string] }
|
||
budget_uj: uint ; the joule ceiling this grant may ever spend
|
||
budget_funds: { currency: string, minor: uint }? ; OPTIONAL money ceiling (§4.8)
|
||
caveats: [caveat] ; OPTIONAL embedded conditions (§4.9); omitted when empty
|
||
expiry: uint ; unix seconds; 0 = no expiry
|
||
parent: content-id? ; the grant this was attenuated from
|
||
delegate_depth: uint? ; further hops permitted; absent = unlimited, 0 = leaf-only (§4.4)
|
||
nonce: bytes(16)
|
||
}
|
||
```
|
||
|
||
`budget_funds` is absent in the baseline grant. Per §3, an absent OPTIONAL field
|
||
is omitted from the canonical encoding entirely, so a grant with no money ceiling
|
||
is byte-identical to one minted before §4.8 existed — its content id, its
|
||
signature, and every published conformance vector are unchanged.
|
||
|
||
A grant is identified by `blake3(canonical_cbor(grant))` and signed by the
|
||
`issuer`'s key (Ed25519 over the canonical bytes). The `resource` is addressed
|
||
**by content, not by URL** — audience binding an attacker cannot spoof by name
|
||
(a content-addressed strengthening of RFC 8707).
|
||
|
||
### 4.1 Attenuation (REQUIRED)
|
||
|
||
Delegation is object-capability attenuation. The holder of a grant (its
|
||
`subject`) MAY mint a child, but the child MUST be **strictly weaker**:
|
||
|
||
- `child.capability` ⊆ `parent.capability` (same resource; actions a subset),
|
||
- `child.budget_uj` ≤ `parent.budget_uj`,
|
||
- the child's expiry MUST NOT outlive the parent's (`0` = never expires),
|
||
- `child.parent` = the parent's content id (forming the chain).
|
||
|
||
A conforming implementation MUST refuse any amplification and MUST refuse
|
||
delegation by anyone other than the parent's subject. A chain therefore can
|
||
never convey more authority — or more energy — than its root.
|
||
|
||
### 4.2 Chain verification (REQUIRED for multi-hop)
|
||
|
||
When a frame is decided against a delegated grant, the enforcement point MUST be
|
||
presented the **full chain, root first, leaf last**, and MUST verify it hop by
|
||
hop: the root has no parent; every later grant's `parent` equals the id of the
|
||
grant before it; each hop is issued by the previous grant's `subject`; each hop
|
||
narrows per §4.1; and every signature in the chain verifies. A structurally
|
||
invalid chain is denied `chain-broken`; an amplifying one
|
||
`attenuation-violation`.
|
||
|
||
**Energy conservation across siblings.** The cost of an allowed action MUST fit
|
||
the remaining budget of **every** grant in the chain and MUST be debited from
|
||
all of them. Two children attenuated from one root therefore share the root's
|
||
budget — delegation can never mint joules.
|
||
|
||
### 4.3 Revocation (REQUIRED)
|
||
|
||
```
|
||
Revocation {
|
||
grant_id: content-id ; the grant being revoked
|
||
revoked_by: did:key ; MUST equal that grant's issuer
|
||
nonce: bytes(16)
|
||
}
|
||
```
|
||
|
||
A revocation is signed by `revoked_by` over its canonical bytes. An enforcement
|
||
point MUST verify authority before honoring one — the ids match, the revoker is
|
||
the grant's issuer, the signature verifies — and MUST then deny (`revoked`) any
|
||
decision against the revoked grant **or any chain containing it**: a delegation
|
||
subtree dies with any of its ancestors. Both the direct and the chained decision
|
||
path MUST enforce this.
|
||
|
||
### 4.4 Delegation control (REQUIRED)
|
||
|
||
A grant MAY carry `delegate_depth`: how many further attenuation hops it
|
||
permits. Absent = unlimited; `0` = **leaf-only** — the holder may act but never
|
||
re-delegate. Like every attenuation axis, depth only narrows: a child minted
|
||
from a depth-d parent carries at most d−1, and chain verification (§4.2) MUST
|
||
refuse a hop under a leaf-only parent (`delegation-forbidden` semantics map to
|
||
`chain-broken` on the wire) and any child whose allowance exceeds its parent's
|
||
bound (an amplification). In group topologies this is the primitive that makes
|
||
a worker a worker: an orchestrator delegates depth-0 grants and the team
|
||
cannot deepen itself.
|
||
|
||
### 4.5 Sharded budgets: distributed enforcement without consensus (REQUIRED baseline)
|
||
|
||
When one grant must be enforceable at N enforcement points, the holder MUST
|
||
shard it: mint N sibling children whose **budgets sum to no more than the
|
||
parent's**, one per enforcement point. Each point then enforces only its own
|
||
shard, locally — no coordination, no consensus — and the root stays conserved
|
||
by mint-time arithmetic. Rebalancing is revocation + re-issuance.
|
||
|
||
A conforming sharding operation MUST refuse slices that sum past the parent.
|
||
Fragmentation (an unused slice in one shard unavailable to another) is the
|
||
deliberate price of coordination-freedom — the same trade cash makes.
|
||
|
||
**Receipt-settled budget channels (OPTIONAL profile).** Where shards are too
|
||
rigid, N enforcement points MAY spend **optimistically** against one grant and
|
||
settle by exchanging signed statements of cumulative local spend:
|
||
|
||
```
|
||
Statement {
|
||
grant_id: content-id
|
||
point: did:key ; the enforcement point making the claim
|
||
spent_uj: uint ; cumulative µJ allowed at this point since open
|
||
seq: uint ; strictly increasing per point
|
||
}
|
||
```
|
||
|
||
A statement is signed by `point` over its canonical bytes. A receiving point
|
||
MUST verify the signature, refuse stale (`seq` not increasing) and regressing
|
||
(`spent_uj` decreasing) statements, and thereafter count the peer's settled
|
||
spend against the shared budget. Between settlements the points can
|
||
collectively overspend by at most what the unsettled window allows — a bounded,
|
||
tunable exposure, and after settlement the overdraft is **provable from the
|
||
signed statements** rather than suspected. Where even bounded overdraft is
|
||
unacceptable, use shards; that is why the baseline is the baseline.
|
||
|
||
### 4.6 Standing grants: bounded authority for event-triggered work (OPTIONAL)
|
||
|
||
An autonomous agent often starts work on its own — on a webhook, a schedule, a
|
||
ticket — with no human present at the firing instant. A long-lived credential
|
||
that any trigger may spend is unbounded authority in an automation costume. A
|
||
**standing grant** keeps the object-capability discipline instead: an ordinary
|
||
grant (§4) the issuer holds, plus a signed **mint policy** that says how a
|
||
trigger may turn it into work. The policy is bound to one grant by id, signed
|
||
by that grant's issuer, and MUST NOT amplify it. It fixes three bounds at once:
|
||
the **event class** permitted to fire it (content-addressed, so a webhook for
|
||
one source cannot spend a grant meant for another), the **per-firing budget**
|
||
(a slice no larger than the grant's budget), and the **maximum number of
|
||
firings**.
|
||
|
||
Each firing mints one fresh child through the ordinary attenuation path (§4.1):
|
||
the child is strictly weaker, its `parent` is the standing grant, and it
|
||
chain-verifies (§4.2) like any delegation — so its receipt traces back to the
|
||
standing grant. A small **mint ledger** (firing count and cumulative spend)
|
||
carried by the enforcement point holds the rate limit and the parent's budget
|
||
conservation across firings by arithmetic alone, no coordination — the same
|
||
local-enforcement property as sharded budgets (§4.5). A firing is refused, and
|
||
the ledger left unchanged, when the event is of the wrong class, the firing
|
||
count is exhausted, or the next firing would push cumulative spend past the
|
||
grant. Conformance class **C20**.
|
||
|
||
### 4.7 Checkpoints and amendments: steering a long run (OPTIONAL)
|
||
|
||
A multi-hour run is not a black box that returns only at the end. At defined
|
||
boundaries the running agent commits to its state — a **checkpoint** — and a
|
||
*distinct* principal may steer it before it continues. The two roles are
|
||
structurally separate (the separation-of-duties rule): the checkpoint's
|
||
**author** is the agent (the grant's `subject`), which commits to its own
|
||
state and cannot steer itself; the **amender** is the grant's `issuer` — the
|
||
steward who delegated the authority and is therefore already a different
|
||
principal. Steering is an issuer power, like revocation (§4.3): an
|
||
**amendment** is a soft revoke that re-bounds the run rather than killing it.
|
||
|
||
A checkpoint cites the active grant and a content-addressed state commitment,
|
||
signed by the agent. An amendment cites a checkpoint, is signed by the grant's
|
||
issuer, MUST differ from the agent, and MUST only **tighten** — the
|
||
continuation's budget is at most the checkpoint grant's, so steering can never
|
||
escalate a running agent's authority; it MAY also hand the agent a new
|
||
instruction-set commitment. The run resumes under a **continuation grant** the
|
||
issuer re-issues to the same agent with the amended budget, linked back to the
|
||
steered grant. This continuation is an issuer re-binding, not a holder
|
||
delegation — an auditor verifies it alongside the signed checkpoint+amendment
|
||
pair, which together record who intervened, when, and how. Conformance class
|
||
**C21**.
|
||
|
||
### 4.8 Dual ceilings: a money budget beside the joule budget (OPTIONAL)
|
||
|
||
A grant MAY carry a second, independent ceiling denominated in **money**
|
||
(`budget_funds`), enforced alongside the joule ceiling of §4. This exists because
|
||
the payments ecosystem authorizes agent spend in a currency (an AP2 mandate names
|
||
dollars), while JCP meters work in joules — and the two MUST NOT be reconciled by
|
||
an invented exchange rate. There is no canonical joules-per-dollar; asserting one
|
||
would fabricate authority. So JCP enforces **both** ceilings and reconciles
|
||
neither.
|
||
|
||
The two dimensions are governed by two different sources of authority: the
|
||
payment mandate bounds the money, the operator bounds the energy. A conforming
|
||
runtime MUST, on each priced decision:
|
||
|
||
- deny with `funds-exceeded` if the action's money cost would carry the grant's
|
||
money spend past `budget_funds.minor`;
|
||
- deny with `currency-mismatch` if the cost is denominated in a currency other
|
||
than `budget_funds.currency` — a foreign-currency cost is refused, never
|
||
converted;
|
||
- on allow, debit **both** the joule and the money spend; on any deny, debit
|
||
**neither**;
|
||
- treat an absent `budget_funds` as "joules alone bound this grant" — the money
|
||
dimension is not present, not a zero ceiling.
|
||
|
||
Because the ceilings are independent, a decision MAY deny on one while the other
|
||
still has room, and the sealed receipt (§8) records *which* bound. A receipt that
|
||
reads "denied: joule ceiling, while the money ceiling had room" is a statement no
|
||
single-dimension authorization can make — and it is the point of the design.
|
||
|
||
**Attenuation (§4.1) applies to both dimensions independently.** A child's money
|
||
ceiling MUST NOT amplify the parent's: a child bounded in money where the parent
|
||
was unbounded is permitted (it only adds a constraint), but dropping a bounded
|
||
parent's ceiling, raising it, or denominating it in a different currency (which
|
||
cannot be shown to narrow without a rate) MUST be refused. A plain attenuation
|
||
that names no money ceiling **inherits** the parent's unchanged — it is never
|
||
silently dropped, which would amplify. Conformance class **C22**.
|
||
|
||
### 4.9 Caveats: the open attenuation axis (OPTIONAL)
|
||
|
||
The axes of §4.1 narrow by set-subtraction — remove actions, lower a ceiling,
|
||
shorten an expiry. They cannot express a condition the issuer did not anticipate.
|
||
A grant MAY therefore carry **caveats**: conditions embedded *inside* the signed
|
||
token that MUST all hold for a decision to be allowed.
|
||
|
||
The point is that a caveat travels **with the capability**. Any enforcement point
|
||
holding the signed grant checks it offline — no call back to the issuer, no
|
||
service-side policy to consult, no shared configuration. That is what
|
||
distinguishes a caveat from a policy engine: policy is something the verifier
|
||
must be told; a caveat is something the token carries.
|
||
|
||
A conforming implementation MUST support:
|
||
|
||
```
|
||
ActionIn([string]) ; the action must be in this set — MAY narrow below the capability
|
||
PayloadIs(content-id) ; the frame's payload must be exactly this — a ONE-SHOT binding
|
||
CallerIs(did:key) ; the caller must be this principal
|
||
NotBefore(uint) ; unix seconds
|
||
NotAfter(uint) ; unix seconds — a window narrower than `expiry`
|
||
MaxCostUj(uint) ; a ceiling on ONE invocation, distinct from the cumulative budget
|
||
```
|
||
|
||
Every predicate ranges only over what an enforcement point holds when it decides:
|
||
the frame, the caller, the clock, and the metered cost. There is deliberately **no
|
||
content predicate** — the runtime sees a frame's `payload_hash`, never its bytes —
|
||
so a condition on content is expressible only as `PayloadIs`, binding an exact
|
||
payload rather than inspecting it. An implementation MUST NOT invent a predicate
|
||
that requires data the decision point does not have.
|
||
|
||
Two of these express authority the fixed axes cannot. `PayloadIs` makes a grant
|
||
**one-shot**: it authorizes one specific call, so a leaked grant cannot be
|
||
replayed against different arguments. `MaxCostUj` bounds a **single invocation**,
|
||
where `budget_uj` bounds the cumulative total — so a delegate cannot spend an
|
||
entire remaining budget in one call.
|
||
|
||
**The metered path (REQUIRED).** A caveat whose predicate ranges over the
|
||
invocation's cost — `MaxCostUj` — cannot be evaluated in the two-phase metered
|
||
flow at authorization time, because the cost is not yet known. Such a caveat MUST
|
||
be enforced at the **debit point** instead, and an implementation MUST NOT report
|
||
a grant as authorized in a way that implies a cost-dependent caveat has passed.
|
||
Every cost-independent caveat MUST be enforced in both phases.
|
||
|
||
When a measured cost breaches a cost-dependent caveat at the debit point, the
|
||
work has already run and the joules are real: the implementation MUST still debit
|
||
them — energy that was spent is always accounted — and MUST record the breach in
|
||
the verdict, so the sealed receipt is evidence of it. Debiting silently, or
|
||
suppressing the debit to make the record look clean, are both non-conforming.
|
||
|
||
**Attenuation (REQUIRED).** Caveats only accumulate. A child MUST carry every
|
||
caveat of its parent; a plain attenuation inherits them unchanged, and a
|
||
caveat-adding attenuation appends. Dropping an inherited caveat is an
|
||
amplification and MUST be refused.
|
||
|
||
Caveats compare by **exact identity**, not by an ordering over their parameters:
|
||
a child retains a parent's caveat only by carrying that identical caveat. There is
|
||
deliberately no notion of "a tighter `MaxCostUj` supersedes a looser one" — such
|
||
an ordering would have to be defined per variant, and a verifier that got it
|
||
wrong would silently widen authority. A delegator that wants a tighter bound adds
|
||
it **alongside** the inherited one; since all caveats must hold, the tighter of
|
||
the two governs. This is the conservative reading: it can never widen, and it may
|
||
be relaxed later without breaking existing grants, whereas the reverse is not
|
||
true. A decision whose grant carries an unsatisfied
|
||
caveat is denied `caveat-unsatisfied`, sealed like any other denial.
|
||
|
||
An empty caveat set is omitted from the canonical encoding, so a grant without
|
||
caveats is byte-identical to one minted before they existed — its content id and
|
||
signature are unchanged. Conformance class **C24**.
|
||
|
||
## 5. Energy (REQUIRED)
|
||
|
||
Every metered cost is integer microjoules with a provenance tag: `HwShunt`
|
||
(hardware reading; a measured figure REQUIRED), `ModelBased` (calibrated model;
|
||
measured MAY be absent), or `Estimator` (coarse constant; measured MUST be
|
||
absent). The runtime debits `cost.uj()` from a grant's remaining budget on every
|
||
allow; the coupling MUST be enforced. The energy axis is what no existing
|
||
agentic protocol standardizes — JCP makes it a precondition, not a report.
|
||
|
||
### 5.1 The metering ladder (REQUIRED behavior, RECOMMENDED backends)
|
||
|
||
The rule: **measured when the platform exposes it, estimated when it doesn't —
|
||
and the provenance always says which.** There is always an estimate: an
|
||
environment with no energy interface still yields a usable figure, it just
|
||
carries `ModelBased` or `Estimator` provenance instead of `HwShunt`. A figure
|
||
MUST NOT claim better provenance than how it was obtained — including when a
|
||
hardware backend fails mid-reading and the figure degrades to a model.
|
||
|
||
Recommended ladder, best first:
|
||
|
||
1. **RAPL** (Linux `powercap`) — package energy counters, wraparound-corrected
|
||
→ `HwShunt`.
|
||
2. **NVML** — the GPU total-energy counter → `HwShunt`.
|
||
3. **Time × calibrated power** — wall-clock duration times a host-calibrated
|
||
active power → `ModelBased`. Always available.
|
||
4. **Constant per-operation estimate** → `Estimator`.
|
||
|
||
## 6. The Frame (REQUIRED)
|
||
|
||
Every message is a typed Frame — one envelope across the tool layer (MCP's job)
|
||
and the agent layer (A2A's job):
|
||
|
||
```
|
||
Frame {
|
||
kind: tool-call | tool-result | agent-task | agent-reply
|
||
trust: control | data
|
||
grant_id: content-id ; the grant this frame exercises
|
||
action: string
|
||
resource: content-id
|
||
payload_hash: content-id
|
||
}
|
||
```
|
||
|
||
`trust = data` marks untrusted output. A conforming runtime MUST refuse to
|
||
exercise any capability from a `data` frame (§7), regardless of its content. Tool
|
||
and agent outputs MUST be tagged `data`.
|
||
|
||
## 7. The decision (REQUIRED)
|
||
|
||
Given a Frame, its [SignedGrant](#4-the-grant-required), the caller's DID, a
|
||
metered `cost`, and the current time, a conforming runtime decides **Allow** or
|
||
**Deny(reason)** by these checks, in order, the first failure naming the reason:
|
||
|
||
1. `data`-tagged frame → **Deny(untrusted-control)**.
|
||
2. grant revoked (§4.3) → **Deny(revoked)**.
|
||
3. the issuer (for a chain: the **root** issuer) is not a configured trust root
|
||
for the frame's resource → **Deny(untrusted-root)**. An unconfigured
|
||
resource is open; once any root is configured, only listed keys anchor
|
||
authority.
|
||
4. frame's `grant_id` ≠ the grant's id → **Deny(wrong-resource)**.
|
||
5. caller ≠ grant's `subject` → **Deny(wrong-subject)**.
|
||
6. issuer signature invalid → **Deny(bad-signature)**.
|
||
7. expired → **Deny(expired)**.
|
||
8. frame's `resource` ≠ capability's resource → **Deny(wrong-resource)**;
|
||
action ∉ capability → **Deny(action-not-granted)**.
|
||
9. prior spend + `cost` > `budget_uj` → **Deny(budget-exceeded)**.
|
||
|
||
On **Allow**, the runtime debits `cost` from the grant's remaining budget. The
|
||
decision is pure and deterministic; no model participates.
|
||
|
||
For a delegated grant the decision takes the full chain (§4.2): structure and
|
||
signatures first (`chain-broken` / `attenuation-violation` / `bad-signature`),
|
||
then revocation against **every** link (`revoked`), expiry of every link, the
|
||
leaf-level checks above, and budget against every link, debiting all on allow.
|
||
|
||
**Trust roots.** An enforcement point is **configured** with the root-issuer
|
||
keys it honors for each resource it guards — the same local, pairwise trust
|
||
shape as an `authorized_keys` file. `did:key` is self-certifying, so there is no
|
||
certificate authority and no hierarchy; cross-organization federation is a
|
||
directory convenience, never a trust dependency. A chain whose root issuer is
|
||
not configured for the frame's resource MUST be denied.
|
||
|
||
## 8. The receipt (REQUIRED)
|
||
|
||
Every decision — allow and deny — seals as a **JCR-1** receipt: canonical
|
||
det-CBOR inside a COSE_Sign1 envelope (RFC 9052, EdDSA/Ed25519, `alg = -8`). The
|
||
`jcp/2` profile signs a flat uint/text core:
|
||
|
||
```
|
||
jcp_receipt = "jcp/2"
|
||
grant_id = hex
|
||
issuer, subject = did:key
|
||
resource, action = hex, string
|
||
payload_hash = hex ; content id of what was acted upon
|
||
frame_kind, trust = "tool-call"…, "control" | "data"
|
||
verdict = "allow" | "deny"
|
||
deny_reason = string ; omitted on allow
|
||
cost_uj = uint
|
||
energy_provenance = "HwShunt" | "ModelBased" | "Estimator"
|
||
budget_remaining_uj, budget_uj = uint
|
||
steward = the signer's id (the COSE kid)
|
||
```
|
||
|
||
`payload_hash` binds the receipt to **what the decision was over**, not merely
|
||
that a decision was taken. Without it a tool-call receipt does not commit its
|
||
arguments, and two decisions differing only in payload seal to identical bytes —
|
||
so a payload substitution is undetectable in the audit. A conforming
|
||
implementation MUST include it.
|
||
|
||
**Profile versioning (REQUIRED).** The predecessor profile `jcp/1` omitted
|
||
`payload_hash`. A verifier MUST read the `jcp_receipt` tag from the signed core
|
||
and recompute the preimage under **that** profile, so receipts sealed as `jcp/1`
|
||
keep verifying; it MUST refuse a tag it does not implement rather than assume a
|
||
profile. Implementations MUST NOT mint `jcp/1`. Published `jcp/1` receipts are
|
||
retained in [`conformance/v1/legacy_jcp1.json`](conformance/v1/legacy_jcp1.json)
|
||
precisely so a verifier can prove it honours the migration.
|
||
|
||
Receipts share the family's JCR-1 **envelope** (canonical det-CBOR +
|
||
COSE_Sign1/EdDSA) and vocabulary, so family tooling verifies signatures and
|
||
decodes cores uniformly. The *core field list is per-profile*: sibling profiles
|
||
(`map/1`, Sandbox, proof, jouleclaw's receipt kinds) sign their own cores, and a
|
||
change to `jcp/…` does not alter theirs. A deny receipt is as load-bearing as an
|
||
allow receipt — the record of a refused attack is evidence.
|
||
|
||
### 8.1 The receipt stream is the write-ahead log (REQUIRED)
|
||
|
||
A receipt carries everything enforcement needs to remember: the grant, the
|
||
verdict, the debit. A receipt sealed for a **multi-hop chain** MUST additionally
|
||
record `chain_ids` — every grant id the decision debited, root first — so the
|
||
stream is self-contained. An enforcement point that crashes MUST be able to
|
||
rebuild its budget state by replaying its own verified receipt stream:
|
||
allow-receipts re-apply their debits to every grant they name, ancestors
|
||
included, with no external index. Durability and audit are the same artifact;
|
||
there is no second database to drift from the evidence.
|
||
|
||
### 8.2 Group accounting (RECOMMENDED profile)
|
||
|
||
When a team works under sub-grants of one root, aggregation of the verified
|
||
receipt stream keyed by `(grant_id, subject)` yields per-worker attribution —
|
||
actions allowed, actions refused, joules spent — computed from evidence rather
|
||
than self-reporting. An aggregator MUST count (not silently skip) receipts that
|
||
fail verification: a ledger that drops entries is not a ledger.
|
||
|
||
## 9. Conformance
|
||
|
||
A conforming implementation passes, with real checks:
|
||
|
||
- **C1 — signed grants + audience binding:** a grant verifies only for its
|
||
issuer's key and authorizes only its exact content-addressed resource.
|
||
- **C2 — attenuation monotonicity:** a child grant can only narrow; any
|
||
amplification or non-holder delegation is refused.
|
||
- **C3 — budget enforcement:** spend beyond the ceiling is denied deterministically
|
||
and the denied call is never debited.
|
||
- **C4 — data/control separation:** a `data` frame can never exercise a capability.
|
||
- **C5 — the receipt:** every decision seals to JCR-1 and verifies; a wrong
|
||
steward fails.
|
||
- **C6 — revocation:** only the issuer revokes; a revoked ancestor kills the
|
||
whole delegation subtree; a forged revocation is never admitted.
|
||
- **C7 — chain conservation:** a multi-hop chain verifies hop by hop; a spliced
|
||
chain is refused; sibling delegations share their root's budget.
|
||
- **C8 — wire binding:** a request round-trips the `jcp/1` framing canonically
|
||
and an enforcement endpoint decides + seals over a live socket.
|
||
- **C9 — energy honesty:** the metering ladder always yields a figure and its
|
||
provenance is internally consistent.
|
||
- **C10 — delegation control:** depth narrows per hop; a leaf-only grant cannot
|
||
be delegated, at mint or in chain verification.
|
||
- **C11 — sharded budgets:** shard slices that sum past the parent are refused
|
||
at mint.
|
||
- **C12 — rotation:** an un-pre-committed key cannot rotate an identity, the
|
||
pre-committed key can, and the identity DID is stable across rotations.
|
||
- **C13 — caller-signed wire + threshold:** an impersonated caller is denied
|
||
`bad-signature`; one share of a 2-of-3 actor cannot sign; two can, and the
|
||
endpoint treats the aggregate as any caller signature.
|
||
- **C14 — replay + group accounting:** a fresh runtime restores spend from the
|
||
verified receipt stream; per-worker attribution matches the original ledger;
|
||
a tampered receipt contributes nothing.
|
||
- **C15 — trust roots:** an unconfigured resource is open; once configured, a
|
||
validly-signed grant from an unlisted root is denied `untrusted-root` on both
|
||
decision paths.
|
||
- **C16 — chained replay:** a chained receipt names every debited link; a fresh
|
||
runtime replays leaf AND ancestor debits exactly and keeps conserving.
|
||
- **C17 — budget channels:** optimistic spends settle by signed statement;
|
||
forged, stale, and regressing statements are refused; post-settlement the
|
||
merged ledger binds and any overdraft is provable.
|
||
- **C18 — DKG:** a dealer-free k-of-n actor signs a standard Ed25519 signature;
|
||
one share cannot.
|
||
- **C-neg:** expired, forged, and wrong-caller frames are denied with the precise
|
||
reason; canonical CBOR rejects floats/bools.
|
||
|
||
Published vectors live in [`conformance/v1/`](conformance/v1/) (`cargo run -p
|
||
jcp-conformance --bin gen-vectors`).
|
||
|
||
## 10. What JCP closes (mapping to the documented failures)
|
||
|
||
| Documented failure | JCP mechanism |
|
||
|---|---|
|
||
| Prompts-as-security; inverse scaling law | §1.1 enforcement outside the model |
|
||
| Tool poisoning / asymmetric trust | §6 data≠control |
|
||
| Ambient authority, static env secrets | §4 unforgeable grants |
|
||
| Confused deputy, token passthrough | §4 content-addressed audience binding |
|
||
| Delegated authority | §4.1–§4.2 attenuation chains, verified hop by hop |
|
||
| A leaked grant lives forever | §4.3 issuer revocation, subtree-transitive |
|
||
| Runaway loops, no energy accounting | §5 enforced joule budgets, conserved across delegation |
|
||
| Mutable / optional audit | §8 signed JCR-1 receipts |
|
||
| Fragmentation across protocols | §6 one frame for tools and agents |
|
||
|
||
## 11. Wire binding: `jcp/1` over a byte stream (OPTIONAL)
|
||
|
||
JCP is transport-agnostic; this section defines the reference binding for any
|
||
ordered byte stream (TCP, Unix socket, pipe, TLS session).
|
||
|
||
**Framing.** Each message is a 4-byte big-endian length followed by the
|
||
message's **canonical det-CBOR** bytes (§3). A receiver MUST reject a message
|
||
longer than its declared cap and MUST reject a non-canonical body — a message
|
||
has exactly one wire form, so content addressing stays injective across the
|
||
wire.
|
||
|
||
**Messages.** One request/response pair per action:
|
||
|
||
```
|
||
WireRequest { frame, chain: [signed-grant…], caller: did:key, cost: energy,
|
||
now: uint, signature: bytes(64) }
|
||
WireResponse { decision, receipt: bytes }
|
||
```
|
||
|
||
`chain` is the full delegation chain, root first (a single un-delegated grant is
|
||
a one-link chain). `now` makes decision time an explicit input — a decision is
|
||
replayable, not ambient. The response carries the deterministic decision and the
|
||
sealed JCR-1 receipt, deny and allow alike.
|
||
|
||
**Caller authentication.** `signature` is Ed25519 by `caller` over the
|
||
canonical bytes of every other field. The endpoint MUST verify it before
|
||
deciding; a request that fails is denied `bad-signature` and sealed like any
|
||
other decision — the refused impersonation is evidence too. A threshold actor
|
||
(§2.2) signs the same field with a k-of-n aggregate, indistinguishable on the
|
||
wire.
|
||
|
||
The wire never carries a secret: grants are signed public objects, and a
|
||
captured request replays into the same deterministic decision and the same
|
||
budget accounting it produced the first time. The endpoint behind the
|
||
enforcement point is invoked only on Allow.
|
||
|
||
## 12. Non-goals
|
||
|
||
JCP does not define a model or a tool registry. It does
|
||
not detect misbehavior with probabilistic "trust scores" — bounded capability and
|
||
enforced budget make the dangerous action unreachable rather than observed.
|
||
Behavioral scoring MAY be layered above as a secondary signal; it is never the
|
||
gate.
|
||
- **C20 — standing grants:** an event-triggered firing mints a strictly-weaker child that traces to the standing grant; the wrong event class, an exhausted firing count, and a firing past the cumulative budget are each refused without advancing the ledger; a policy that would amplify its grant is refused at author time.
|
||
- **C21 — checkpoints + amendments:** an agent checkpoints its own state; only the grant's issuer — a principal distinct from the agent — may amend, only to tighten; an agent steering itself, a non-issuer amending, a widening amendment, and an amendment bound to a different checkpoint are each refused.
|
||
- **C23 — receipt binds its payload + profile migration:** a `jcp/2` receipt commits the `payload_hash` it decided over; substituting only the payload (same grant, action, and cost) fails verification — the forgery a payload-blind receipt could not detect; and receipts published under `jcp/1` still verify, because the verifier recomputes the preimage under the profile the receipt names rather than assuming one.
|
||
- **C24 — caveats:** a caveat embedded in the grant is enforced offline by the decision point; `PayloadIs` makes a grant one-shot (a replay with different arguments is denied `caveat-unsatisfied`); `MaxCostUj` bounds a single invocation independently of the cumulative budget; a caveat may narrow below the capability; attenuation inherits every parent caveat and refuses to drop one; and a grant with no caveats encodes byte-identically to one minted before they existed.
|
||
- **C22 — dual ceilings:** a money ceiling beside the joule ceiling is enforced independently — the first to bind denies (`funds-exceeded` / `budget-exceeded`), a foreign-currency cost is refused `currency-mismatch` rather than converted, an allow debits both dimensions and a deny debits neither, and an absent money ceiling encodes byte-identically to a joules-only grant (so its content id and signature are unchanged). Attenuation narrows money independently: dropping, raising, or re-denominating a bounded parent's ceiling is refused; a plain attenuation inherits it.
|