9df9bb875756e56aec0bed0416bec4f02b51757a
7 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9df9bb8757 |
contract(issue#2): scaffold ratatoskr.sessions — create + list
Issue #2: ratatoskr.sessions covers the session-lifecycle endpoints needed by --send --new (POST /sessions) and the eventual TUI startup picker (GET /sessions). Two FN blocks (create_session, list_sessions) plus two shared frozen dataclasses (SessionInfo, SessionPage). complexity=low; estimated 150 LOC. Bundles both endpoints in one contract because they share the response- envelope shape — SessionInfo carries the union of POST-response fields (message_count) and list-item fields (name, archived, tags), with the origin-conditional fields defaulting to None. INV-001/002 spell out which fields come from which source so callers can rely on the discriminator. INV-006 refuses out-of-range limit (< 1 or > 200) client-side: spec §GET /sessions says the server returns 422; the client checks first so a 422 from this endpoint indicates server-side spec drift, not a client bug. INV-003 codifies the opaque-cursor discipline (spec §Pagination: "Cursors are opaque to clients — do not parse or construct them."). list_sessions threads next_cursor verbatim; never base64-decodes. Exception .body truncation to [:1024] inherited from issue #1's SseConnectFailed/CancelFailed precedent. First contract in this repo to carry a ## Out of scope H2. Future /volva-code-review consults will auto-resolve that section instead of needing --out-of-scope overrides. Six explicit exclusions: Bifrost binding (Worldtree #160), ephemeral/Saga sessions, single- session fetch, PATCH/DELETE mutation, history pagination, transparent multi-page iteration. Server retry/backoff is caller's policy. dependencies: lists issue #1 as a convention-dependency only — no code import; same API-consumption posture (caller-owned httpx client, async-native, frozen dataclasses, no Worldtree-source imports). prd: pinned to issue #2 body SHA-256 01fbbd52b6d90eb0 at 2026-05-21T04:45:06+00:00; scripts/contract_drift_check.py returns clean. |
||
|
|
c17af18351 |
fix(sse_client): address Volva code-vs-contract drift (issue #1)
Volva's code-spec review (thread 01KS4CP6ZZ1F) surfaced four code-vs-
contract drift findings on the TDD-passing implementation. All four
addressed here; no contract amendments required.
1. _iter_events fell off the end of aiter_sse() normally on clean EOF
before any Done/Error/Cancelled. Per INV-001 the iterator MUST NOT
raise StopAsyncIteration before a terminal event unless the HTTP
connection drops, in which case it raises SseConnectionDropped.
Clean EOF before terminal is the same semantic — the stream ended
without delivering its contracted invariant. Fix: track terminal_seen
inside _iter_events; after the async-for completes, if not seen,
raise SseConnectionDropped(last_seen_sse_id=...). Two new tests:
test_clean_eof_before_terminal (one text then EOF) and
test_zero_event_eof (empty stream — last_seen_sse_id is None).
2. SseConnectFailed and CancelFailed both store .body without
truncation; ERROR_ROUTING specifies resp.read()[:1024]. Fix
truncates in each exception's __init__ before storing. New test
test_connect_failed_body_truncated (503 + 5000-byte body → 1024)
and test_cancel_failed_truncates_body (same shape on cancel).
3. _parse_sse_id PRE-001 specifies `assert isinstance(raw, str)`.
Previous code called raw.split(":") directly, which raises an
incidental AttributeError on non-str inputs — not the contracted
precondition path. Fix adds the assert. New test
test_non_string_input covers int and None.
4. Cancel ERROR_ROUTING said httpx.HTTPStatusError other status →
CancelFailed, but no test exercised the branch. test_cancel_failed_
truncates_body covers this (above) — single test double-covers
findings 2 and 4.
43 tests GREEN (42 sse_client + boundary smoke); ruff clean.
Meta-note from Volva: TDD caught the main happy/adversarial SSE shape,
resume header/body, turn-id flip, and cancel races. The remaining
misses were "negative space" cases (clean premature EOF, exception
payload truncation, untested generic cancel branch). Calibration
evidence that cross-model review pulls weight on what same-model
TDD's hypothesis-space doesn't probe.
|
||
|
|
02f2a04b37 |
feat(sse_client): implement issue #1 contract via TDD
Implements docs/contracts/issues/1.contract.md. Four entry points
(stream_turn, reconnect_turn, cancel_turn, _parse_sse_id) + nine
typed Event variants + ten domain exceptions. 37 tests covering
every TESTS: entry verbatim, plus the boundary smoke test still
passes.
Tracer-bullet ordering per the contract's per-FN tracer tags:
_parse_sse_id (foundation; happy_simple) → stream_turn
(happy_one_text_done) → reconnect_turn (happy_resume_from_seq_3) →
cancel_turn (happy_cancel). Each FN's tracer went RED then GREEN
before its other tests landed.
Shared SSE-iteration logic (INV-002 sse_id presence + INV-003
turn_id stability + terminal-break) lives in private _iter_events
helper. expected_turn_id=None gives stream_turn's "establish from
first event" semantics; expected_turn_id=N gives reconnect_turn's
"first event is already a flip-candidate" semantics — the
two-entry-point distinction Volva surfaced during the paraphrase
round.
A few implementation choices worth recording:
- _parse_sse_id uses a `^-?\\d+$` regex pre-check to reject any
whitespace before int() is called. Python's `int(" 3 ")` silently
strips, which would have made the trailing_whitespace adversarial
test pass for the wrong reason.
- The connection_drop test uses a custom httpx.AsyncByteStream
subclass (_DropAfter) that yields chunks then raises
RemoteProtocolError mid-stream. respx alone can't simulate
mid-stream HTTP errors.
- ToolResult.result and ToolStart.arguments are typed as Any
because the server's tool wire shape varies per tool; the spec
doesn't pin a generic schema.
- Boundary smoke test (no core.* / worldtree.* imports under
src/ratatoskr/) still GREEN — INV-005 holds.
Also: one E501 line-length fix in test_no_worldtree_imports.py
that ruff flagged once the new tests pulled it into scope.
|
||
|
|
1526f0bc8e |
contract(issue#1): amend per Volva paraphrase — body, id range, INV-003
Volva's contract paraphrase round (thread 01KS4B3B0Y62) surfaced three
real contract-time ambiguities — addressing each here before applying
ready-for-agent.
1) reconnect_turn body was a punt. STEP 2 literally said "json={'content':
''} OR with no body (TBD per spec — confirm during implementation)".
The spec §Reconnect flow example shows POST with Content-Type:
application/json and a body shaped {"content": "..."} — the wire schema
requires content; the server identifies the resume target via the
Last-Event-ID header and does NOT re-process content (spec line 732:
"agent's tools and LLM call run exactly once regardless of disconnects/
reconnects"). reconnect_turn now takes content: str explicitly; STEP 2
sends json={"content": content}. Caller convention: pass the original
content sent to stream_turn. POST-002 added to assert byte-for-byte
body shape; new test body_threads_content covers it.
2) _parse_sse_id allowed turn_id and seq ≥ 0 — too loose. Spec §SSE id
format line 705 says seq starts at 1 (resets per turn); turn_id is
from SQLite turns.id (autoincrement, ≥1). Tightened POST-001 to require
both ≥1; STEP 5 raises ValueError on either < 1. Test happy_zero_seq
flipped to adversarial zero_seq; added zero_turn_id and negative_seq.
INV-002 tightened to reflect the same range.
3) INV-003 (TurnIdFlip) had a subtle wording gap between stream_turn
(first event ESTABLISHES turn_id; cannot be a flip) and reconnect_turn
(expected turn_id parsed FROM last_event_id BEFORE connect; first event
is already a flip-candidate). Volva noticed the reconnect test said
"first event was not yielded" while stream_turn semantics depend on
the first event being yielded unconditionally. Spelled out both entry
points in INV-003 as a numbered sub-list. reconnect_turn STEP 4 and
test turn_id_flip_on_first_event reworded to match.
Volva flags #3 (MalformedSseId-vs-ValueError split) and #5 (exactly-
one-terminal as server-assumed, not client-verified) reviewed and kept
as-is — both intentional. Drift check unchanged: amending the contract
does not touch the pinned issue body, so prd: hashes remain valid.
|
||
|
|
999b0b4765 |
contract(issue#1): pin sse_client to gitea issue + seed default labels
Convert the sse_client contract into an issue-scoped contract bound to the freshly-filed gitea issue #1. Frontmatter migrates from module-shape (module:/purpose:) to issue-shape (target_module:/scope:/prd:) per CONTRACT-FORMAT §2.1.I. The prd: block pins to issue #1's body SHA-256 (abcbc49467e86f1d at 2026-05-21T03:57:37+00:00); drift check verifies the pin matches the live issue body. scripts/contract_drift_check.py needs pyyaml; added to [dev] in pyproject.toml. Without it the drift check (and the contract parser) fail with ModuleNotFoundError — that's a scaffold hole I'd hit again on a fresh checkout. Also seed 17 default labels on gitea via tea so issue tracking has a working vocabulary out of the gate. Five buckets: Sleipnir gating (ready-for-agent, blocked-needs-contract, blocked-needs-dependency), triage (needs-triage, needs-architect-decision, needs-info), type (bug, enhancement, task, documentation), resolution (duplicate, wontfix, invalid), Ratatoskr-specific area (sse-client, tui, cli, observability). Labels are gitea-side state — not in this commit. Known: contract_parser.py --validate ERRORs on the issue-scoped frontmatter because the parser is v2.0-shape. CONTRACT-FORMAT §2.1.L H10 explicitly marks parser kind-aware validation as a Brokkr-side follow-up. Parser is a canonical-synced file so we do NOT patch it locally (would drift from corviduo-project-template). |
||
|
|
72d477f516 |
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.
|
||
|
|
9703eb2b6b |
init: seed Ratatoskr from corviduo-project-template + ship v0 scaffold
Worldtree Conversation API debug TUI. Multi-pane observability dashboard: chat transcript + persona/Vili affect log + tool events + admin events + Bifrost state + tool inventory + (opt-in) raw server log. Design locked at docs/design-brief.md (originated as brokkr-smithy/docs/ratatoskr-design-brief.md). Operator-locked decisions: - Textual application-shell framework (multi-pane dashboard, not REPL). - Separate repo + separate dev team (no Worldtree-source imports). - httpx-sse for SSE consumption (reference Python SSE-resume impl). - Triple version-skew mitigation: spec-pin in pyproject.toml + recorded SSE snapshot tests + conformance smoke. Initial pin: Worldtree v0.19.0 at 55101e909abcd2219833266b6f905c5bc956e0f0. - Persona pane: label-don't-refuse PII posture. - Server-log pane: opt-in via --server-log <path>. - Two-stage Ctrl-C (cancel then exit). - Markdown rendering default-on; --raw opt-out. In the box: - docs/design-brief.md — the locked design with full rationale. - docs/SPEC-PIN.md — Worldtree spec pin + bump procedure. - docs/conversation-api-spec.md + docs/conversation_api.contract.md — vendored Worldtree spec snapshots at the pinned SHA. - pyproject.toml — Python 3.12, hatchling, uv-managed, deps locked. - src/ratatoskr/ — stub package (cli.py raises NotImplementedError). - tests/test_no_worldtree_imports.py — boundary smoke test PASSING. - tests/snapshots/README.md — recording convention for SSE snapshot tests. Not in the box yet: - Gitea remote (operator/infra-ops to register at vh/ratatoskr). - Implementation — the dev team owns this; design brief is the spec. Origin: althing thread 01KS3R34XD3N6HMK91VXESHGW7 (worldtree-dev → brokkr-smithy-dev, 2026-05-20). Volva consulted via thread 01KS3VF6W33N3V5FNMGQ91YNVD. |