Triaged the heid-bug-hunt panel (Gróa 8 / Hulda 6 / Regin 6; Heid source-checked +
refuted 2 Regin FPs). The lens pulled real weight — confirmed bugs the conformance
review structurally could not see.
Confirmed bugs fixed:
- SessionRetired (410) stream-open maps to wt.SessionApiFailed, but neither cli
_run_turn nor web gen() caught it → crash / dropped SSE stream. Both presenters now
catch it (cli → exit 20; web → labeled `event: error`). (Gróa#2) + cli regression test.
- cli forwarded consumer_key unconditionally; an UNBOUND create with the env key set
would auth as the Bifrost consumer, not the default bearer. Guarded in the adapter
(consumer_key only when bifrost is set). (Gróa#4 + Regin#4) + test.
- cli _turn_id_from_sse_id crashed on a None/non-str sse_id (web guarded, cli didn't)
→ now tolerant. (Gróa#1 + Hulda#2) + test.
- _cancel_and_log broadened to `except Exception` — after the code-review's ApiError
default, a cancel could raise SessionApiFailed it didn't catch, breaking INV-009
(never-raise). (Gróa#3, Heid-endorsed over Regin's refuted mechanism).
Open-world degrade-not-crash (contract posture): render hardened — float duration_ms
(_format_duration_safe), non-mapping usage/snapshot guards, unknown event type
degrades instead of asserting (Gróa#5/#6 + Hulda#3); web _event_to_browser_payload
guards a non-mapping `raw` (Hulda#4); web _wt_client bearer extraction is now
case-insensitive + whitespace-robust (Hulda#5 + Regin#5). + render-degrade test.
Rejected (verified): Regin#1 (httpx IS caught), Regin#2 (wtsdk IS worldtree_sdk),
Regin#3 (sse_client.AgentNotAvailable IS caught by SseConnectFailed) — all FPs;
Hulda#1 (deleted funcs "break callers") — grep-verified zero callers pre-deletion.
Accepted-known-risk: lenient sse_id parse, CancelFailed status=0, async-gen aclose
(pre-existing pattern, not a cutover regression).
Suite 497 green; wt/cli/web ruff + wt mypy clean. Patch.
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.
Completes the adapter's session/turn surface, still additive and non-breaking (no
surface rewired, no hand-rolled path deleted — the cli/web rewire + deletions +
live smoke are part 2b).
- stream_turn: drives the SDK's resilient stream (auto-resume absorbs the old
reconnect_turn) and yields SDK TurnEvents, re-wrapping the stream's TERMINAL SDK
errors into ratatoskr's caller-semantic exceptions per DEC-2 (SessionRetired →
SessionApiFailed; AgentNotAvailable / TurnLaunchUnavailable / MalformedSse* /
TurnIdFlip → ratatoskr's same-named types; ConnectionDropped → SseConnectionDropped;
ConnectFailed / terminal ResumeError → SseConnectFailed). The presenter keeps
catching ratatoskr types (part 2b aligns the except clauses).
- cancel_turn: returns the SDK CancelResult (a 200 cancelled=False is the benign
late-cancel race, B-CAN-3), mapping the typed cancel races onto ratatoskr's
CancelTurnNotFound / CancelAlreadyCompleted / CancelFailed.
- SseConnectionDropped.last_seen_sse_id widened to SseId | str | None: the SDK's
resume cursor is a raw composite-id str (the cutover's target form); the
hand-rolled path's SseId stays accepted until it is deleted. The one live reader
(stream_turn_resilient) generalizes cleanly — a str cursor is already the id.
Suite 570 green (555 + 15); wt.py + sse_client.py mypy + ruff clean. Patch.
First slice-2 increment: the presenter-independent sessions routes, additive and
non-breaking (no surface rewired, no hand-rolled path deleted yet — the cli/web
rewire + deletions + live smoke land in part 2).
- create_session / list_sessions / get_session_messages / get_session_tools over
WorldtreeClient.sessions.*, each building the request from ratatoskr's domain
params and mapping the SDK's ApiError floor by ROUTE (INV-CUT-2): create 404 →
AgentNotFound, bound 502 → BifrostHandshakeFailed, list 422 cursor_invalid →
InvalidCursor, else the SessionApiFailed default.
- Open-world reads returned VERBATIM (parity-pass posture): the routes return the
SDK's open dicts, not ratatoskr's typed SessionInfo/SessionPage — those typed
result shapes retire when the presenters are rewired to read mappings (adopt the
dep's canonical open-world way, reference-impl doctrine).
- Transitional: wt imports the caller-semantic exceptions + BifrostBinding from the
retiring sessions module (one-way, no cycle); they relocate into the adapter as
their call-sites are rewired.
- Cancel + the resilient turn STREAM are deferred to part 2, where they wire into
the async presenter loop and are validated by the live smoke.
Suite 555 green (541 + 14); mypy strict + ruff clean. Patch (internal, additive).
Slice-1 of the SDK cutover (docs/contracts/worldtree_sdk_cutover.contract.md):
the adapter chokepoint onto worldtree-sdk 1.0.0, unit-tested but not yet wired
to any surface (that is slice-2).
- build_client(base_url, *, api_key, admin_key=None, transport) constructs the
single WorldtreeClient over a ratatoskr-owned injected httpx.AsyncClient.
INV-CUT-1: the SDK is given the transport (_owns_client=False) and never closes
it — proven by a test asserting aclose() leaves ratatoskr's transport open.
- translate_error implements the § Error map DEFAULT: SDK ApiError → the adapter's
SessionApiFailed (carrying the SDK's parsed status/error_code/body); every
discriminated WorldtreeError subclass passes through by identity. Route-specific
rows land at their call-sites in later slices (the route is the discriminator).
- SessionApiFailed gains error_code vs the retiring sessions.py copy (extends it
per the contract error-map row); the two coexist transiently and reconcile in
slice-2 (DEC-4 incremental cutover — nothing wires the adapter this slice, so
they never meet at runtime).
Deletes no hand-rolled path, so DEC-4's live-smoke bar does not apply yet.
Suite 541 green (534 + 7 new); mypy + ruff clean. Patch (internal foundation;
the cutover's minor bump is DEC-6 at slice-7 ship).