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.
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ratatoskr"
|
name = "ratatoskr"
|
||||||
version = "0.17.16"
|
version = "0.17.17"
|
||||||
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
description = "Worldtree Conversation API debug TUI — multi-pane observability dashboard"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -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]:
|
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."""
|
"""Scope-only request summary — NEVER record bodies / PAD content."""
|
||||||
if path == "/bifrost/handshake":
|
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":
|
if plane == "affect":
|
||||||
return {} # affect stays conduit-opaque — no PAD content surfaced
|
return {} # affect stays conduit-opaque — no PAD content surfaced
|
||||||
args = req.get("args") or {}
|
args = req.get("args") or {}
|
||||||
|
|||||||
@@ -231,6 +231,23 @@ class TestOpFeedMemory:
|
|||||||
assert len(sink.events) == 1
|
assert len(sink.events) == 1
|
||||||
assert sink.events[0].op == "handshake"
|
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:
|
async def test_sink_failure_never_breaks_dispatch(self) -> None:
|
||||||
"""sink_swallow [adversarial]: a raising sink must NOT break the dispatch
|
"""sink_swallow [adversarial]: a raising sink must NOT break the dispatch
|
||||||
path — the search still returns 200 (POST-003 / INV-007)."""
|
path — the search still returns 200 (POST-003 / INV-007)."""
|
||||||
|
|||||||
Reference in New Issue
Block a user