Skip to content

fix(test-controller): sandbox gate returns PERMISSION_DENIED, but the mode-gate storyboard requires FORBIDDEN #1011

Description

@numarasSigmaSoftware

adcp/server/test_controller.py's sandbox-authority gate denies live-mode callers with PERMISSION_DENIED:

# adcp/server/test_controller.py — _apply_sandbox_gate
        return _controller_error(
            "PERMISSION_DENIED",
            ...

The AdCP 3.1.1 conformance storyboard that grades this exact path requires FORBIDDEN:

# dist/compliance/3.1.1/universal/comply-controller-mode-gate.yaml
# phase live_account_denial → step deny_live_caller
        validations:
          - check: field_value
            path: 'error'
            allowed_values:
              - 'FORBIDDEN'
            description: 'Error code is FORBIDDEN — seller detected live-mode account and denied dispatch before scenario execution'

The storyboard's own narrative is explicit that this is the graded behavior: "the seller MUST refuse with error: FORBIDDEN before dispatching any scenario," and it describes itself as "the keystone for the (Sandbox) AAO Verified tier."

The prose spec agrees on the code and scopes it precisely — dist/docs/3.1.1/building/by-layer/L3/comply-test-controller.mdx, §Sandbox gating: "FORBIDDEN is reserved for the in-sandbox case where the caller is authorized to call the controller but params reference a non-sandbox account."

Effect: a seller wiring dispatch(...) with an account_resolver and returning its result verbatim fails comply_controller_mode_gate, because the graded field is an exact-match allowlist of one value.

Note the adjacent semantic distinction, in case it affects the fix: the same spec section requires that a live-mode caller probing the tool by name receive the transport's standard unknown-tool error (indistinguishable from a seller that never implemented the controller) rather than any authorization error — FORBIDDEN is only for the in-sandbox caller referencing a non-sandbox account. If the current gate is intended to cover the probing case rather than the referenced-account case, then the divergence may be one of layering rather than a simple string.

Verified against adcp==6.6.0; a2a-sdk==1.0.1.

Related: #1007, #1008, #1009, #1010 (other A2A/server seam reports from the same audit).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions