Skip to content

fix: REST has no catch-all envelope for untyped top-level failures (cross-transport parity) #1706

Description

@numarasSigmaSoftware

Follow-up from #1547 re-review. The "top-level / pre-handler failure surfaces the AdCP two-layer error envelope on the wire (not a transport-native error)" contract is currently graded — and on REST, implemented — only partially across transports.

State per transport

  • A2A — covered. on_message_send's outer handler returns a failed Task carrying the two-layer envelope; graded by @T-UC-002-ext-nl-unsupported (A2A-only, correctly, since NL dispatch is an A2A entry point).
  • MCP — envelope is carried. _translate_to_tool_error (src/core/tool_error_logging.py) runs normalize_to_adcp_error() on any exception and builds the two-layer envelope, so an untyped top-level failure still surfaces AdCPToolError with the envelope. (Message-scrub for untyped exceptions is the separate concern in fix: sanitize untyped-error messages before the buyer-facing wire/webhook #1587.)
  • RESTgap. src/app.py registers exception handlers for AdCPError, ValueError, RequestValidationError, PermissionError, and ToolError, but there is no @app.exception_handler(Exception) catch-all. An untyped top-level exception (e.g. RuntimeError, a DB/OS error) escaping a REST endpoint gets FastAPI's default bare 500 with no AdCP envelope.

Proposed fix

  1. Add a catch-all @app.exception_handler(Exception) in src/app.py that routes through safe_adcp_error(exc) (leak-safe: untyped → generic SERVICE_UNAVAILABLE envelope, no str(exc)) and returns _envelope_response(...), mirroring the MCP boundary. Framework exceptions with their own handlers (HTTPException, RequestValidationError) are unaffected — a generic Exception handler only catches what no more-specific handler claims.
  2. Add per-transport integration tests grading "untyped top-level failure → two-layer envelope" on REST and MCP (A2A already covered), each asserting the same envelope values in that transport's shape.

Scope note

Split out of #1547 deliberately: #1547's scope is A2A error routing, and a REST catch-all changes every REST endpoint's untyped-500 shape — a cross-transport parity change that deserves its own review. Adjacent to #1587 (which scrubs the untyped-error message); the catch-all here should use the same safe_adcp_error sink, so the two are natural to land together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions