Skip to main content

コンプライアンステストコントローラー

コンプライアンステストコントローラーは開発/ステージング専用のアフォーダンスであり、本番時の概念ではありません。 AAO グレーディングはそれを要求も使用もしません。AAO コンプライアンスハートビートは、すべてのリクエストに account.sandbox: true を付けてセラーの登録された本番 URL に対してストーリーボードを駆動し、セラーの本番スタックがフラグを尊重する責任を負います — コントローラーエンドポイントは不要です。セラーは、自身の統合テストをサポートするため開発またはステージング環境でコントローラーを実装してもよい(MAY) — ライフサイクルステートマシンを決定的に歩く、フィクスチャをシードする、そうでなければ実時間を待つ必要がある遷移を強制する。それがその目的です。本番デプロイで公開してはなりません(MUST NOT)(下の Sandbox gating を参照)。コントローラーが AAO Verified (Sandbox) にどう関係するか混乱していますか? フレーミング決定については #4379 を参照してください: (Sandbox) は「実本番エンドポイントが完全なストーリーボードスイート全体でサンドボックスフラグ付きトラフィックを正しく処理する」ことを証明します。コントローラーは あなたの テストのための開発者側のアフォーダンスであり、AAO 側のグレーディングメカニズムではありません。
AdCP は、アカウント、クリエイティブ、メディアバイ、SI セッション、配信レポートのライフサイクルステートマシンを定義します。これらのステートマシンの多くの遷移はセラー開始です — クリエイティブ承認、アカウント停止、予算枯渇、配信計上。ストーリーボードランナーはバイヤー開始フローのみを行使でき、セラー開始遷移を未テストのままにします。 コンプライアンステストコントローラー は、決定的ローカルテストをサポートするためセラーが開発/ステージング環境で公開するオプションのツールです。ランナーがセラー側の状態遷移をオンデマンドでトリガーでき、開発中にエンドツーエンドのライフサイクル検証を可能にします。

動機

テストコントローラーなしでは、コンプライアンステストは観測的です: アクションを発火し、存在する状態を読み返し、進む。これはスキーマ違反を捕捉しますが動作違反は捕捉しません。

Sandbox gating

セラーは本番デプロイで comply_test_controller を公開してはなりません(MUST NOT) — 誰にも、どの表面でも。ツールは tools/list(MCP)とエージェントカードの skills[](A2A)から不在でなければならず(MUST)、compliance_testing ブロックは get_adcp_capabilities から不在でなければならず(MUST)、ディスパッチはトランスポートの標準未知ツールエラー(例: MCP の JSON-RPC -32601 Method not found、A2A の未知スキル拒否)を返さなければなりません(MUST) — ツールを実装しないセラーの同一トランスポートレスポンスと区別できない。これらの表面のいずれかでツールを公開する本番デプロイは、ディスパッチがゲートされているかどうかにかかわらず非適合です。 正準パターンは 2 つのデプロイです: 1 つは本番(コントローラー未配線)、1 つはサンドボックス/ステージング(すべての来訪者向けにコントローラー配線)。セラーはサンドボックス/ステージングデプロイでのみ comply_test_controller を公開します。そのようなデプロイに認証できる任意のプリンシパルがそれを呼べます。 セラーは代わりに、混合サンドボックス/ライブプリンシパルを持つ単一デプロイを実行し、解決されたアカウントのモードでゲートしてプリンシパルごとにツールを投影してもよい(MAY)。これは実装パターンであり、正準モデルではありません。このパターンを選ぶセラーは 3 つの表面すべてを一貫してゲートしなければなりません(MUST): tools/list(または skills[])、compliance_testing ケイパビリティブロック、ディスパッチ。部分的な投影 — 例: tools/list をゲートするが compliance_testing ブロックをライブプリンシパルに可視のまま残す、または名前でプローブするライブプリンシパルに(未知ツールではなく)FORBIDDEN を返す — は非適合です。それはデプロイスコーピングが閉じるディスカバリーサイドチャネルを再開きます。 FORBIDDEN は、呼び出し元がコントローラーを呼ぶ権限があるが params が非サンドボックスアカウントを参照するサンドボックス内ケースのために予約されています。サンドボックスゲートは、ツール登録時だけでなく、アカウント参照についてリクエストごとに強制されます。 サンドボックス認証情報のプロビジョニングと、本番をサンドボックス/ステージングデプロイから分離するメカニズムはセラー固有で、この仕様の範囲外です。セラーは、ストーリーボードランナーが適切に接続できるよう、サンドボックスアクセスメカニズムを文書化しなければなりません(MUST)。 ストーリーボードランナーは、本番と信じる接続上で tools/list(または skills[])内の comply_test_controller の存在、または get_adcp_capabilities 内の compliance_testing ブロックの存在を、ハードな適合性失敗として扱わなければなりません(MUST)。

