Skip to content

Commit 37d2cda

Browse files
bokelleyclaude
andauthored
fix(webhooks): reject unknown AdCP status in create_a2a_webhook_payload (#605)
The builder previously fell back to ``TASK_STATE_UNSPECIFIED`` when the caller's status didn't match the AdCP→A2A mapping table. The wire normalizer then rewrote that to the literal string ``"unspecified"``, which is not a valid A2A v0.3 ``TaskState`` (the spec enum is submitted | working | input-required | completed | canceled | failed | rejected | auth-required | unknown). Buyer receivers validating against the schema reject the webhook — silent wire-level breakage. Raise ``ValueError`` at the builder boundary instead. The AdCP→A2A mapping is closed, so an unknown value is always a caller bug; failing loud beats producing a payload buyers will reject mid-delivery. Closes #603. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6a06e88 commit 37d2cda

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

src/adcp/webhooks.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,18 @@ def create_a2a_webhook_payload(
574574
# Tolerate the hyphenated form servers may echo back.
575575
"input-required": pb.TaskState.TASK_STATE_INPUT_REQUIRED,
576576
}
577-
task_state_enum = adcp_to_task_state.get(status_value, pb.TaskState.TASK_STATE_UNSPECIFIED)
577+
if status_value not in adcp_to_task_state:
578+
# Falling back to TASK_STATE_UNSPECIFIED would normalize to the
579+
# string ``"unspecified"`` on the wire, which is not a valid A2A
580+
# v0.3 ``TaskState`` — buyer receivers validating against the
581+
# spec reject the webhook. Fail loud at the builder boundary
582+
# instead of producing a silently-broken envelope.
583+
raise ValueError(
584+
f"Unknown AdCP task status {status_value!r}; expected one of "
585+
f"{sorted(set(adcp_to_task_state))}. AdCP→A2A status mapping is "
586+
"closed — an unknown value indicates a caller bug."
587+
)
588+
task_state_enum = adcp_to_task_state[status_value]
578589

579590
# Build parts for the message/artifact.
580591
parts: list[pb.Part] = []

tests/test_webhooks_to_wire_dict.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,17 @@ def test_unsupported_type_raises_type_error() -> None:
127127
"""Silent fallthrough would mask integration bugs — fail loud."""
128128
with pytest.raises(TypeError, match="Unsupported webhook payload type"):
129129
to_wire_dict("not a payload") # type: ignore[arg-type]
130+
131+
132+
def test_a2a_unknown_status_raises_value_error() -> None:
133+
"""Unknown AdCP status must fail at the builder, not produce an
134+
invalid-on-the-wire ``"unspecified"`` TaskState that buyer receivers
135+
reject. (Issue #603.)
136+
"""
137+
with pytest.raises(ValueError, match="Unknown AdCP task status"):
138+
create_a2a_webhook_payload(
139+
task_id="task_123",
140+
status="not-a-real-status", # type: ignore[arg-type]
141+
context_id="ctx_456",
142+
result={"media_buy_id": "mb_1"},
143+
)

0 commit comments

Comments
 (0)