A real-world AP2 mandate authorizes agent spend in a currency (dollars), while
JCP meters work in joules. The two cannot be reconciled by an exchange rate —
there is no canonical joules-per-dollar, and inventing one fabricates authority.
Until now a currency-denominated mandate yielded no joule ceiling at all
(Ap2Mandate::joule_budget -> None) and simply could not be gated. This adds the
second dimension so the mandate is enforceable without any conversion.
Design: a Grant may carry an OPTIONAL budget_funds { currency, minor } beside
budget_uj. Both ceilings are enforced independently; the first to bind denies,
and the sealed receipt records which one. A receipt that reads "denied: joule
ceiling, while the money ceiling had room" is a statement no single-dimension
authorization can make -- that is the point.
- jcp-schema: Funds (integer minor units -- money is typed, and the canonical
encoding forbids floats); Grant.budget_funds: Option<Funds> with
skip_serializing_if, so a joules-only grant encodes BYTE-IDENTICALLY to one
minted before this field existed -- content id, signature, and every published
conformance vector unchanged (proven by test, and the vectors still verify).
New deny reasons FundsExceeded / CurrencyMismatch.
- jcp-runtime: Price, decide_priced -- deny on whichever ceiling binds first,
refuse a foreign currency (never convert), debit both on allow / neither on
deny, and treat an absent money ceiling as "joules alone bound" (not zero).
- jcp-grant: attenuate INHERITS the parent's money ceiling (dropping it would
amplify); attenuate_priced + funds_narrows narrow it independently and refuse
a raise, a drop, or a currency change.
- jcp-bridge / jcp-a2a: the priced path end to end -- Ap2Mandate::funds_ceiling,
A2aProfile::from_mandate_priced (money ceiling from the mandate, joule ceiling
from the operator), serve_task_priced. Tested: a USD mandate now gates and
denies on funds-exceeded, sealed in a verifiable receipt.
- SPEC.md: normative §4.8 + conformance class C22.
The other ~11 files are the compiler-forced budget_funds: None at each Grant
literal -- deliberately NOT defaulted, so a new construction site must decide
rather than silently inherit unbounded money authority.
Full workspace green (40 suites), clippy clean, existing a2a example unchanged.
Follow-up (not done): a published C22 conformance vector in conformance/v1/.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
538 lines
28 KiB
Markdown
538 lines
28 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)
|
||
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**.
|
||
|
||
## 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/1` profile signs a flat uint/text core:
|
||
|
||
```
|
||
jcp_receipt = "jcp/1"
|
||
grant_id = hex
|
||
issuer, subject = did:key
|
||
resource, action = hex, string
|
||
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)
|
||
```
|
||
|
||
Receipts are byte-compatible with the family's JCR-1 verifiers. 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.
|
||
- **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.
|