Skip to content

Commit aae27eb

Browse files
mikemolinetclaude
andauthored
fix(messages.send): body_received is dict not flat string (empirical wire-shape) (#40)
Hotfix for cueapi-python PR #39. Empirically verified 2026-05-11 ~23:17Z via direct curl probe: substrate's X-CueAPI-Verify-Echo response includes ``body_received`` as the PARSED request body (a dict with to/body/subject/priority/etc fields), NOT a flat string per the original spec wording. Before fix: SDK compared ``response["body_received"]`` (dict) against ``body`` (str) — type mismatch → ALWAYS raised BodyVerifyMismatchError on every default-auto-verify send. Regression in PR #39. After fix: SDK extracts ``body_received.body`` (str) and compares against sent body (str). Includes defensive isinstance check that falls through gracefully if a future substrate rev flattens the echo back to a string. Tests updated to use the dict shape; 1 new defensive test pins the backward-flat-string path. 23 of 23 messages tests pass. This hotfix is unblocking — every auto-verify send was broken in main until this lands. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b3743d3 commit aae27eb

2 files changed

Lines changed: 57 additions & 19 deletions

File tree

cueapi/resources/messages.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -132,28 +132,40 @@ def send(
132132

133133
response = self._client._post("/v1/messages", json=payload, headers=headers)
134134

135-
# Verify echo if requested. The substrate-side echo lands in
136-
# response[_VERIFY_ECHO_FIELD] when Layer 1 is deployed; absent
137-
# otherwise (no-op in that case).
135+
# Verify echo if requested. Empirically-locked wire shape (probed
136+
# 2026-05-11 ~23:17Z): substrate echoes back the PARSED request
137+
# body as a dict under ``body_received``. To diff the message
138+
# body specifically, extract ``body_received.body`` (str) and
139+
# compare against the str the caller sent.
140+
#
141+
# Backward-compat: pre-Layer-1 substrate omits ``body_received``
142+
# entirely → SDK no-ops. Also defensive: if a future substrate
143+
# rev returns body_received as a flat string (per the original
144+
# spec text), the .get("body") chain returns None gracefully via
145+
# the isinstance check, falling through to no-op rather than
146+
# raising spuriously.
138147
if auto_verify and isinstance(response, dict):
139-
received = response.get(_VERIFY_ECHO_FIELD)
140-
if received is not None and received != body:
148+
received_dict = response.get(_VERIFY_ECHO_FIELD)
149+
received_body: Optional[str] = None
150+
if isinstance(received_dict, dict):
151+
received_body = received_dict.get("body")
152+
elif isinstance(received_dict, str):
153+
# Defensive: future substrate rev may flatten the echo.
154+
received_body = received_dict
155+
if received_body is not None and received_body != body:
141156
msg_id = response.get("id", "<unknown>")
142-
divergence = first_divergence_byte(body, received)
143-
if divergence == -1 and len(body) != len(received):
144-
# One body is a proper prefix of the other; length
145-
# mismatch is the signal. Report at boundary of the
146-
# shorter body.
147-
divergence = min(len(body), len(received))
157+
divergence = first_divergence_byte(body, received_body)
158+
if divergence == -1 and len(body) != len(received_body):
159+
divergence = min(len(body), len(received_body))
148160
raise BodyVerifyMismatchError(
149-
f"Body received by substrate ({len(received)} bytes) differs from "
161+
f"Body received by substrate ({len(received_body)} bytes) differs from "
150162
f"body sent ({len(body)} bytes); first divergence at byte "
151163
f"{divergence}. Likely cause: caller-side shell expansion of "
152164
f"$(...) / backticks / ${{VAR}} in the body arg before Python "
153165
f"received it. Mitigations: pass body via file (Path.read_text) "
154166
f"or use --message-file in cueapi-cli.",
155167
sent_body=body,
156-
received_body=received,
168+
received_body=received_body,
157169
first_divergence_byte=divergence,
158170
message_id=msg_id,
159171
)

tests/test_messages_resource.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,12 +236,18 @@ def test_opt_out_omits_header(self):
236236
assert "X-CueAPI-Verify-Echo" not in headers
237237

238238
def test_byte_identical_response_returns_normally(self):
239-
"""When server echoes back the same body, send() returns response."""
239+
"""body_received.body matches sent body → success.
240+
241+
Empirically-locked wire shape (probed 2026-05-11 ~23:17Z):
242+
substrate echoes back PARSED request body as dict under
243+
``body_received``, NOT a flat string. SDK extracts
244+
``body_received.body`` to compare against sent body.
245+
"""
240246
mock_client = MagicMock()
241247
mock_client._post.return_value = {
242248
"id": "msg_x",
243249
"delivery_state": "queued",
244-
"body_received": "hi",
250+
"body_received": {"to": "y", "body": "hi", "subject": None, "priority": 3},
245251
}
246252
r = MessagesResource(mock_client)
247253

@@ -250,14 +256,19 @@ def test_byte_identical_response_returns_normally(self):
250256
assert result["id"] == "msg_x"
251257

252258
def test_raises_on_body_mismatch(self):
253-
"""When server echo differs from sent body, raises BodyVerifyMismatchError."""
259+
"""body_received.body differs from sent body raises BodyVerifyMismatchError."""
254260
from cueapi.exceptions import BodyVerifyMismatchError
255261

256262
mock_client = MagicMock()
257263
mock_client._post.return_value = {
258264
"id": "msg_mutated",
259265
"delivery_state": "queued",
260-
"body_received": "body with INJECT (caller's shell command-substituted)",
266+
"body_received": {
267+
"to": "y",
268+
"body": "body with INJECT (caller's shell command-substituted)",
269+
"subject": None,
270+
"priority": 3,
271+
},
261272
}
262273
r = MessagesResource(mock_client)
263274

@@ -286,16 +297,31 @@ def test_no_op_when_substrate_omits_echo_field(self):
286297
def test_opt_out_skips_verify_even_if_substrate_echoes(self):
287298
"""auto_verify=False: even if substrate sends body_received, don't check."""
288299
mock_client = MagicMock()
289-
# Mismatched echo but opt-out → no exception
290300
mock_client._post.return_value = {
291-
"id": "msg_x", "body_received": "DIFFERENT BODY",
301+
"id": "msg_x",
302+
"body_received": {"to": "y", "body": "DIFFERENT BODY"},
292303
}
293304
r = MessagesResource(mock_client)
294305

295306
result = r.send(from_agent="x", to="y", body="hi", auto_verify=False)
296307

297308
assert result["id"] == "msg_x"
298309

310+
def test_defensive_accepts_flat_string_body_received(self):
311+
"""Defensive: if a future substrate rev flattens body_received
312+
back to a string (per the original spec wording), SDK still
313+
verifies correctly. Belt + suspenders for spec drift."""
314+
mock_client = MagicMock()
315+
mock_client._post.return_value = {
316+
"id": "msg_x",
317+
"body_received": "hi", # flat-string variant
318+
}
319+
r = MessagesResource(mock_client)
320+
321+
result = r.send(from_agent="x", to="y", body="hi")
322+
323+
assert result["id"] == "msg_x"
324+
299325

300326
class TestFirstDivergenceByte:
301327
"""Pure helper for diagnostic byte-position-of-first-difference."""

0 commit comments

Comments
 (0)