open-standards/jcp/SPEC.md
Transaction Science 80971b27d4 jcp §4.11: an orchestrator's spend died with its process, so a restart was a fresh budget
`jcp-orchestrate`'s module doc claims "the whole run is an ordered ledger of
sealed decisions — the audit a bare meta-harness has no way to produce." That
was true for as long as the process lived. Item states, the receipts, and — the
part that matters — each harness's spend were held in memory: spend lives in
`self.runtime`, and a fresh `Runtime` starts at zero. A crash loop could spend a
harness's joule budget over and over, and a task already paid for and run would
be dispatched again.

A run now emits a `JournalEntry` for every transition through a `Journal` the
caller supplies. `MemoryJournal` is the default and keeps exactly the old
behaviour; a deployment that wants the run to survive its process writes the
entries somewhere that does. `Orchestrator::replay` rebuilds a run from them:
items, attempts, the receipt ledger, and the spend.

**Spend is re-derived, not asserted.** Each allowed dispatch goes back through
`Runtime::decide`, so the budgets land where the original run left them. Two
reasons for replaying rather than restoring a total: a replay cannot produce a
spend the gate would not have produced, and a stored total is one more figure
that can be wrong. The grants reproduce exactly because `register` mints them
with a fixed nonce, so an identical registration yields identical grant ids.
`Runtime` has no spend setter and did not need one.

The test that matters is not that the number comes back: it is that the resumed
budget still **binds**. A harness with 1 000 µJ that has spent 900 must refuse
the next 300 µJ dispatch, and does.

**The interrupted case is left unresolved on purpose.** The dispatch is
journalled *before* the harness runs, because the decision has already debited
the grant and recording it afterwards would lose the debit for anything
interrupted between the two. So a `Dispatched` with no `Settled` means the
process died while a harness held the task, and whether the work happened is not
knowable from the journal — only that harness knows. Such an item resumes as
`WorkState::Interrupted { harness, attempt }`, is counted separately in the
report, keeps its joules debited, and is **not** re-dispatched: the loop only
offers `Pending` items. Retrying risks paying twice for work already done,
discarding risks losing it, and choosing between those without asking the
harness is not a decision this crate can make.

A journal naming an unregistered harness, or a dispatch for a task no
`Submitted` entry introduced, is refused rather than guessed — the grant the
decision was made against cannot be reproduced.

SPEC §4.11 makes it normative beyond this crate, since the hole is in any
enforcement point that can restart: record every allowed decision durably,
re-establish the spend before deciding anything new (replay RECOMMENDED over a
stored total), record the decision before the action, and never silently retry
or mis-report an interrupted one.

jcp-rs workspace: 409 passed, clippy clean on jcp-orchestrate.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-09 02:50:39 -04:00

