Grant applied (rule-based Heimdall allow, worldtree-dev). Smoke: create mimir session -> seed -> 201 (seq=0, phase=seeded, turn_id=1798) -> GET /messages reads back a plain role=assistant turn (model-invisible provenance confirmed). Coverage-map #347 row + persistent-memory upgraded pending -> live-proven.
17 KiB
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.3.0 (Worldtree/docs/conversation-api-openapi.json, sha 36148179…) — 41 path×method groups (2.3.0 added POST /sessions/{id}/history, #347) |
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.pygate); the prose markdown is thetolerate_driftreference. 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.3.0, path groups) | 41 | 19 | 0 | 22 |
| 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.3.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}/history (authored-history-write, #347) |
✅ | sessions.py:583 write_authored_history → cli.py:758 --seed-first-message |
v1: author=assistant, effects=none, per-session idempotency; 404→AuthoredHistoryUnavailable (hide-existence: feature-absent, never probe); 409/422 mapped. LIVE-PROVEN 2026-07-06 on personal :8081 (grant applied via a rule-based Heimdall allow, worldtree-dev): create mimir session → seed → 201 (seq=0, phase=seeded, turn_id=1798) → GET /messages reads it back as a plain role=assistant turn (model-invisible provenance confirmed). Hide-404 for ungranted is unit+probe covered |
GET /sessions/{id}/messages (history) |
✅ | sessions.py:635 get_session_messages → cli.py:758 --seed-first-message read-back |
un-deferred as the #347 seed read-back — confirms model-invisible provenance (a seed reads back as a normal role=assistant turn) |
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:198list_sessionsexists, no caller: the startup session-picker (design-brief §4 v1) was never wired.POST /sessions/{id}/messages+Last-Event-ID—sse_client.py:524reconnect_turnexists, no caller: the reference SSE-resume impl (design-brief §8d) was never wired.GET /agents/{id}— consumer-agent lookup (GET /agents/<owner>:<name>with the owner key) is manual-curl-only, not in code.
In-scope gaps — CONVERGED (re-closed 2026-07-06 after the #347 re-open)
Every in-scope REST I/O point is covered. The audit first converged
2026-07-01; Worldtree's #347 (authored-history-write, OpenAPI 2.3.0) then added
one new in-scope path-group, re-opening the audit with a single gap — now closed
(v0.19.6). The original frontier (design-brief §5 observability panes +
presenter-wiring sub-gaps + Tier-2 tail) remains 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). - Authored-history-write (#347) + messages read-back —
write_authored_history+get_session_messagesvia--seed-first-message(v0.19.6; live-proof pending thesession.history.writegrant).
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} |
🚫 | 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_updateis 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, re-closed 2026-07-06 (#347)
Every in-scope I/O point is covered. The frontier is empty: REST 19/41 ✅ with zero in-scope gaps (the other 22 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:
- ✅ DONE — Session picker (
v0.18.7) + SSE-resume (v0.18.5/.6). - ✅ DONE —
GET /capabilities+GET /me(v0.18.8,--whoami). - ✅ 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-scopedGET /sessions/{id}/tools(item 5). - ✅ DONE — AdminEvents pane (
v0.18.11,GET /admin/eventsSSE, session-filtered; admin-key; live-auth-proven). #11's blocker was already satisfied (admin key carriesadmin.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):
- ✅ DONE —
GET /sessions/{id}/tools(v0.18.9, owner-scoped tool inventory in the TUI Tools pane). - ✅ DONE — Transient-characters CRUD (4 endpoints) +
POST /sessions/{id}/persona_state(v0.19.1,--characters+--set-persona-padone-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
- 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).
- 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.jsonas the authoritative drift gates (pinned in.corviduo-canonicals.toml,canonical_drift.pygreen); prose markdown kept astolerate_driftreference. Spec pin advanced f1b59f8 (v0.35.16) → 5810a26 (v1.0.0b2). This map now audits against a frozen, diffable target.