Files
2026-07-13 13:12:33 +08:00

81 lines
2.9 KiB
Python

"""Wire-contract freeze for the session.event.error payload.
The error payload keys are a public protocol contract (CLAUDE.md: public RPC
field names are stable). ``error_id`` joins a user-visible ``(ref: …)`` code
to its durable turn_errors row — clients and bug reports depend on it.
- Renaming or removing any frozen key is a contract break and must fail here.
- Adding a key requires deliberately extending the frozen set in this file.
"""
from __future__ import annotations
from dataclasses import asdict
from opensquilla.engine.types import ErrorEvent
from opensquilla.gateway.rpc_sessions import _normalize_terminal_event_payload
from opensquilla.session.terminal_reply import append_error_ref
NORMALIZED_ERROR_KEYS = frozenset(
{
"message",
"code",
"error_id",
"terminal_message",
"terminal_reason",
"error_message",
"turn_outcome",
}
)
def _synthetic_error_payload() -> dict:
event = ErrorEvent(message="Agent error", code="agent_error", error_id="abcd1234")
payload = asdict(event)
payload.pop("kind")
return payload
def test_error_event_dataclass_carries_error_id() -> None:
payload = _synthetic_error_payload()
assert payload["error_id"] == "abcd1234"
def test_normalized_error_payload_keys_are_frozen() -> None:
normalized = _normalize_terminal_event_payload(
"session.event.error", _synthetic_error_payload()
)
assert set(normalized) == NORMALIZED_ERROR_KEYS
def test_normalized_error_payload_message_carries_ref() -> None:
normalized = _normalize_terminal_event_payload(
"session.event.error", _synthetic_error_payload()
)
assert normalized["message"].endswith("(ref: abcd1234)")
assert normalized["terminal_message"].endswith("(ref: abcd1234)")
# Idempotent under the CLI client's re-normalization pass.
assert append_error_ref(normalized["message"], "abcd1234") == normalized["message"]
def test_error_payload_without_ref_is_unchanged() -> None:
event = ErrorEvent(message="Agent error", code="agent_error")
payload = asdict(event)
payload.pop("kind")
normalized = _normalize_terminal_event_payload("session.event.error", payload)
assert "(ref:" not in normalized["message"]
def test_channel_reply_carries_ref() -> None:
# Pins the exact composition both channel_dispatch ErrorEvent sites use —
# if a refactor stops threading the ref into channel replies, this fails.
from opensquilla.gateway.channel_dispatch import _terminal_payload_from_error_event
from opensquilla.session.terminal_reply import build_terminal_reply
event = ErrorEvent(message="Agent error", code="agent_error", error_id="abcd1234")
reply = append_error_ref(
build_terminal_reply(_terminal_payload_from_error_event(event)),
event.error_id or None,
)
assert reply == "The task failed before it could finish. (ref: abcd1234)"