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
Summary
Building on adcp#3831 (registered
BILLING_NOT_PERMITTED_FOR_AGENTwitherror.details.scope-discriminated recovery shape) and adcp#3871 (proposedAGENT_SUSPENDED/AGENT_BLOCKED), there's a follow-up gap: thedetails.scopeanddetails.reasonaxes insideerror-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:
details.scopedetails.reason/details.statusPERMISSION_DENIED'agent'status: 'suspended' | 'blocked'PERMISSION_DENIED'agent'reason: 'sandbox-only'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 inerror-details/schemas yet.Proposal
1. Register
details.scopeas a shared enum incore/error.json(or a newerror-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.jsonwithadditionalProperties: 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: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.PERMISSION_DENIEDwithoutdetails.scope).*_NOT_FOUNDfamily.Why this matters
adcp-client-python), future Go/Rust/etc SDKs all need the same vocabulary.details.scope+details.status/details.reasonwithout parsing prose, same way they do witherror.codetoday.additionalProperties: falseprevents adopters inventingreason: 'over-quota'in incompatible prose.Cross-links
scope: 'agent', status: '...')scope: 'agent', reason: 'sandbox-only')