74d41eb559
Triaged the heid-code-review panel (Gróa + Hulda substantive, Regin zero=weak).
Adopted (genuine adds):
- cancel_turn + stream_turn gain a defensive `except ApiError -> SessionApiFailed`
default after their discriminated branches. INV-CUT-2 ("every ApiError is mapped;
default SessionApiFailed") now holds STRUCTURALLY on those routes, not by coupling
to the SDK's internal guarantee that it maps them to discriminated types. + tests.
- get_session_tools error-path test (symmetric with messages).
- Contract § Error map amended: added the stream ProtocolError rows
(Malformed*/TurnIdFlip -> ratatoskr same-named), clarified the cancel row (the SDK
RAISES the typed races -> ratatoskr exceptions, only a 200/cancelled=False is a
CancelResult; caller surface stays exception-based per DEC-2), and noted the
ApiError default holds on stream+cancel too.
Rejected (category-5, wrong-grounding) — 2/3 arms flagged create's bound-502 as
"should gate on error_code like list's 422+cursor_invalid". Verified against the SDK
parser (not in the arms' file set): the bound-502 body is
{"error_code":"bifrost_handshake_failed","detail":{"bifrost_error":...}}, and the
SDK's envelope parser PREFERS the nested detail (which lacks error_code), so
ApiError.error_code resolves to "unknown" — gating would REGRESS handshake detection
(the cli/web integration tests caught it). INV-002 also makes the handshake the sole
bound-502 cause. Kept the any-bound-502 mapping; documented WHY in code + contract.
Accepted-as-is: create_session -> Mapping annotation (intentional open-world
passthrough, already documented in the route-map note; category 3).
Suite 493 green; wt.py mypy + ruff clean. Patch.