Slice-6 (admin: bifrost inspection + admin-events SSE) done through the full House Code Discipline, v0.21.19–.20, suite 494 green, live-proven (real session.created event re-wrapped end-to-end on :8081), both heid gates cleared. Current state advanced to slice-7 (teardown, LAST) next; Recent-decisions index entry + detail file added; substrate at v0.21.20. Consumer client layer now fully cut over (6/7 slices). persistent-memory.md stays ~345 lines (over the ~300 soft cap): dominated by the non-archivable Current state block + <30-day July entries (guarded), so archival can't reach the 250 target — left as-is per the stop-where-the-guards-stop rule.
106 lines
6.7 KiB
Markdown
106 lines
6.7 KiB
Markdown
# 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]].
|