Skip to content

Do not unwrap advisory errors[] as failures when success payload is present #2014

@bokelley

Description

@bokelley

Summary

The SDK response unwrap path treats any top-level errors[] as a failure, even when AdCP 3.1 permits non-fatal advisory errors[] alongside a populated success response.

In the AdCP training-agent storyboards, this forced a canonical-format advisory step to use raw MCP auth so the compliance runner could validate the actual wire response instead of having the SDK unwrap it as an exception/failure.

Repro from adcontextprotocol/adcp

Branch/workspace context: bokelley/finish-4591, AdCP 3.1.0-beta.4, SDK @adcp/sdk 7.11.

The storyboard step is in:

static/compliance/source/protocols/media-buy/scenarios/canonical_formats.yaml

The step get_seeded_divergent_product returns a populated get_products success response with a non-fatal producer advisory:

validations:
  - check: response_schema
    description: "Divergent dual-emission remains a populated get_products success response"
  - check: field_value
    path: "products[0].product_id"
    value: "canonical_formats_divergent_display"
  - check: field_value
    path: "errors[0].code"
    value: "FORMAT_DECLARATION_DIVERGENT"
  - check: field_value
    path: "errors[0].source"
    value: "producer"

The intended response shape is roughly:

{
  "status": "completed",
  "products": [
    { "product_id": "canonical_formats_divergent_display" }
  ],
  "cache_scope": "public",
  "errors": [
    {
      "code": "FORMAT_DECLARATION_DIVERGENT",
      "source": "producer",
      "message": "..."
    }
  ]
}

When called through the current SDK path, the top-level errors[] is treated as a failure even though success-arm fields are present. Using raw MCP auth preserves the wire response and the storyboard passes:

TENANT_PATH=sales PUBLIC_TEST_AGENT_TOKEN=${PUBLIC_TEST_AGENT_TOKEN:-storyboard-local-token} \
  npx tsx server/tests/manual/run-storyboards.ts \
  --filter canonical_formats --verbose

Result:

media_buy_seller/canonical_formats
✓ 12P / 0S / 0N/A

Expected behavior

The SDK unwrap logic should distinguish terminal error-only responses from success responses that carry advisory errors[].

At minimum, it should not throw/unwrap as a failure when:

  • errors[] is present, and
  • the response also contains success-arm fields for the task, such as products[] for get_products, or an async submitted envelope where that schema permits advisory errors.

Envelope-only fields such as context, ext, status, task_id, or version fields should not by themselves make { errors, ... } a success. The discriminator should be task/schema-aware or use an explicit success-field allowlist.

Why this matters

AdCP 3.1 uses advisory errors[] for non-fatal producer warnings. SDK callers need access to the success payload plus advisory details; treating every top-level errors[] as terminal forces buyers and compliance tests onto raw MCP for valid 3.1 responses.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions