v1 coverage-audit Tier-2 quick win. The owner-scoped tool-inventory endpoint (#183) had no caller; wire it into the TUI Tools pane. - sessions.py: get_session_tools (GET /sessions/{id}/tools) — owner- scoped (consumer key, no admin scope), 200 -> parsed dict verbatim, non-200 -> SessionApiFailed. Mirrors get_persona_state / get_me. - tui.py: _format_tool_inventory helper + _hydrate_session_tools best-effort worker (mirrors _hydrate_persona), wired unconditionally in on_mount. Writes the merged {agent_id, builtin_tools, bifrost_tools} inventory the LLM saw at turn-fire into the Tools pane + audits; never crashes on failure. - Covers the design-brief 5 "Tools widget" via the reachable owner endpoint (the admin variant stays a gap only for cross-user debug). - Contract #2 amended (FN) + validated. TDD: 3 wrapper tests + 1 format-helper unit + 2 hydrate integration tests. Coverage: REST 10/40. Suite 544 green; touched code ruff-clean.
14 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.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.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.2.0, path groups) | 40 | 10 | 8 | 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.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) |
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 — the convergence frontier (debug-observability path)
Tier 1 — the debug-observability core (design-brief'd for v1, unbuilt):
| Endpoint | Status | Why in-scope |
|---|---|---|
GET /admin/events |
⬜ | design-brief §5 v1 AdminEvents pane; = issue #11, blocked on admin.events.read scope (infra-ops) |
GET /admin/sessions/{id}/bifrost |
⬜ | design-brief §5 v1 BifrostState widget — never built; admin-key-gated |
GET /admin/sessions/{id}/tools |
⬜ | design-brief §5 v1 Tools widget — never built; admin-key-gated |
(GET /sessions picker · resume) |
⬜ | sub-gaps above — presenter-wiring only, wrappers exist |
Tier 2 — rounds out I/O coverage under A (postdates the design-brief):
| Endpoint | Status | Why in-scope |
|---|---|---|
POST /sessions/{id}/persona_state (write) |
⬜ | affect-injection is debug-relevant; pairs with our provider affect plane |
POST /characters · DELETE /characters/{id} · GET /characters/{id}/state · GET /models/available-for-characters |
⬜ | transient-characters (Echo) is a session-creation routing path a debug client should be able to drive a turn through |
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/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)
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). - BifrostState + Tools widgets (
GET /admin/sessions/{id}/bifrost— the admin session-bifrost inspection) — design-brief'd v1, unbuilt. Admin-key-gated. (The Tools half is effectively covered by the owner-scopedGET /sessions/{id}/toolsinventory, item 5 — the admin/admin/sessions/{id}/toolsremains a gap only for cross-user operator debug.) - #11 — AdminEvents pane — blocked on an
admin.events.readscope grant (infra-ops). The single externally-blocked item; everything else can ship without it.
Tier 2 — rounds out coverage:
- ✅ DONE —
GET /sessions/{id}/tools(v0.18.9, owner-scoped tool inventory in the TUI Tools pane). - Transient-characters routing (4 endpoints) +
POST /sessions/{id}/persona_state.
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.