> ## Documentation Index
> Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
> Use this file to discover all available pages before exploring further.

# エラーハンドリング

> AdCP のエラーハンドリング: プロトコルエラー、タスク失敗、バリデーションエラーの標準コード、復旧戦略、指数バックオフによるリトライロジック。

AdCP は全オペレーションで一貫したエラーハンドリングを行います。エラー分類を理解し、適切な復旧戦略を実装することが堅牢な統合には不可欠です。

## 準拠レベル

セラーはエラーハンドリングを段階的に採用できます。各レベルは前のレベルの上に構築されます:

| Level       | 実装する内容                                                                                                                        | エージェントができること                             |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| **Level 1** | すべてのエラーに `code` と `message` を返す                                                                                               | エラーコードで失敗を分類できる                          |
| **Level 2** | `recovery`、`retry_after`、`field`、`suggestion` を追加する                                                                           | 一時的エラーの自動リトライと修正可能なエラーの自己訂正ができる          |
| **Level 3** | [トランスポートバインディング](/docs/building/operating/transport-errors) で MCP の `structuredContent` または A2A のアーティファクト `DataPart` にエラーを入れる | プログラム的クライアントがテキスト解析なしに型付きエラーオブジェクトを取得できる |

**Level 1** は準拠実装の最低要件です。**Level 2** でエージェント主導の復旧が可能になります — `recovery` がなければエージェントはエラーコードから推測するしかありません。**Level 3** で `@adcp/client` のようなクライアントライブラリが完全な型付きエラーオブジェクトを提供できます。

## エラーの分類

### 1. プロトコルエラー

AdCP ビジネスロジック外の通信・接続問題:

* ネットワークタイムアウト
* 接続拒否
* TLS/SSL エラー
* JSON パースエラー

**対応:** 指数バックオフでリトライします。

### 2. タスクエラー

`status: "failed"` で返るビジネスロジックの失敗:

* 在庫不足
* 無効なターゲティング
* 予算バリデーション失敗
* リソース未検出

**対応:** `recovery` フィールドを確認して、リトライするか、リクエストを修正するか、エスカレートするかを判断します。

### 3. バリデーションエラー

スキーマ検証に失敗する不正リクエスト:

* 必須項目の欠落
* 無効な型
* 範囲外の値

**対応:** リクエスト形式を修正して再送します（多くは開発時の問題です）。

## エラーレスポンス形式

