Files
ratatoskr/persistent-memory.d/2026-07-19-worldtree-sdk-cutover-slice-6-complete.md
vh 8274ed2d89 memory: /snapshot — worldtree-sdk cutover slice-6 complete (de9a5ba→11ae2f0)
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.
2026-07-19 13:16:58 -07:00

106 lines
6.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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]].