ツール定義

Schemas: comply-test-controller-request.json | comply-test-controller-response.json コンプライアンステストコントローラーを実装するセラーは以下をしなければなりません(MUST):
  • サンドボックスモードでのみツールを公開(上のサンドボックスゲートを参照)
  • 本番と同じ状態遷移ルールを強制 — 無効な遷移はエラーを返さなければならない(MUST)
  • 強制された状態変更を後続の読み取り(list_creativesget_media_buys など)に反映
params の description は、MCP クライアント(LLM を含む)が条件付きスキーマ分岐ではなく description を読むため、各シナリオの param 形状をインラインします。SDK コード生成に適した形式的検証スキーマについては、下のシナリオごとの定義を参照してください。

Scenarios

force_creative_status

クリエイティブを指定されたステータスに遷移させます。セラーは クリエイティブライフサイクルステートマシン に従い有効な遷移を強制しなければなりません(MUST)。 Params: Example:

force_account_status

アカウントを指定されたステータスに遷移させます。セラーは アカウントライフサイクルルール を強制しなければなりません(MUST) — 終端状態(rejectedclosed)は退出できません。 Params: Example:

force_media_buy_status

メディアバイを指定されたステータスに遷移させます。セラーはメディアバイライフサイクルを強制しなければなりません(MUST) — rejectedpending_creatives または pending_start からのみ有効です。 Params: Example:

force_create_media_buy_arm

呼び出し元の認証済みサンドボックスアカウントからの次の create_media_buy 呼び出しを特定のレスポンスアームに形作ります。v1 は 2 つのアームをサポートします: submitted(非同期タスクエンベロープ、まだ media_buy_id なし)と input-required(errors 分岐)。force_media_buy_status と異なり、エンティティは遷移しません — まだメディアバイがありません — したがってレスポンスは previous_state/current_state ではなく forced.arm を運びます。 submitted アームのワイヤー形状はそれ以外は実装依存です: ほとんどのセラーはほとんどのバイを同期的にルーティングし、どのバイヤー側リクエスト形状も確実に非同期をトリガーしません。このシナリオはストーリーボードがアームをピン留めできるようにし、退行したセラー(例: status: submitted の下で media_buy_id を発行)が黙って適合性を通過できないようにします。 Params: Example:
Response. 登録されたディレクティブを運ぶ ForcedDirectiveSuccess 形状:
forced.task_idarm: submitted のときのみ存在します。 Consumption and idempotency. ディレクティブは呼び出し元の認証済みサンドボックスアカウント(アカウント + プリンシパルペア)にキーされ、そのアカウントからの次の create_media_buy 呼び出しで消費されます。新しいディレクティブなしの後続呼び出しはセラーのデフォルトアームを返します。バイヤー側 idempotency_key セマンティクスは変わりません: 呼び出し元が既にディレクティブを消費した create_media_buy リクエストをリプレイする場合、セラーはキャッシュされたレスポンスをリプレイしなければならず(MUST)(リクエスト冪等性キャッシュが勝つ)、今や空のディレクティブスロットに対して再評価してはなりません(MUST NOT)。セラーは、同じトランスポート接続内でも、異なるアカウントまたはプリンシパルからの create_media_buy 呼び出しに対してディレクティブをマッチしてはなりません(MUST NOT)。ディレクティブが消費される前の 2 つ目の force_create_media_buy_arm 呼び出しは前のものを上書きします。

force_get_products_arm / force_get_signals_arm

呼び出し元の認証済みサンドボックスアカウント(アカウント + プリンシパルペア)からの次のキュレートディスカバリー呼び出しを submitted タスクエンベロープに形作ります。force_get_products_armbuying_mode: "brief" または "refine"get_products にのみ適用されます。force_get_signals_armdiscovery_mode: "brief"(または省略、brief がデフォルト)の get_signals にのみ適用されます。ホールセールフィード読み取りは同期フィードアクセスであり、これらのディレクティブを消費してはならず(MUST NOT)、ディレクティブが存在するというだけで Submitted アームを返してはなりません(MUST NOT)。 ディレクティブは force_create_media_buy_arm と同じ理由で存在します: バイヤーはリクエスト形状だけから確実に非同期ディスカバリーをトリガーできませんが、適合性はクライアントとセラーがタスク結果パスを尊重することを証明する決定的な方法を必要とします。submitted エンベロープは statustask_id(プラス message のようなオプションの助言フィールド)のみを運びます。終端の products[]proposals[]、または signals[] は、get_task_status(レガシー tasks/get)と任意の登録されたプッシュ通知を通じてタスク完了時に着地します。 Params: Examples:
Response. 両シナリオとも force_create_media_buy_arm と同じ ForcedDirectiveSuccess 形状を返し、forced.armforced.task_id を運びます。 Consumption and idempotency. ディレクティブは呼び出し元の認証済みサンドボックスアカウント(アカウント + プリンシパルペア)にキーされ、その同じアカウントからの次の一致するディスカバリー呼び出しで消費されます。セラーは、製品ディレクティブを get_signals に、シグナルディレクティブを get_products に、brief/refine ディレクティブをホールセールモードに、または任意のディレクティブを異なるアカウントまたはプリンシパルにマッチしてはなりません(MUST NOT)。消費前の同じ操作に対する 2 つ目のディレクティブは前のディレクティブを上書きします。リクエスト冪等性リプレイセマンティクスは変わりません: ディレクティブを消費したディスカバリーリクエストがリプレイされる場合、セラーはキャッシュされた submitted エンベロープを返し、新しいディレクティブを消費しません。

force_task_completion

以前に submitted された非同期タスクを、バイヤー供給の結果ペイロードで completed に解決します。force_*_arm シナリオの相棒: それらのシナリオはセラーを submitted エンベロープに駆動します。これはタスクストアエントリーを completed に遷移させ登録された結果をスタンプすることでループを閉じます。バイヤーは、push_notification_config.url へのセラーのプッシュ通知と、status: "completed" をレポートする後続の get_task_status 呼び出しを通じて完了を観測します。呼び出し元が include_result: true を要求するとき、get_task_status は元の非同期操作に一致する型付き終端結果ペイロードを返します。 submitted → completed ライフサイクルはそれ以外は非決定的です — 実タスク完了は帯域外シグナル(IO 副署名、バッチプロセッサー cron、ガバナンス人間レビュー)に乗ります。ストーリーボードは待てません。このシナリオは、ランナーがディレクティブ登録直後に完了を決定的にピン留めできるようにし、バイヤー側ポーリングアサーションがバイヤーが本番で観測するのと同じワイヤー形状で発火するようにします。 Params: Example:
Response. 状態遷移成功形状を返します:
ソース状態は submittedworking、または input-required でなければならない(MUST)。他のソースは INVALID_TRANSITION を返します。task_id が呼び出し元のアカウントに未知なら、セラーは NOT_FOUND を発行しなければならず(MUST)、タスクが既に終端(completed / failed / canceled)なら INVALID_TRANSITION を発行しなければなりません(MUST)。タスクを failed に強制することはこのシナリオの範囲外です。force_create_media_buy_arm の input-required アームがバイヤー入力必要失敗パスをカバーします。 Replay semantics. タスクが終端になる前の同一 params でのリプレイは冪等な no-op です。タスクが終端になる前の分岐する params でのリプレイは登録された結果を上書きしなければなりません(MUST)(last-write-wins) — force_create_media_buy_arm の「2 つ目の呼び出しが上書き」と同じ前例。タスクが終端になった後、すべてのリプレイは params にかかわらず INVALID_TRANSITION を返します。 Cross-protocol obligations.
  • プッシュ通知。 バイヤーが元の create_media_buypush_notification_config.url を登録した場合、完了強制は登録された result ペイロードで webhook を発火しなければなりません(MUST)(完了データの正準 3.0 配信パス)。そうでなければストーリーボードは終端ステータスのポーリングのみをテストでき、結果のプッシュ配信はテストできません。
  • simulate_delivery / simulate_budget_spend media_buy_id を運ぶ有効な CreateMediaBuyResponse で completed に強制されると、結果のメディアバイはそれらのシナリオでアドレス可能でなければなりません(MUST)。force_task_completion を通じたラウンドトリップは、同期フローを通らずにメディアバイを必要とするストーリーボードのサポートされたパスです。
