Skip to content

docs: stop the remote-error table claiming to be the enumeration - #385

Merged
Yaraslaut merged 1 commit into
masterfrom
fix/358-remote-error-table
Sep 1, 2026
Merged

docs: stop the remote-error table claiming to be the enumeration#385
Yaraslaut merged 1 commit into
masterfrom
fix/358-remote-error-table

Conversation

@Yaraslaut

Copy link
Copy Markdown
Member

Closes #358.

docs/spec/error_handling.md's "Remote error envelopes" section listed 8 refusals and described unknown envelope kind as covering anything that is not register/deregister/execute. Both were wrong in the way that matters: the table presented itself as the enumeration while being a subset, and the one row whose job is to say when it fires named three kinds where the code has eight.

Re-derived, not copied

The issue's own figures were treated as a starting point, not as input. scripts/scenario/scenario_coverage.py already extracts both universes mechanically from remote.hpp's server half and wire::decode, and its self-test pins them. Running that extractor reports:

  • 8 envelope kindsassign attach deregister execute hello instances register schemas
  • 17 refusals — 14 fixed strings plus 3 prefixes (envelope decode failed: , payload missing required field(s): , unknown envelope kind: )

The issue estimated "~16", which was low. Every site was then read in remote.hpp to confirm, which is where the multiplicity comes from: 8 unauthorized sites where the old table implied 3, and 3 sites each for too many models and connection closed.

The judgement the issue handed over

It asked whether some refusals were "operational rather than protocol-level", and whether an intentional omission deserved a sentence. The answer is that several are operational, and the fix is not one longer table — a client author reading a flat list of 17 cannot tell which ones they should code against.

The section is now organised by what a client should do about it:

  1. malformed request — fix the request;
  2. refused on the merits — the request was well-formed and the answer is no;
  3. the server's condition produced it — retry may work, with a "reachable when" column, since three of its five rows need a LimitPolicy field that no default sets and only one ladder rung configures;
  4. handleInline's message, under its own heading, because no transport can reach it.

The head states the arithmetic, so a reader can check completeness rather than trust it, and the unknown envelope kind description now matches the code.

Verification

scripts/check_spec_citations.sh exits 0 after the edit. CHANGELOG.md entry under ### Fixed.

Not verified: no mechanical check ties this table to the extractor, so drift is still only caught indirectly — by scenario_coverage.py's pin test failing when a refusal is added to remote.hpp, which forces a human to decide where it belongs but does not point at this file. Wiring the table itself to the extractor would be its own change.

Note for review order: #377 (fix/351-execute-ticket-guard) is open against remote.hpp and changes no refusal literal, so this table is stable against it either way it merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GnSnsebJ92Kti1D7m3RwYJ

@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

error_handling.md's "Remote wire errors" table listed 8 refusals and read
as the complete set, and its `unknown envelope kind` row told a client
implementer the refusal fires for anything that is not
register/deregister/execute. Both are wrong, and the second is wrong in
the way that matters: dispatchMessage compares env.kind against eight
values, and attach, assign, instances, schemas and hello all reach real
branches that never produce that message.

The real numbers, re-derived rather than taken from the issue's "~16"
estimate. scenario_coverage.py's own extractor -- remote.hpp's server half
above `class SimulatedRemoteBackend`, plus the body of wire::decode --
reports 8 envelope kinds and 17 refusals, 14 fixed messages and 3 with a
runtime suffix:

  assign attach deregister execute hello instances register schemas

  assign requires a typeId / attach requires a typeId / connection closed
  / handleInline does not support execute (reply is asynchronous) /
  instances requires a typeId / model not found / protocol version
  unsupported / register requires a typeId / schemas requires a typeId /
  server busy / server shutting down / timeout / too many models /
  unauthorized, and the prefixes envelope decode failed: / payload missing
  required field(s): / unknown envelope kind:

Each site was then read in remote.hpp to fill the Raised in and Cause
columns, which is where the eight `unauthorized` sites and the three each
for `too many models` and `connection closed` come from -- the old row
named three of the eight.

The judgement the issue handed over: yes, several of these are
operational rather than protocol-level, and mixing them cost a reader the
one thing the message text never tells them, which is whether retrying can
help. The section is now three tables -- the request is malformed, the
request is refused on the merits, the server's own condition produced it
-- with a fourth heading for the one message no transport can reach
(handleInline's, which is an in-process C++ API). The third table gains a
"reachable when" column, because three of its five rows need a LimitPolicy
field no default and only one ladder rung ever sets.

The head states the count and where it comes from, so the claim of
completeness is checkable rather than asserted: 17 extracted, 16 in the
tables, the 17th called out separately, plus two rows sourced from outside
those headers (registry.hpp's `unknown model type:` and the exc.what()
passthrough).

Verified: scripts/check_spec_citations.sh exits 0 (711 references, 59
cited sections, wire.md tables complete), and the extraction above is
scenario_coverage.py's own, which exits 0 with kinds 8/8 and refusals
11/17 covered plus 6 written exemptions.

Not verified: I did not add a check that ties this table to the extractor.
Nothing mechanically stops it drifting again -- what does exist is
test_real_headers_carry_exactly_these_refusals failing when a refusal is
added, which forces a human to that decision without pointing them here.

Closes #358.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GnSnsebJ92Kti1D7m3RwYJ
@Yaraslaut
Yaraslaut force-pushed the fix/358-remote-error-table branch from b9652c8 to 4727101 Compare September 1, 2026 15:43
@Yaraslaut
Yaraslaut merged commit d1641e1 into master Sep 1, 2026
29 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error_handling.md's remote-error table lists 8 refusals and 3 envelope kinds; the server has ~16 and 8

1 participant