Skip to content

spec(errors): register details.scope vocabulary + reason sub-discriminator (follow-up to #3831, #3871) #3883

Description

@bokelley

Summary

Building on adcp#3831 (registered BILLING_NOT_PERMITTED_FOR_AGENT with error.details.scope-discriminated recovery shape) and adcp#3871 (proposed AGENT_SUSPENDED / AGENT_BLOCKED), there's a follow-up gap: the details.scope and details.reason axes inside error-details/ schemas need explicit registration to prevent cross-language SDK drift.

Background

The JS SDK's BuyerAgentRegistry implementation (adcontextprotocol/adcp-client#1269) emits the following structured-error shapes today as Phase 1 placeholders:

Code details.scope details.reason / details.status Source
PERMISSION_DENIED 'agent' status: 'suspended' | 'blocked' Stage 4 of #1269 (status enforcement)
PERMISSION_DENIED 'agent' reason: 'sandbox-only' Phase 1.5 of #1269 (sandbox_only enforcement)

adcp#3831 introduced BILLING_NOT_SUPPORTED.scope: 'capability' \| 'account' as the discriminator pattern. The JS SDK extends that with 'agent', but neither the values nor the sibling keys (status, reason) are registered in error-details/ schemas yet.

Proposal

1. Register details.scope as a shared enum in core/error.json (or a new error-details/scopes.json):

```json
{
"scope": {
"type": "string",
"enum": ["capability", "account", "agent"],
"description": "Discriminator for which axis of the seller's relationship triggered this error. Established values: 'capability' (seller-wide), 'account' (per-account-relationship), 'agent' (per-buyer-agent)."
}
}
```

2. Register error-details/agent-permission-denied.json with additionalProperties: false:

```json
{
"type": "object",
"properties": {
"scope": { "const": "agent" },
"status": { "enum": ["suspended", "blocked"] },
"reason": { "enum": ["sandbox-only"] }
},
"required": ["scope"],
"additionalProperties": false,
"description": "When emitted with PERMISSION_DENIED, identifies the per-agent gate that fired. Exactly one of status / reason populates depending on the gate."
}
```

3. Document the cross-tenant onboarding oracle clamp mirroring adcp#3831's posture for BILLING_NOT_PERMITTED_FOR_AGENT:

  • Sellers MUST emit details.scope: 'agent' ONLY when buyer-agent identity has been established via signed-request derivation OR a credential-to-agent mapping in the seller's onboarding record.
  • Bearer-credentials-without-mapping → uniform-response shape (PERMISSION_DENIED without details.scope).
  • Same threat model as *_NOT_FOUND family.

Why this matters

  • Cross-language parity. Without registered values, every SDK invents its own discriminator. Python SDK (adcp-client-python), future Go/Rust/etc SDKs all need the same vocabulary.
  • Buyer dispatch. Buyers can switch on details.scope + details.status / details.reason without parsing prose, same way they do with error.code today.
  • Schema validation. additionalProperties: false prevents adopters inventing reason: 'over-quota' in incompatible prose.

Cross-links

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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