The tenant policy register that lands with #565 carries a carried-channel matrix, tenancy.Carriages() in internal/tenancy/channels.go: for each MCP method and protocol era, the channels a refusal can reach a caller through (a JSON-RPC error and its code, a tools/call result with isError, an HTTP status from the gate, a closed stream). Its own comment says what it is: "what go-sdk v1.8.0 does rather than what the protocol would permit", and "an SDK upgrade that changes what is carried edits this table in the same pull request".
Nothing enforces that sentence. The matrix was written from a probe I ran by hand against go-sdk v1.8.0 (nine tests driving an SDK server in process over httptest, both eras, stateless and stateful, logging what arrived on the wire and asserting nothing), and that probe is not in the repository. Dependabot bumps go-sdk weekly, and a bump that changes a channel would merge green with a matrix that no longer describes the server; the register's Validate would then accept or refuse rows against channels the SDK no longer carries.
The ground is moving. Three of our own go-sdk pull requests and one issue touch how a request is answered or ended, and #961 tracks them:
What to do
Turn the probe into an asserting integration test in the main module that holds tenancy.Carriages() to what the pinned go-sdk actually does, so the pull request that bumps go-sdk fails when a channel changes and names the row.
- Place it beside the matrix, as
internal/tenancy/channels_integration_test.go in package tenancy_test (the external-package qualifier the test-file naming rule allows). go-sdk is already a dependency of the module, and a test import does not reach the server binary, so internal/tenancy stays a leaf in production; TestDependencies_TestSupport_NeverReachesTheServerBinary keeps holding.
- For every
Carriage row, drive the SDK in the era the row names and assert that each listed channel is observed and that no unlisted refusal channel is. The pseudo-methods the SDK does not own (http, startup, eviction) stay out; expiry is the SDK's and is in.
- Keep as assertions the facts the probe observed that other decisions rest on without being matrix rows: which middleware error codes reach the client unchanged, how load shedding answers, that a typed nil result is handled the way the register assumes (the probe isolates that one in a subprocess of the test binary, and the test should too), and that
SubscriptionsListenResult cannot be constructed by application code, which listenStreams and CLAUDE.md rely on.
- On failure, the message names the row, the era, the channel expected and the channel observed, and says that the fix is an edit to
channels.go in the same pull request.
Done when
- The test runs in the ordinary unit suite (
go test ./internal/...), in process, with no Docker and no network, and passes against go-sdk v1.8.0.
- Changing one channel in
Carriages() makes it fail with a message naming that row.
docs/development/tenant-policy-spec.md (Refusal channels) and the comment on Carriage say that the matrix is now checked against the SDK rather than asserted by it.
Sequenced after the #565 stack merges, since the matrix arrives with it, and before the work on #961, where it pays off first.
The tenant policy register that lands with #565 carries a carried-channel matrix,
tenancy.Carriages()ininternal/tenancy/channels.go: for each MCP method and protocol era, the channels a refusal can reach a caller through (a JSON-RPC error and its code, atools/callresult withisError, an HTTP status from the gate, a closed stream). Its own comment says what it is: "what go-sdk v1.8.0 does rather than what the protocol would permit", and "an SDK upgrade that changes what is carried edits this table in the same pull request".Nothing enforces that sentence. The matrix was written from a probe I ran by hand against go-sdk v1.8.0 (nine tests driving an SDK server in process over
httptest, both eras, stateless and stateful, logging what arrived on the wire and asserting nothing), and that probe is not in the repository. Dependabot bumps go-sdk weekly, and a bump that changes a channel would merge green with a matrix that no longer describes the server; the register'sValidatewould then accept or refuse rows against channels the SDK no longer carries.The ground is moving. Three of our own go-sdk pull requests and one issue touch how a request is answered or ended, and #961 tracks them:
What to do
Turn the probe into an asserting integration test in the main module that holds
tenancy.Carriages()to what the pinned go-sdk actually does, so the pull request that bumps go-sdk fails when a channel changes and names the row.internal/tenancy/channels_integration_test.goinpackage tenancy_test(the external-package qualifier the test-file naming rule allows). go-sdk is already a dependency of the module, and a test import does not reach the server binary, sointernal/tenancystays a leaf in production;TestDependencies_TestSupport_NeverReachesTheServerBinarykeeps holding.Carriagerow, drive the SDK in the era the row names and assert that each listed channel is observed and that no unlisted refusal channel is. The pseudo-methods the SDK does not own (http,startup,eviction) stay out;expiryis the SDK's and is in.SubscriptionsListenResultcannot be constructed by application code, whichlistenStreamsand CLAUDE.md rely on.channels.goin the same pull request.Done when
go test ./internal/...), in process, with no Docker and no network, and passes against go-sdk v1.8.0.Carriages()makes it fail with a message naming that row.docs/development/tenant-policy-spec.md(Refusal channels) and the comment onCarriagesay that the matrix is now checked against the SDK rather than asserted by it.Sequenced after the #565 stack merges, since the matrix arrives with it, and before the work on #961, where it pays off first.