887 lines
51 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
**Propagation.** The rule above binds an enforcement point *once it holds the
revocation*, and says nothing about how it comes to hold one. §4.3 requires an enforcement point to deny a revoked grant **once it holds the
revocation**, and says nothing about how it comes to hold one. With a budget
sharded across N enforcement points (§4.5), a revocation that has reached three
of five leaves the grant live at two, and — the part that matters for an audit —
an allow issued by a point that *had not yet heard* is indistinguishable, after
the fact, from an allow by a point that heard and ignored it. The first is a
propagation delay. The second is a violation of this specification.
An implementation that supports revocation **MUST** define how revocations reach
its enforcement points, and **SHOULD** let each point state how current its
revocation knowledge is, as a marker monotonic in whatever its revocation feed
counts — a sequence number, a log index, a timestamp. JCP neither defines nor
interprets the marker; it requires only that a larger value mean "has seen at
least as much".
Given a revocation issued at marker `M`, an allow from a point at
`known_through < M` is explicable and one at `known_through >= M` is not. Where a
point states no marker, an auditor **MUST** treat its allows as *unverifiable*
for revocation rather than as compliant — "did not say" is not "was current".
### 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. It MAY also state what the run has **spent** so far: a
cumulative microjoule figure with the provenance grade of §5. An amendment
cites a checkpoint, is signed by the grant's issuer, MUST differ from the
agent, and MUST only **tighten**; it MAY also hand the agent a new
instruction-set commitment.
What "tighten" is measured against is normative, and it is the **remainder**,
not the issued budget:
- Where the checkpoint states its spend, an amendment MUST NOT set a budget
above `grant.budget_uj − spent.uj`, saturating at zero. Both the amending
side and the verifying side MUST enforce this: an enforcement point handed a
signed amendment never calls the amending routine, so a check that lives only
there is not a check.
- Where the checkpoint states no spend, the ceiling is the issued
`grant.budget_uj`. This is the weaker rule and is retained so that a
checkpoint written before the field existed remains valid; implementations
MUST NOT read an absent spend as zero spend, and SHOULD report the remainder
as unknown rather than as the full budget.
- A declared spend is cumulative, so across one run it MUST NOT decrease, and a
checkpoint that once stated a spend MUST NOT later omit it. Otherwise an agent
recovers its full issued ceiling by going quiet.
The figure is the agent's own signed claim, not an independent measurement; its
`provenance` grade travels with it so the amender can see which kind of claim it
is, and a deployment that needs more binds receipts (§5) to the checkpoint.
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**.
### 4.10 Flow: what a value's provenance may exercise (OPTIONAL)
A grant answers *who* may do *what*, for *how many joules*. It does not answer
where the **argument** came from. An agent holding a legitimate grant, handed a
value that originated in an untrusted channel, exercises real authority on
attacker-chosen input — and every check above passes, because the grant is valid
and the action is granted.
A value therefore carries a **label**, and a grant MAY carry a ceiling the label
must sit below.
```
Label { integrity: Integrity, origins: Origins, readers: Readers }
```
The three components order independently and the label order is their product.
`⊥` is a principal-authored value from no channel, readable by all — the label of
anything in a run that has touched no untrusted source. `⊤` is untrusted, from
anywhere, readable by nobody.
**JCP consumes a label; it does not compute one.** Propagation — the label of a
result being at least the join of the labels of everything that went into it — is
the host's job, because only the host sees the data move. JCP's contribution is
the gate and, below, the rule that an *uncomputed* label is not a clean one. A
host that propagates badly will present a label JCP cannot audit; what JCP
guarantees is that a host which propagates at all gets a decision bound to what
it reported, and that one which does not gets a refusal rather than a pass.
A grant expresses its ceiling as a `FlowBelow` caveat (§4.9) carrying a
`FlowCeiling`: the ceiling label, and optionally a `tainted_budget_uj` — a
separate, smaller joule allowance for calls whose argument is not `⊥`. Where a
chain carries several, they compose to the **strictest**; attenuation never
widens one.
An enforcement point MAY also configure a **taint floor** per resource — the flow
analogue of a trust root (§7). The ceiling is what the *issuer* will permit; the
floor is what the *enforcement point* will accept regardless of who granted what.
Default-open, so an unconfigured point behaves exactly as before.
**The gate.** A decision is denied unless the argument's label sits at or below
the grant's ceiling **and** the resource's taint floor does not refuse its
origins. Three cases are distinguished, and the distinction is the point:
| facts | meaning | result |
|---|---|---|
| absent | provenance was **not computed** | **fails closed** where a ceiling is present; a ceiling-free grant is unaffected |
| `⊥` | computed, and clean | passes any ceiling |
| labelled | computed, and tainted | tested against the ceiling and the floor |
"Not computed" **MUST NOT** be read as "clean". An un-migrated caller that cannot
say where its argument came from has not established that the argument is safe,
and a grant whose issuer took the trouble to set a ceiling MUST NOT be exercised
on an unlabelled value.
**Where the check sits is normative.** It MUST run *after* capability and
signature verification — the ceiling lives inside an authenticated grant, so
judging flow before the signature would seal a verdict over attacker-chosen
policy — and *before* caveats and the budget test, so that **a tainted call costs
zero joules**. A denial on flow is the same invariant as a denial on capability:
nothing is debited and nothing runs.
A grant carrying no `FlowBelow` caveat is byte-identical to one minted before
flow existed, and an enforcement point with no taint floor configured decides
exactly as it did before. Conformance class **C25**.
### 4.11 Durable spend: a restart is not a fresh budget (REQUIRED where a run can restart)
A grant's budget is enforced against the spend an enforcement point has
accumulated, and that accumulation is state. Where the enforcement point can
restart — any long-running orchestrator, worker pool or gateway — an
implementation that holds the spend only in memory gives every grant its full
budget back on restart. A crash loop then spends a grant's budget repeatedly,
and the ceiling that was meant to bound the work bounds one process lifetime
instead.
An implementation whose enforcement point can restart therefore **MUST** record
every allowed decision durably, and on restart **MUST** re-establish the spend
from that record before deciding anything new. Re-establishing it by **replaying
the recorded decisions through the gate** is RECOMMENDED over restoring a stored
total: a replay cannot produce a spend the gate would not have produced, and a
stored total is one more figure that can be wrong or tampered with.
**The decision is recorded before the action runs.** Recording it afterwards
loses the debit for any action interrupted between the two — the grant was
debited when the decision was made, so a record written after the fact reports a
budget that has more left in it than it does.
That ordering leaves one state the record cannot resolve. A recorded decision
with no recorded outcome means the enforcement point stopped while the action
was in flight, and **whether the action took effect is not knowable from the
record** — only the party that was running it knows. An implementation **MUST
NOT** silently retry such an action, and **MUST NOT** report it as completed or
as failed. Its energy **MUST** stay debited, because the decision that debited it
was made and sealed. The honest resolution is to surface it as unresolved,
naming the party that held it, and let an operator or that party settle it:
retrying risks performing paid work twice, discarding risks losing it, and
choosing between those without asking is a decision the record does not support.
## 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.
`HwShunt` covers a vendor energy counter as well as a dedicated shunt. A vendor
counter's count is a real reading, not an estimate; what it lacks is an
independent root — how the vendor derives it is undisclosed, and the
platform's own software relays it — which is a gap in provenance, not in
accuracy. A reading earns the grade only while its counter is live:
**Liveness (REQUIRED).** A reading from a vendor energy counter (RAPL, NVML,
Apple IOReport, ...) MAY be graded as a hardware reading only while the counter
is demonstrably live for the domain the figure claims: the implementation MUST
have observed the counter covering that domain advance under load (for Apple
IOReport: the CPU energy rail itself advancing in at least 5 sample intervals,
in at least 4 of every 5, over at least 300 ms while the probing thread burns
CPU). Where the figure sums several counters of the domain — a RAPL package
counter per package, an IOReport CPU energy rail per die — every one of them
MUST be observed live: a frozen one beside live ones would be left out of a
figure that claims the whole domain. A counter that is present but not live —
frozen, or republished in lumps — MUST NOT produce a hardware-graded figure; the implementation falls back to
its estimator or reports the work unmetered, and SHOULD record why. A figure
MUST NOT claim coverage of a domain its counter does not read; where the format
has a coverage field, such a figure is Partial.
**Plausibility (REQUIRED where a bound is readable).** Liveness is necessary and
not sufficient: a counter can advance continuously and still not be counting
what it claims. An NVML total-energy counter on an RTX 4050 Laptop GPU (driver
595.84) advances in every sample interval while running at four to thirty times
the board's own power draw for as long as it is polled — perfectly live, and no
count of that board's energy. A figure from it would debit energy the device
never spent.
So where the platform exposes a bound that is read **independently of the
counter** — the board's maximum power limit, the board's instantaneous power
draw — a hardware-graded figure's implied rate MUST be checked against it, and a
counter whose rate exceeds the bound MUST NOT produce a hardware-graded figure.
An implementation SHOULD record the rate and the bound it broke, not merely that
it broke one: a counter a tenth over a bound and one twenty times over are the
same refusal and not the same finding, and re-grading a past refusal against a
revised bound is only possible if the rate survived the check.
A bound reading that is itself impossible is not a reading: the same RTX 4050
reports about 590 W, past its own 65 W limit, for a second after it wakes. Such a
reading MUST be discarded — and only that reading. One broken sample is not a
broken bracket, and discarding the whole judgement on it reports an indeterminate
where the evidence supports a finding: on the reference hardware a single spike
among otherwise sane 14.8 W samples would otherwise excuse a counter running
persistently at four to thirty times the board's draw. Discarding them
individually also keeps the comparison honest, since a spike left in a mean makes
the bound it derives *more* permissive.
Only where **no usable reading remains** is the counter **unjudgeable** rather
than implausible: there is then no yardstick, so there is no finding about the
counter. It MUST NOT produce a hardware-graded figure either, because unjudgeable
is not admissible and declining is the safe direction, but an implementation MUST
NOT report it as a counter that overran. The two call for different operator
responses — replacing a counter, or waiting for a board to settle — and
conflating them loses that. A bound that is a static limit rather than a reading
(the board's maximum power limit) is never discarded, so a broken draw sample
cannot excuse a counter that overran that limit outright.
Where no such bound is readable, this requirement does not apply; liveness
stands alone.
The record of why is the implementation's to choose; the reference meter
exposes each counter's liveness status, and which rung it selected with the
status of every rung above it that it declined. Coverage
is defined in §5.2.
### 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`, selected only while every package counter is live under the
probe's CPU burn, by the rule above as IOReport's CPU rails are. A span over
which no package counter advanced is metered by a lower rung. The package
counters are the top-level zones whose `name` is `package-<n>`, every
socket's, each corrected at its own `max_energy_range_uj`; no other zone is
summed or probed. A `psys` zone already includes the packages, so summing it
beside them counts their energy twice, and a `dram` zone is not CPU energy.
2. **NVML** — the GPU total-energy counter → `HwShunt`, `Partial` (§5.2): it
reads the GPU and not the CPU's share of the work. Selected only while the
counter is live by the same rule; the probe cannot load the GPU, so for
this counter live means published continuously, advancing on the device's
own draw. A span over which it did not advance is metered by a lower rung.
3. **Per-process CPU energy** (macOS `proc_pid_rusage`, `ri_energy_nj`) —
the kernel's count of the CPU energy of the metering process's own
threads → `HwShunt`, `Whole` for work that runs on those threads.
Selected only while the count is live by the rule above. Its domain is
that process's threads alone: not other processes, not its child
processes, not the GPU, the Neural Engine or DRAM. A span over which the
process reaped a child (its reaped children's CPU time moved), or at whose
end it has a child, running or not yet reaped, is `Partial` (§5.2): the
child's energy is not in the count. It ranks above the
SoC-wide counter because it attributes the work's own CPU energy rather
than the machine's. A span over which it did not advance is metered by a
lower rung.
4. **Apple IOReport** (the Energy Model group) — the SoC energy counters
→ `HwShunt`, selected only while every CPU energy rail (one per die on a
multi-die part) is live by the rule above. A span over which no CPU rail
advanced is metered by the next
rung, never graded `HwShunt`. Where the CPU rail is not live but the GPU rail
is, the rung is declined rather than reporting the GPU rail as a `Partial`
reading of the work: the GPU rail is machine-wide (display compositing
moves it), so booking it to a task misattributes energy the task did not
spend, and the partial figure would under-debit the budget, the CPU's share
going unmetered. The whole-task estimate below is the honest figure.
5. **Time × calibrated power** — wall-clock duration times a host-calibrated
active power → `ModelBased`. Always available.
6. **Constant per-operation estimate** → `Estimator`.
### 5.2 Coverage (REQUIRED where a figure is partial)
Provenance says how a figure was obtained; **coverage** says how much of the
work it accounts for: `Whole` (the default) or `Partial`. A reading can be
hardware-rooted and still cover only part of the work — an accelerator counter
on a host whose CPU has none — and its remainder is unmeasured, not zero. A
`Partial` figure MUST NOT be presented as a task total.
On the wire coverage is carried only when it is `Partial`, and omitted when
`Whole`, so a figure sealed before the field existed keeps its exact bytes; a
verifier reads an absent field as `Whole`. The `jcp/2` receipt carries it as
`energy_coverage` (§8); the `intercept/1` step receipt of the reference
interceptor chain (`jcp-intercept`) carries it as `coverage`, beside its
`provenance`. In `intercept/1` the field, when present, has exactly one value,
`"Partial"`; a verifier MUST refuse any other value, `"Whole"` included, rather
than read it either way.
## 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"
energy_coverage = "Partial" ; omitted when Whole (§5.2)
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, and only up to what the grant has **left** at that checkpoint; an agent steering itself, a non-issuer amending, a widening amendment, an amendment above the remainder of a checkpoint that stated its spend, 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.
- **C25 — flow:** a value's propagated label must sit at or below the grant's ceiling and clear the resource's taint floor; an argument whose provenance was **not computed** fails closed against a ceilinged grant rather than passing as clean; several ceilings in a chain compose to the strictest and attenuation never widens one; a flow denial debits nothing, so a tainted call costs zero joules; and a grant with no flow caveat encodes byte-identically to one minted before flow 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.