fix(sessions): address Volva code-vs-contract drift (issue #2)
Volva's code-spec review (thread 01KS4EKVKKGF) surfaced three findings
on the TDD-passing sessions module. All three addressed; one carries
a collateral contract amendment to keep INV-002 truthful.
1) drift: archived=item.get("archived", False) returned None for an
explicit "archived": null in the response. dict.get(k, default) only
fires the default when the key is absent — it does NOT default for
explicit-null values. The dataclass type is `bool` (not `bool | None`)
and INV-002 says explicit-null → False; the .get() form silently
violated both. Fixed: archived=item.get("archived") or False
(handles absent, null, false, and true cleanly).
INV-002 wording was the source of the bug — I introduced the
mis-spelled form during the Volva amendment round. Updated to spell
out the .get(default) foot-gun explicitly so future readers (and
future paraphrase rounds) don't fall back to the broken pattern.
2) test-gap: no test exercised explicit-null archived/tags. The
_list_item() helper had its own defaulting layer (tags=None →
["work"]) so a happy path test couldn't catch the underlying drift.
Added test_explicit_null_list_defaults using a raw dict to bypass
the helper. Catches the drift directly.
3) precision: message_count=body.get("message_count") could silently
default to None while POST-003 required it non-None. INV-001 prose
literally said "body['message_count']" (bracket access) so the
STEP 5 .get() was the contract's own internal inconsistency.
Aligned the code to bracket access (matches sibling required
fields like session_id) and amended STEP 5 + INV-001 to spell out
the strict semantics explicitly.
Volva's meta-note: "modest weight" — TDD caught the main surface;
this round caught a narrow Python .get() semantics edge that no
human reading would have spotted without explicit-null priors.
Still pulls real weight: that's the kind of bug that ships and
shows up months later when a server starts emitting null where
it used to omit a field.
63 tests GREEN (42 sse_client + 20 sessions + 1 boundary).
Ruff clean. Drift check still GREEN against the pinned issue body.
This commit is contained in:
@@ -164,6 +164,31 @@ def _list_item(
|
||||
|
||||
|
||||
class TestListSessions:
|
||||
@respx.mock
|
||||
async def test_explicit_null_list_defaults(self) -> None:
|
||||
"""INV-002: explicit-null archived -> False; explicit-null tags -> []."""
|
||||
raw_item = {
|
||||
"session_id": "s1",
|
||||
"agent_id": "mimir",
|
||||
"created_at": "2026-04-15T12:00:00+00:00",
|
||||
"last_active": "2026-04-15T12:05:00+00:00",
|
||||
"metadata": {},
|
||||
"name": None,
|
||||
"archived": None,
|
||||
"tags": None,
|
||||
}
|
||||
respx.get("https://w.example/sessions").mock(
|
||||
return_value=httpx.Response(
|
||||
200, json={"items": [raw_item], "next_cursor": None}
|
||||
)
|
||||
)
|
||||
async with httpx.AsyncClient(base_url="https://w.example") as client:
|
||||
page = await list_sessions(client)
|
||||
info = page.items[0]
|
||||
assert info.archived is False, "explicit-null archived must default to False"
|
||||
assert info.tags == [], "explicit-null tags must default to []"
|
||||
assert info.name is None
|
||||
|
||||
@respx.mock
|
||||
async def test_happy_first_page(self) -> None:
|
||||
"""happy_first_page [happy,tracer]: one item + next_cursor -> SessionPage shape."""
|
||||
|
||||
Reference in New Issue
Block a user