Buyer-side observation. このシナリオが実行された後、登録された result はすべての呼び出し元供給フィールドを保持してバイヤーの push_notification_config.url(3.0 正準パス)に配信されます。セラーはセラー制御フィールド(例: created_atdsp_* ID、正規化された通貨ケーシング)で拡張してもよい(MAY)が、呼び出し元供給値を上書きしてはなりません(MUST NOT)。後続の tasks/get(task_id)status: "completed" を返さなければなりません(MUST)。result ペイロードはサンドボックスでバイヤー制御でセラーのストアを通じてラウンドトリップします — webhook 経由でそれを受け取るバイヤーは、バイト自体を起源としたという事実にかかわらず、ペイロードを信頼できないセラー出力として扱わなければなりません(MUST)(AdCP 規約に従い)。これは force_task_completion を、webhook 配信パスでバイヤー側サニタイズをテストするときランナーが敵対的ペイロードを注入する自然な場所にします。

force_session_status

SI セッションを終端ステータスに遷移させます。そうでなければ実タイムアウトを待つ必要があるタイムアウトと終了シナリオのテストを可能にします。termination_reason param は原因をシミュレートし、ストーリーボードランナーがセラーが後続レスポンスで正しい理由をレポートすることを検証できます。 Params: Example:

simulate_delivery

メディアバイの合成配信データを注入します。get_media_buy_delivery への後続呼び出しはこのデータを反映しなければなりません(MUST)。配信シミュレーションは加算的です — 各呼び出しが既存の配信合計に加算します。 配信と予算は独立したシステムです。 simulate_delivery は広告サーバーがレポートするものを記録します。simulate_budget_spend は課金システムが追跡するものを記録します。セラーの本番システムはこれらを結合してもしなくてもよい — テストコントローラーは結合を仮定しません。 Params: Example:

simulate_budget_spend

指定されたパーセンテージまでの予算消費をシミュレートします。実支出を待たずに予算しきい値アラートと payment_required 遷移のテストを可能にします。これはアカウントレベルの財務状態に影響する唯一のシナリオです。 simulate_budget_spend を呼んだ後、セラーはシミュレートされた消費を get_account_financials に反映しなければなりません(MUST)。具体的には:
  • total_spend(または同等)はシミュレートされた金額を反映しなければならない(MUST)
  • remaining_budget(または同等)はそれに応じて減らされなければならない(MUST)
  • 予算利用率パーセンテージは spend_percentage に一致しなければならない(MUST)
Params: account_id または media_buy_id の少なくとも 1 つが必要です。ターゲットエンティティは非ゼロ予算が構成されていなければならず(MUST)、そうでない場合コントローラーは INVALID_PARAMS を返すべきです(SHOULD)。 Example:

seed_product

