Slice-5 (characters + me/capabilities/models) done through the full House Code Discipline, tags v0.21.16–.18, suite 488 green, live-smoke-proven on :8081/b128, both heid gates cleared. Current state / in-flight advanced to slice-6 (admin) next; Recent-decisions index entry + detail file added; substrate at v0.21.18. persistent-memory.md stays ~333 lines (over the ~300 soft cap): the length is dominated by the non-archivable Current state / in-flight block plus <30-day July entries (guarded), so archival can't reach the 250 target — left as-is per the stop-where-the-guards-stop rule.
7.0 KiB
worldtree-sdk cutover — SLICE-5 COMPLETE (characters + me/capabilities/models)
[2026-07-19] Slice 5 of 7 of the worldtree-sdk consumer cutover (issue #20;
contract docs/contracts/worldtree_sdk_cutover.contract.md). Full House Code
Discipline end-to-end: contract slice-notes → TDD → LIVE smoke → heid-code-review →
fixup → heid-bug-hunt → fixup. Both heid panels cleared. Suite 488 green.
Commits (tags v0.21.16–.18, on main, not-yet-pushed)
deab762feat — the six routes ontoratatoskr.wt, hand-rolled deleted.d86d6dffix — heid-code-review fixups (CLI presenter degrade-not-crash).4e20030fix — heid-bug-hunt fixups (CLI open-world container-type hardening).
What migrated
get_me / get_capabilities / list_character_models / create_character /
get_character_state / delete_character moved off the hand-rolled httpx wrappers
onto client.me.get() / client.capabilities.get() /
client.models.available_for_characters() / client.characters.create|state|delete.
All six are open-world reads/acks returned verbatim; none carries a discriminated
SDK error, so each maps any ApiError → the SessionApiFailed default —
NO new § Error map rows (exact parity with the retired path, which never
discriminated a status/code on these routes).
CLI-only rewire — --whoami (me + capabilities) and --characters
(models → create → state → delete) build a wt.build_client over the injected
_probe_client transport and catch wt.SessionApiFailed + ConnectFailed. No
web-server caller for any of these six routes.
Deleted the six hand-rolled sessions.py wrappers (net −5 mypy no-any-return
errors); endpoint_for_plane + get_session_bifrost (slice-6) + the exception
classes stay. Retired the matching test_sessions.py classes (TestGetMe,
TestGetCapabilities, TestTransientCharacters); kept TestEndpointForPlane +
TestGetSessionBifrost.
Decisions made at TDD (contract § slice-5 notes)
create_characteromitsstatewhen None — SDK-idiomatic inline literal (per branch, to type-check against the SDK'sCreateCharacterInputTypedDict without importing its private_types); server-equivalent to the retired explicitstate: null(Worldtree's field defaults None either way). The only wire-shape change; the sole call-site never sets state.delete_characterreturns the SDK's open ACK verbatim (-> Mapping | None, not normalized to the hand-rolledNone; 204 → None). The CLI ignores it.
LIVE SMOKE (:8081/b128, WORLDTREE_API_KEY, INV-CUT-5 / DEC-4 cleared)
Drove both probes end-to-end through the CLI (python -c "from ratatoskr.cli import main; main([...])" — the ratatoskr console script isn't on PATH here; python -m ratatoskr.cli imports without calling main, no __main__ guard). --whoami
rendered real identity (user_id ratatoskr, tier user, scopes incl. character.*,
key_id c990f0be) + ephemeral_template echo. --characters drove the full
lifecycle: char-rp catalog → created char_… → state pad=[0.234,−0.136,0.065]
read-back → deleted. Observed real success, not merely non-crash.
heid-code-review (thread 01KXXRN50K…) — 2 fixups
Panel: Gróa + Regin zero (adapter/route-map/error-map faithful); Hulda flagged 2 source-confirmed CLI open-world-presenter crash holes + a live-smoke test-gap. Both holes fixed (the null/element layer):
_format_whoamiscopes:', '.join(me.get('scopes', []))crashes on a present-nullscopes(.get(k, [])returns None, not the default) or a non-string element. The contract names_format_whoamithe degrade-not-crash exemplar — the cited exemplar had an un-hardened line (allowed_roleswas hardened in slice-4,scopeswas not)._characters_probemodelitems: the slice-5or []guarded the list-level null but not each entry ([None]/["x"]/[{"name":123}]). Test-gap (live-smoke not in the file set) → accept (it WAS run + recorded).
heid-bug-hunt (thread 01KXXS9S45…) — 3 fixups + 1 accept + 1 dismiss
Cold spec-free diff-scoped panel over the post-code-review-fixup diff. Adapter +
route-map + ConnectFailed-at-call-sites sound against the declared invariants
(all arms agree). 4 real findings, all CLI open-world paths — the container-type
layer BELOW the null/element holes the code-review had just fixed (the code
comments cite the CR; the two consults were firewalled from each other and converged
independently):
- [bug, fixed] non-iterable
scopes/allowed_roles—{"scopes": 123}→123 or [] == 123→for s in 123TypeError. New_display_seq(value)helper degrades any non-list (scalar / bare string / null / absent) to empty; applied to both. - [bug, fixed] non-iterable
items—{"items": 123}, same class. Guardmodelsis a Mapping ANDitemsis a list before iterating. - [robustness, fixed] non-mapping top-level
created/state— a non-mapping SDK passthrough (created=[...]) →.getAttributeError.isinstance(_, Mapping)guard → clean exit-20 abort /pad=None. - [robustness, ACCEPTED] the probe leaks its transient character on a mid-lifecycle
failure — create → state → delete linear, no
finally. PRE-EXISTING (retired probe had the identical structure — "cutover did not worsen it"), TTL-bounded, one-shot diagnostic; atry/finallywould swallow a happy-path delete-failure (delete is both teardown and a tested step). Gróa + Heid concur accept is defensible. Documented in contract § slice-5 notes. - [DISMISSED]
sessions.pydroppedget_me/etc. (Hulda, caller-contract) — the intended DEC-3 no-backwards-compat migration (all in-repo callers rewired same-diff); Heid labels it intended-surface-change, not a defect.
Regin BH calibration note: Regin (glm-5.2 non-reasoning) found only the leak, missed the 3 crash paths, and self-graded "0 confirmed" on a control-flow-guaranteed finding — consistent with the crystallized Regin-unreliable-on-bug-hunts pattern (its code-review work this session was reliable). Gróa was the BH standout.
The cumulative lesson (foot-gun for slices 6-7)
Open-world SDK reads need degrade-not-crash guarding at THREE levels, and the two heid lenses caught different ones: the CODE-REVIEW (conformance) caught the null/element layer; the cold BUG-HUNT (robustness) caught the container-type layer below it. Run BOTH — they are complementary, not redundant. The three levels:
- container-type — the field value may be a truthy non-iterable scalar (
123) or a bare string;or []only catches null/absent. Guardisinstance(_, (list, tuple)). - element-type — each entry may be a non-mapping; guard
isinstance(m, dict). - top-level-mapping — the whole read may be a non-mapping passthrough; guard
isinstance(_, Mapping)before any.get.
See also 2026-07-19-worldtree-sdk-cutover-slice-4-complete (the slice-4 arc + the
-m double-module class-identity foot-gun).