失敗した処理はステータス `failed` とエラー詳細を返します。エラーオブジェクトは [`error.json`](https://adcontextprotocol.org/schemas/v3/core/error.json) スキーマに従います:

```json theme={null}
{
  "status": "failed",
  "message": "Budget is below the seller's minimum for this product",
  "errors": [
    {
      "code": "BUDGET_TOO_LOW",
      "message": "Budget is below the seller's minimum for this product",
      "recovery": "correctable",
      "field": "budget.total",
      "suggestion": "Increase budget to at least 500 USD",
      "details": {
        "minimum_budget": 500,
        "currency": "USD"
      }
    }
  ]
}
```

### エンベロープ vs. ペイロードエラー — 二層モデル

AdCP はエラーを 2 つの異なる場所で公開し、実装者は状況に応じて正しい層を設定する必要があります。これはエージェントとストーリーボードの間でエラー形状のドリフトが起きる最も一般的な原因です。

| 層                 | キー                                                  | いつ設定するか                                 | 形状                                                                                       |
| ----------------- | --------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------- |
| **タスクペイロード**      | `payload.errors[]`（またはトランスポートに応じてトップレベル `errors[]`） | タスクが実行され、ペイロードが 1 つ以上の問題（致命的または非致命的）を報告 | [`error.json`](https://adcontextprotocol.org/schemas/v3/core/error.json) に従うエラーオブジェクトの配列 |
| **トランスポートエンベロープ** | `adcp_error`                                        | タスクが失敗し、トランスポートに型付き・抽出可能なシグナルが必要        | [`error.json`](https://adcontextprotocol.org/schemas/v3/core/error.json) に従う単一のエラーオブジェクト |

**致命的なタスク失敗は両方の層を設定すべきです（SHOULD）。** ペイロードは任意のプロトコルがそのまま読める構造化された `errors[]` 配列を運び、トランスポートエンベロープは MCP/A2A クライアントがペイロードを再解析せずに型付きエラーを抽出できるよう `adcp_error` を運びます。2 つのうち片方だけを設定するのが、ほとんどの相互運用バグの原因です — トランスポートエンベロープを読むランナーはエラーを見ず、ペイロードを読むランナーはトランスポート上にエラーシグナルを見ません:

```json theme={null}
// MCP — structuredContent AND payload both carry the error
{
  "content": [{"type": "text", "text": "{\"adcp_error\":{\"code\":\"BUDGET_TOO_LOW\", ...}}"}],
  "isError": true,
  "structuredContent": {
    "adcp_error": { "code": "BUDGET_TOO_LOW", "message": "...", "recovery": "correctable" },
    "payload": {
      "errors": [
        { "code": "BUDGET_TOO_LOW", "message": "...", "recovery": "correctable", "field": "budget.total" }
      ]
    }
  }
}
```

```json theme={null}
// A2A — artifact DataPart carries adcp_error; if the agent also surfaces payload via a sibling DataPart, errors[] lives there
{
  "status": { "state": "failed" },
  "artifacts": [{
    "artifactId": "error-result",
    "parts": [
      { "kind": "data", "data": { "adcp_error": { "code": "BUDGET_TOO_LOW", ... } } },
      { "kind": "data", "data": { "errors": [{ "code": "BUDGET_TOO_LOW", "field": "budget.total", ... }] } }
    ]
  }]
}
```

**非致命的なエラーはペイロードのみを設定します。** 警告を報告する `status: "submitted"` または `status: "input-required"` タスク（例: 「このメディアバイは手動承認が必要です — \[警告詳細]」）は、ペイロードの `errors[]` に `severity: "warning"` を設定しますが、`adcp_error` を設定してはなりません（MUST NOT）。トランスポートエンベロープは「タスクが失敗した」を示すものであり、警告チャネルではありません。

**ストーリーボードバリデーター。** 失敗したタスクのエラーコードをアサートする際は、`check: field_present, path: "errors"` ではなく `check: error_code` を優先してください。`error_code` は形状非依存です — ランナーは `adcp_error.code`（トランスポート）または `errors[0].code`（ペイロード）のいずれかから解決します。直接の `path: "errors"` チェックはアサーションをペイロード形状に固定し、エージェントがコンフォーマントであってもトランスポートエンベロープ経由でのみエラーを表面化するエージェントに対して失敗します。[Storyboard authoring — Asserting on errors](/docs/contributing/storyboard-authoring#asserting-on-errors)を参照してください。

**判別付き拒否アーム。** タスクレスポンスが構造化された拒否アーム（例: `AcquireRightsRejected`、`CreativeRejected` — ルールは [`GOVERNANCE_DENIED`](https://adcontextprotocol.org/schemas/v3/enums/error-code.json) のワイヤー配置ガイダンスを参照）を定義する場合、スペック的に正しい拒否レスポンスはワイヤー上にエラーコードを運びません — 拒否アームはスキーマ層で `not: { required: [errors] }` を強制します。`check: error_code` のアサートはコンフォーマントなエージェントに対して失敗します。代わりに判別子でアサートしてください: `check: field_value, path: "status", value: "rejected"`。これは `acquire_rights` のガバナンス拒否と `creative_approval` のポリシー拒否のパターンです。2 つのパスを混在させるアサーション（拒否アームを持つタスクに `error_code`、持たないタスクに `field_value`）は、非スペックの見解をストーリーボードに焼き込みます。

### エラーオブジェクトのフィールド

これらのフィールドは [`error.json`](https://adcontextprotocol.org/schemas/v3/core/error.json) スキーマで定義されています:

| Field         | Type   | Required | Description                                                                                                                                                                                                                                                                                                                                |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `code`        | string | Yes      | [標準語彙](#標準エラーコード)またはセラー固有の機械判読用エラーコード                                                                                                                                                                                                                                                                                                      |
| `message`     | string | Yes      | 人向けのエラー説明                                                                                                                                                                                                                                                                                                                                  |
| `recovery`    | string | No       | エージェントの復旧分類: `transient`、`correctable`、`terminal`                                                                                                                                                                                                                                                                                          |
| `retry_after` | number | No       | リトライまでの待機秒数（一時的エラー）                                                                                                                                                                                                                                                                                                                        |
| `field`       | string | No       | JSONPath-lite 形式のフィールドパス（例: `packages[0].targeting`）。`issues` が存在する場合、セラーはこれを RFC 6901 から JSONPath-lite に変換した `issues[0].pointer` に設定しなければなりません（MUST、例: `/packages/0/targeting` → `packages[0].targeting`）。将来のメジャーバージョンで非推奨になります。                                                                                                          |
| `issues`      | array  | No       | バリデーション失敗の構造化リスト。各エントリは `pointer`（RFC 6901）、`message`、`keyword`（拒否した JSON Schema キーワード — `required` / `type` / `format` など）、および任意で `schema_id`、`schemaPath`、`discriminator` を運びます。`schema_id` / `schemaPath` / `discriminator` のセマンティクス、本番発行ルール、`schema_id` の解決パスは [Validator-internals フィールド](#validator-internals-フィールドissues)を参照してください。 |
| `suggestion`  | string | No       | エラーの修正提案                                                                                                                                                                                                                                                                                                                                   |
| `details`     | object | No       | 追加のコンテキスト固有情報。セラーは pre-3.1 コンシューマーとの後方互換性のため `issues[]` をここに `details.issues` としてミラーしてもよい（MAY）。新しいコンシューマーはトップレベルの `issues` フィールドを優先すべきです（SHOULD）。                                                                                                                                                                                          |

### Validator-internals フィールド（`issues`）

各 `issues[]` エントリの 3 つの任意フィールドは、ペイロードを拒否したスキーマ要素を名指しするため、エージェントはバリエーションを探る代わりに 1 回のイテレーションでバリデーションエラーから復旧できます:

| Field           | Shape                                                                | Purpose                                                                                                           |
| --------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `schema_id`     | string — 公開された `$id`（例: `/schemas/3.1.0/core/activation-key.json`）   | 拒否した（サブ）スキーマの正準名。3.1+ コンシューマーの主要ハンドル。                                                                             |
| `schemaPath`    | string — JSON Schema ツリーパス（例: `#/properties/packages/items/oneOf/1`） | バリデーター内部のトラバーサル。3.0.x 後方互換のため保持。3.1+ コンシューマーは `schema_id` を優先すべき（SHOULD）。（将来のメジャーで `schema_path` にリネーム。）          |
| `discriminator` | array of `{property_name, value}`                                    | const 判別の `oneOf` / `anyOf` についてバリデーターが選択したバリアント。ペイロード内に存在する値がソース。OpenAPI 3.x の `discriminator.propertyName` と整合。 |

**`schema_id` の解決。** 文字列はスキーマの `$id` です。スキーマを読み込むには:

* **HTTPS 正準:** `https://adcontextprotocol.org` を前置します（例: `https://adcontextprotocol.org/schemas/3.1.0/core/activation-key.json`）。キャッシュ可能。バージョンごとに不変。
* **SDK バンドル:** `@adcp/sdk` と `adcp-client-python` はオフライン解決のためスキーマバンドルを同梱します — バンドルツリーに対して `$id` で検索します。
* **バンドルツリーの注意点。** 事前解決されたバンドルツリー（`/schemas/{version}/bundled/...`）から提供されるツールは、`$id` を保持したままサブスキーマをインライン化します（3.1+、#3868 を参照）— ただしバンドル内の各サブスキーマの**最初の出現**でのみです。同じソーススキーマが複数の同居場所から参照される場合、`$id` は最初のインラインにのみ付与され、後続の出現は、SDK のエラーレポートがスキーマツリーを遡る際に最も近い `$id` を持つ祖先（通常はレスポンスルート）にフォールバックします。サブツリーに巻き上げられた `$defs` 参照を含むサブスキーマも、`$id` を保持するとローカルフラグメント解決が壊れるため、バンドル時に `$id` が剥がされます。#3868 より前に生成されたバンドルを読むコンシューマーはレスポンスルートの `$id` のみを見ます。`$id` が `/bundled/<tool>-response.json` で終わるかを確認して pre-#3868 のケースを検出し、その場合はバンドルスキーマを `pointer` で辿ってフォールバックします。

**判別子のセマンティクス。** セラーは、(a) 拒否したスキーマが const 判別の `oneOf` / `anyOf` であり、(b) 判別子プロパティがペイロードに存在する場合にのみ `discriminator` を設定します。ワイヤーフィールドは呼び出し元が送った値を報告します — 部分一致ヒューリスティクスに基づくバリデーターの推論ではありません — ため、Ajv、Python `jsonschema`、`gojsonschema` にわたって決定論的です。生き残るバリアントがゼロの場合、セラーは `discriminator` を省略しなければなりません（MUST、省略が「バリデーターがターゲットバリアントを局所化できなかった」というエージェントへのシグナル）。複合判別子（例: `audience-selector` の `(type, value_type)`）の場合、エントリは拒否したスキーマの `properties` ブロックでの宣言順に並びます。

判別子プロパティがペイロードから*欠落*している場合（バリデーターがブランチ選択すら開始できない）、セラーは `discriminator` を省略します — 復旧シグナルは、欠落した判別子プロパティを名指す `keyword: "required"` を持つ兄弟の `issues[]` エントリから来ます。「必須の判別子フィールドが欠落」＋「この issue に `discriminator` なし」を読むエージェントは、名指されたプロパティを設定して復旧します。バリアントに一致しなかった値を持つ `discriminator` 配列を見るエージェントは、別の値に切り替えて復旧します。

**本番発行ルール（公開スペックのスタンス）。** 3 つのフィールドはすべて、**拒否した要素がセラーが `get_adcp_capabilities` で宣伝するバージョンの公開スペックに存在する場合**、本番で発行しても安全です。根拠は replay-locally です: スキーマは adcontextprotocol.org で公開され、すべての SDK にバンドルされているため、同じペイロードに対して同じバリデーターを実行する敵対者は同じブランチ選択を導出します — ワイヤーフィールドは彼らが計算できない情報を運びません。

replay-locally の議論には、セラーが順守しなければならないカーブアウトがあります:

* **プライベート拡張。** カスタム `oneOf` ブランチ、サーバー専用のサブスキーマ、`additionalProperties: true` で重ねた enum サブセットを実行するセラーは、拒否した要素が公開スペックに存在しない場合、`schema_id`、`schemaPath`、`discriminator` を発行してはなりません（MUST NOT）。発行は、敵対者が再現できないセラー内部のバリデーション状態を漏らします。*実装:* 公開 + プライベートの混在バリデーションツリーを実行するセラーは通常、(a) 公開専用とプライベート専用のバリデーターを別々にコンパイルし公開ランからのみ `schema_id` を発行するか、(b) 各 `$id` をソースバンドルにマッピングするサイドテーブルでコンパイル済みスキーマを計装します。
* **バージョンスキュー。** プレリリースまたはポストリリースのスキーマに対して検証するセラーは、`get_adcp_capabilities` で名指したバージョンの公開バンドルに存在しない `$id` を持つ `schema_id` を発行してはなりません（MUST NOT）。
* **サーバー狭窄化された公開要素。** セラーがサーバー側で公開 enum、パターン、数値範囲をテナント固有のサブセットに絞る場合（例: 公開スキーマでは `["a","b","c"]` を受け入れるが、この呼び出し元には `["a"]` 以外をすべて拒否）、セラーは公開 `schema_id` に対して `keyword: "enum"`（または `pattern` / `minimum` / `maximum`）で `VALIDATION_ERROR` を返してはなりません（MUST NOT）。公開スペックの再現は値を受け入れますが、セラーはプライベート状態で拒否します。拒否が公開スキーマ要素に誤帰属されないよう、代わりに `POLICY_VIOLATION` または `UNSUPPORTED_FEATURE` を使用します。「公開再現は受け入れ、セラーは拒否」の構造的差分自体が、セラーのプライベートサブセットの指紋です。
* **カスタムキーワード。** `keyword` は JSON Schema Draft 7 / 2020-12 の語彙から取らなければなりません（MUST）— バリデーター固有のカスタムキーワード（Ajv `addKeyword`、`instanceof`）を使うセラーはそれらをワイヤーに発行してはなりません（MUST NOT）。
* **プローブの簡潔性。** セラーは、上記のカーブアウトが適用されない場合でも、本番エンベロープを簡潔に保つため、これら 3 つのフィールドをレート制限されたエンドポイントの dev/sandbox レスポンスにスコープしてもよい（MAY）。フィールドの省略は常にコンフォーマントです。

## 標準エラーコード

標準エラーコードは [`error-code.json`](https://adcontextprotocol.org/schemas/v3/enums/error-code.json) で定義されています。語彙は**オープン**です: `error.code` はワイヤー上 `string` として型付けされ、標準コードは文書的で、送信者は標準セット外のコードを発行してもよい（MAY）。

### Forward-compatible decoding（規範的）

**エラーコード語彙はオープンです。** `error.code` は [`core/error.json`](https://adcontextprotocol.org/schemas/v3/core/error.json) で `string` として型付けされています — 閉じた enum ではありません — ため、厳格な JSON Schema バリデーターは任意の文字列値を受け入れなければなりません（MUST）。`error-code.json` の標準語彙は文書的であり、ワイヤーレベルで送信者も受信者も制約しません。

**受信者は未知のコードをデコードしなければなりません（MUST）。** AdCP バージョン X にピン留めされた受信者が、バージョン X+1 で導入された `error.code`（または標準語彙外のプラットフォーム固有コード）を運ぶレスポンスをデコードする場合:

1. レスポンスを整形式として扱う — エンベロープを拒否したり、デシリアライズ例外を投げたり、汎用プロトコルエラーに格下げしたりしてはなりません（MUST NOT）。
2. 存在する場合、`error.recovery`（エラーエンベロープのトップレベルフィールド）から復旧分類を回復します。`error.recovery` が規範的なキャリアであり、`error-code.json` の `enumMetadata.recovery` は文書的なミラーです。
3. `error.recovery` が不在の場合（レガシー送信者）、保守的なデフォルトを適用します。`transient` が未知のコードの安全なデフォルトです — リトライ・ウィズ・バックオフは terminal 分類より悪くなり得ず、マニフェストの [`error_code_policy.default_unknown_recovery`](https://adcontextprotocol.org/schemas/v3/manifest.schema.json) がこれを正準のフォールバックとして文書化しています。`transient` デフォルトは [§ リトライロジック](#リトライロジック)のリトライルールで**制限されます** — 受信者は `maxRetries` とジッター付き指数バックオフスケジュールを適用しなければならず（MUST）、`transient` デフォルトで無限にループしてはなりません（MUST NOT）。敵対的またはバグのある送信者は、コンフォーマントなクライアントに対して無制限のリトライを誘発できません。オープン enum のデコードルールは受信者をリトライバジェットから免除しません。

**送信者は受信者のピン留め語彙外のコードを発行してもよい（MAY）。** 3.1 時代のコード（例: `PROPOSAL_NOT_FOUND`）を 3.0 ピン留めの受信者に発行する送信者はスペックに違反しません — 受信者は上記ルールによりそれを処理する必要があります。送信者が受信者のピン留めバージョンを知る場合（`adcp_version` エンベロープエコーまたはケイパビリティディスカバリー経由）、同等が存在するときはそのバージョンの語彙からコードを優先すべきです（SHOULD）。同等がないときは、より新しいまたはプラットフォーム固有のコードを発行してもよい（MAY）。

**送信者はすべてのエラーで `error.recovery` を設定しなければなりません（MUST）。** これはバージョンスキューにわたる復旧セマンティクスの規範的なキャリアです — 受信者は知らないコードを確実に分類できませんが、常に `error.recovery` を読めます。省略は forward-compat ルールを無効にします。

**なぜ重要か。** Forward-compatible デコードは、将来のメンテナンスライン（3.1.x、4.0.x、…）が古い受信者を壊さずに新しいエラーコードを追加的に出荷できるようにするワイヤーレベルの不変条件です。それがなければ、すべての新コードは次のマイナーまで保留されるワイヤー変更です — これが 3.0.x の現在の drift-lint ポリシーです。3.1+ でこれを備えると、新コードはメンテナンスラインの寿命中に登録でき、これはアダプターの実世界の拒否パスが新コードを表面化する際に重要です。

**3.0.x ポリシーは変更なし。** 3.0.x 受信者はこの規範ルールより前なので、3.0.x はサポート期間の残りの間ワイヤー安定を保ちます — 新コードは依然として次のマイナーで着地します。このルールは 3.1 以降の受信者契約を定めます。

**Not-found の優先順位。** 参照された識別子が解決しない場合、解決される型がリクエストから既知であれば、セラーはリソース固有のコードを返すべきです（SHOULD）: `product_id` には `PRODUCT_NOT_FOUND`、`package_id` には `PACKAGE_NOT_FOUND`、`media_buy_id` には `MEDIA_BUY_NOT_FOUND`、`creative_id` には `CREATIVE_NOT_FOUND`、`signal_id` には `SIGNAL_NOT_FOUND`、SI `session_id` には `SESSION_NOT_FOUND`、`account_id` には `ACCOUNT_NOT_FOUND`、ガバナンス `plan_id` には `PLAN_NOT_FOUND`。専用コードのないリソース型（例: プロパティリスト、コンテンツ基準、権利付与、SI オファリング、プロポーザル、カタログ、イベントソース、コレクションリスト、ブランド、個別プロパティ）には `REFERENCE_NOT_FOUND` にフォールバックします。専用の標準コードを欠く型付きパラメーターは、カスタムの `*_NOT_FOUND` コードを作るのではなく `REFERENCE_NOT_FOUND` を使わなければなりません（MUST）— 語彙は上流のスペック変更で成長し、セラーごとのインフレでは成長しません。クライアントは最初に `error.code` で分岐すべきです（SHOULD）。リソース固有のコードにより、クライアントは `error.field` を解析せずにディスパッチできます。

**多相パラメーター。** 未解決の識別子が多相または型なしパラメーター（複数のリソース型を受け入れるフィールド）経由で供給された場合、解決される型にリソース固有のコードが存在しても、セラーは `REFERENCE_NOT_FOUND` を使わなければなりません（MUST）。多相パラメーターで型固有のコードを使うと、解決される型が未認可の呼び出し元に漏れます。多相性はツールスキーマのパラメーターの宣言された形状に対して — **いかなるルックアップの前にも** — 評価されるため、汎用の `reference_id` パラメーターは id が何に解決されるかに関わらず `REFERENCE_NOT_FOUND` にディスパッチします。ディスパッチ後に解決される型で評価すると漏洩が再導入されます。

ツールの宣言されたパラメーター形状は、あるツールバージョンについてすべての呼び出し元にわたって同一でなければならず（MUST）、ディスパッチルールは呼び出し元のアイデンティティに条件付けられてはなりません（MUST NOT）。テナント B には `property_list_id` を、テナント A には `reference_id` のみを公開するスキーマは、ケイパビリティディスカバリー自体を列挙オラクルに変えます（2 つのアイデンティティでスキーマを読み、diff を取る）。

**アクセス不能な参照への統一レスポンス。** 統一レスポンス要件はこの語彙の**すべての** not-found コード（`REFERENCE_NOT_FOUND`、`SIGNAL_NOT_FOUND`、`CREATIVE_NOT_FOUND`、`MEDIA_BUY_NOT_FOUND`、`PACKAGE_NOT_FOUND`、`SESSION_NOT_FOUND`、`ACCOUNT_NOT_FOUND`、`PLAN_NOT_FOUND`）に適用されます: セラーは「存在するが呼び出し元にアクセス権がない」に対して「存在しない」と同じレスポンスを返さなければなりません（MUST）。2 つを決して区別しないでください — これがクロステナント列挙が着地する方法です。

この MUST は `error.code` だけでなく、すべての観測可能なチャネルをカバーします:

* **エラーオブジェクト。** `error.code`、`error.message`、`error.field`、`error.details` は 2 つのケース間でバイト等価でなければなりません（MUST）。`REFERENCE_NOT_FOUND` にフォールバックする型付きパラメーターでは、`error.field` は true-miss と resolve-then-deny にわたって同一でなければならず（MUST）— 両方で省略されるか、両方で型中立な名前に置換されます。`error.field` は、パラメーター名が型中立な場合（例: `reference_id`）に入力パラメーターを名指してもよい（MAY）。元のパラメーター名が型を明かす場合（例: `property_list_id`）、`error.field` は省略されるか中立な名前に置換されなければなりません（MUST）。`error.message` は汎用でなければなりません（MUST、`"Property list not found"` のようなリソース修飾テキストなし）。`REFERENCE_NOT_FOUND` については特に、セラーは `error.field`、`error.details`、リソース修飾された `error.message` を通じて解決される型を漏らしてはなりません（MUST NOT）。パラメーターが配列（例: `catalog_ids`、`format_ids`）の場合、`error.field` は配列パラメーター自体を名指さなければなりません（MUST）。セラーは `error.details` で特定の解決不能な要素を列挙してもよい（MAY）— ただし要素が呼び出し元によってそのまま供給された場合のみです。セラーは要素レベルで「供給された要素は解決したが呼び出し元が未認可」を「供給された要素は存在しない」と区別してはなりません（MUST NOT）。それは配列エントリの粒度で列挙オラクルを再導入します。
* **トランスポートステータス。** HTTP ステータスコード、A2A `task.status.state`、MCP `isError` は 2 つのケース間で同一でなければなりません（MUST）。
* **レスポンスヘッダー。** `ETag`、`Cache-Control`、リソース型ごとのレート制限バケット、CDN タグ、および値または存在がリソース型で異なる任意のヘッダーは同一でなければなりません（MUST）。
* **副作用。** Webhook ディスパッチと監査ログ書き込みは同一でなければなりません（MUST）— resolve-then-deny パスは、true-miss がしないような形で、テナント監査行を書いたり、バックグラウンド作業（検索インデクサー更新、キャッシュウォーマー、アクセスログアグリゲーター）をキューイングしたり、リソース型ごとのクォータ/レート制限カウンターをインクリメントしたり、任意のサブスクライバー（リソース所有者を含む）に Webhook を発火したりしてはなりません（MUST NOT）。resolve-then-deny パスがテナントごとの DB シャードまたはキャッシュに触れる場合、共同テナントの観測者がストレージ層メトリクスで区別できないよう、true-miss パスも同じ形状のストレージに触れなければなりません（MUST、例: 両方をテナント非依存のリゾルバー経由でルーティング）。
* **可観測性。** ダウンストリームのログ、APM スパン、サードパーティのエラーレポートテレメトリ（Sentry、Datadog、Rollbar など）は、呼び出し元にアクセス権がないとき解決されるリソース型でタグ付けされてはなりません（MUST NOT）。true-miss が発するトレースは resolve-then-deny が発するトレースと構造的に区別不能でなければなりません（MUST）。

レイテンシーの等価性を別個の要件ではなく帰結にするため、セラーは両方のパスで同じ形状の解決・認可作業を実行しなければなりません（MUST）— **resolve-then-authorize**、「未知の id」でショートサーキットしてはなりません。true-miss でも、セラーは同等の形状の認可判断を実行しなければなりません（MUST、例: 空のプリンシパルセットに対して、または呼び出し元自身のテナントをデコイとして）。これにより ACL グラフのサイズと形状で変わる認可者レイテンシーがサイドチャネルにならないようにします。ルックアップ前の入力検証（UUID 形式、長さ、正規表現）は、リクエスト内容のみで決定論的である場合に限り許可されます（同じ入力 → 同じ判定、呼び出し元や存在に関わらず）。

非規範的な実装ノート: `SELECT ... WHERE id = ? AND tenant = ?` のような単一クエリパターンは*見た目は*統一的ですが、行が別のテナントに存在するかどうかで実行計画、バッファプールのタッチ、認可者の呼び出しが異なります。id で解決し、次に読み込んだ行（true-miss では空の行）に対して認可する二段階パターンを、観測的統一性を自然に生む唯一のパターンとして優先してください。

**キャッシュの温かさ**は別個のオラクルです: テナント B の id に対する温かいキャッシュは、最近誰かがそれにアクセスしたことを示します。セラーはキャッシュ投入を認可でゲートしてはなりません（MUST NOT）— true-miss の id は resolve-then-deny と同じ TTL でキャッシュ・アズ・ミスされなければならず（MUST）、または not-found レスポンスではキャッシュ読み取りがバイパスされなければなりません（MUST）。

**自分で検証する。** ペア化プローブの `adcp fuzz` 不変条件は、ツールごとに 2 つのレスポンスを比較して統一レスポンス準拠を確認します。テナントセットアップ要件と CLI 呼び出しは [Validate Your Agent — Preparing to test uniform error responses](/docs/building/verification/validate-your-agent#preparing-to-test-uniform-error-responses)を参照してください。フルストレングステストには 2 つの分離されたテナントが必要です。単一テナントの実行は「存在しない」レッグのみをカバーします。

### 認証とアクセス

| Code                       | Recovery      | Description                                                                                                                                                                    | Resolution                                                                                                                                                      |
| -------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_REQUIRED`            | correctable\* | 認証が必要、または提示された認証情報が拒否された                                                                                                                                                       | 欠落時は認証情報を提供する。拒否時はオペレーターにエスカレートする — 下記の警告を参照                                                                                                                    |
| `CREDENTIAL_IN_ARGS`       | terminal      | 認証素材または呼び出し元提供の信頼素材が、該当するトランスポート認証/信頼チャネルではなくリクエスト args（トップレベル、`context`、`ext`、その他ネスト位置）に置かれた。インバウンドのバイヤープリンシパル認証情報と、評価器ペイロードフィールドに紛れ込んだ評価器呼び出しの認証情報や JWK/JWKS/JWKS-URI 素材を含む。 | 自動リトライしない — 自動リトライは認証情報を再ログする。認証情報を該当するトランスポート認証/信頼チャネルに移し（[クレデンシャルの配置](/docs/building/by-layer/L2/authentication#credential-placement)）、漏洩した認証情報をローテーションし、再送する |
| `ACCOUNT_NOT_FOUND`        | terminal      | アカウント参照を解決できない                                                                                                                                                                 | `list_accounts` で確認するか、セラーに連絡する                                                                                                                                 |
| `ACCOUNT_SETUP_REQUIRED`   | correctable   | 使用前にアカウントのセットアップが必要                                                                                                                                                            | `details.setup` の URL または手順を確認する                                                                                                                                |
| `ACCOUNT_AMBIGUOUS`        | correctable   | 自然キーが複数のアカウントに解決する                                                                                                                                                             | 明示的な `account_id` またはより具体的な自然キーを渡す                                                                                                                              |
| `ACCOUNT_PAYMENT_REQUIRED` | terminal      | 未払い残高の支払いが必要                                                                                                                                                                   | バイヤーが請求を解決しなければなりません                                                                                                                                            |
| `ACCOUNT_SUSPENDED`        | terminal      | アカウントが停止されている                                                                                                                                                                  | セラーに連絡して解決する                                                                                                                                                    |

<Warning>
  **`AUTH_REQUIRED` のサブケース — 拒否された認証情報を自動リトライしない。** ワイヤーコードはエージェントが異なる扱いをしなければならない 2 つの運用上異なるケースを運びます:

  * **認証情報が欠落** → 認証情報を提供して 1 回リトライ。エージェントループ内で修正可能。
  * **認証情報が提示されたが拒否された**（期限切れ、失効、または署名不正） → 自動リトライ**しない**。認証情報ローテーションのためオペレーターにエスカレート。拒否された認証情報を SSO エンドポイントに再提示すると、ブルートフォースプローブと区別不能なリトライ嵐パターンが生じます — セラーの不正検知が呼び出し元エージェントをレート制限、停止、またはアラートするかもしれません。

  `CREDENTIAL_IN_ARGS` は関連するが別個のケースで、認証素材または呼び出し元提供の信頼素材が該当するトランスポート認証/信頼チャネルではなく**タスクペイロード**に置かれたものです。そのコードは `terminal`（自動リトライは認証情報を再ログする）で、ルール＋カーブアウト（プッシュ通知の Webhook 認証、リレートポロジー）は [クレデンシャルの配置](/docs/building/by-layer/L2/authentication#credential-placement)にあります。

  将来のマイナーリリースはこのコードを `AUTH_MISSING`（correctable）と `AUTH_INVALID`（terminal）に分割します。それまでは、エージェントは認証情報が失敗したリクエストに添付されていたかで分岐します:

  ```javascript theme={null}
  case 'AUTH_REQUIRED': {
    // The caller's request builder records whether an auth header was attached.
    // The error-handling SDK surfaces this on `error.request_had_credentials` (or you
    // pass it in from your own request wrapper).
    const requestHadCredentials = Boolean(error.request_had_credentials);
    if (!requestHadCredentials) {
      // Sub-case (a) — provide credentials and retry.
      await refreshCredentials();
      return retry();
    }
    // Sub-case (b) — credentials were presented and rejected.
    // Treat as terminal at the application layer; surface to operator.
    console.error('Credential rejected — needs human rotation:', error.message);
    throw error;
  }
  ```
</Warning>

### 請求とアカウントセットアップ

リクエストの請求またはアカウント形状の値がセラーに受け入れられない場合に [`sync_accounts`](/docs/accounts/tasks/sync_accounts) が返します。2 つの請求拒否コードは*どのゲート*が発火したかを区別するため、エージェントはプロースを解析せずに正しい復旧（自律リトライ vs 人へのエスカレーション）にディスパッチできます。これらのコードが乗る二層アイデンティティモデルは [バイヤーエージェントのアイデンティティ](/docs/building/by-layer/L2/accounts-and-agents#buyer-agent-identity)を参照してください。

| Code                              | Recovery    | Description                                                                                                                                       | Resolution                                                                                                                                                                                                                                                                 |
| --------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BILLING_NOT_SUPPORTED`           | correctable | セラーが要求された `billing` 値を、セラー全体のケイパビリティレベル（`supported_billing` が値を含まない）またはアカウント関係ごとのレベル（例: セラーは一般に `operator` 請求を受け入れるが、この特定アカウントのオペレーターと直接関係がない）で拒否 | `get_adcp_capabilities` の `supported_billing` を確認し、サポートされる値で再送するか `billing` を省略する。存在する場合は [`billing-not-supported.json`](https://adcontextprotocol.org/schemas/v3/error-details/billing-not-supported.json) に従い `error.details.scope`（`"capability"` または `"account"`）で分岐する |
| `BILLING_NOT_PERMITTED_FOR_AGENT` | correctable | セラー全体のケイパビリティは要求値を受け入れるが、呼び出し元バイヤーエージェントの商業関係が受け入れない（例: パススルー専用としてオンボード — 支払い関係なし — のため `operator` 請求のみ許可）                                        | 存在する場合は `error.details.suggested_billing`（通常 `operator`）でリトライ。不在の場合、拒否は terminal-pending-onboarding — エージェントは自動リトライしてはならず（MUST NOT）、バイヤー側の人間に表面化してセラーとの支払い関係のオンボーディングをオフラインで完了させなければならない（MUST）                                                                             |
| `PAYMENT_TERMS_NOT_SUPPORTED`     | correctable | セラーが要求された `payment_terms` 値を受け入れない                                                                                                                | `payment_terms` を省略してデフォルトを受け入れるか、別のサポート値でリトライするか、オフラインで交渉する                                                                                                                                                                                                               |
| `BRAND_REQUIRED`                  | correctable | ブランド参照なしで請求可能なオペレーションが試みられた                                                                                                                       | リクエストに `brand`（`domain` と任意の `brand_id`）を含める                                                                                                                                                                                                                               |

規範的要件:

* **確立されたエージェントアイデンティティなしの統一レスポンス。** `BILLING_NOT_PERMITTED_FOR_AGENT` は呼び出し元のセラーとのオンボード済み商業状態に基づいて `BILLING_NOT_SUPPORTED` と異なります。アイデンティティ確立なしに per-agent コードを返すと、未認証プローブがコード選択を「このエージェントは agent-billable としてオンボードされているか？」のオラクルとして使えます — [`*_NOT_FOUND` 統一レスポンスルール](#標準エラーコード)と同じ形状です。境界線: セラーは、[Agent identity](/docs/building/by-layer/L1/security#agent-identity) に従う署名付きリクエスト導出、またはセラーのオンボーディングレコードのクレデンシャル・トゥ・エージェントマッピングを通じてエージェントアイデンティティが確立された場合にのみ `BILLING_NOT_PERMITTED_FOR_AGENT` を発行しなければなりません（MUST）。それ以外のすべてのケース — 特定のエージェントレコードにマップされていない bearer 認証情報を含む — では、セラーは `BILLING_NOT_SUPPORTED` を返さなければならず（MUST）、`"account"` スコープヒント自体がアカウント関係ごとのオラクルとして機能しないよう、このパスでは `error.details.scope` を省略しなければなりません（MUST）。
* **`BILLING_NOT_PERMITTED_FOR_AGENT` の details 形状はクランプされる。** `error.details` は [`error-details/billing-not-permitted-for-agent.json`](https://adcontextprotocol.org/schemas/v3/error-details/billing-not-permitted-for-agent.json) に準拠しなければなりません（MUST）: `rejected_billing`（エコー）と任意の単一の `suggested_billing` リトライ値。スキーマは `additionalProperties: false` を設定します。形状はエージェントの完全な許可請求サブセット、レートカード、支払条件、信用限度、請求エンティティ、その他の per-agent 商業状態を運んではなりません（MUST NOT）— 単一プローブでの完全サブセット開示は、まさにクランプが防ぐオラクルです。
* **ワンショットリトライ。** `error.details.suggested_billing` でリトライして 2 つ目の `BILLING_NOT_PERMITTED_FOR_AGENT` を受け取ったバイヤーエージェントは、再度リトライするのではなく人間に表面化しなければなりません（MUST）。復旧はセラーが提案する単一のフォールバックに制限されます。さらなるイテレーションはセラーの設定ミスまたはエージェントが自律的に解決できないオンボーディング状態を示します。

**復旧ディスパッチ — 例。** 2 つの請求コードは異なる復旧をします。実装者は単一のリトライパスに折りたたむのではなく、明示的に分岐すべきです（SHOULD）。以下のスニペットは、他のタスク例で使う `@adcp/sdk/testing` ラッパーではなく、セラーが直接返すレスポンス形状（`sync_accounts` レスポンスの `accounts[].errors[]` 配列 — [task reference](/docs/accounts/tasks/sync_accounts) を参照）を使います。

```javascript theme={null}
async function syncAccountsWithRecovery(client, account) {
  const result = await client.syncAccounts({ accounts: [account] });
  const error = result.accounts[0]?.errors?.[0];
  if (!error) return result;

  switch (error.code) {
    case 'BILLING_NOT_SUPPORTED': {
      // Seller-wide or per-account gate. Check capabilities, dispatch on scope.
      const scope = error.details?.scope;  // "capability" | "account"
      if (scope === 'capability') {
        // The seller never accepts this value. Pick from supported_billing.
        const supported = error.details?.supported_billing ?? [];
        if (supported.length === 0) return surfaceToHuman(error);
        return client.syncAccounts({
          accounts: [{ ...account, billing: supported[0] }],
        });
      }
      // Per-account-relationship reject — the operator-on-this-account isn't
      // billable directly. Try the next-most-permissive value the seller's
      // capability allows.
      return tryNextBillingValue(client, account, error);
    }

    case 'BILLING_NOT_PERMITTED_FOR_AGENT': {
      // Per-buyer-agent commercial gate. Autonomous retry only when the seller
      // suggests a fallback; otherwise surface — the agent cannot extend its
      // own commercial relationship.
      const suggested = error.details?.suggested_billing;
      if (!suggested) return surfaceToHuman(error);
      return client.syncAccounts({
        accounts: [{ ...account, billing: suggested }],
      });
    }

    default:
      throw error;
  }
}
```

**例エンベロープ** — `BILLING_NOT_PERMITTED_FOR_AGENT`、パススルー専用バイヤーエージェントが `operator` へのフォールバックを受け取る:

```json theme={null}
{
  "accounts": [{
    "brand": { "domain": "nova-brands.com", "brand_id": "spark" },
    "operator": "pinnacle-media.com",
    "action": "failed",
    "status": "rejected",
    "errors": [{
      "code": "BILLING_NOT_PERMITTED_FOR_AGENT",
      "message": "This buyer agent is onboarded as passthrough-only; only operator billing is permitted.",
      "recovery": "correctable",
      "details": {
        "rejected_billing": "agent",
        "suggested_billing": "operator"
      }
    }]
  }]
}
```

### 認可（RBAC）

呼び出し元が認証されているがリクエストの特定スコープを欠く場合に返されます。強制はセラーローカルです。発見可能性は [`sync_accounts`](/docs/accounts/tasks/sync_accounts) と [`list_accounts`](/docs/accounts/tasks/list_accounts) のレスポンスのアカウントごとエントリの `authorization` オブジェクト経由です。完全な形状は [Caller authorization](/docs/accounts/overview#caller-authorization) を参照してください。

| Code                  | Recovery    | Description                                                                    | Resolution                                                                                                                    |
| --------------------- | ----------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `PERMISSION_DENIED`   | correctable | 汎用の認可失敗、または必要な署名付き認証情報（例: `governance_context`）が欠落・検証失敗・別のプラン/セラー/フェーズ向けに発行された | `check_governance` を呼んで有効なトークンを発行するか、根本的な権限を解決するためセラーに連絡する                                                                    |
| `SCOPE_INSUFFICIENT`  | correctable | 呼び出されたタスクがこのアカウントの呼び出し元の `allowed_tasks` にない                                   | `sync_accounts` または `list_accounts` でアカウントの `authorization` を再読み込みして呼び出し元の実際の `allowed_tasks` を発見し、許可されたタスクを使うか、より広いスコープを要求する |
| `READ_ONLY_SCOPE`     | correctable | 呼び出し元のスコープが `read_only: true`。呼び出されたタスクは状態を変更する                                | 非変更の代替を使うか、変更を許可するスコープを要求する                                                                                                   |
| `FIELD_NOT_PERMITTED` | correctable | リクエストフィールドがこのタスクの呼び出し元の `field_scopes` 許可リストにない                                | 許可されないフィールドを削除するか、より広いフィールドスコープを要求する                                                                                          |
| `AGENT_SUSPENDED`     | terminal    | 呼び出し元バイヤーエージェントのこのセラーとの商業関係が一時停止されている                                          | バイヤー側の人間に表面化する。セラーとのオフライン再オンボーディングで解決するかもしれない。エージェントは一方的に停止を解除できない。                                                           |
| `AGENT_BLOCKED`       | terminal    | 呼び出し元バイヤーエージェントのこのセラーとの商業関係が恒久的に拒否されている                                        | バイヤー側の人間に表面化する。関係はセラーとのオフラインのオペレーターアクションを通じてのみ復活する。                                                                           |

規範的要件:

* **`FIELD_NOT_PERMITTED` は `error.field` を設定しなければなりません（MUST）** — 正確な問題フィールドパス（例: `packages[0].budget`、`end_time`）を。それがなければ、エージェントはフィールドを削除してリトライすることで確実に自動復旧できません。複数フィールドが許可されない場合、各 `error.field` が明確になるようセラーは問題フィールドごとに 1 つのエラーを返すべきです（SHOULD）。単一エラーを返す場合、`error.details.fields` が完全なリストを含んでもよい（MAY）。
* **`SCOPE_INSUFFICIENT` は `error.details.introspection_hint` を含むべきです（SHOULD）** — セラーが sync/list で `authorization` オブジェクトをサポートする場合。ストローマン形状: `{ "task": "sync_accounts", "account": { ... } }`、呼び出し元にスコープを再発見する場所を指し示します。これはコーディングエージェントの「エラーが出た。どうすればいい？」ループを閉じます。
* **4 つのコードすべては `adcp_error` エンベロープフィールドとペイロード `errors[]` 配列の両方を設定しなければなりません（MUST）** — 上記の二層モデルに従い。スコープエラーはスキーマエラーと同じ方法で型付きクライアントライブラリに引き継がれます。

`SCOPE_INSUFFICIENT` vs. `PERMISSION_DENIED`: タスク自体がこのアカウントのこの呼び出し元に付与されていない場合は `SCOPE_INSUFFICIENT` を使います。`PERMISSION_DENIED` は認証情報形状の失敗（署名付きコンテキストの欠落、署名検証の失敗）と、スコープが正しい抽象化ではない汎用のセラーポリシー拒否に予約します。

`SCOPE_INSUFFICIENT` vs. `UNSUPPORTED_FEATURE`: 両方が該当する場合 — セラーがタスクを実装しておらず、かつ呼び出し元がいずれにせよスコープされない — セラーは `SCOPE_INSUFFICIENT` を返すべきです（SHOULD）。`UNSUPPORTED_FEATURE`（呼び出し元はセラーを切り替えなければならない）より actionable（呼び出し元はより広いスコープを要求できる）だからです。実装*する*が呼び出し元に公開されていないタスクに `UNSUPPORTED_FEATURE` を返すセラーは、呼び出し元が対応できないケイパビリティ情報を漏らしています。

`FIELD_NOT_PERMITTED` vs. `VALIDATION_ERROR`: フィールドはタスクスキーマ上有効で、異なるスコープの呼び出し元からは受け入れられます。この呼び出し元の `field_scopes` に含まれないことを理由に拒否されます。

**認可エラーの `recovery: correctable` について。** 4 つの authz コードすべては `correctable` に分類され、*リクエストを修正して再送できる*ことを意味します。これはエージェントが自律的にリクエストを修正できることを意味**しません** — `SCOPE_INSUFFICIENT` と `READ_ONLY_SCOPE` の「修正」はオペレーターからの帯域外のスコープ付与であり、エージェントは自分で実行できません。エージェントは同じ認証情報に対して自動リトライするのではなく、authz エラーをオペレーターに表面化すべきです（SHOULD）。固定スコープに対するリトライループは防御姿勢ではなくバグです — 下記の制限された曖昧性解消リトライという狭い例外を除いて。`FIELD_NOT_PERMITTED` のみがエージェント自律の復旧パス（許可されないフィールドを削除して再送）を持ちます。

**`SCOPE_INSUFFICIENT` と `READ_ONLY_SCOPE` のリトライ曖昧性解消。** セラーの 300 秒の認可リフレッシュウィンドウ内でのいずれかのコードの単一レスポンスは、クロスレプリカのちらつき — スペックがセラーに生成を禁じるが、バイヤーが実際には遭遇する一時的なインフラアーティファクト — と観測的に区別不能です。エラーをオペレーター介入が必要な確定的な `correctable` シグナルとして分類する前に、バイヤーはスコープが本当に不十分かを確立するため、制限されたリトライバジェット（3 回以下、各回 1〜5 秒のジッター付きバックオフで分離）を使い果たしてもよい（MAY）。これは曖昧性解消ロジックであり、correctable エラーからの復旧ではありません。制限されたリトライが成功なく尽きた後、バイヤーはエラーを表面化しなければならず（MUST）、自律的にリトライを続けてはなりません（MUST NOT）。このリトライ例外は `FIELD_NOT_PERMITTED` には適用されません — エージェント自律の strip-and-resubmit パスがそれに優先します。`READ_ONLY_SCOPE` の失効シナリオに関する注意を含む完全なガイダンスは [Buyer response to SCOPE\_INSUFFICIENT within the refresh window](/docs/accounts/overview#buyer-response-to-scope_insufficient-within-the-refresh-window)を参照してください。

**`FIELD_NOT_PERMITTED` — 両層を設定するエンベロープとペイロードの例:**

```json theme={null}
{
  "adcp_error": {
    "code": "FIELD_NOT_PERMITTED",
    "message": "Caller's field_scopes for update_media_buy does not include this field",
    "recovery": "correctable",
    "field": "packages[0].budget"
  },
  "payload": {
    "errors": [
      {
        "code": "FIELD_NOT_PERMITTED",
        "message": "Caller's field_scopes for update_media_buy does not include this field",
        "recovery": "correctable",
        "field": "packages[0].budget",
        "details": {
          "task": "update_media_buy",
          "permitted_fields": ["reporting_webhook"]
        }
      }
    ]
  }
}
```

`field` 値は削除するものを正確に特定します。`details.permitted_fields`（任意、助言的）は問題のタスクの許可リストを列挙し、エージェントがリトライ前に検証できます。`SCOPE_INSUFFICIENT` については、呼び出し元にスコープを再発見する場所を指すため `details.introspection_hint: { "task": "list_accounts", "account": { ... } }` を設定します。

#### エージェントごとの認可ゲート

per-buyer-agent ゲートは 3 つの異なる拒否パスにわたって発火し、それぞれ独自の判別子を持つため、呼び出し元はプロースを解析せずにディスパッチできます:

| Code                | `details.scope`               | `details.reason` | Meaning                                                                                                                                                                                                                            |
| ------------------- | ----------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AGENT_SUSPENDED`   | —（`details.scope` なし。コードが判別子） | —                | エージェントのセラーとの商業関係が一時停止。再オンボーディングで解決するかも。`recovery: "terminal"`。                                                                                                                                                                     |
| `AGENT_BLOCKED`     | —（`details.scope` なし。コードが判別子） | —                | エージェントの商業関係が恒久的に拒否。自律復旧なし。`recovery: "terminal"`。                                                                                                                                                                                  |
| `PERMISSION_DENIED` | `"agent"`                     | `"sandbox_only"` | エージェントがサンドボックストラフィック専用にプロビジョニングされ、リクエストが非サンドボックスアカウントに対するもの。[`error-details/agent-permission-denied.json`](https://adcontextprotocol.org/schemas/v3/error-details/agent-permission-denied.json) に従い `additionalProperties: false`。 |

per-agent 商業ステータス拒否（`AGENT_SUSPENDED`、`AGENT_BLOCKED`）は [`BILLING_NOT_PERMITTED_FOR_AGENT`](#請求とアカウントセットアップ) の先例に従います — コード自体が判別子で、レスポンスは明示的な `error.details.scope` フィールドを運びません。`PERMISSION_DENIED + scope:"agent"` パスは、`reason` が `error-details/agent-permission-denied.json` の閉じた enum に登録された非ステータスのプロビジョニングゲートに予約されます。`"agent"` 値は [`enums/error-scope.json`](https://adcontextprotocol.org/schemas/v3/enums/error-scope.json) の共有判別子語彙の登録済みサブセットです。

**新コードを作るか `reason` enum を拡張するか。** ライフサイクル終端の per-agent 状態（suspended、blocked、将来の deny-list スタイルの状態）は専用コードを得ます — 異なる `recovery` 分類を運び、一級判別子に値します。非ステータスのプロビジョニングゲートと一時的拒否は、代わりに `error-details/agent-permission-denied.json` の `reason` enum（例: `sandbox_only`）を拡張します。スロットリング形状の拒否は新しい per-agent コードではなく `RATE_LIMITED` を再利用します。dedicated-code-per-state パターンは、ライフサイクル語彙が有限だから成立するのであり、すべての新ゲートのためのオープンレジストリとしてではありません。

**3.0.5 プレースホルダーからの移行ノート。** 3.0.5 は per-agent ライフサイクルのプレースホルダーとして `details.status: ["suspended", "blocked"]` 軸を持つ `agent-permission-denied.json` を出荷しました。3.1 はそのプレースホルダーを専用の `AGENT_SUSPENDED` / `AGENT_BLOCKED` コードに統合します — `status` 軸は `agent-permission-denied.json` から削除され、スキーマは `scope:"agent" + reason:"sandbox_only"` のみを受け入れます。3.0.5 プレースホルダーに対して統合したセラーは専用コードに移行しなければなりません（MUST）。プレースホルダー形状は保持されません。

規範的要件（3 つのコードすべてに一律適用）:

* **確立されたエージェントアイデンティティなしの統一レスポンス。** per-agent 拒否は、[Agent identity](/docs/building/by-layer/L1/security#agent-identity) に従う署名付きリクエスト導出、またはセラーのオンボーディングレコードのクレデンシャル・トゥ・エージェントマッピングを通じてバイヤーエージェントアイデンティティが確立された場合にのみ意味があります。セラーはそのパスでのみ `AGENT_SUSPENDED` / `AGENT_BLOCKED` または `details.scope: "agent"` 付き `PERMISSION_DENIED` を発行しなければなりません（MUST）。それ以外のすべてのケース — 特定エージェントレコードにマップされていない bearer 認証情報を含む — では、汎用の `PERMISSION_DENIED` を返し、`error.details.scope` を省略しなければなりません（MUST）。アイデンティティ確立なしに per-agent コード（または per-agent スコープ）を返すと、未認証プローブがコード選択をクロステナントのオンボーディングオラクルとして使え、[`*_NOT_FOUND` 統一レスポンスルール](#標準エラーコード)と [`BILLING_NOT_PERMITTED_FOR_AGENT`](#請求とアカウントセットアップ) が閉じるのと同じ形状です。
* **未確立アイデンティティで省略ルールがカバーするチャネル。** MUST はすべての観測可能なチャネルに適用されます — `error.code` / `error.message` / `error.field` / `error.details`（未確立アイデンティティパスで message は汎用でなければならない）、HTTP ステータス、A2A `task.status.state`、MCP `isError`、レスポンスヘッダー（ETag、Cache-Control、per-agent レート制限バケット、CDN タグ）、副作用（監査ログ書き込み、Webhook ディスパッチ、バックグラウンドジョブのキューイング、per-agent クォータカウンター、DB シャードルーティング、オンボーディングレコードのキャッシュ投入）、可観測性（ログ、APM スパン、Sentry/Datadog/Rollbar タグは未確立アイデンティティパスでエージェントアイデンティティ、エージェントレコード参照、per-agent ゲート分類を運んではならない）。多相性はツールスキーマの宣言されたパラメーター形状に対して*いかなるオンボーディングレコードのルックアップの前にも*評価されます — ツールの宣言された形状は、アイデンティティが確立されたかに関わらずすべての呼び出し元にわたって同一でなければなりません（MUST）。
* **レイテンシー等価性。** セラーは両方のパスで同じ形状のオンボーディングレコードルックアップを実行しなければなりません（MUST、例: unmapped-credential パスで空のエージェントレコードに対して resolve-then-authorize）。ルックアップレイテンシーが「認証情報がエージェントにマップされていない」を「認証情報が suspended/blocked エージェントにマップされる」と区別しないように。resolve-then-authorize、decision-of-equivalent-shape、`*_NOT_FOUND` ルールが要求するのと同じ姿勢。キャッシュ投入はアイデンティティ確立でゲートされてはなりません（MUST NOT）— さもなくばキャッシュ自体がヒット/ミスタイミングを通じてオラクルになります。
* **リトライカウンターのサイドチャネル。** 「自律リトライなし」ルール（下記）自体がサイドチャネルになってはなりません。セラーは、未確立アイデンティティパスで、true-unauthenticated パスと異なる per-agent リトライ/バックオフカウンターをインクリメントしたり、異なる `retry_after` を発行したり、異なるレート制限ヘッダーを表面化したりしてはなりません（MUST NOT）。no-retry ルールに準拠するバイヤーエージェントは、その準拠が別のテナントが読めるセラー側の per-agent 可観測性に反映されてはなりません（MUST NOT）。
* **3 つのパスのいずれにも per-agent 商業状態なし。** `AGENT_SUSPENDED`、`AGENT_BLOCKED`、`PERMISSION_DENIED + scope:"agent"` のいずれも、エージェントの完全な許可請求サブセット、レートカード、支払条件、信用限度、請求エンティティ、連絡チャネル、カスタム reason 文字列、その他の per-agent 商業状態を運びません — 単一プローブでの完全サブセット開示は、まさにクランプが防ぐオラクルです。`AGENT_SUSPENDED` / `AGENT_BLOCKED` は `error.details` ペイロードを運びません。`PERMISSION_DENIED + scope:"agent"` は `error-details/agent-permission-denied.json`（`scope` + 登録済み `reason`、`additionalProperties: false`）に準拠しなければなりません（MUST）。新しい `reason` 値は、クロス言語 SDK がプロース解析なしにディスパッチできるようスキーマの enum に追加されなければなりません（MUST）。将来の per-agent 状態面（エスカレーションチャネル、lift-policy URL）は、この形状を緩めるのではなく、独自のクランプされた details 形状を持つ新しい専用コードに属します。スキーマの `additionalProperties: false` はスキーマ検証ガードです。セラーはセラー側コンポーザーから受け取った認識されないまたは拡張キーを欠陥として扱い、送信前に削除しなければなりません（MUST）。
* **per-agent ゲートで自律リトライなし。** 3 つのコードすべては実際上 terminal です: `AGENT_SUSPENDED` と `AGENT_BLOCKED` は `enumMetadata` の `recovery: "terminal"` で直接宣言します。`PERMISSION_DENIED + scope:"agent"` パスはワイヤーレベルで `correctable`（登録済み `PERMISSION_DENIED` 分類に一致）ですが実際上は terminal-pending-onboarding です — エージェントはサンドボックス専用プロビジョニングを一方的に解除できません。バイヤーエージェントは 3 つのいずれでも自動リトライするのではなくバイヤー側の人間に表面化しなければなりません（MUST）。再試行はゲートを強化するだけです。

**復旧ディスパッチ — 例。** 最初に `error.code` で分岐し、`PERMISSION_DENIED` の per-agent ゲートのみ `details.scope` にフォールスルーします:

```javascript theme={null}
async function dispatchAuthzError(error) {
  // Per-agent commercial status — code is the discriminator, no details payload.
  if (error.code === 'AGENT_SUSPENDED' || error.code === 'AGENT_BLOCKED') {
    return surfaceToHuman({ code: error.code });
  }

  if (error.code !== 'PERMISSION_DENIED') throw error;

  // Generic credential-shaped failure — no scope on details.
  if (!error.details?.scope) {
    return refreshGovernanceContextAndRetry(error);
  }

  // Per-agent provisioning gate — terminal-pending-onboarding.
  if (error.details?.scope === 'agent') {
    const { reason } = error.details;
    // reason: 'sandbox_only'
    return surfaceToHuman({ code: error.code, reason });
  }

  throw error;  // Unknown scope — surface rather than guess.
}
```

**例エンベロープ** — `AGENT_SUSPENDED`:

```json theme={null}
{
  "adcp_error": {
    "code": "AGENT_SUSPENDED",
    "message": "Buyer agent's commercial relationship with this seller is suspended.",
    "recovery": "terminal"
  }
}
```

**例エンベロープ** — サンドボックス専用プロビジョニングゲート付き `PERMISSION_DENIED`:

```json theme={null}
{
  "adcp_error": {
    "code": "PERMISSION_DENIED",
    "message": "This buyer agent is provisioned for sandbox traffic only.",
    "recovery": "correctable",
    "details": {
      "scope": "agent",
      "reason": "sandbox_only"
    }
  }
}
```

サンドボックス専用パスのワイヤーレベル `recovery: "correctable"` は、[`error-code.json`](https://adcontextprotocol.org/schemas/v3/enums/error-code.json) の `enumMetadata` に従う `PERMISSION_DENIED` の登録済み分類です — SDK は `details.scope` に基づいて登録値を切り替えてはなりません（MUST NOT）。バイヤーエージェントはワイヤーレベルの `recovery` フィールドに関わらず拒否を terminal-pending-onboarding として扱い、人間に表面化し、自動リトライしないようにしなければなりません（MUST）。（suspended/blocked パスでは、コード自体が `recovery: "terminal"` を直接運ぶため、この注意は適用されません。）

### リクエストバリデーション

| Code                     | Recovery    | Description                 | Resolution                                        |
| ------------------------ | ----------- | --------------------------- | ------------------------------------------------- |
| `INVALID_REQUEST`        | correctable | リクエストが不正またはスキーマ制約に違反している    | リクエストパラメーターを確認して修正する                              |
| `UNSUPPORTED_FEATURE`    | correctable | このセラーがサポートしていない機能を要求している    | `get_adcp_capabilities` を確認してサポートされていないフィールドを削除する |
| `POLICY_VIOLATION`       | correctable | リクエストがコンテンツまたは広告ポリシーに違反している | エラー詳細のポリシー要件を確認する                                 |
| `COMPLIANCE_UNSATISFIED` | correctable | 必要な開示事項をターゲットフォーマットで満たせない   | 必要な開示機能をサポートするフォーマットを選択する                         |

### インベントリと商品

| Code                         | Recovery    | Description                                                                        | Resolution                                                                                                      |
| ---------------------------- | ----------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `PRODUCT_NOT_FOUND`          | correctable | 参照した商品 ID が不明または期限切れ                                                               | 無効な ID を削除するか、`get_products` で再探索する                                                                             |
| `PRODUCT_UNAVAILABLE`        | correctable | 商品が売り切れまたは利用不可                                                                     | 別の商品を選択する                                                                                                       |
| `PROPOSAL_EXPIRED`           | correctable | 参照したプロポーザルの `expires_at` が過ぎている                                                    | `get_products` を実行して新しいプロポーザルを取得する                                                                              |
| `PROPOSAL_NOT_FOUND`         | correctable | `proposal_id` がセラーに不明（未確定、誤テナント、またはキャッシュから退避）                                      | `get_products` を `buying_mode: "refine"` + `action: "finalize"` で再発行して現在の proposal\_id を取得する                    |
| `MULTI_FINALIZE_UNSUPPORTED` | correctable | `refine[]` が複数の `action: "finalize"` エントリを運んだ。セラーがアトミックな複数プロポーザルコミットを保証できない        | 単一プロポーザルの finalize 呼び出しを順次実行する（`get_products` 呼び出しごとに 1 つの finalize）                                            |
| `REQUOTE_REQUIRED`           | correctable | 要求された更新が、元の見積もりが価格付けされたエンベロープ（予算、日付、ボリューム、ターゲティング）の外にある。`pricing_option` はロックされたまま | 更新を現在の見積もりに合わせるか、商品/条件を再発見するか、利用可能ならパッケージを追加するか、別のメディアバイを作成する。3.1 は `update_media_buy` の修正見積もりアーティファクトを定義していない。 |
| `SIGNAL_NOT_FOUND`           | correctable | 参照されたシグナルがカタログに存在しない                                                               | `get_signals` で `signal_id` を検証するか、このエージェントからの利用可能性を確認する                                                        |
| `AUDIENCE_TOO_SMALL`         | correctable | オーディエンスセグメントが最小サイズを下回っている                                                          | ターゲティングを広げるか、より多くのオーディエンスメンバーをアップロードする                                                                          |

### 予算とクリエイティブ

| Code                 | Recovery    | Description               | Resolution                                                     |
| -------------------- | ----------- | ------------------------- | -------------------------------------------------------------- |
| `BUDGET_TOO_LOW`     | correctable | 予算がセラーの最小値を下回っている         | 予算を増やすか `capabilities.media_buy.limits` を確認する                  |
| `BUDGET_EXHAUSTED`   | terminal    | アカウントまたはキャンペーン予算を使い切った    | バイヤーが資金を追加するか予算上限を増やさなければなりません                                 |
| `CREATIVE_NOT_FOUND` | correctable | 参照されたクリエイティブがライブラリに存在しない  | `list_creatives` で `creative_id` を検証するか、`sync_creatives` で登録する |
| `CREATIVE_REJECTED`  | correctable | クリエイティブがコンテンツポリシーレビューに不合格 | セラーの `advertising_policies` に従って修正する                           |

### システム

| Code                  | Recovery       | Description                                                                                                                                                                                                                                                                                 | Resolution                                                                              |
| --------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `RATE_LIMITED`        | transient      | リクエストレートを超過した                                                                                                                                                                                                                                                                               | `retry_after` 秒待ってからリトライする                                                              |
| `SERVICE_UNAVAILABLE` | transient      | セラーサービスが一時的に利用不可                                                                                                                                                                                                                                                                            | 指数バックオフでリトライする                                                                          |
| `STALE_RESPONSE`      | transient（助言的） | 非致命的: 上流/サブエージェントが到達不能だったため、セラーがフレッシュネス目標を過ぎたキャッシュから設定済みペイロードを提供した。`SERVICE_UNAVAILABLE`（空ペイロード + 致命的）とは異なる。`error.details` は [`error-details/stale-response.json`](pathname:///schemas/v3/error-details/stale-response.json) に従う                                                           | キャッシュされたペイロードを受け入れるか、フレッシュなデータのため後でリトライする — `error.details.cache_age_seconds` を検査して判断する |
| `CONFIGURATION_ERROR` | terminal       | セラー側のデプロイ設定ミス（例: `mock_upstream_url` の欠落、未宣言の `upstream_url`、未設定の環境変数）。`SERVICE_UNAVAILABLE`（一時的）と `INVALID_REQUEST`（バイヤー修正可能）とは異なる。セラーはトランスポート失敗マーカー（HTTP 5xx、MCP `isError: true`、A2A `failed`）を立てなければならない（MUST）。`error.message` はオペレーターが対処可能な詳細を運び、認証情報・接続文字列・スタックトレースを含んではならない（MUST NOT） | セラーのオペレーターに表面化する。自動リトライしない — リトライは設定ミスのデプロイを解決しない                                       |
| `CONFLICT`            | transient      | 同時変更が検出された                                                                                                                                                                                                                                                                                  | リソースを再読み込みして最新状態でリトライする                                                                 |
| `REFERENCE_NOT_FOUND` | correctable    | 専用の not-found コードを持たない参照リソースの汎用フォールバック。[Not-found の優先順位](#標準エラーコード)を参照                                                                                                                                                                                                                      | 適切なディスカバリータスクで識別子を検証する。存在する場合はリソース固有のコードを優先する                                           |

## 復旧分類

`recovery` フィールドを使ってエラーの処理方法を決定します:

| Recovery      | 意味                                      | アクション                            |
| ------------- | --------------------------------------- | -------------------------------- |
| `transient`   | 一時的な失敗（レート制限、サービス停止、競合）                 | `retry_after` 後または指数バックオフでリトライする |
| `correctable` | リクエストを修正して再送可能（無効フィールド、予算不足、クリエイティブ不合格） | リクエストを変更してリトライする                 |
| `terminal`    | 人の対応が必要（アカウント停止、支払い必要）                  | 人のオペレーターにエスカレートする                |

未知の `recovery` 値（前方互換性）は `terminal` として扱います。

```javascript theme={null}
function isRetryable(error) {
  // Use recovery field when available
  if (error.recovery) {
    return error.recovery === 'transient';
  }

  // Network errors are retryable
  if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
    return true;
  }

  // Fall back to error code matching
  return ['RATE_LIMITED', 'SERVICE_UNAVAILABLE', 'CONFLICT'].includes(error.code);
}
```

## リトライロジック

このセクションのルールは、呼び出し元がリトライしてよいすべての `transient` 分類エラーを制限します。これには [§ Forward-compatible decoding](#forward-compatible-decoding規範的) の下で未知のエラーコードに適用される `transient` デフォルトを含みます。未知のコードをデコードして `transient` にフォールバックする受信者は、下記の `maxRetries` とジッター付き指数バックオフスケジュールを適用しなければなりません（MUST）。オープン enum のデコードルールは受信者をリトライバジェットから免除しません。`code=GO_FOREVER, recovery=transient` を発行する敵対的またはバグのある送信者は、コンフォーマントなクライアントに対して無制限のリトライを誘発できません。

### Normative throttling behavior

これらのルールは、呼び出し元がスロットリングカテゴリのエラー（`RATE_LIMITED`、または `recovery` が `transient` で `details` が [`rate-limited`](https://adcontextprotocol.org/schemas/v3/error-details/rate-limited.json) の detail 形状に準拠する任意のエラー）を受け取ったときに適用されます:

* 呼び出し元は存在する場合 `retry_after` を尊重しなければならず（**MUST**）、示された秒数より早く同じリクエストをリトライしてはなりません（**MUST NOT**）。
* 呼び出し元は `retry_after` が不在の場合、ジッター付き指数バックオフを使うべきです（**SHOULD**）。ベース 2 秒、上限 60 秒、±25% ジッターが安全なデフォルトです。
* 呼び出し元は非スロットリングエラー（例: `INVALID_REQUEST`、`CREATIVE_REJECTED`）をスロットリングされたかのように扱ってはなりません（**MUST NOT**）。他の理由で拒否されたレスポンスをバックオフのケイデンスでリトライするのは防御姿勢ではなくバグです。
* 呼び出し元は繰り返されるスロットリングを無限にリトライするのではなくオペレーターに表面化すべきです（**SHOULD**）。持続する `RATE_LIMITED` レスポンスは一時的な瞬きではなく、キャパシティまたはポリシーのシグナルです。
* セラーは、行儀の良い呼び出し元が意図的にバックオフできるよう、リクエストを黙ってキューイングまたはドロップするのではなく、`retry_after` を設定した `RATE_LIMITED` を返すべきです（**SHOULD**）。
* セラーは、呼び出し元が 429 ごとに反応するのではなく先を計画できるよう、[`rate-limited`](https://adcontextprotocol.org/schemas/v3/error-details/rate-limited.json) の detail 形状（`limit`、`remaining`、`window_seconds`、`scope`）を設定してもよい（**MAY**）。

### 指数バックオフ

リトライ可能なエラーには指数バックオフを実装します:

```javascript theme={null}
async function retryWithBackoff(fn, options = {}) {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    maxDelay = 60000
  } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (!isRetryable(error) || attempt === maxRetries) {
        throw error;
      }

      // Use retry_after when available, otherwise exponential backoff
      const retryAfter = error.retry_after ||
        Math.min(baseDelay * Math.pow(2, attempt), maxDelay);

      // Add jitter to prevent thundering herd
      const jitter = retryAfter * (0.75 + Math.random() * 0.5);
      await sleep(jitter);
    }
  }
}
```

### レート制限の処理

```javascript theme={null}
async function handleRateLimit(error, retryFn) {
  if (error.recovery !== 'transient' &&
      error.code !== 'RATE_LIMITED') {
    throw error;
  }

  const retryAfter = error.retry_after || 60;
  console.log(`Rate limited. Waiting ${retryAfter} seconds...`);

  await sleep(retryAfter * 1000);
  return retryFn();
}
```

## エラーハンドリングパターン

### 基本的なエラーハンドラー

```javascript theme={null}
async function handleAdcpError(error) {
  // Use recovery classification when available
  switch (error.recovery) {
    case 'transient':
      const delay = error.retry_after
        ? error.retry_after * 1000
        : 5000;
      await sleep(delay);
      return retry();

    case 'correctable':
      // Surface suggestion so the request can be fixed
      if (error.suggestion) {
        console.log('Suggestion:', error.suggestion);
      }
      if (error.field) {
        console.log('Problem field:', error.field);
      }
      throw error;

    case 'terminal':
      console.error('Terminal error:', error.message);
      throw error;
  }

  // Fall back to error code matching
  switch (error.code) {
    case 'AUTH_REQUIRED':
      await refreshCredentials();
      return retry();

    case 'INVALID_REQUEST':
      console.error('Validation error:', error);
      throw error;

    default:
      console.error('AdCP error:', error);
      throw error;
  }
}
```

### ユーザーフレンドリーなメッセージ

技術的なエラーをユーザー向けメッセージに変換します:

```javascript theme={null}
const USER_MESSAGES = {
  'RATE_LIMITED': 'Too many requests. Please wait a moment and try again.',
  'BUDGET_TOO_LOW': 'This is below the seller\'s minimum budget. Increase your budget.',
  'PRODUCT_NOT_FOUND': 'One or more products could not be found. Try searching again.',
  'ACCOUNT_SUSPENDED': 'Your account has been suspended. Contact the seller to resolve.',
  'SERVICE_UNAVAILABLE': 'The service is temporarily unavailable. Please try again in a few minutes.',
  'CREATIVE_REJECTED': 'Your creative did not pass policy review. Check the suggestion for details.',
  'AUDIENCE_TOO_SMALL': 'Your target audience is too small. Try broadening your targeting.'
};

function getUserMessage(code, fallbackMessage) {
  return USER_MESSAGES[code] || fallbackMessage || 'An unexpected error occurred. Please try again.';
}
```

### 構造化されたエラーログ

デバッグのためにコンテキスト付きでエラーを記録します:

```javascript theme={null}
function logError(error, context = {}) {
  console.error('AdCP Error:', {
    code: error.code,
    recovery: error.recovery,
    message: error.message,
    field: error.field,
    timestamp: new Date().toISOString(),
    ...context,
    // Don't log sensitive data
    // NO: credentials, briefs, PII
  });
}
```

## Webhook のエラーハンドリング

### Webhook 配信失敗

Webhook 配信に失敗した場合、ポーリングにフォールバックします:

```javascript theme={null}
class WebhookErrorHandler {
  async onDeliveryFailure(taskId, error) {
    console.warn(`Webhook delivery failed for ${taskId}:`, error);

    // Start polling as fallback
    this.startPolling(taskId);

    // Track failure for monitoring
    this.metrics.incrementCounter('webhook_failures');
  }

  async startPolling(taskId) {
    const response = await adcp.call('tasks/get', {
      task_id: taskId,
      include_result: true
    });

    if (['completed', 'failed', 'canceled'].includes(response.status)) {
      await this.processResult(taskId, response);
    } else {
      // Schedule next poll
      setTimeout(() => this.startPolling(taskId), 30000);
    }
  }
}
```

### Webhook ハンドラーのエラー

Webhook エンドポイント内のエラーを丁寧に扱います:

```javascript theme={null}
app.post('/webhooks/adcp', async (req, res) => {
  try {
    // Always respond quickly
    res.status(200).json({ status: 'received' });

    // Process asynchronously
    await processWebhookAsync(req.body);
  } catch (error) {
    // Log error but don't fail the response
    console.error('Webhook processing error:', error);

    // Move to dead letter queue for investigation
    await deadLetterQueue.add(req.body, error);
  }
});
```

## 復旧戦略

### コンテキストの復旧

コンテキストが期限切れの場合は新しい会話を開始します:

```javascript theme={null}
async function callWithContextRecovery(request) {
  try {
    return await adcp.call(request);
  } catch (error) {
    if (error.code === 'INVALID_REQUEST' &&
        error.message?.includes('context not found')) {
      // Clear stale context and retry
      delete request.context_id;
      return await adcp.call(request);
    }
    throw error;
  }
}
```

### 部分的成功の扱い

一部のオペレーションは部分的に成功する場合があります:

```json theme={null}
{
  "status": "completed",
  "message": "Created media buy with warnings",
  "media_buy_id": "mb_123",
  "errors": [
    {
      "code": "COMPLIANCE_UNSATISFIED",
      "message": "Required disclosure position not supported by one placement",
      "field": "packages[0].placements[2]",
      "suggestion": "Choose a format that supports the required disclosure positions"
    }
  ]
}
```

部分成功を処理します:

```javascript theme={null}
function handlePartialSuccess(response) {
  if (response.status === 'completed' && response.errors?.length) {
    // Show warnings to user
    for (const warning of response.errors) {
      showWarning(warning.message, warning.suggestion);
    }
  }

  // Continue with successful result
  return response;
}
```

## ガバナンスエラーパターン

[`check_governance`](/docs/governance/campaign/tasks/check_governance) はエラーオブジェクトではなく `status` フィールドを返します。ガバナンス結果はプロトコル的な意味でのエラーではありません — それらは判断です。AdCP タスクエラーとは別に扱ってください。

| ガバナンスステータス   | 意味           | アクション        |
| ------------ | ------------ | ------------ |
| `approved`   | プランがガバナンスを通過 | 進む           |
| `conditions` | 制約付きで承認      | 条件を適用し、再チェック |
| `denied`     | プランがガバナンスに違反 | オペレーションをブロック |

ガバナンスエージェントが内部的に人間のレビューを必要とする場合（例: アクションがエージェントの権限を超える）、`check_governance` は任意の非同期タスクのように振る舞います — `submitted`/`working` ステータスを返し、最終的に `approved` または `denied` に解決します。これは特別なロジックではなく標準の[非同期タスクライフサイクル](/docs/building/by-layer/L3/task-lifecycle)で扱ってください。

プロトコル層からのガバナンスエラー（ガバナンス判断とは対照的に）は標準のエラー形式を使います。最も一般的なもの:

| Code              | Recovery    | 発生するとき                                       |
| ----------------- | ----------- | -------------------------------------------- |
| `PLAN_NOT_FOUND`  | correctable | `check_governance` の前に `sync_plans` が呼ばれなかった |
| `INVALID_REQUEST` | correctable | 必須フィールドの欠落（例: `plan_id`、`caller`）            |
| `AUTH_REQUIRED`   | correctable | ガバナンスエージェントが認証を要求                            |

## 設定エラーパターン

`CONFIGURATION_ERROR` はセラー側のデプロイ欠陥を示します — `mode: 'mock'` で宣言されたが `mock_upstream_url` のないアカウント、`upstream_url` のない `mode: 'live'` または `mode: 'sandbox'` のプラットフォーム、セラープロセスで未設定の必須環境変数。バイヤーは修正できず、リトライは解決できず、セラー側のオペレーターが対処しなければなりません。カタログには設計上汎用の `INTERNAL_ERROR` コードがなく、`CONFIGURATION_ERROR` は意図的により狭い — 修復が「これをセラーのオペレーターに報告する」である actionable なスライスをカバーします。そのプロファイルに合わない不透明なクラッシュはカタログ非コード化のままです。セラーはプラットフォーム固有のコードを返してもよく（MAY）、バイヤーは[前方互換ルール](#復旧分類)に従って `recovery` 分類にフォールバックします。

### 集約シグナル: リクエストごとに terminal、セラーごとに障害

単一の `CONFIGURATION_ERROR` はそれを受け取ったリクエストについて `terminal` です — バイヤーはセラー側の人間に表面化しなければならず（MUST）、自動リトライしてはなりません（MUST NOT）。短いウィンドウ内での同じセラーからの繰り返しの `CONFIGURATION_ERROR` は別種の運用シグナルです: セラー側の障害。バイヤー側のダッシュボードとアラートは、リクエストごと terminal の扱いとは別に、セラーごとの集約 `CONFIGURATION_ERROR` レートを障害インジケーターとして扱うべきです（SHOULD、例: 単一セラーから M 分に N 回発生でページング）。この収束が重要なのは、集約 `CONFIGURATION_ERROR` を汎用の terminal エラーとバケット化するバイヤーは、コードの存在意義であるセラー分離された障害シグナルを失うからです。

### error.message: オペレーターが対処可能、デプロイ内部ではない

コード自体が判別子です — `CONFIGURATION_ERROR` は `error.details` 形状を運びません（`AGENT_SUSPENDED` / `AGENT_BLOCKED` の[最小開示の先例](#エージェントごとの認可ゲート)が適用）。`error.message` が診断を運び、セラーはデプロイ内部をバイヤーに漏らさずにセラー側のオペレーターに有用なレベルに調整すべきです（SHOULD）。message はワイヤー可視です — 認証情報、接続文字列、完全なファイルパス、スタックトレースを含んではなりません（MUST NOT）。

有用（オペレーターは対処でき、バイヤーは悪用可能なことを何も学ばない）:

```json theme={null}
{
  "code": "CONFIGURATION_ERROR",
  "message": "account is mode='mock' but no mock_upstream_url declared in metadata; populate it in the AccountStore",
  "recovery": "terminal"
}
```

有用でない（オペレーターは既に問題があると知っていた。バイヤーはセラーのファイルシステムの場所を学ぶ）:

```json theme={null}
{
  "code": "CONFIGURATION_ERROR",
  "message": "configuration error",
  "recovery": "terminal"
}
```

漏洩（やってはいけない）:

```json theme={null}
{
  "code": "CONFIGURATION_ERROR",
  "message": "ECONNREFUSED postgres://admin:hunter2@10.0.1.42:5432/prod (at /opt/seller/src/db/pool.ts:127)",
  "recovery": "terminal"
}
```

## ベストプラクティス

1. **まず `recovery` を確認する** — エラーの処理方法として最も信頼できるシグナルです
2. **リトライを実装する** — 一時的エラーは指数バックオフを使用します
3. **レート制限を尊重する** — `retry_after` の値を順守します
4. **未知のコードを適切に扱う** — `recovery` 分類にフォールバックします
5. **コンテキスト付きログ** — デバッグ用に `code`、`recovery`、`field` を含めます
6. **フォールバックを用意する** — 常に代替策を持ちます（例: Webhook 失敗時のポーリング）
7. **terminal エラーはリトライしない** — 人のオペレーターにエスカレートします
8. **部分成功に対応する** — 成功レスポンスの警告も処理します

## 次のステップ

* **Transport Bindings**: エラーが MCP と A2A でどう伝達されるかは [Transport Errors](/docs/building/operating/transport-errors)
* **Task Lifecycle**: ステータス処理は [Task Lifecycle](/docs/building/by-layer/L3/task-lifecycle)
* **Webhooks**: Webhook エラー処理は [Webhooks](/docs/building/by-layer/L3/webhooks)
* **Security**: 認証エラーは [Security](/docs/building/by-layer/L1/security)
