feat(#20): characters + me/capabilities/models onto the wt adapter (slice-5)

Slice-5 of the worldtree-sdk cutover: migrate the remaining consumer READS +
transient-character CRUD off the hand-rolled httpx wrappers onto the
`ratatoskr.wt` adapter over the SDK, and delete the retired path.

Adapter (`wt.py`): add `get_me` / `get_capabilities` / `list_character_models`
/ `create_character` / `get_character_state` / `delete_character` over
`client.me` / `client.capabilities` / `client.models` / `client.characters.*`.
All six are open-world reads/acks returned verbatim; none carries a
discriminated SDK error, so each maps any `ApiError` → the `SessionApiFailed`
default (INV-CUT-2) — exact parity with the retired path. No new Error-map rows.

Decisions (contract § slice-5 notes): `create_character` omits `state` when None
(SDK-idiomatic inline literal, server-equivalent to the retired explicit null);
`delete_character` returns the SDK's open ACK verbatim (`-> Mapping|None`, not
normalized to None).

CLI rewire (`cli.py`): `--whoami` (me + capabilities) and `--characters`
(models → create → state → delete) build a `wt.build_client` over the injected
probe transport and catch `wt.SessionApiFailed` + `ConnectFailed`. Open-world
degrade-not-crash carried (cumulative cutover foot-gun): `_characters_probe`
reads `items` null-safe and extracts `character_id` defensively (clean abort,
no hard-index KeyError); `_format_whoami` widened to `Mapping`.

Deleted the six hand-rolled `sessions.py` wrappers (net -5 mypy no-any-return);
`endpoint_for_plane` + `get_session_bifrost` (slice-6) + the exception classes
stay. Retired the corresponding `test_sessions.py` classes; added the slice-5
adapter tests + a CLI malformed-create-abort test.

LIVE SMOKE (:8081, b128) — INV-CUT-5 / DEC-4 cleared: `--whoami` rendered real
identity + capabilities; `--characters` drove the full lifecycle end-to-end
(char-rp catalog → created char_8c00006e… → PAD read-back → deleted).

Suite 483 green; ruff clean; mypy at the 2 pre-existing baseline errors.
Patch bump 0.21.15 → 0.21.16 (the cutover MINOR is deferred to slice-7, DEC-6).
This commit is contained in:
vh
2026-07-19 11:00:41 -07:00
parent 4f74645a00
commit deab7627eb
10 changed files with 436 additions and 286 deletions
@@ -247,6 +247,37 @@ re-anchor its coverage-map rows.
per the heid-code-review slice-4 precision flag (the § Error map 404 rows assume a
well-formed Tier-3 id reaches the route).
### Slice-5 notes (Characters + me/capabilities/models, decided at TDD)
- **No new § Error map rows.** All six routes (`me.get`, `capabilities.get`,
`models.available_for_characters`, `characters.create` / `.state` / `.delete`) are
open-world reads/acks (B-OPEN-2) whose SDK ops carry NO discriminated error (no
`map_error`), so every `ApiError` maps to the default `SessionApiFailed` — exact
parity with the retiring hand-rolled path, which likewise raised only its generic
`SessionApiFailed` on any non-2xx (never discriminating a status/code on these
routes). The route-map table above already lists all six.
- **`create_character` body — omit `state` when None.** The adapter sends
`{"character": …}` plus `"state"` only when the caller supplies a non-None state
(the SDK forwards the body dict as-is via httpx `json=`). This drops the hand-rolled
path's redundant explicit `"state": null` — server-equivalent (Worldtree's
`CreateCharacterRequest.state` defaults None whether omitted or explicit-null),
SDK-idiomatic (matches the SDK's `CreateCharacterInput` `NotRequired` shape), and
invisible at the sole call-site (`--characters` never passes a state). Adopt-
canonical over byte-for-byte wire parity.
- **`delete_character` returns the SDK's open ack verbatim (`-> Mapping | None`).**
The SDK route returns an open-world ack body (not 204 — `CharacterDeleteResult`), so
the adapter passes it through rather than normalizing to the hand-rolled `None`
(parity posture: no None-normalization of an open-world read). On a 204 no-content
the SDK yields `None`, so the return type is `Mapping | None`; the sole call-site
(`--characters`) ignores the value, so the change is unobservable.
- **Open-world presenter degrade-not-crash (cumulative foot-gun).** `_format_whoami`
is already hardened (slice-4 heid bug-hunt). The rewired `_characters_probe` extracts
the created id defensively (`created.get("character_id")` + type-guard → clean abort,
never a hard-index KeyError) since the create ACK is now an open-world SDK read.
- **CLI-only rewire.** `me` / `capabilities` / `characters` / `models` have NO
web-server caller — only the `--whoami` and `--characters` CLI one-shot probes. The
web surface is untouched this slice.
## Out of scope
- Bifrost PROVIDER planes (memory/affect) — hand-rolled, ADR-0009, untouched.