Compare commits

..

2 Commits

Author SHA1 Message Date
vh 359dbb1436 release: v0.18.0 — composite both-plane Bifrost binding (#18 closed)
Publishing-minor for the #18 arc: ratatoskr now exposes a COMPOSITE Bifrost
endpoint (build_combined_app, :8392) so one bound Worldtree session drives BOTH
the memory.* and affect.* planes through a single endpoint — completing the
Tier-3 consumer round-trip (durable memory + live PAD from one binding).

Shipped as patches v0.17.15 (affect.fetch prerequisite) → v0.17.16 (composite) →
v0.17.17 (#17 op-feed fix); this minor publishes the milestone.

Live-smoke (against real stores + bifrost 0.10.0 on a running :8392): handshake
grants BOTH caps by store presence; the PAD read route serves real sindra PAD;
both planes dispatch through the one endpoint at a single bound session_id with
the op-feed deriving plane per path. The remaining WT-driven turn is gated on
infra-ops adding :8392 to Worldtree's BIFROST_CLIENT_ALLOWED_HOSTS (requested).
2026-06-19 23:48:33 -07:00
vh d60b77d4f1 fix(#17): op-feed handshake reads the real capabilities field, not capabilities_requested
The dispatch-layer op-feed's handshake req-summary read req.get("capabilities_requested"),
a field that never exists on the wire — bifrost's handshake handler reads
request_body["capabilities"] (reference_server/_protocol.py:181). So the op-feed's
caps_requested was silently ALWAYS None on every handshake. Read the real field.

Surfaced by the heid-code-review panel (Regin) during the #18 D1 review — a latent
#17 observability bug, not D1 drift. Regression test asserts caps_requested is
populated from a handshake body's capabilities.

Suite 502 -> 503 green.
2026-06-19 23:34:19 -07:00
4 changed files with 24 additions and 3 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "ratatoskr"
version = "0.17.16"
version = "0.18.0"
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
readme = "README.md"
requires-python = ">=3.12"
+5 -1
View File
@@ -135,7 +135,11 @@ def _ids_summary(args: dict[str, Any]) -> list[Any]:
def _req_summary(plane: str, path: str, op: str, req: dict[str, Any]) -> dict[str, Any]:
"""Scope-only request summary — NEVER record bodies / PAD content."""
if path == "/bifrost/handshake":
return {"caps_requested": req.get("capabilities_requested")}
# The handshake REQUEST field is `capabilities` (bifrost reference_server
# _protocol.py:181 reads request_body["capabilities"]) — NOT the transposed
# `capabilities_requested`, which never existed on the wire (caps_requested
# was silently always None). Fixed per the heid-code-review #17 catch.
return {"caps_requested": req.get("capabilities")}
if plane == "affect":
return {} # affect stays conduit-opaque — no PAD content surfaced
args = req.get("args") or {}
+17
View File
@@ -231,6 +231,23 @@ class TestOpFeedMemory:
assert len(sink.events) == 1
assert sink.events[0].op == "handshake"
async def test_handshake_req_summary_reads_real_capabilities_field(self) -> None:
"""The handshake req-summary reads the REAL wire field `capabilities` (bifrost
_protocol.py:181), not the transposed `capabilities_requested` — so caps_requested
is actually populated (heid-code-review #17 catch). A bad-version handshake still
emits the OpEvent carrying the requested caps from the request body."""
sink = _RecordingSink()
app, _store = _wrapped_memory_app(sink)
resp = await _post(
app,
"/bifrost/handshake",
{"bifrost_version": "99.0.0", "mcp_version": "0.4.0", "capabilities": ["memory"]},
None,
)
assert resp.status_code != 200
assert len(sink.events) == 1
assert sink.events[0].req_summary == {"caps_requested": ["memory"]}
async def test_sink_failure_never_breaks_dispatch(self) -> None:
"""sink_swallow [adversarial]: a raising sink must NOT break the dispatch
path — the search still returns 200 (POST-003 / INV-007)."""
Generated
+1 -1
View File
@@ -1052,7 +1052,7 @@ wheels = [
[[package]]
name = "ratatoskr"
version = "0.17.16"
version = "0.18.0"
source = { editable = "." }
dependencies = [
{ name = "httpx" },