# Ratatoskr v1 coverage map _The v1 convergence-target ledger. Ratatoskr has **no self-defined feature roadmap**: v1 = consume all of Worldtree's I/O points, reached when Worldtree hits 1.0 (operator, 2026-06-19; auto-memory `project-ratatoskr-v1-derived-from-worldtree-io-coverage`). This file is that coverage map — every Worldtree v1-frozen I/O point × ratatoskr's coverage status, so "are we at v1?" is a ledger lookup, not a judgement call._ **First authored:** 2026-06-30 (the v1 coverage-audit kickoff). --- ## Frozen target Worldtree is at **v1.0.0b2** — approaching 1.0, with its wire surfaces now **FROZEN** (Worldtree `docs/v1-schema-freeze-manifest.md`, #326). The audit anchors against the frozen machine-readable artifacts, NOT the prose markdown: | Worldtree v1 surface | Frozen anchor | Ratatoskr role | |---|---|---| | Conversation REST API | OpenAPI `info.version` **2.2.0** (`Worldtree/docs/conversation-api-openapi.json`, sha `dbdf4e24…`) — **40 path×method groups** | **client** (debug TUI / web) | | Conversation SSE events | `conversation-api-sse-events.schema.json` (sha `9deeebf4…`) — **11 discriminated event types** | **client** | | Bifrost wire (consumer protocol) | wire **v0.6** STABLE/FROZEN (`bifrost==1.0.0`) — memory + affect planes | **provider** (Worldtree dispatches into us) | > **Pin drift (finding P-1) — REMEDIATED 2026-06-30.** Ratatoskr formerly > vendored only the **prose markdown** (`docs/conversation-api-spec.md`), which > is byte-identical to live Worldtree's but frozen at v0.35.16-era content (last > WT edit 2026-05-31) and does **not** document the b2 surface (7 endpoints > below, the 409/503 on messages-POST #331, the unified error envelope #328, the > SSE schema). Remediated: the **OpenAPI 2.2.0 + SSE-schema JSON are now vendored > and pinned** (`.corviduo-canonicals.toml` → `canonical_drift.py` gate); the > prose markdown is the `tolerate_drift` reference. Spec pin advanced to > 5810a26 (v1.0.0b2). See § Pin remediation. **7 endpoints new in b2 OpenAPI, absent from our vendored markdown:** `/admin/keys/bulk`, `/admin/persona/archive`, `/admin/persona/erase`, `/admin/usage`, `/embed`, `/judgments`, `/me/usage`. --- ## Scorecard **Scope mandate: A (ledger-mandate), locked by operator 2026-06-30.** v1 "done" = every frozen I/O point is **classified** (covered or excluded-with-rationale), zero unaccounted. NOT "feature-complete client." All scope-pending rows are now resolved (§ Surface 1, scope-resolution table). | Surface | Points | ✅ covered-live | ⬜ gap (in-scope) | 🚫 excluded-by-design | |---|---|---|---|---| | REST (OpenAPI 2.2.0, path groups) | 40 | 17 | 0 | 23 | | SSE events | 11 | 11 | 0 | 0 | | Bifrost provider planes | 8 verbs | 8 | 0 | (10 gated verbs deferred) | **Legend.** ✅ consumed in code AND live-proven against real Worldtree · ⬜ a debug-observability I/O point we should cover but don't yet (the convergence frontier) · 🚫 deliberate non-goal per the design-brief negative clauses + the A mandate. Counts are at the **path-group** level; mixed-method groups are footnoted (e.g. `/sessions` POST is ✅ but its `GET` picker is an unwired sub-gap). --- ## Surface 1 — Conversation REST API (OpenAPI 2.2.0) ### Covered — client path (ratatoskr's core identity) | Endpoint | Status | Where consumed | Note | |---|---|---|---| | `POST /sessions` | ✅ | `sessions.py:307` → `cli.py:482`,`tui.py:1508`,`web/server.py:155` | + `end_user_id`, `bifrost` binding; 404→AgentNotFound, 502→BifrostHandshakeFailed | | `POST /sessions/{id}/messages` (turn stream, SSE) | ✅ | `sse_client.py:484` `stream_turn` → cli/tui/web | the primary surface; 409→AgentNotAvailable, 503→TurnLaunchUnavailable (b2 #331) | | `POST /sessions/{id}/turns/{turn_id}/cancel` | ✅ | `sse_client.py:581` → cli/tui/web | two-stage Ctrl-C; 404/409 mapped | | `GET /agents` | ✅ | `sessions.py:341` → `tui.py:1472`,`web/server.py:100` | Tier-1 roster; merged with local index | | `GET /agents/{id}/persona_state` | ✅ | `sessions.py:384` → `tui.py:1132`,`web/server.py:386` | persona hydrate; 404/403 mapped | | `POST /agents/define` | ✅ | `tier3.py:175` → `_run_define` | Tier-3 create | | `PATCH /agents/{id}` | ✅ | `tier3.py:219` → `_run_patch` | Tier-3 mutate (system_prompt/model) | | `DELETE /agents/{id}` | ✅ | `tier3.py:242` → `_run_delete` | Tier-3 hard-delete | | `GET /me` | ✅ | `sessions.py:411` `get_me` → `cli.py` `--whoami` | identity/whoami probe; 401→SessionApiFailed | | `GET /capabilities` | ✅ | `sessions.py:428` `get_capabilities` → `cli.py` `--whoami` | Echo ephemeral-template discovery | | `GET /sessions/{id}/tools` | ✅ | `sessions.py:411` `get_session_tools` → `tui.py` `_hydrate_session_tools` | owner-scoped tool inventory in the TUI Tools pane (#183) | | `GET /admin/sessions/{id}/bifrost` | ✅ | `sessions.py:428` `get_session_bifrost` → `tui.py` `_hydrate_bifrost_state` | admin-scoped BifrostState pane (#176); admin key (`RATATOSKR_ADMIN_API_KEY`); live-auth-proven | | `GET /admin/events` (SSE) | ✅ | `sse_client.py` `stream_admin_events` → `tui.py` `_stream_admin_events` | admin lifecycle SSE stream (#11), session-filtered AdminEvents pane; admin key; live-auth-proven | | `GET /models/available-for-characters` | ✅ | `sessions.py` `list_character_models` → `cli.py` `--characters` | character-capable model profiles (#161) | | `POST /characters` | ✅ | `sessions.py` `create_character` → `cli.py` `--characters` | create transient character (#161) | | `GET /characters/{id}/state` | ✅ | `sessions.py` `get_character_state` → `cli.py` `--characters` | live character PAD/emotions (#161) | | `DELETE /characters/{id}` | ✅ | `sessions.py` `delete_character` → `cli.py` `--characters` | remove transient character (#161) | | `POST /sessions/{id}/persona_state` | ✅ | `sessions.py` `set_persona_state` → `cli.py` `--set-persona-pad` | persona-state write / affect injection (freeform body — unpinned in the frozen surface) | **Sub-gaps inside ✅ path groups** (the method we use is live; a sibling method on the same path is an unwired frontier item — see frontier Tier 1): - `GET /sessions` — `sessions.py:198` `list_sessions` exists, **no caller**: the startup session-picker (design-brief §4 v1) was never wired. - `POST /sessions/{id}/messages` + `Last-Event-ID` — `sse_client.py:524` `reconnect_turn` exists, **no caller**: the reference SSE-resume impl (design-brief §8d) was never wired. - `GET /agents/{id}` — consumer-agent lookup (`GET /agents/:` with the owner key) is **manual-curl-only**, not in code. ### In-scope gaps — CONVERGED (zero remaining, 2026-07-01) **Every in-scope REST I/O point is now covered.** The frontier that opened this audit (the design-brief §5 observability panes + the presenter-wiring sub-gaps + the Tier-2 tail) is fully closed: - Session picker + SSE-resume — wired (`v0.18.5`–`.7`). - Persona · Tools · BifrostState · AdminEvents panes — all built + live (`v0.18.x`–`v0.19.0`). - Transient-characters CRUD + persona-state write — consumed via `--characters` / `--set-persona-pad` (`v0.19.1`). The only remaining not-consumed in-scope method is `GET /agents/{id}` (consumer- agent lookup, manual-curl-only) — a sub-method on an already-✅ path group, not a path-group gap. Everything else is covered or excluded-by-design below. ### Excluded by design — the design-brief negative clauses | Endpoint(s) | Status | Rationale (design-brief / memory) | |---|---|---| | `PATCH /sessions/{id}` · `DELETE /sessions/{id}` | 🚫 | §4: rename/delete happen outside the tool (`sessions_cli.py`) | | `GET /sessions/{id}/messages` (history) | 🚫 | §6: single-session live transcript, no history fetch | | `GET /sessions/{id}` | 🚫 | session detail — identity is footer-visible, no detail view | | `GET /sessions/{id}/tool-events` | 🚫 | §5: tool calls observed **inline from SSE** `tool_start`/`tool_result`; persisted-events endpoint is opt-in only | | `GET /admin/sessions/{id}/tools` | 🚫 | **covered-by-alternative** — the owner-scoped `GET /sessions/{id}/tools` (✅) serves the Tools inventory; this admin variant is only for cross-user operator debug, out of the single-session focus (§6) | | `GET/POST /admin/keys` · `DELETE/POST /admin/keys/{id}` · `POST /admin/keys/{id}/rotate` · `DELETE/POST /admin/keys/bulk` · `POST /admin/keys/bulk/rotate` | 🚫 | §6: **NOT a Worldtree-admin tool** (key mgmt) | | `POST /admin/sessions/{id}/retire` | 🚫 | admin session mutation | | `POST /admin/persona/{archive,erase}` | 🚫 | admin persona GDPR ops (new in b2) | | `POST /admin/users/{id}/tier` | 🚫 | admin user mgmt | | `GET /me/usage` · `GET /admin/usage` | 🚫 | usage metering — not turn-flow observability (new in b2) | | `GET /healthz` · `GET /readyz` | 🚫 | liveness probes — low debug value (could become a connect preflight; park) | | `GET /search` | 🚫 | §5: consumer-product feature, not turn-flow (was "defer to v2") | | `GET/POST /uploads` · `DELETE/GET /uploads/{id}` | 🚫 | §6: no uploads — consumer-product feature | | `GET /pending` · `GET /sessions/{id}/pending` | 🚫 | §5: poll-only, no turn-flow signal (was "optional, skip") | | `POST /embed` | 🚫 | embedding utility — no turn flows through it | | `POST /judgments` | 🚫 | LLM-as-judge A/B eval (`response_a`/`response_b`/`rubric`) — standalone eval utility, not turn-flow | ### Scope-resolution record (the 11 ❓ rows, resolved under A) The 2026-05-20 design-brief deferred several surfaces; the 2026-06-19 reframe ("v1 = full I/O coverage") put them back in tension. **Resolved 2026-06-30 under mandate A** (debug-observability identity intact; classify, don't build-all): | Endpoint(s) | Resolution | |---|---| | `GET /search` · `uploads` (×2) · `pending` (×2) · `POST /embed` · `POST /judgments` | 🚫 **excluded** — consumer-product / eval / poll utilities, not turn-flow observability | | `characters` (×4) · `POST /sessions/{id}/persona_state` | ⬜ **in-scope** (frontier Tier 2) — session-routing + affect-injection debug paths | Nothing remains ❓. The ⬜-vs-🚫 line follows the debug-observability test: *does a turn flow through it / is it a layer worth watching live?* --- ## Surface 2 — SSE events (11/11 ✅) Every frozen SSE event type is parsed in `sse_client.py:_envelope_for_type` (342-411) and rendered by all three presenters (cli/tui/web). **Full coverage.** `text` · `worker_phase` · `thinking` · `text_boundary` · `tool_start` · `tool_result` · `done` · `error` · `cancelled` · `awaiting_llm_first_token` · `affect_update` > Caveat (not a gap): `affect_update` is wire-verified to emit **zero** events > for consumer (Tier-3) agents — the persona-strip SSE path never populates for > them (memory 2026-06-18). The handler is correct; the upstream emitter is > silent. PAD for consumer agents is surfaced via our own provider read route > (`GET /affect/state/{id}`, #18 D2), not this event. --- ## Surface 3 — Bifrost provider planes (8/8 ✅, live-proven) Ratatoskr **implements** the provider side; Worldtree dispatches into it. Live-proven end-to-end through real WT turns (#17/#18 smokes; combined `:8392` WT-driven smoke 2026-06-20). **Memory plane** — covers the entire `bifrost.memory.MemoryDataStore` protocol (required: `describe_store`, `get`, `get_many`, `search`, `upsert_many`) **plus** `delete_many`: | Verb | Where | | |---|---|---| | `describe_store` | `memory_store.py:140` | advertises caps (sync) | | `search` | `memory_store.py:224` | vector recall; scope_all AND / scope_any OR | | `get` / `get_many` | `memory_store.py:293` / `:305` | point reads | | `upsert_many` | `memory_store.py:150` | idempotent batch write; optimistic lock | | `delete_many` | `memory_store.py:314` | transactional delete | **Affect plane** — covers `bifrost…InMemoryAffectStore` (`emit`, `fetch`): | Verb | Where | | |---|---|---| | `emit` | `affect_store.py:47` | conduit-opaque snapshot upsert (LWW) | | `fetch` | `affect_store.py:116` | `{found, snapshot}`; mandatory since bifrost 0.10.0 strong-or-absent gate | Plus the non-wire PAD read route `GET /affect/state/{agent_id}` (`affect_store.py:189`) and the combined `:8392` endpoint advertising both caps by store-presence (`combined.py:46`). **Deferred-gated (advertised-unsupported, correctly out-of-scope for the basic plane):** `scan`, `get_edges_for`, `upsert_edges`, `mark_invalid`, `mark_superseded`, `patch_many`, `commit_checkpoint`, `lease_job`, `read_checkpoint`, `health`. These live only in the bifrost reference *extended* store, not the `MemoryDataStore` protocol; deferred per the #294 re-scope (memory 2026-06-15). Re-evaluate only if Worldtree's Tier-3 path starts exercising them. --- ## Convergence frontier (the v1 to-do) — CLOSED 2026-07-01 **Every in-scope I/O point is covered.** The frontier is empty: REST 17/40 ✅ with **zero in-scope gaps** (the other 23 REST path-groups are excluded-by-design), SSE 11/11, Bifrost provider planes 8/8. v1 convergence (per scope A: "every frozen I/O point classified, zero unaccounted") is **met** — ratatoskr cuts v1 when Worldtree tags 1.0. The arc, for the record: **Tier 1 — debug-observability core:** 1. ✅ **DONE** — Session picker (`v0.18.7`) + SSE-resume (`v0.18.5`/`.6`). 2. ✅ **DONE** — `GET /capabilities` + `GET /me` (`v0.18.8`, `--whoami`). 3. ✅ **DONE** — BifrostState pane (`v0.18.10`, `GET /admin/sessions/{id}/bifrost`, admin-key; live-auth-proven). The Tools half was already covered by the owner-scoped `GET /sessions/{id}/tools` (item 5). 4. ✅ **DONE** — AdminEvents pane (`v0.18.11`, `GET /admin/events` SSE, session-filtered; admin-key; live-auth-proven). #11's blocker was already satisfied (admin key carries `admin.events.read`). **Tier 1 complete** — the admin/debug-observability core (Persona · Tools · BifrostState · AdminEvents) is fully built. **Tier 2 — rounds out coverage (all that remains):** 5. ✅ **DONE** — `GET /sessions/{id}/tools` (`v0.18.9`, owner-scoped tool inventory in the TUI Tools pane). 6. ✅ **DONE** — Transient-characters CRUD (4 endpoints) + `POST /sessions/{id}/persona_state` (`v0.19.1`, `--characters` + `--set-persona-pad` one-shot probes). The last in-scope client I/O points. --- ## Pin remediation (finding P-1) — DONE 2026-06-30 Re-pinned to the frozen machine-readable artifacts (the chosen option): `conversation-api-openapi.json` (2.2.0) + `conversation-api-sse-events.schema.json` are vendored under `docs/` and pinned in `.corviduo-canonicals.toml` (`worldtree-conversation-api-openapi-v2`, `-sse-events-v1`), drift-gated by `canonical_drift.py`. The prose markdown stays as a `tolerate_drift` reference (`-spec-v1`). `pyproject.toml` spec pin advanced f1b59f8 → 5810a26 (v1.0.0b2); `docs/SPEC-PIN.md` records the bump. This map now audits a frozen, diffable target — re-running the audit is a `canonical_drift.py` check away. --- ## Decisions 1. **Scope mandate — RESOLVED 2026-06-30: A (ledger-mandate).** v1 "done" = every frozen I/O point classified, zero unaccounted; NOT feature-complete client. The ⬜-vs-🚫 line for the 11 formerly-pending rows is locked above (§ Scope-resolution record). 2. **Pin remediation (P-1) — RESOLVED 2026-06-30 (re-pin to machine-readable).** Vendored `conversation-api-openapi.json` (2.2.0) + `conversation-api-sse-events.schema.json` as the authoritative drift gates (pinned in `.corviduo-canonicals.toml`, `canonical_drift.py` green); prose markdown kept as `tolerate_drift` reference. Spec pin advanced f1b59f8 (v0.35.16) → 5810a26 (v1.0.0b2). This map now audits against a frozen, diffable target.