fix: error codes leave the sub-range the specification retired (WP-15) - #16
Merged
Conversation
"-32000 to -32019 -- legacy. Codes in this sub-range were allocated by implementations before this policy was introduced. New codes MUST NOT be allocated in this sub-range, and new implementations SHOULD NOT use codes from this sub-range at all. Apart from -32002, receivers MUST NOT assume any specific meaning for these codes." Broker is a new implementation written in 2026. CLAUDE.md and HANDOFF 7.2 both mandated the sub-range; both are wrong and the spec wins. The eight codes move to 1000..1019 -- outside the JSON-RPC reserved range entirely, as "New error codes ... SHOULD be allocated outside the JSON-RPC reserved range (-32768 to -32000)" asks. The low ordinal is preserved so triage knowledge transfers: -32007 became 1007. 1002 stays skipped, mirroring -32002, which was resource-not-found before this revision. LegacyCode maps each new code back to the one it replaced, so a client mid-migration can still triage on the old number.
… and the docs that mandated it
Task 8 moved the codes; this is everything that had to move with them.
BROKER_EMIT_LEGACY_ERROR_CODE (default true, scheduled for removal) attaches
data.legacyCode so a client mid-migration can triage on either number. Both
transports apply it at exactly one point -- writeError and a new
Stdio.errorResponse, which replaces seven scattered NewErrorResponse call
sites -- so envelope/errors.go stays the sole authority for the error surface.
WithLegacyCode merged only when Data was already a map, so a struct payload was
demoted to data.detail. CodeScopeDenied carries {"requiredScope": ...}, which
8.4 calls the actionable part of the error, so with the flag at its default a
client reading error.data.requiredScope found nothing there -- the transition
aid breaking the one field it exists to protect. A struct now round-trips
through JSON and merges at the top level; only a non-object keeps "detail".
MCP/2026-07-28/SHOULD/no-errors-in-legacy-range makes the mistake detectable
rather than merely no longer made. SHOULD, because "MUST NOT be allocated"
binds allocation, which no black-box scan can observe; "SHOULD NOT use ... at
all" is the emission half, which it can. -32002 is excluded so a
resource-not-found defect is reported once by its MUST rather than twice.
And the documents that told an implementer to allocate there: CLAUDE.md,
HANDOFF 7.2 and gotcha 14, and MIGRATION.md's allocation table -- the last now
carrying a section for readers who already allocated in the sub-range. The
7.2 change is recorded as a deliberate divergence in the style docs/PRD.md
established.
Three more the grep found. tests/e2e/test_mrtr.py still asserted -32003,
-32004, -32000 and -32007, so make test-e2e would have failed against the
migrated broker. And two remediation strings inside the scanner itself: the
reserved-range rule told every server it grades to "move implementation-defined
error codes into -32000..-32019", which is shipped advice to violate the spec.
29 of 29 seeded violations detected, 0 false positives, make check green.
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements §3.1 of
docs/superpowers/specs/2026-08-23-sentinel-v1-design.md.The quote this exists for
Broker is a new implementation written in 2026.
CLAUDE.mdandHANDOFF.md§7.2 both mandated the sub-range it should not use. A repository that ships a conformance scanner cannot also ship instructions to violate the spec it scans for.What moved
HandleNotResolvable-320001000MRTRFlowExpired-320011001MRTRArgumentsMutated-320031003MRTRStateInvalid-320041004MRTRResultNoLongerAvailable-320051005TokenBudgetExceeded-320061006ScopeDenied-320071007AuditWriteFailed-320081008The low ordinal is preserved so triage knowledge transfers.
1002stays skipped, mirroring-32002.A bug this PR introduced and then fixed
WithLegacyCodemerged only whenDatawas already amap[string]any, so a struct payload was demoted todata.detail.CodeScopeDeniedcarries{"requiredScope": …}— which §8.4 calls the actionable part of the error — so with the flag at its default, a client readingerror.data.requiredScopewould have found nothing there. A transition aid that breaks the one field it exists to protect is worse than no transition aid. A struct now round-trips through JSON and merges at the top level; only a non-object payload keeps thedetailwrapper.Making it detectable rather than merely no longer made
MCP/2026-07-28/SHOULD/no-errors-in-legacy-rangeprovokes five error paths and reports any code from the retired sub-range. SHOULD, not MUST: "MUST NOT be allocated" binds allocation, which no black-box scan can observe; "SHOULD NOT use … at all" is the emission half, which it can.-32002is excluded so a resource-not-found defect is reported once by its MUST rather than twice.Found by grepping for the old numbers
tests/e2e/test_mrtr.pystill asserted-32003,-32004,-32000,-32007—make test-e2ewould have failed against the migrated broker.no-errors-in-reserved-rangetold every server it grades to "Move implementation-defined error codes into-32000…-32019". The scanner was dispensing the exact advice this PR exists to retract.docs/demo/README.md's sample error body, andMIGRATION.md's allocation table row-32000 … -32019 | you.Verification
Test plan
make checkgreenmake measureregenerated; recall 100%, false positives 0WithLegacyCodemutation-checked (neutering the wrap fails the transport test)make test-e2eagainst a full compose stack (thestripe/smokescreenimage would not pull locally; the updated e2e assertions are unverified by execution)