# 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]].