後続のストーリーボードステップが安定した ID で製品を参照できるよう、呼び出し元供給の product_id を持つ製品フィクスチャを作成(またはアップサート)します。フィクスチャが明示的に hidden とマークしない限り、コントローラーはシードされた製品を認証済みアカウントの下で get_products 経由で発見可能にしなければなりません(MUST)。 なぜこのシナリオが存在するか。 ストーリーボードは "test-product" のようなフィクスチャ ID をハードコードし、セラーが一致する製品を持つことを期待します。シードシナリオなしでは、すべての実装者が適合性スイートがどの ID を期待するかを再発見し手動でエイリアスしなければなりません。seed_product はその発見を明示的でストーリーボード作成のコントラクトに置き換えます。 Params: ベンダーメトリック前提条件テストには、外部ベンダーカタログには seed_measurement_catalog を優先してください。製品フィクスチャは、製品コントラクトと参照される測定スナップショットを 1 つのフィクスチャで必要とするローカルハーネスの互換性フォールバックとして、{ vendor, metrics[] } として形作られた measurement_catalogs[] エントリーも運べます。同じベンダーに両方が供給される場合、明示的な seed_measurement_catalog スナップショットがそのコンプライアンスセッションで優先されます。 Example:

seed_pricing_option

既存のシードされた製品に価格オプションを追加(またはアップサート)します。ストーリーボードが最初の seed_product 呼び出しに含まれなかった特定の価格オプションを必要とするとき、またはオプションの属性がセラーのデフォルトから分岐する必要があるときに使います。 Params: Example:

seed_creative

特定のライフサイクルステータスでクリエイティブフィクスチャを作成します。ガバナンスと配信ストーリーボードが最初に sync_creatives をラウンドトリップせずに事前承認されたクリエイティブを参照できるようにします。 Params: Example:

seed_plan

メディアプランフィクスチャを作成します。最初に完全なブリーフィング + プロポーザルフローを実行せずに特定のプランに対してアサートするガバナンスストーリーボードで使われます。 Params: Example:

seed_media_buy

create_media_buy フローをバイパスして、指定されたライフサイクル状態でメディアバイフィクスチャを作成します。既存のバイに対してガバナンスまたは配信動作をアサートする必要があるストーリーボードで使われます。 Params: Example:

seed_measurement_catalog

セラーのコントローラーがこのシナリオをアドバタイズするとき、コンプライアンスセッションのため測定ベンダーの get_adcp_capabilities.measurement.metrics[] スナップショットをシードします。メディアバイストーリーボードは、製品レベルのベンダーメトリックケイパビリティを外部ベンダーカタログディスカバリー前提条件から区別するため、このシナリオを伴う明示的な comply_test_controller ステップを使います。ストーリーボードは、SDK アダプターセットがまだこのシナリオを採用していないコントローラーの互換性フォールバックとして、このスナップショットを seed_product.fixture.measurement_catalogs[] に運ぶこともできます。同じベンダーに両方のソースが存在するとき、明示的な seed_measurement_catalog シードが権威的です。 Params: Example:

