All checks were successful
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 55s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 17s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m12s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 3m18s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 4m14s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m37s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m41s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m10s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m50s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m16s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 8m12s
`Efficiency::wire()` sealed `energy_uj` and `provenance` and nothing about
coverage. So a GPU-only reading (a hardware counter that reads the
accelerator and not the CPU work that drove it) reached a verifier as the
whole unit's energy. The receipt also sealed a `uj_per_accepted_token` over
that reading, which presented it as what the work cost per token. jcp SPEC
§5.2 already said a `Partial` figure MUST NOT be presented as a task total,
and listed the receipts that carry coverage. efficiency/1 was not among them.
`aggregate()` then summed energy across receipts with no coverage and no
provenance, and its per-unit figure came out as clean as a whole one. §5.3
says aggregation takes the floor of both and is never a step at which a
grade improves.
Now:
- The receipt carries `energy_coverage: "Partial"` when the figure is
partial, and omits it when the figure is whole. A whole receipt, and every
receipt sealed before the field existed, keeps its bytes.
- A partial receipt seals no `uj_per_accepted_token`. A partial figure
divided by tokens is not what the work cost per token, nor a bound on it,
since a partial figure can fall short of the work or exceed it.
- `read_payload`, split out of `verify` so the payload rules can be replayed
without a signature, refuses:
- a partial receipt carrying the headline;
- any `energy_coverage` but "Partial" ("Whole" included; absence is Whole);
- a provenance label that is not one of the three.
- `Totals` carries `provenance_floor` and `coverage`. A total over any partial
receipt is partial, gives no per-unit energy, and reports value per joule
as its worst value (0.0).
SPEC §5.2 now lists efficiency/1 with these rules. A published pack,
`conformance/v1/efficiency.json` (from `gen_efficiency_vectors`), carries the
readings and refusals and the totals sets must sum to. `tests/efficiency_pack.rs`
replays it from disk.
Tests: 3 new unit tests and 4 pack replays. All 9 mutations of the new rules
are killed. The pack alone catches the headline, coverage-floor and
provenance-floor mutations.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1403 lines
85 KiB
Markdown
1403 lines
85 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, carries every caveat of the grant before it (§4.9) and no
|
||
wider money ceiling (§4.8); and every signature in the chain verifies. A
|
||
structurally invalid chain is denied `chain-broken`; an amplifying one
|
||
`attenuation-violation`. The rules `attenuate` holds when it mints are held
|
||
again here, because a chain arrives signed by whoever minted it.
|
||
|
||
A delegated grant (one with a `parent`) MUST NOT be decided on its own. Alone,
|
||
nothing shows that it narrows what its issuer held, so its budget, caveats and
|
||
expiry would be taken at their word, as a root's are. A single-grant decision
|
||
over a delegated grant is denied `chain-broken`.
|
||
|
||
**Caveats bind on every link.** A chained decision MUST evaluate the caveats of
|
||
every grant in the chain, exactly as a single-grant decision evaluates its own
|
||
(§4.9); a chain is never a way past a condition its root imposed. In the
|
||
two-phase metered path a chain is authorized without debit, cost-dependent
|
||
caveats waiting, and then charged with the measured cost, which is debited from
|
||
every link. A priced chained decision applies both ceilings of §4.8 to **every**
|
||
link: the joule ceilings first, then the money ceiling of each link that has
|
||
one. An allow debits both dimensions from every link, so siblings share their
|
||
parent's money as they share its joules; a deny debits neither.
|
||
|
||
**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
|
||
ModelIn([content-id]) ; the frame must name its model (§6), and it must be one of these
|
||
```
|
||
|
||
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.
|
||
|
||
Three 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. `ModelIn` says **which model** the grant
|
||
authorizes. The capability's `resource` names the endpoint, and one endpoint can
|
||
run many models; `ModelIn` names the model content addresses this grant may
|
||
run there. It ranges over the frame's `model` field (§6), which the runtime that
|
||
dispatches the call fills with the content address of the model it will load,
|
||
and it **fails closed**: a frame that names no model does not satisfy it, so a
|
||
dispatcher that does not say what it will run cannot exercise the grant. This is
|
||
authorization by content address, not a registry (§12): the grant names the
|
||
bytes it permits, and nothing has to be looked up. A delegate narrows the set
|
||
by adding a smaller `ModelIn` beside the inherited one; both must hold, so the
|
||
intersection governs.
|
||
|
||
**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.
|
||
|
||
**What "the argument" covers.** Where an enforcement point forwards a request to
|
||
an endpoint, the argument whose label is gated is everything the request carries
|
||
to the endpoint except the routing that names the action. That includes fields
|
||
the protocol leaves open: extension parameters, side-channel metadata, and
|
||
envelope members the enforcement point does not otherwise interpret. Any such
|
||
field it leaves unlabelled is a way past the ceiling. The enforcement point MUST
|
||
label each of these fields, and MUST seal it with the arguments, or else MUST
|
||
NOT forward it. It MAY leave out the protocol's own fixed machinery (a version, a
|
||
capability set, a progress token) where folding that machinery would taint every
|
||
request. If it does, it MUST say which fields it leaves out. A host that admits
|
||
every value it labels SHOULD label a request's parts so that combining them adds
|
||
no text of its own. Otherwise the framing becomes shared text that carries one
|
||
tainted request's label into every request after it.
|
||
|
||
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.
|
||
|
||
**One writer per record (MUST).** The replay above re-establishes spend *from*
|
||
the record, so two enforcement points sharing one record each re-establish the
|
||
**same** spend, each decide against it, and each append. The budget is spent
|
||
twice and every entry still verifies — the requirement above is defeated one
|
||
layer beneath itself, and nothing in the record shows it. An implementation
|
||
**MUST** therefore ensure a single writer per record: it **MUST** take
|
||
exclusive access when it opens the record, and **MUST** fail to start rather
|
||
than proceed where another writer already holds it.
|
||
|
||
An exclusive claim an operating system releases when the holding process dies
|
||
is preferable to one an implementation must clean up, because a crash then
|
||
leaves nothing to recover. Where a platform cannot provide exclusivity at all,
|
||
the implementation **MUST** fail to open the record rather than proceed —
|
||
deciding against a record another endpoint may also be writing is the overspend
|
||
this requirement exists to prevent, and a platform limit is not a licence for
|
||
it. An implementation that instead proceeds **MUST** make that fact checkable
|
||
at run time rather than leave the type to imply a property it does not hold.
|
||
|
||
**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` (a model
|
||
calibrated on the host or against a reading, or a real reading apportioned by
|
||
a model; measured MAY be absent), or `Estimator` (a figure nothing calibrated:
|
||
a coarse constant, or a model run at a default or assumed parameter, such as
|
||
time × a default power; 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.
|
||
|
||
The check is **two-sided**. Where the bound is the board's own draw, a counter
|
||
whose rate falls far below that draw MUST NOT produce a hardware-graded figure
|
||
either. This is the more dangerous of the two directions. Under-reporting is the
|
||
unsafe direction at a ceiling (§5), because it admits work the budget cannot
|
||
cover; a counter running fast fails safe, because it only spends a budget
|
||
sooner. A counter republished in lumps and sampled across its republish period
|
||
reads low rather than frozen, so liveness alone does not see it. The floor MUST
|
||
allow for the draw reading's own imprecision. It MUST NOT fire where the board
|
||
is near idle, because a correctly reporting counter accrues almost nothing there.
|
||
The reference meters refute a rate under a quarter of the board's mean draw,
|
||
after the draw's ±5 W accuracy is taken off it. Either bound refutes and never
|
||
verifies: a counter inside both is not refuted, and that is not a finding that
|
||
it is correct.
|
||
|
||
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 or an
|
||
estimate (rungs 5 and 6 below).
|
||
|
||
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 and
|
||
hands nothing to another process (below). 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. Nor is work those threads hand to
|
||
another process: a system service that handles a message of theirs
|
||
(on macOS the kernel bills that service's CPU work to the process
|
||
beside the count, `ri_billed_energy`, never in it), a server reached
|
||
over a socket or a pipe. So a span over work that calls a system
|
||
service, or any other process, is not `Whole` for that work, and the
|
||
reference meter, which does not read the billed count on this rung,
|
||
does not detect it: an implementation SHOULD meter such work on another
|
||
rung, or grade its figure `Partial`. A span over a computation the
|
||
implementation knows runs on the metering thread alone and exchanges
|
||
nothing with any other process (the reference gateway's flow scan, a
|
||
pure computation over the process's own memory) is unaffected, and is
|
||
not made `Partial` by a child the process happens to have either: no
|
||
part of that work can be in the child or in a service. The count is
|
||
folded only when a thread leaves a core, and a process's read of its
|
||
own count does not fold the thread reading (its CPU time is current at
|
||
the read, its energy is not: a thread that burned 1 ms between two
|
||
reads of its own process read no advance in most tries, and up to a
|
||
quantum's worth from before the first read in others), so at each end
|
||
of a span the reading thread SHOULD leave its core just before it
|
||
reads, and an implementation that relies on that MUST check that it
|
||
did. A sleep does not always take a thread
|
||
off its core: when the sleep's timer fires before the thread has
|
||
blocked, the thread goes on without leaving (after 1 µs sleeps at load
|
||
averages of 85 to 118, 4 of about 44,000 showed no context switch, and
|
||
the read after missed the whole burn before it). The reference meter
|
||
sleeps 50 µs, then longer up to 5 ms, until the thread's own energy
|
||
count (`proc_pidinfo`'s `PROC_PIDTHREADCOUNTS`) moved across a sleep,
|
||
which it does only when the thread is folded; a process-wide
|
||
context-switch count is no such check, since another thread's switch
|
||
moves it. A read whose thread was not seen to leave its core is no
|
||
reading of that end, and the span is metered by a lower rung. Work on
|
||
the thread that opens and closes the span is then read whole, and the
|
||
figure also holds what every other thread of the process spent inside
|
||
it: over that work's CPU energy on those threads, not under it.
|
||
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.
|
||
|
||
The same count, read for every process of ANOTHER process tree the
|
||
metering process can read — the process the work runs in, such as the
|
||
server a gateway relays a call to, and its descendants — meters that
|
||
work → `HwShunt`. Its domain is the CPU energy of the tree's processes
|
||
over the span (as above, not DRAM): the advance of every process present
|
||
at both ends, plus the whole count of every process born inside the
|
||
span. A process is identified by its pid and its start time, so a reused
|
||
pid is another process. The count is folded only when a thread leaves a
|
||
core, so at each end of the span a member's count MUST be read with none
|
||
of its threads on a core or queued for one, or the member is a gap
|
||
(below). Nor is a member at rest while one of its threads is in a wait
|
||
that no signal ends (the kernel's own waits: for a block being written,
|
||
a page being brought in, a lock). That thread is off its core and
|
||
folded, and it runs again when the kernel is done, with nothing
|
||
arriving; a stop that took the member for rest there would read the
|
||
span short by the rest of its work, with nothing seen missing. So at
|
||
each end a member with a thread in such a wait MUST be waited out as a
|
||
running one is, or is the same gap. A walk of the tree reads a parent
|
||
before it lists and reads
|
||
the parent's children, so a child that exits and is reaped in between
|
||
is in that walk neither as a member nor in its parent's reaped time;
|
||
the walk at each end MUST therefore be confirmed by another, one after
|
||
the other, that agrees with it on the tree's processes, each one's
|
||
parent, reaped-descendant CPU time and exit time, with no listed child
|
||
gone by its read, or that end is a gap (below). The span SHOULD end
|
||
after the work's reply. The figure is all the tree spent inside the
|
||
span, whatever for: work the process defers past an earlier reply, or
|
||
does on a timer of its own, is read in whichever span is open when it
|
||
runs, and work after this span's stop is in no figure of this span. So
|
||
`Whole` says that nothing was seen missing from the tree's energy over
|
||
the span, not that all of it was spent on the span's own work. The
|
||
figure is `Partial` (§5.2) when the meter sees any of these over the
|
||
span; each is energy of the tree's over the span that the figure does
|
||
not hold, but for the ones noted:
|
||
- a descendant reaped inside the span (the reaper's reaped-descendant
|
||
CPU time moved; a reaped process's energy is in no counter), unless
|
||
it had already exited at the start, its energy then all spent before
|
||
the span;
|
||
- a member that exited inside the span: its record is final and is
|
||
counted, but it lacks the energy of the member's last time on a core
|
||
(an exiting thread's last slice is never folded into it: up to all of
|
||
a short-lived process's energy), and its GPU time is lost at exit, so
|
||
its GPU use is unknown;
|
||
- a member present at the start and gone at the stop (reaped, or moved
|
||
out of the tree): what it spent in the span before it went is not in
|
||
the figure;
|
||
- a member present at the stop that started before the span and was not
|
||
read at its start: its energy from before the span cannot be
|
||
separated, so none of it is counted;
|
||
- a member that could not be read (a process of another user), or
|
||
whose children could not be listed;
|
||
- a member not at rest at an end. Still running: at the stop its
|
||
reading lacks up to a scheduler quantum of energy per running thread;
|
||
at the START its reading is short by as much, so the figure may hold
|
||
up to that much energy spent just BEFORE the span. Or with a thread
|
||
in a wait no signal ends, in the middle of its work: what that thread
|
||
goes on to spend falls after the stop, or, at the START, inside the
|
||
span though the work began before it. This one can make the figure
|
||
exceed the tree's energy over the span, so a `Partial` tree figure
|
||
with it is not a lower bound, and an implementation that names the
|
||
gaps beside the figure SHOULD name the end (the reference gateway:
|
||
`unsettled-at-start`, `unsettled-at-stop`);
|
||
- an end whose walk no agreeing walk confirmed before the meter stopped
|
||
waiting: the tree kept changing, and a process that came and went may
|
||
be missing;
|
||
- a member holding a GPU client at either end: GPU time its client
|
||
recorded, or GPU use the reader cannot rule out, because a GPU
|
||
client's recorded time is per live command queue and a queue
|
||
created and released inside the span takes its time with it; or GPU
|
||
time that could not be read;
|
||
- a member that has held Neural Engine model memory in-process;
|
||
- work another process did on a member's behalf and the platform billed
|
||
to it (below): the figure holds the bill, which is short of the
|
||
span's delegated work by what no bill carries, and can hold a bill
|
||
for work done before the span that was credited inside it — so, like
|
||
a member still running at the start, it can make the figure exceed
|
||
the tree's energy over the span (the reference gateway: `delegated`);
|
||
- a member credited inside the span with service to other processes
|
||
(below): the credit is subtracted, and need not be the span's own
|
||
service — a credit for service done before the span makes the figure
|
||
short, service done in the span and credited after its stop leaves
|
||
the figure over — so it too can make the figure exceed the tree's
|
||
energy over the span (the reference gateway: `serviced`);
|
||
- a member credited inside the span with more service to other
|
||
processes than it spent and was billed (below): the figure may
|
||
subtract energy spent before the span.
|
||
|
||
**Delegated work.** Where the platform bills the CPU work one process
|
||
does on another's behalf to the other, the figure SHOULD count it. On
|
||
macOS the kernel's bank ledgers do: a service's thread that adopts a
|
||
client's voucher while it handles the client's message (an XPC or other
|
||
mach message carries one) is charged, at its next voucher change, with
|
||
the CPU time and energy it spent, which raise the client's
|
||
`ri_billed_energy` and `ri_billed_system_time` (not part of its
|
||
`ri_energy_nj`) and the servicer's `ri_serviced_energy` and
|
||
`ri_serviced_system_time` (part of its own), readable for any process of
|
||
the same user without privilege. Each member's count is then its own
|
||
energy, less its serviced energy, plus its billed energy, and the figure
|
||
the members' advances of it, summed signed: work a member did for other
|
||
processes is theirs, work others did for it is the tree's, and work one
|
||
member did for another is counted once, through the payer. A servicing
|
||
thread is credited at its next voucher change, not as it works, so a
|
||
credit inside a span need not be that span's service: a span over which
|
||
any member's serviced energy or serviced CPU time moved is `Partial`,
|
||
whether or not its net stayed above zero. A member whose net advance is
|
||
below zero — a credit for service done before the span landing inside
|
||
it — is counted as it is, never wrapped, and is a gap too. The bill over
|
||
a span is not the span's delegated work, and errs both ways. Over: a
|
||
bill is credited at the servicing thread's next voucher change, so a
|
||
bill for work done before the span — an earlier call's late credit —
|
||
lands in whichever span is open, and is added to its figure. Short: it
|
||
carries none of the GPU or Neural Engine energy spent in the service
|
||
(one on-device inference request on the reference hardware drew 6.73 J
|
||
on the Neural Engine and billed 1.79 J); none of the service's work on
|
||
threads that never adopt the voucher, or before they do (starting a
|
||
per-client service process); no credit that lands after the span (a
|
||
servicing thread is credited at its next voucher change, which can be
|
||
a second and more after the reply); nothing billed to another
|
||
originator (a scheduling daemon's background work, a policy daemon's
|
||
scan of the client's binary); and nothing sent over a socket or a
|
||
pipe, which carries no voucher, or off the host. So a span over which
|
||
any member's billed energy or billed CPU time moved is `Partial`, its
|
||
figure holding the bill, and an implementation that names the gaps
|
||
beside the figure SHOULD name it (the reference gateway: `delegated`).
|
||
Such a span's stop SHOULD wait, briefly and within a bound, for the
|
||
billed counts to hold, so that a credit landing just after the reply
|
||
falls in its own span rather than the next (the reference meter reads
|
||
them every 5 ms until two reads agree, at most 20 times). The billed
|
||
counts take no part in deciding that a member is settled: another
|
||
process's thread credits them, and the member's run state says nothing
|
||
of when.
|
||
|
||
An implementation MUST NOT grade a tree's figure `Whole` on an empty
|
||
list alone. `Whole` further requires the operator's declaration that the
|
||
process hands no work to a process outside its tree — a system service,
|
||
on-device model inference run by a system daemon, a database server, a
|
||
remote API — because a reader of the tree cannot see all such
|
||
delegation: billing shows only work done on a voucher, and the rest of
|
||
that work's energy is charged to the process that does it, which may be
|
||
another user's and unreadable. Without the declaration the figure is
|
||
`Partial`. The declaration does not outweigh evidence: a span with a
|
||
bill (above) is `Partial` under it, the bill showing work done outside
|
||
the tree. A span whose root is gone or unreadable at either end, or
|
||
whose figure did not advance, is metered by a lower rung. The reference
|
||
gateway (`jcp-gateway`) meters each gated call this way where the
|
||
upstream server is a local process it can read.
|
||
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 an active power
|
||
calibrated on the host (a measurement of that host's draw, such as its
|
||
draw under load read from a shunt or a live counter) → `ModelBased`.
|
||
6. **Estimate** → `Estimator`: a figure no host calibrated. Either wall-clock
|
||
duration times a default or assumed active power — a documented default,
|
||
a nameplate or datasheet figure, another host's calibration — or a
|
||
constant per-operation figure. Always available. Time × power is graded by
|
||
its power: the power is the whole of the model, so a power not calibrated
|
||
on the host makes the figure a constant scaled by time, and grading it
|
||
`ModelBased` would claim better provenance than how it was obtained. A
|
||
counter rung's span that degrades because its counter did not read the
|
||
span (above) lands here, unless the implementation holds a host-calibrated
|
||
power, in which case it lands on rung 5.
|
||
|
||
### 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.
|
||
|
||
The `efficiency/1` outcome receipt (§5.3) carries it as `energy_coverage`,
|
||
under the same one-value rule. Its headline figures are ratios of a task total:
|
||
energy per accepted token on the receipt, and energy per completed unit over a
|
||
ledger. So a `Partial` receipt MUST NOT carry `uj_per_accepted_token`, and a
|
||
verifier MUST refuse one that does. A total summed from receipts of which any
|
||
is `Partial` is `Partial` and gives no per-unit energy figure. A partial figure
|
||
divided by tokens is not what the work cost per token, nor a bound on it,
|
||
because a partial figure may fall short of the work or exceed it. The vectors
|
||
are in [`conformance/v1/efficiency.json`](conformance/v1/efficiency.json).
|
||
|
||
### 5.3 Traceability across operation, activity and outcome (REQUIRED where an outcome claims energy)
|
||
|
||
§5.1 grades a figure and §5.2 says how much of the work it covers. Neither says
|
||
which *level* the figure belongs to, and a deployed system is answerable at
|
||
three that are not interchangeable:
|
||
|
||
| level | artifact | what it answers |
|
||
|---|---|---|
|
||
| **operation** | the `jcp/2` work receipt (§8) | what one unit of work cost |
|
||
| **activity** | the AR-1 action receipt | what one *authorised* thing did — grant checked, environment attested, work performed, effect committed, bytes egressed |
|
||
| **outcome** | `efficiency/1` | whether any of it bought anything |
|
||
|
||
Two rules follow, and both exist because the levels were previously joined by
|
||
nothing.
|
||
|
||
**An outcome's energy MUST name its activities by content-id.** An outcome that
|
||
identifies its unit of work by a label can assert any total it likes, and the
|
||
level where the number is most load-bearing is then the level with no binding
|
||
at all. A verifier MUST be able to resolve each named activity and re-add the
|
||
figures. An outcome naming no activity is not traceable and MUST NOT be
|
||
presented as though it were.
|
||
|
||
**The difference MUST be named, not absorbed.** The sum of an outcome's
|
||
operations is not the outcome's energy. Authorising, attesting, verifying and
|
||
pushing bytes off the device all cost joules that no operation receipt claims —
|
||
AR-1 carries exactly one energy figure and it belongs to the work. That
|
||
difference is the cost of being accountable, and it is reported as a signed
|
||
residual in exact integer microjoules:
|
||
|
||
```text
|
||
residual_uj = outcome_total_uj − Σ activity.joules_uj
|
||
```
|
||
|
||
The residual MUST NOT be clamped. Negative means the bound activities claim more
|
||
than the outcome admits — double-counted work, or an activity bound to the wrong
|
||
outcome — which is a finding. An activity bound twice MUST be refused before the
|
||
sum: counting it twice inflates the accounted figure and shrinks the residual,
|
||
flattering the trace in the one direction that matters.
|
||
|
||
The composite provenance is the **floor** across the outcome and every activity,
|
||
never an average and never the best: one `Estimator` part makes the whole trace
|
||
estimated, because a trace cannot be more measured than the part of it that was
|
||
not measured. Coverage takes the floor the same way. Aggregation MUST NOT be a
|
||
step at which a grade improves.
|
||
|
||
### 5.4 Dynamism: what a deviation establishes, and what it never does (OPTIONAL)
|
||
|
||
A total cannot distinguish a throttled part from a contended host from a cold
|
||
cache, and on silicon nobody has profiled it cannot say what normal was. Where
|
||
an implementation carries energy as a time-resolved series, the following hold.
|
||
|
||
**Provenance and coverage are per-window.** Counters do not have uniform
|
||
resolution — a package counter may be continuous while an accelerator counter is
|
||
live only while a queue is — so one grade for a series is a grade for its best
|
||
window.
|
||
|
||
**A baseline MUST declare where it was measured, and MUST NOT be applied
|
||
outside it.** An expectation is a fit, and a fit is only a fit somewhere.
|
||
Comparing against a baseline taken on different silicon, at a different
|
||
occupancy, or at a coarser sampling window manufactures deviations that say
|
||
nothing about the workload. An undeclared baseline is inapplicable, so
|
||
forgetting to declare under-claims rather than inventing findings.
|
||
|
||
**A cause explains a deviation only with evidence.** A named operational cause
|
||
— thermal limit, host contention, cold start, frequency scaling, a meter
|
||
coverage gap — MUST carry a content-id for the reading that supports it.
|
||
Otherwise "explained" becomes the laundering channel: anything inconvenient is
|
||
labelled and disappears.
|
||
|
||
**Absence of detection is not absence.** A phenomenon shorter than one sampling
|
||
window is invisible to it. That case is *unresolvable*, never *within band*.
|
||
Reporting nothing found at a resolution that could not have found it is the
|
||
quietest available way to be wrong.
|
||
|
||
**There MUST NOT be a verdict of intent.** Energy does not carry intent. A
|
||
deviation says the joules did not match the expectation; it cannot say why, and
|
||
on an uncharted stack it cannot say the expectation was right. An unexplained
|
||
window is a request for a look, not a finding of wrongdoing, and a conformant
|
||
implementation MUST NOT collapse unexplained, inapplicable and unresolvable
|
||
windows into a single score — they mean "look at this", "we have no yardstick"
|
||
and "we could not have seen it", which call for different responses.
|
||
|
||
### 5.5 Conservation, not anomaly detection (REQUIRED where a grant is reconciled)
|
||
|
||
The strong instrument is conservation, and it needs no model of normal.
|
||
|
||
Because every figure is integer microjoules and the sums are exact, the smallest
|
||
representable discrepancy against a grant of `N` µJ is `1_000_000 / N` parts per
|
||
million — on a one-joule grant, **1 ppm**. A drift of a fraction of a percent is
|
||
three orders of magnitude above that floor. The question "do these joules fit
|
||
anywhere" is therefore answerable arithmetically, where "is this unusual" is not.
|
||
|
||
This property does not survive the usual construction, and that is the point.
|
||
Where an energy figure is *derived from a performance proxy* — utilisation times
|
||
a thermal design figure, tokens times a coefficient, instructions times
|
||
picojoules-per-operation — its model error is tens of percent, and a
|
||
fractional-percent drift is invisible by construction: it sits far inside the
|
||
error bar of the estimator that produced it. Deriving energy from performance
|
||
does not merely lose accuracy, it destroys the ability to notice that the books
|
||
do not balance. A conformant implementation MUST report whether the floor it
|
||
quotes rests on a measured figure or a modelled one; quoting a 1 ppm floor over
|
||
`Estimator` windows is exact arithmetic over a model's opinion.
|
||
|
||
### 5.6 The physical bound: refutation, never verification (OPTIONAL)
|
||
|
||
The joules were spent and the heat left the die; that quantity is not
|
||
negotiable. The *measurement* of it is exactly as forgeable as any other report
|
||
— a counter can be lied about, a figure fabricated, a `HwShunt` grade stamped on
|
||
a number from nowhere.
|
||
|
||
What energy has that a synthetic unit does not is an **external referent**. A
|
||
token count, a call count, a request rate can be any number whatsoever: nothing
|
||
outside the accounting system constrains them, so a fabricated one is
|
||
indistinguishable from a true one. A joule count is bounded by the part's power
|
||
envelope and the window's duration.
|
||
|
||
An implementation MAY therefore refute a figure that exceeds what the part could
|
||
have drawn, or falls below what a powered, executing part must draw — the second
|
||
being the shape a frozen counter takes, and the reason §5's liveness rule
|
||
exists. Bounds SHOULD be deliberately permissive (burst rating for the ceiling,
|
||
absolute idle for the floor) so that a refutation is unassailable.
|
||
|
||
Passing a physical bound MUST NOT be reported as verification. A fabricated
|
||
figure inside the envelope passes, and must: the bound establishes only that the
|
||
figure is not impossible.
|
||
|
||
**The one thing that cannot be faked is how the energy was consumed.** A report
|
||
of a profile is just numbers, but a part cannot draw the profile of one
|
||
computation while performing another. This closes the hole conservation leaves:
|
||
a total can be balanced by padding — invent an operation and attribute the
|
||
surplus to it — but a fictional operation contributes no draw, so the observed
|
||
character stops matching the declared work mix.
|
||
|
||
Character is also what survives unfamiliar hardware. The magnitudes move with
|
||
the process node, the power envelope and the memory hierarchy, which is why a
|
||
band baseline fails there; a compute-bound steady state is flat on any part, a
|
||
cold start ramps on any part, a queue-driven workload alternates on any part. A
|
||
declared shape MUST be declared in advance — classifying the observed shape and
|
||
then deciding what it must have been is post-hoc pattern matching. An
|
||
inconsistency refutes the declaration. It attributes no cause and names no
|
||
intent.
|
||
|
||
## 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
|
||
model: content-id? ; OPTIONAL: the model the action runs (§4.9 ModelIn)
|
||
}
|
||
```
|
||
|
||
`model` is filled by the runtime that dispatches the call, which knows the
|
||
content address of the model it will load. It is absent for an action that runs
|
||
no model, and an absent `model` is omitted from the canonical encoding, so such
|
||
a frame, and every receipt and vector over it, is byte-identical to one built
|
||
before the field existed.
|
||
|
||
`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`.
|
||
|
||
### 6.1 Continuations: work that answers later (REQUIRED where an enforcement point relays it)
|
||
|
||
An endpoint may answer a request before its work is done. It may return a
|
||
**handle** to work that goes on, whose outcome the client fetches in later
|
||
exchanges that name the handle (MCP's tasks; an A2A task). Or it may return an
|
||
**interim result** asking the client for input, which the client answers by
|
||
repeating the request with its answers attached (MCP's `input_required`). Each
|
||
later exchange moves the first request's work and the first request's kind of
|
||
payload: the outcome is the action's output, and the answers go to the
|
||
endpoint as its arguments do. So an enforcement point that relays such
|
||
exchanges and treats the later ones as discovery has a door beside its gate.
|
||
Such an enforcement point:
|
||
|
||
1. MUST decide each later exchange as a **continuation** of the first: against
|
||
the grant the first was decided on, at the moment the later exchange is
|
||
made, so a grant revoked, expired or spent since refuses it (§7), and the
|
||
outcome of work the grant no longer covers is not fetched under it. It MUST
|
||
seal each continuation's decision (§8) over a payload that commits to the id
|
||
of the receipt it continues (the first's, or the previous continuation's),
|
||
so that one piece of work's receipts form a chain within the stream.
|
||
2. MUST refuse, and seal the refusal of, a later exchange naming a handle it did
|
||
not see returned to a request it decided. A handle is a bearer string the
|
||
endpoint minted, and no grant covers it until the enforcement point has bound
|
||
it to one. A handle it has closed (item 4) or forgotten (a restart, a bound on
|
||
what it keeps) is refused the same way: fail closed.
|
||
3. MUST flow-check what the client authored in a later exchange (the answers)
|
||
as it does the first request's arguments (§4.10), and MUST admit what comes
|
||
back — status text and the outcome alike — as `data`, labelled as the output
|
||
of the first request's action and joined with the label of everything the
|
||
client has sent the work. An exact echo of a handle or state the endpoint
|
||
minted MAY be left out of the flow check, being the endpoint's own text;
|
||
anything else the client sends, a handle it made up included, MUST be
|
||
checked. What the enforcement point forwards SHOULD be built from the fields
|
||
it decided over, so that nothing it did not check reaches the endpoint.
|
||
4. MUST meter and debit each later exchange (§5), and, when the outcome is
|
||
final, MUST seal one **closing record**: a `tool-result` (or `agent-reply`)
|
||
frame tagged `data`, debiting nothing, whose payload is the content address
|
||
(§3) of a record naming the first receipt, every continuation's receipt, the
|
||
joules metered across them, and the outcome's digest. The endpoint works
|
||
between exchanges, inside no exchange's span, so the record MUST mark that
|
||
sum `Partial` (§5.2) unless the meter read the work between them.
|
||
|
||
The reference gateway (`jcp-gateway`) does this for MCP's `tasks/get`,
|
||
`tasks/update` and `tasks/cancel`, and for a `tools/call`, `resources/read` or
|
||
`prompts/get` retried with `inputResponses`; it advertises it in its
|
||
`server/discover` and `initialize` responses as `continuations`.
|
||
|
||
## 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; a chained
|
||
decision enforces an inherited caveat; and a chain whose child dropped one does
|
||
not verify.
|
||
- **C8 — wire binding:** a request round-trips the `jcp/1` framing canonically
|
||
and an enforcement endpoint decides + seals over a live socket; a request
|
||
presented again gets its first response unchanged and is not debited again,
|
||
and one stamped outside the window is refused `stale-request`.
|
||
- **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;
|
||
- **C19 — injection resistance:** a tool-poisoning corpus presented through an untrusted frame cannot steer a decision; the trust tag is structural, so data cannot become control. (Implemented in the harness since before this entry: §9 listed every other clause but omitted this one.)
|
||
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 |
|
||
| Results fetched later, outside the gate (task handles, interim input requests) | §6.1 continuations, chained to the receipt they continue |
|
||
|
||
## 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). The response carries the deterministic decision and the
|
||
sealed JCR-1 receipt, deny and allow alike.
|
||
|
||
**Time is the enforcement point's.** The endpoint MUST decide at its own clock.
|
||
`now` is the time the caller signed the request, a freshness stamp: the endpoint
|
||
MUST deny a request whose `now` differs from its clock by more than its
|
||
freshness window as `stale-request`, and MUST NOT decide any request as of the
|
||
time it names. A caller that chose the decision time could stamp a request
|
||
before its grant expired, or inside a time caveat it has since left.
|
||
|
||
**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.
|
||
|
||
**A request is decided once.** The wire never carries a secret: grants are
|
||
signed public objects, and a request is a signed public object too. The endpoint
|
||
MUST remember every request it decided while that request's stamp is inside the
|
||
window, together with its response. A request presented again MUST get that same
|
||
response, the same decision and the same receipt, and MUST NOT be decided,
|
||
debited or recorded a second time. That holds across a restart, which replays
|
||
the record (below) and so recovers each response: the decision is deterministic
|
||
and so is the seal. A captured request is therefore worth nothing beyond what
|
||
its first exchange carried. A caller whose response was lost recovers it by
|
||
sending the same request again. Refusing the repeat instead would leave that
|
||
caller only one way to learn the outcome: stamp the request again, which is a
|
||
new request and decides and debits the same action a second time. A request
|
||
refused before it was decided (stale, unauthenticated, or `unrecorded`) is not
|
||
remembered. Once a request's stamp has left the window, a repeat of it is stale.
|
||
Two byte-identical requests are one request, so a caller that means the same
|
||
action twice stamps the second later.
|
||
|
||
**The cost is the holder's measurement.** `cost` is the requester's own figure,
|
||
and the endpoint debits it as given, so a budget binds only as honestly as that
|
||
figure was measured. The requester MUST therefore be the party that metered the
|
||
action: the grant's holder is the enforcement point that ran it, as a steward is
|
||
in Mesh, rather than the agent the grant governs reporting its own consumption.
|
||
The endpoint behind the enforcement point is invoked only on Allow.
|
||
|
||
**What the endpoint holds survives it.** An endpoint that can restart is bound
|
||
by §4.11: it MUST record each request it admits before deciding it, and each
|
||
revocation it honours before applying it, and on restart MUST replay them
|
||
through the gate, each request at the time it was decided, before deciding
|
||
anything new. That re-establishes the spend, the revocations, and the memory of
|
||
requests still inside the window. A request it cannot record is denied
|
||
`unrecorded`.
|
||
|
||
## 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; `ModelIn` admits only a frame naming one of its models, and denies a frame naming another or none; 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. A priced chained decision applies the parent's money ceiling to its child's spend, and a delegated grant decided without its chain is denied `chain-broken`.
|
||
- **C26 — one writer per record:** a durable decision record admits one writer. A second holder of the same record is refused rather than opening it, because two would each replay it, each re-establish the same remaining budget and each debit against it — spending it twice while every entry still verifies (§5). The exclusive claim is released when the holder goes away, so a restart can reopen the record it previously held, and a platform that cannot provide exclusivity fails the open rather than proceeding.
|