Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/adcp/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,18 @@ def create_a2a_webhook_payload(
# Tolerate the hyphenated form servers may echo back.
"input-required": pb.TaskState.TASK_STATE_INPUT_REQUIRED,
}
task_state_enum = adcp_to_task_state.get(status_value, pb.TaskState.TASK_STATE_UNSPECIFIED)
if status_value not in adcp_to_task_state:
# Falling back to TASK_STATE_UNSPECIFIED would normalize to the
# string ``"unspecified"`` on the wire, which is not a valid A2A
# v0.3 ``TaskState`` — buyer receivers validating against the
# spec reject the webhook. Fail loud at the builder boundary
# instead of producing a silently-broken envelope.
raise ValueError(
f"Unknown AdCP task status {status_value!r}; expected one of "
f"{sorted(set(adcp_to_task_state))}. AdCP→A2A status mapping is "
"closed — an unknown value indicates a caller bug."
)
task_state_enum = adcp_to_task_state[status_value]

# Build parts for the message/artifact.
parts: list[pb.Part] = []
Expand Down
14 changes: 14 additions & 0 deletions tests/test_webhooks_to_wire_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,17 @@ def test_unsupported_type_raises_type_error() -> None:
"""Silent fallthrough would mask integration bugs — fail loud."""
with pytest.raises(TypeError, match="Unsupported webhook payload type"):
to_wire_dict("not a payload") # type: ignore[arg-type]


def test_a2a_unknown_status_raises_value_error() -> None:
"""Unknown AdCP status must fail at the builder, not produce an
invalid-on-the-wire ``"unspecified"`` TaskState that buyer receivers
reject. (Issue #603.)
"""
with pytest.raises(ValueError, match="Unknown AdCP task status"):
create_a2a_webhook_payload(
task_id="task_123",
status="not-a-real-status", # type: ignore[arg-type]
context_id="ctx_456",
result={"media_buy_id": "mb_1"},
)
Loading