シードのセマンティクスと順序

  • フィクスチャ形状。 fixture は許容的(additionalProperties: true)に保たれ、ストーリーボード作成者が各テストが必要とする最小限の形状を宣言できます。フィクスチャは対応するドメインスキーマ(seed_product には core/product.jsonseed_pricing_option には core/pricing-option.jsonseed_creative には media-buy/sync-creatives-request.json の creative-item 形状、seed_media_buy には core/media-buy.jsonseed_plan にはプランスキーマ)に適合すべきです(SHOULD)。seed_measurement_catalog.metrics[]get_adcp_capabilities.measurement.metrics[] をミラーします。セラーは明らかに不正な形式のフィクスチャを INVALID_PARAMS で拒否してもよい(MAY)。
  • 再シード時の冪等性。 同じ主 ID と最初と等価な fixture を持つ 2 つ目の呼び出しは成功し previous_state: "existing"success: true を返すべきです(SHOULD)。分岐する フィクスチャを持つ 2 つ目の呼び出しは、どのフィールドが分岐したかを説明する error_detail を伴う INVALID_PARAMS を返さなければなりません(MUST) — セラーは黙ってマージまたは更新してはなりません(MUST NOT)。実行中にフィクスチャ状態を変える必要があるストーリーボードは、再シードではなく force_* シナリオを使わなければなりません(MUST)。これは同じストーリーボードをセラー全体で決定的に保ちます。
  • 外部キー順序。 ランナーは、セラーが子の前に参照される親を受け取るよう、依存関係順にフィクスチャをシードします。依存関係 DAG:
    具体的には: seed_pricing_option の前に seed_product。フィクスチャがそれらを参照するとき seed_media_buy の前に seed_productseed_creativeseed_plan すべて。fixtures: ブロックを宣言するストーリーボードは、ランナーがトポロジカルソートできる順序でエントリーをリストしなければならない(MUST) — 存在しない製品の seed_pricing_option、または最初にシードされなかったクリエイティブ/製品/プランを参照する seed_media_buy を受け取るセラーは、親を自動作成するのではなく INVALID_PARAMS を返さなければならない(MUST)。
  • サンドボックススコープ。 シードされたフィクスチャは認証済みサンドボックスアカウントにのみ存在します。NOT_FOUNDforce_* と同じように適用されます — 呼び出し元のアカウントの親製品を見られないセラーは、黙って別のテナントにフォールバックするのではなく NOT_FOUND を返さなければなりません(MUST)。
  • ケイパビリティアドバタイズ。 特定のシードシナリオを実装しないセラーは、そのシナリオ名に対して UNKNOWN_SCENARIO を返さなければなりません(MUST)。ランナーは、prerequisites.controller_seeding がそのシナリオを要求するストーリーボードの seed_* 上の UNKNOWN_SCENARIO をカバレッジギャップとして扱います — それらのストーリーボードは failed ではなく not_applicable としてグレードされます。これは 馴染みのない seed_* 名にも適用されます: enum は拡張のためオープン(下記参照)なので、ランナーはセラーが決して見たことのないシナリオを発行するかもしれません。セラーとランナーは、認識されないシナリオ値をスキーマ拒否するのではなく UNKNOWN_SCENARIO で応答しなければなりません(MUST)。
  • 拡張のためオープンな enum。 scenario enum は時間とともに新しい値を追加します(専門分野が要求するにつれ新しいシードシナリオが着地)。ランナーとセラーは、認識しないシナリオ文字列を受け入れ、ハードにスキーマ検証を失敗させるのではなく UNKNOWN_SCENARIO で応答しなければなりません(MUST) — そうでなければすべての新しい enum 値が古い実装の破壊的変更になります。

レスポンス形状

