Compare commits

..

1 Commits

Author SHA1 Message Date
vh 2ba4244e9e feat(#1): route TUI + web presenters through stream_turn_resilient
Complete b1's design-brief §8b promise ("all presenters share the
consumer"): the TUI and web SSE consumers now resume transparently on
a mid-stream drop, same as cli --send (v0.18.5). The TUI is the primary
beneficiary — long-lived dev sessions across laptop suspend.

Name-for-name swap of stream_turn -> stream_turn_resilient at the two
remaining consumer loops (tui.py:1321, web/server.py:294) + their
imports. No behavioral change on the happy path (resilient == stream
when there is no drop); suite 518 green; touched lines ruff+mypy clean
(pre-existing tui/web lint debt left untouched per surgical rule).
2026-06-30 15:46:04 -07:00
5 changed files with 7 additions and 7 deletions
+1 -1
View File
@@ -149,7 +149,7 @@ decision. Captures rationale that won't be obvious from code alone.
- `[2026-06-30]` **Finding P-1 (pin drift) + pin-remediation PENDING.** We vendor the PROSE markdown (`docs/conversation-api-spec.md`), which is **byte-identical to live WT's** but frozen at v0.35.16-era content (last WT edit 2026-05-31) — it does NOT capture b2: 7 new endpoints (admin/keys/bulk, admin/persona/{archive,erase}, admin/usage, embed, judgments, me/usage), the 409/503 on messages-POST (#331), the unified error envelope (#328), or the SSE schema. **WT's authoritative v1 truth is now the FROZEN OpenAPI 2.2.0 + SSE-schema JSON** (`Worldtree/docs/v1-schema-freeze-manifest.md`). So the previously-deferred "re-vendor markdown to b2" is a **near-no-op** (markdown content identical). **Pending operator nod:** re-pin to the machine-readable artifacts (recommended — drift-checkable via `canonical_drift.py`, makes the coverage map reproducible vs a frozen diffable target) vs markdown-only. Deferred (not auto-applied) because it adds vendored artifacts + a canonical-sync pin = substrate change with CI-gating reach. **→ RESOLVED 2026-06-30 (operator: "a then b").** Vendored `conversation-api-openapi.json` (2.2.0) + `conversation-api-sse-events.schema.json` + re-copied the prose markdown; pinned all three in `.corviduo-canonicals.toml` (OpenAPI+SSE = strict drift gates, markdown = `tolerate_drift` reference); advanced `worldtree-spec-rev` f1b59f8→5810a26 + `worldtree-version` v0.29.0(STALE, never bumped from the v0.35.16 pin)→v1.0.0b2 + `pinned-on`→2026-06-30; SPEC-PIN.md history row added. `canonical_drift.py` green (10/10). `pin:`-only, no version bump (no client-facing code change; the b2 409/503 + error-envelope were already consumed in v0.18.3/.4).
- `[2026-06-30]` **(b) Tier-1 frontier SCOPED, ready for a contract-first TDD cycle (next focused work).** The primitives already exist + are contracted + tested; the gap is PRESENTER-level wiring. Two slices: **(b1) SSE-resume** — contract #1 (`ratatoskr.sse_client`) DELIBERATELY makes resume caller-owned ("on `SseConnectionDropped`, the caller MAY invoke `reconnect_turn`"); `reconnect_turn` (sse_client.py:524) has NO caller. Gap = a SHARED resume-orchestration wrapper (catch `SseConnectionDropped` → track last-seen `sse_id``reconnect_turn` → continue), consumed by all 3 presenters per design-brief §8b "share the consumer, branch the presenter" (NOT per-presenter — that forks the consumer). New function block → **amend contract #1** (additive FN, e.g. `stream_turn_resilient`) then TDD (RED: drop-mid-stream→resume continuity; GREEN: wrapper; wire `cli --send` first as the tracer). Resume design pre-locked: in-process Last-Event-ID only, cross-process deferred to v2 (design-brief §8d). **(b2) session-picker** — `list_sessions` (sessions.py:198) has NO caller; add a Textual DataTable startup picker (>1 session) + `--session <id>`/`--new` CLI flags (design-brief §4, decisions pre-locked). Both pre-locked → heid-contract-review likely skippable as ceremony (small additive amendments to mature specs); heid-code-review still valuable. **#11 AdminEvents stays BLOCKED** on `admin.events.read` scope (infra-ops).
- `[2026-06-30]` **(b1) SSE-resume SHIPPED (`v0.18.5`) — `stream_turn_resilient` (sse_client.py).** The shared resume-orchestration surface (design-brief §8b): wraps `stream_turn`+`reconnect_turn`, catches `SseConnectionDropped` (mid-stream drop OR clean-EOF-before-terminal) → resumes from last-seen `sse_id` via `reconnect_turn` (Last-Event-ID), up to `max_reconnects` (default 5); non-drop reconnect failures (412/410/400/TurnIdFlip/SseConnectFailed) PROPAGATE per contract #1's "surface, not recover". `last_seen` persists ACROSS attempts (a zero-event reconnect drop falls back to the prior attempt's id). Direct in-session TDD against a contract-#1 amendment (8 cases incl. two-drops, max-reconnects-exhausted, zero-budget, buffer-expired-propagates, unresumable-zero-event). Wired `cli --send` (`cli.py:396`, swapped `stream_turn``stream_turn_resilient`; **tui/web still on bare `stream_turn` — follow-up to route them through the wrapper**). Suite 518 green; ruff+mypy clean on touched code (pre-existing cli.py:400/543 mypy warts left untouched per surgical rule); contract #1 validates OK. **heid-code-review NOT run** (small additive well-TDD'd wrapper; offered to operator). **b2 (session-picker + `--session`/`--new` flags) still pending.**
- `[2026-06-30]` **(b1) SSE-resume SHIPPED (`v0.18.5`) — `stream_turn_resilient` (sse_client.py).** The shared resume-orchestration surface (design-brief §8b): wraps `stream_turn`+`reconnect_turn`, catches `SseConnectionDropped` (mid-stream drop OR clean-EOF-before-terminal) → resumes from last-seen `sse_id` via `reconnect_turn` (Last-Event-ID), up to `max_reconnects` (default 5); non-drop reconnect failures (412/410/400/TurnIdFlip/SseConnectFailed) PROPAGATE per contract #1's "surface, not recover". `last_seen` persists ACROSS attempts (a zero-event reconnect drop falls back to the prior attempt's id). Direct in-session TDD against a contract-#1 amendment (8 cases incl. two-drops, max-reconnects-exhausted, zero-budget, buffer-expired-propagates, unresumable-zero-event). Wired ALL THREE presenters through it (`v0.18.6`): `cli --send` (`cli.py:396`), TUI (`tui.py:1321`), web (`web/server.py:294`) — each a name-for-name `stream_turn``stream_turn_resilient` swap (the §8b "all presenters share the consumer" promise, fully kept; the TUI is the primary resume beneficiary — long-lived sessions / laptop-suspend). Suite 518 green; ruff+mypy clean on touched code (pre-existing cli.py:400/543 mypy warts left untouched per surgical rule); contract #1 validates OK. **heid-code-review NOT run** (small additive well-TDD'd wrapper; offered to operator). **b2 (session-picker + `--session`/`--new` flags) still pending.**
_41 older entries (2026-05-* — the original debug-TUI/web build era) archived to archival-memory.md._
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.18.5"
version = "0.18.6"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
+2 -2
View File
@@ -68,7 +68,7 @@ from ratatoskr.sse_client import (
TurnIdFlip,
WorkerPhase,
cancel_turn,
stream_turn,
stream_turn_resilient,
)
# ---- Australis theme (https://github.com/lkraven/australis) ------------------
@@ -1318,7 +1318,7 @@ class RatatoskrApp(App[int]):
pass
try:
async for event in stream_turn(self.client, self.session_id, content):
async for event in stream_turn_resilient(self.client, self.session_id, content):
if self.active_turn_id is None:
self.active_turn_id = event.sse_id.turn_id
self._write_turn_headers(self.active_turn_id)
+2 -2
View File
@@ -50,7 +50,7 @@ from ratatoskr.sse_client import (
SseConnectionDropped,
TurnIdFlip,
cancel_turn,
stream_turn,
stream_turn_resilient,
)
@@ -291,7 +291,7 @@ async def _stream_turn_endpoint(request: Request) -> StreamingResponse:
try:
handle.status = "streaming"
try:
async for event in stream_turn(client, session_id, handle.content):
async for event in stream_turn_resilient(client, session_id, handle.content):
# v0.16.0: capture the upstream (Worldtree-assigned)
# turn_id from the first event so cancel paths target
# the real upstream turn, not our local counter.
Generated
+1 -1
View File
@@ -1052,7 +1052,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.18.5"
version = "0.18.6"
source = { editable = "." }
dependencies = [
{ name = "httpx" },