Files
amethyst/commons
Claude 8288899b60 fix(contextvm): a CEP-22 response never completed its own call
Pointing the live harness at oversized transfer found three bugs that no
unit test could have found, because each one was a thing the fixture did
that a real server does not.

1. We declared no CEP-35 surface at all. `initialize`'s capabilityTags
   defaulted to empty and nothing ever passed any, so a spec-correct
   ContextVM server learned nothing about us — and it only chunks a
   CEP-22 response, or opens a CEP-41 stream, for a client that declared
   it can take one. Both profiles were dead on every live session, which
   is also why msg_sub_many has never had a live proof. The surface now
   rides the session's first message whatever that message is, since a
   session that opens with a tool call rather than a handshake would
   otherwise still declare nothing.

2. A completed transfer could not end the call. `onNotification`
   returned Unit, so after reassembling the response the transport went
   back to waiting for a direct one — which a chunking server never
   sends, the chunked response being the response. It returns
   JsonRpcMessage? now; non-null completes the call. CEP-41 keeps
   returning null, because `close` is not an answer.

   The unit test missed this by having the fixture ALSO answer normally,
   so the call was ended by that answer and reassembly only had to win a
   tie-break. The fixture now sends frames and nothing else, and the
   test fails without the fix.

3. The deadline bounded the whole call. Both profiles deliver one
   response as a run of notifications, each its own signed, wrapped,
   published relay event, so a 100 KB transfer ran past the 20 s default
   and was killed mid-run with every frame arriving and parsing
   correctly. timeoutMs now bounds silence, which is what a caller
   actually wants bounded. Subscriptions keep the old meaning through an
   explicit TimeoutMode.TOTAL: their timeout is a budget the sync loop
   re-opens on, and a busy stream bounded by silence would never return.

Harness: tier-b.sh gains the three tools the two-party lifecycle never
reaches — join_request_store, join_request_take_many and kp_remove, via
a third account who asks to join rather than being invited — plus a
backlog big enough to trip the coordinator's 48000-byte threshold.
`cordn fetch --json` reports oversized_transfers so the run can tell a
server that chunked from one that never had to; without it a reassembled
response is indistinguishable from a direct one, by design.

Also corrects an assertion I had backwards: reading a join request does
not consume it. Retirement rides the next call after an accept or
decline, so a reader that lost its process between listing and accepting
has to still find the request — two amy runs are exactly that case.

Verified live against the reference coordinator: TIER B PASSED with
oversized_transfers=1, and CLIENT INTEROP PASSED unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n
2026-09-23 18:52:12 +00:00
..