状態遷移レスポンス(force_*

Success:
Failure (invalid transition):
Failure (unknown entity):

シミュレーションレスポンス(simulate_*

simulate_delivery response:
simulated フィールドはこの呼び出しで注入された値をエコーバックします。cumulative フィールドは、このメディアバイの加算カウンターと支出の実行合計、プラス最新の非加算リーチウィンドウとビューアビリティ状態を返し、呼び出し元が get_media_buy_delivery をチェックする前に期待される状態を検証できます。 simulate_budget_spend response:

エラーコード

コントローラーは、ストーリーボードランナーが特定の失敗モードをアサートできるよう構造化エラーコードを使わなければなりません(MUST):
コントローラー固有 enum。 コントローラーレスポンスの error フィールドは、comply-test-controller-response.json で定義されたコントローラー固有の語彙を使い、タスクレベルエラーを統制する正準セラーレスポンス error-code.json enum とは別です。INVALID_TRANSITION はコントローラー固有です(ステートマシンプリミティブは、セラーレベルエラーコードが INVALID_STATE に折りたたむ遷移対状態の区別を公開する)。コントローラーレスポンスのストーリーボードアサーションは、check: error_code ではなく path: "error" または直接 field_value チェックを使います — 形状非依存の error_code チェックは、コントローラー自身のレスポンススキーマではなく、タスクレスポンスエラー(adcp_error / ペイロード errors[])用です。

冪等性

状態遷移シナリオ(force_*)は冪等です: 現在状態に一致するステータスを強制すると、previous_statecurrent_state に等しい成功を返します。これは、ランナーが一時的失敗後にリトライするときのフレーキーなテストを避けます。 シミュレーションシナリオ(simulate_*)は冪等では ありませんsimulate_delivery は既存合計に加算し、simulate_budget_spend は現在の支出レベルを置き換えます。

テスト表面

セラーの状態の記録がどこに存在するかが、ストーリーボードテストループがどう閉じるかを決定します。状態ローカルセラー(典型的には SSP、クリエイティブエージェント)は上の seed_* シナリオ経由でセラーの DB に書き込みます。セラーの読み取りハンドラーは同じストアを消費し、seed→read ループが自然に閉じます。アップストリームプロキシセラー(プラットフォームにプロキシする DSP、リテーラーカタログを読むリテールメディアネットワーク、シグナルブローカー)は、読み取りハンドラーがセラーの制御しないシステムに到達するためその方法でループを閉じられません。TypeScript SDK は、まず実アダプター呼び出しを実行し、次にシードされたフィクスチャをレスポンスにマージする TestControllerBridge を出荷します。どちらのパスも AAO Verified (Spec) が証明するワイヤー形式通過を獲得します。どちらのパスも (Sandbox) が証明するものではありません — それはセラーの本番スタックが実世界の副作用なしに account.sandbox: true を尊重するかどうかをカバーする別の軸です。 このパターンの両実装のクロスページフレーミング、SDK の _bridge 助言マーカー、ランタイムシグナル曖昧性解消テーブルはすべて、適合性仕様 → Test surfaces and the storyboard loop に存在します。

コンプライアンステストモード

セラーのツールリストに comply_test_controller が存在するかどうかが、コンプライアンステスターがどのモードを使うかを決定します:

ケイパビリティディスカバリー

セラーはすべてのシナリオをサポートせずにテストコントローラーを実装してもよい。ストーリーボードランナーは、最初のインタラクションとして scenario: "list_scenarios"comply_test_controller を呼ぶべきです(SHOULD)。これをサポートするセラーは実装されたシナリオのリストを返します:
list_scenarios を実装するセラーは、comply-test-controller-request.jsonscenario enum にそのまま現れるシナリオ名で応答しなければなりません(MUST)。カスタムセラー固有シナリオ名はコンプライアンスコントラクトの一部ではありません。ストーリーボードランナーは正準 enum 外のシナリオにディスパッチしないため、それらをリストしても目的はありません。seed_product をサポートするセラーは文字列 "seed_product" で応答しなければなりません(MUST) — "create_test_product" や他のバリアントではなく。 list_scenarios を実装しないセラーは UNKNOWN_SCENARIO を伴うエラーを返すべきです(SHOULD)。これが起こると、ランナーは各シナリオを個別に試み、UNKNOWN_SCENARIO レスポンスをカバレッジギャップ(失敗ではない)として扱います。これは、list_scenarios をスキップする早期実装者がペナルティを受けないことを意味します — ランナーは試行を通じてサポートされたシナリオを発見します。

観測モード(デフォルト)

comply_test_controller が利用できないとき:
  • ランナーはバイヤー開始フローを実行しレスポンススキーマを検証
  • セラーアクションを要求するステートマシン遷移はスキップ
  • 助言観測が何をテストできなかったかを記録

決定的モード

comply_test_controller が利用可能なとき:
  • ランナーは各ライフサイクルのすべての到達可能な状態を歩く
  • エッジケースを強制: 終端状態、無効な遷移、エラーコード
  • 強制された状態変更が後続の読み取りに反映されることを検証
  • 操作ゲートをテスト(例: アカウントが suspended のとき create_media_buy がブロックされる)
ランナーは決定的モードで 3 つの結果カテゴリーを区別します:
  • Scenario not supportedlist_scenarios または UNKNOWN_SCENARIO エラーで返される。失敗ではなくカバレッジギャップとしてレポート。
  • Transition correctly rejected — コントローラーが無効な状態変更に INVALID_TRANSITION を返した。これは pass。
  • Unexpected failure — コントローラーが有効であるべき遷移にエラーを返した、または失敗すべき遷移に成功した。これはコンプライアンス失敗。

例: 決定的モードでのクリエイティブライフサイクル

例: 決定的モードでのアカウント操作ゲート

例: 決定的モードでのメディアバイライフサイクル

例: 配信と予算の検証

認定階層

専門分野スコープのシード要件。 Stateful compliance はまた、セラーが認定する専門分野をカバーする seed_* シナリオを実装することを要求します。UNKNOWN_SCENARIOnot_applicable グレーディングは、欠けている表面積の正直なカバレッジレポート用であり、適合性からの一括オプトアウトではありません — sales-non-guaranteed を認定するセラーは少なくとも seed_productseed_pricing_option を実装しなければならず(MUST)、creative-ad-server を認定するセラーは seed_creative を実装しなければならず(MUST)、governance-delivery-monitor を認定するセラーは seed_plan(とストーリーボードが要求する場合 seed_media_buy)を実装しなければなりません(MUST)。static/compliance/source/specialisms/ のストーリーボード作成者はストーリーボードが必要とするフィクスチャを宣言します。セラーはそのリストを認定上の専門分野に一致させます。

実装ガイダンス

セラー向け

  1. comply_test_controller をデプロイレベルでゲートする — tools/list(または A2A skills[])に現れてはならず(MUST NOT)、compliance_testing ケイパビリティブロック経由でアドバタイズされてはならず(MUST NOT)、本番デプロイで未知ツールにディスパッチしなければならない(MUST)。完全なルールについては Sandbox gating を参照。
  2. 本番ステートマシンロジックを再利用する — コントローラーは同じ内部遷移関数を呼ぶべきで、バイパスしない
  3. 遷移ルールを強制する — rejected が本番で終端なら、force_media_buy_status(rejected → active) はコントローラー経由でも失敗しなければならない
  4. 変更を即座に反映する — 強制された遷移の後、次の list_* または get_* 呼び出しは更新された状態を返さなければならない

コンプライアンステスター向け

  1. tools/list 経由のプロファイルディスカバリー中にツールを検出
  2. list_scenarios を呼びどのシナリオがサポートされるかを発見
  3. ベースラインとして観測モードを実行 — どこでも動く
  4. コントローラーが利用可能なとき決定的シナリオを上に重ねる
  5. どのモードが使われたかをレポートしカバレッジギャップを失敗から区別
  6. コントローラーの遷移検証自体をテスト — 無効な遷移は黙って成功するのではなく INVALID_TRANSITION を返すべき

設計決定

  1. セラーは遷移順序を検証する。 コントローラーは本番と同じステートマシンルールを強制する。決して processing でなかったクリエイティブに force_creative_status(approved) を呼ぶことはエラー — コントローラーは本番と同様にそれを拒否する。ここで参照されるライフサイクルステートマシンはそれぞれのプロトコル仕様で定義される(クリエイティブライフサイクルアカウントライフサイクルメディアバイライフサイクルSI セッションライフサイクル を参照)。
  2. テストは自己完結的。 各テストは既存のものを再利用するのではなく専用エンティティ(メディアバイ、クリエイティブ、アカウント)を作成すべき(SHOULD)。これは加算シミュレーション呼び出し(simulate_delivery)がリセットメカニズムを必要とせずに既知のゼロ状態から始まることを保証する。reset シナリオは不要。コンプライアンステスターは、複数のストーリーボードランナーインスタンスが同じサンドボックスに対して並行実行するときの衝突を避けるため、テストエンティティに一意の識別子(例: UUID)を使うべき(SHOULD)。サンドボックスエンティティのクリーンアップ(例: TTL ベースの期限切れ)はセラーの責任。
  3. 配信シミュレーションは合成マーカーを使う。 simulate_delivery レコードは、セラーが内部的に簿記に使える synthetic: true フィールドを含んでもよい(MAY)。ランナーはこのマーカーを無視する — にかかわらず同じスキーマに対して get_media_buy_delivery レスポンスを検証する。これはテストの正しさに影響せずにセラーの実装ハードルを下げる。
  4. 1 ツール、多シナリオ。 単一ツール設計は、7 つの別々のツールの約 1,400 トークンに対しコンテキストウィンドウコストを約 500 トークンに保つ。セラーは 1 つのサンドボックスゲートを実装する。ランナーは 1 つのツールを検出する。list_scenarios イントロスペクションは、ツールごとの存在検出を要求せずに部分実装を処理する。