# worldtree-sdk cutover — SLICE-6 COMPLETE (admin: bifrost inspection + admin-events SSE) `[2026-07-19]` Slice 6 of 7 of the worldtree-sdk consumer cutover (issue #20; contract `docs/contracts/worldtree_sdk_cutover.contract.md`). Full House Code Discipline; both heid panels cleared. Suite **494 green**. The meatiest slice — SSE stream + admin auth + an event-shape decision. ## Commits (v0.21.19–.20, on `main`, PUSHED this session) - **`de9a5ba`** feat — the two admin routes onto `ratatoskr.wt`, hand-rolled deleted. - **`bba57e1`** fix — heid-code-review fixups (stale docstring + None-cursor test). **NO version bump** (docs + test only, SemVer skip rule). - **`11ae2f0`** fix — heid-bug-hunt fixups (admin-stream + bifrost hardening). ## What migrated (WEB-only) `get_session_bifrost` → `client.admin.sessions.bifrost(id)` (open-world dict verbatim, any `ApiError` → `SessionApiFailed` default — no new Error-map row). `stream_admin_events` → `client.admin.stream_events(last_event_id=…)`. **Both consumed ONLY by `web/server.py`** (`_session_bifrost_endpoint` + `_admin_events_endpoint`) — the coverage-map's `tui.py` rows were STALE (the grep found zero TUI callers), so the TUI-deprecation wrinkle was moot. Corrected the coverage-map rows to `web/server.py`. Deleted the hand-rolled `sessions.get_session_bifrost` + `sse_client.stream_admin_events` (+ ruff-cleaned the orphaned `httpx`/`httpx_sse`/`json`/`AsyncIterator` imports). Retired `test_sse_client.py` WHOLESALE (its last test was the admin stream; slice-2 had already removed the turn-stream tests) + `test_sessions.py`'s `TestGetSessionBifrost`. `sessions.py` is now down to `endpoint_for_plane` + exception classes; `sse_client.py` to `SseId` + `AdminEvent` + exception classes. ## Decisions made at TDD (contract § slice-6 notes) - **Admin auth moves from a per-call `Authorization` header to the client's `admin_auth`.** The SDK's `admin.*` routes use `admin_auth` (set via `build_client(admin_key=…)`), NOT a header. So `_wt_client(client, *, admin_key=None, …)` was extended, and the two web endpoints pass `admin_key`. The web already guards `if not admin_key: 400`, so the SDK's pre-HTTP `ConfigurationError` (W-5) is unreachable from the surface. - **`AdminEvent` re-wrap (chosen over yield-through).** The SDK's `AdminEvent` diverges from ratatoskr's: `admin_id: int|float` (`nan` for id-less) vs `id: int`; None-able `type`/`data` vs a dotted-str / `{}`-default dict. The web filter + SSE formatter read `ev.id`/`ev.type`/`ev.data`. The adapter re-wraps at the boundary — `id = admin_id if int else 0`, `type = ev.type if isinstance str else ""`, `data = dict if Mapping else {}` — degrading the open-world None/nan ONCE and keeping the web endpoint + filter + the `AdminEvent` domain type UNCHANGED (preserves the web surface). **Rejected:** yield SDK events through + rewire the web filter (heavier churn; scatters the None/nan hardening). This is implementation-level (reversible, no module-boundary change), decided autonomously + flagged to the operator with the rejected alternative. ## The ApiError-not-ConnectFailed gotcha (TDD → integration test) First mapped the admin-stream non-200 as `ConnectFailed`. The web INTEGRATION test (respx mocking a real 500) exposed that the SDK admin stream raises `ApiError("admin_stream_failed", status=…)` on a non-200 open — the unit fake couldn't model it. Fixed to `ApiError → SseConnectFailed`. Lesson: a web integration test catches what the adapter unit fake structurally can't. ## LIVE SMOKE (:8081, readonly-admin key — INV-CUT-5 / DEC-4 cleared) Drove the WEB surface (via `httpx.ASGITransport` over `create_app(client_factory, admin_key=…)`) against real :8081. The bifrost endpoint returned an admin-authed clean 404 `session_not_bifrost_bound` envelope (auth + route + mapping proven — a 404 not a 401/403 = the admin key authenticated). :8081's admin stream is idle (no heartbeats in 15s raw), so I generated activity: streamed admin events while concurrently creating a session (`POST /sessions {agent_id: mimir, end_user_id: …}` → 201) and observed the real `session.created` admin event (id=32/34) re-wrapped cleanly (id int, type str, data dict); threwaway session cleaned up (DELETE → 204). NOTE: a raw `POST /sessions` needs `end_user_id` (422 without it). ## heid-code-review (thread 01KXXYRNNY…) — 3/3 no drift Gróa + Regin zero; Hulda "no slice-6 implementation drift." Only minor doc/test looseness: a stale `_session_bifrost_endpoint` docstring ("overrides the Authorization header" → corrected to "rides on the client's admin_auth"), and an admin-stream None-cursor test-gap (added). Hulda's "web endpoints under-tested" was **source-VOIDED by Heid** — those tests live in `test_web_server.py`, which wasn't in the consult embed (excerpt-elides-tests trap). ## heid-bug-hunt (thread 01KXXZBW74…) — 4 real findings, all fixed The cold spec-free hunt earned its keep: the CR found the admin surface CONFORMANT, but judging against the general `ConnectFailed` floor + the degrade-never-crash promise it surfaced 4 hardening gaps: - **[bug, 3/3] `stream_admin_events` never mapped `ConnectFailed`** — the SDK admin-stream open DOES raise it (connect-time / auth-resolution; confirmed in SDK source), `stream_turn` + the bifrost GET both catch it, and this endpoint's OWN `:633` comment claimed it did. An unmapped ConnectFailed escaped the web gen's `except (Sse*)` → aborted SSE with no `stream_error`. Now mapped → `SseConnectFailed`. - **[bug, 2/3] non-str `type` crashed the web filter** — `ev.type or ""` (falsy-only) let a truthy non-str `type` (123) reach `.startswith` → AttributeError. Now `isinstance`-guarded (matches admin_id/data). Same container-type class as the slice-5 bug-hunt. - **[robustness] `dict(bstate)` 500 on a non-mapping bifrost body** — I introduced it in slice-6 (`JSONResponse(bstate)` → `dict(bstate)`). Now degrades to `{}`. - **[robustness] transport leak** — `_wt_client` ran before the try/finally in the SSE gen; a construction failure would leak the httpx transport. Moved inside the try. Voided (Heid): Regin's `dict(ev.data)` TypeError — the `isinstance(_, Mapping)` guard already handles it. ## Next: slice-7 (teardown, the LAST slice) Drop the `httpx-sse` dep from `pyproject.toml` (SDK owns SSE parsing — verify nothing else imports it), retire wire contracts #2/#15, relocate the `AdminEvent`/exception classes if `sse_client.py`/`sessions.py` end up ~empty, final coverage-map re-anchor, and the **MINOR bump per DEC-6 (needs operator approval)** publishing the cutover milestone. See also [[2026-07-19-worldtree-sdk-cutover-slice-5-complete]] (the container-type degrade-not-crash lesson) and [[2026-07-19-worldtree-sdk-cutover-slice-4-complete]].