contract(sse_client): first contract — SSE consumer, reconnect, cancel

The natural smallest unit to TDD against per design-brief §3. Bundles
stream_turn + reconnect_turn + cancel_turn + private _parse_sse_id into
one module because the SSE-resume flow is structurally coupled — cancel
needs the turn_id parsed from the SSE wire id:, reconnect re-uses the
same parsed SseId, and stream_turn is what produces them.

Hard invariant INV-002 forces every yielded Event to carry a parsed
SseId(turn_id, seq) lifted from the composite {turn_id}:{seq} id:
wire field. This closes the foot-gun design-brief §3 explicitly names:
hand-rolled data:-only parsing silently drops the id: line and breaks
SSE-resume invisibly.

v2.1 format used; test categories adversarial/scenario/trace flagged
warn-only by the v2.0 parser (CONTRACT-FORMAT §2.1.L H10 is a known
Brokkr-side parser follow-up). FN block list parses cleanly.

Scaffold also verified at this commit: uv pip install -e ".[dev]"
resolves clean against the lockfile (now committed), and the boundary
smoke test (tests/test_no_worldtree_imports.py) passes.
This commit is contained in:
vh
2026-05-20 20:50:26 -07:00
parent 9703eb2b6b
commit 72d477f516
3 changed files with 1423 additions and 12 deletions
+11 -12
View File
@@ -30,35 +30,33 @@ separate dev team rather than an in-tree Worldtree tool.
## Current state / in-flight
**Status: v0 scaffold.** Design is locked; implementation has not started.
The dev team owns the implementation pass.
**Status: scaffold verified + first contract authored.** Design is locked;
implementation begins next, TDD against `sse_client.contract.md`.
What's in the repo:
- `docs/design-brief.md` — the locked design (copy from `brokkr-smithy/docs/ratatoskr-design-brief.md`).
- `docs/SPEC-PIN.md` — Worldtree spec pin documentation + bump procedure.
- `docs/conversation-api-spec.md` — vendored Worldtree spec at the pinned SHA.
- `docs/conversation_api.contract.md` — vendored Worldtree server-side contract at the pinned SHA.
- `docs/contracts/sse_client.contract.md` — **first contract authored** (2026-05-20). Module `ratatoskr.sse_client`. v2.1, complexity=high. Four FN blocks: `stream_turn`, `reconnect_turn`, `cancel_turn`, `_parse_sse_id`. Tracer tests per FN tagged. Parser warnings on v2.1-only test categories (`adversarial`/`scenario`/`trace`) are expected — parser is v2.0, format spec H10 is a known Brokkr-side follow-up.
- `pyproject.toml` — Python 3.12, hatchling, uv-managed. Deps: httpx, httpx-sse, textual. Dev deps: pytest, pytest-asyncio, respx, ruff, mypy, textual-dev.
- `src/ratatoskr/__init__.py` + `cli.py` — stubs.
- `tests/test_no_worldtree_imports.py` — boundary smoke test (fails if any `core.*` or `worldtree.*` import shows up under `src/ratatoskr/`).
- `tests/test_no_worldtree_imports.py` — boundary smoke test (passes; verified 2026-05-20).
- `tests/snapshots/README.md` — recording/replay convention for SSE snapshot tests.
What's NOT in the repo yet:
- Gitea remote — local `git init` only. Operator-mediated remote setup
(infra-ops can register the gitea repo at `gitea.phasefinal.com/vh/ratatoskr`
when the dev team is ready).
- Implementation of `ratatoskr.sse_client` — next move, TDD per the contract's tracer tests.
- Gitea remote — operator provided `git@gitea.phasefinal.com:vh/ratatoskr.git` on 2026-05-20; about to be added + first push at the same commit as this update.
- CLAUDE.md customization — currently using the canonical template's
generic CLAUDE.md. The dev team may want to add Ratatoskr-specific
conventions on first substantive work.
- Implementation — see `docs/design-brief.md` for the locked shape.
**Branch:** `main`, no remote yet.
**Branch:** `main`. Remote: `origin → git@gitea.phasefinal.com:vh/ratatoskr.git` (added 2026-05-20).
**Next natural moves:**
1. Operator hands off to the Ratatoskr dev team (mechanism TBD).
2. Dev team runs `uv venv && uv pip install -e ".[dev]"`, confirms boundary test passes.
3. Dev team writes the first contract under `docs/contracts/` for the SSE consumer module — that's the natural smallest unit to TDD against.
4. (Parallel) operator or infra-ops creates the gitea remote.
1. Implement `ratatoskr.sse_client` via TDD per `docs/contracts/sse_client.contract.md`. Vertical slice — start with `_parse_sse_id` (the foundation; tracer test `happy_simple`), then `stream_turn` (tracer test `happy_one_text_done`), then `reconnect_turn` (tracer test `happy_resume_from_seq_3`), then `cancel_turn` (tracer test `happy_cancel`). Each FN's tracer test gets RED → GREEN before any other test in that FN.
2. Record real SSE snapshot fixtures from a running Worldtree (per `tests/snapshots/README.md`) once `stream_turn` is GREEN against mocks — gives version-skew detection per design-brief §2.
3. Build presenters: `--send` stdout presenter first (simplest consumer of `stream_turn`); Textual TUI second. Both consume the same `Event` iterator.
## Recent decisions
@@ -76,6 +74,7 @@ decision. Captures rationale that won't be obvious from code alone.
- `[2026-05-20]` **Single-session-per-launch + startup picker.** No in-app `/switch`. CLI flags `--session <id>` and `--new` for scripted use. Session identity always visible in Textual footer.
- `[2026-05-20]` **Markdown rendering default-on; `--raw` opt-out.** Don't pre-design `--no-stream-formatting` (Volva: add only if streaming-markdown rendering is empirically ugly).
- `[2026-05-20]` **Non-interactive `--send` mode.** Single SSE consumer module, two presenters (TUI + stdout). Keeps Ratatoskr honest as an API consumer; useful for CI / scripted probes.
- `[2026-05-20]` **First contract: `ratatoskr.sse_client`.** Bundles `stream_turn` + `reconnect_turn` + `cancel_turn` + private `_parse_sse_id` into one module — the SSE-resume flow is coupled (cancel needs `turn_id` from the SSE wire `id:`, reconnect re-uses the same parsed `SseId`), so they share a contract. Hard invariant INV-002 makes the composite `{turn_id}:{seq}` `id:` parsing load-bearing — closes the foot-gun the design-brief §3 names (hand-rolled `data:`-only parsing silently drops the `id:`). v2.1 test categories `adversarial`/`scenario`/`trace` used freely; parser warns but format spec §2.1.E permits them.
## Tried and abandoned