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

2026-10-19

Transaction Science
jcp-gateway: the receipt-decoding test helper keeps its doc comment
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 40s
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 1m2s
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 3m6s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m17s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m58s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m32s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m41s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m13s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m20s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m26s
The task tests were inserted between jcr1_payload's doc comment and the
function, so the comment documented the first constant after it and clippy
(-D warnings, macOS meter job) refused the empty line after it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 08:28:09 -04:00
Transaction Science
jcp-gateway: a task's exchanges and an interim result's retry are decided as continuations of the call
Some checks failed
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 54s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 7s
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 1m24s
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 3m15s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m30s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m38s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m11s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m10s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 5m58s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m28s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 4m8s
MCP 2026-07-28 lets a gated call answer before its work is done, and both
ways it does so went around the gate.

A task handle (resultType "task"): the client fetches the outcome with
tasks/get, answers the server with tasks/update, stops it with
tasks/cancel. All three passed through as discovery. The outcome, which
is the tool's actual output, reached the model unmetered and unsealed and
was never admitted to the flow store, so a value read through a task could
be sent on by a later call with nothing to fold it against. And any task
id at all was relayed, whoever's it was.

An interim result (resultType "input_required"): the client repeats the
call with its answers in inputResponses. Only `arguments` were folded, so
an answer carrying a value from an untrusted read reached the server past
the flow ceiling.

Now:

- A task handle is bound to the grant of the call it came back from. Each
  tasks/* exchange naming it is decided against that grant at the moment
  it is made (revoked, expired or spent refuses it), metered, debited, and
  sealed over a payload that commits to the receipt it continues. Its
  reply is admitted as untrusted data under the join of everything the
  client has sent the task. The request sent upstream is rebuilt from the
  fields decided over, so nothing else the client adds reaches the server.
- The tasks/get that brings the outcome closes the task: one zero-cost
  result frame (tool-result, data) sealed over the content address of a
  closing record that names the opening receipt, every exchange's receipt,
  the joules metered across them and the outcome's digest. The record is
  Partial: the server works between exchanges, in no span the gateway
  reads.
- A tasks/* exchange naming a task this gateway did not open, or has
  closed, is refused, sealed, and never reaches the server.
- A retry's answers (inputResponses, requestState) are part of its sealed
  payload and are flow-checked with the arguments. The payload commits to
  the receipt of the interim result it answers.
- Handles the server minted (a task id, a request key, a requestState) are
  left out of the flow check only when they are an exact echo of what the
  gateway saw the server mint. A 36-byte UUID would otherwise taint every
  poll under a ceiling, since the scan window is 16 bytes. A handle the
  client made up is checked.
- server/discover carries the advertisement, as initialize does. The
  advertisement lists the task methods and `continuations: 1`.
- Open tasks and pending interim results are bounded (1024 each).

SPEC §6.1 states the rule in protocol-neutral terms. §10 gains a row
for it.

Known limits, stated in the code:
- Open tasks are kept in memory. After a restart, polls of earlier tasks
  are refused (fail closed).
- Work the server does between exchanges is not metered.
- Server-pushed notifications/tasks are outside the request/response
  relay.

Tests: 12 new, against scripted upstreams. All 19 mutations of the new
logic are killed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-19 07:31:05 -04:00