jcp-gateway: a task's exchanges and an interim result's retry are decided as continuations of the call #336

Merged
dcharlot merged 2 commits from fix/jcp-gateway-gates-tasks-and-mrtr into main 2026-10-19 11:04:50 -04:00
3 changed files with 1192 additions and 44 deletions

View file

@ -1049,6 +1049,53 @@ before the field existed.
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
@ -1212,6 +1259,7 @@ jcp-conformance --bin gen-vectors`).
| 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)

View file

@ -8,9 +8,10 @@
//!
//! The gateway speaks newline-delimited JSON-RPC with its own client on
//! stdin/stdout and with the spawned MCP server over the child's stdio. Every
//! `tools/call` is gated, metered, debited, and sealed; receipts stream to
//! `JCP_GATEWAY_RECEIPTS` (default `./jcp-gateway-receipts.cose`, length-
//! prefixed envelopes) and a one-line summary per call goes to stderr.
//! method in `JCP_ENFORCED_METHODS` is gated, metered, debited, and sealed (a
//! task's exchanges as continuations of the call that opened it); receipts
//! stream to `JCP_GATEWAY_RECEIPTS` (default `./jcp-gateway-receipts.cose`,
//! length-prefixed envelopes) and a one-line summary per call goes to stderr.
//!
//! That file is also read back at startup, because SPEC §8.1 is REQUIRED of
//! an enforcement point: it rebuilds its budget state by replaying its own

File diff suppressed because it is too large Load diff