From 0131a9b64c3fbaf348529bbd014c349a7ee3a7e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 20:40:27 +0000 Subject: [PATCH] Version Packages --- .../account-discovery-must-normative.md | 5 - .../fix-hmac-framing-skill-9421-default.md | 5 - ...rward-merge-storyboard-schema-allowlist.md | 8 - CHANGELOG.md | 7 + .../compliance/3.0.9/domains/brand/index.yaml | 163 + .../3.0.9/domains/creative/index.yaml | 412 + .../3.0.9/domains/governance/index.yaml | 683 + .../domains/media-buy/creative-reception.yaml | 247 + .../3.0.9/domains/media-buy/index.yaml | 769 + .../scenarios/create_media_buy_async.yaml | 232 + .../creative_fate_after_cancellation.yaml | 414 + .../scenarios/delivery_reporting.yaml | 205 + .../scenarios/governance_approved.yaml | 211 + .../scenarios/governance_conditions.yaml | 196 + .../scenarios/governance_denied.yaml | 192 + .../scenarios/governance_denied_recovery.yaml | 244 + .../scenarios/invalid_transitions.yaml | 284 + .../scenarios/inventory_list_no_match.yaml | 143 + .../scenarios/inventory_list_targeting.yaml | 271 + .../scenarios/measurement_terms_rejected.yaml | 196 + .../scenarios/pending_creatives_to_start.yaml | 250 + .../scenarios/proposal_finalize.yaml | 247 + .../media-buy/scenarios/refine_products.yaml | 148 + .../domains/media-buy/state-machine.yaml | 442 + .../3.0.9/domains/signals/index.yaml | 266 + .../domains/sponsored-intelligence/index.yaml | 256 + dist/compliance/3.0.9/index.json | 324 + .../3.0.9/protocols/brand/index.yaml | 163 + .../3.0.9/protocols/creative/index.yaml | 412 + .../3.0.9/protocols/governance/index.yaml | 683 + .../media-buy/creative-reception.yaml | 247 + .../3.0.9/protocols/media-buy/index.yaml | 769 + .../scenarios/create_media_buy_async.yaml | 232 + .../creative_fate_after_cancellation.yaml | 414 + .../scenarios/delivery_reporting.yaml | 205 + .../scenarios/governance_approved.yaml | 211 + .../scenarios/governance_conditions.yaml | 196 + .../scenarios/governance_denied.yaml | 192 + .../scenarios/governance_denied_recovery.yaml | 244 + .../scenarios/invalid_transitions.yaml | 284 + .../scenarios/inventory_list_no_match.yaml | 143 + .../scenarios/inventory_list_targeting.yaml | 271 + .../scenarios/measurement_terms_rejected.yaml | 196 + .../scenarios/pending_creatives_to_start.yaml | 250 + .../scenarios/proposal_finalize.yaml | 247 + .../media-buy/scenarios/refine_products.yaml | 148 + .../protocols/media-buy/state-machine.yaml | 442 + .../3.0.9/protocols/signals/index.yaml | 266 + .../sponsored-intelligence/index.yaml | 256 + .../specialisms/audience-sync/index.yaml | 280 + .../3.0.9/specialisms/brand-rights/index.yaml | 350 + .../scenarios/governance_denied.yaml | 204 + .../specialisms/collection-lists/index.yaml | 359 + .../specialisms/content-standards/index.yaml | 572 + .../specialisms/creative-ad-server/index.yaml | 383 + .../generative-seller.yaml | 758 + .../creative-generative/index.yaml | 746 + .../specialisms/creative-template/index.yaml | 413 + .../governance-aware-seller/index.yaml | 136 + .../governance-delivery-monitor/index.yaml | 441 + .../governance-spend-authority/denied.yaml | 221 + .../governance-spend-authority/index.yaml | 330 + .../specialisms/property-lists/index.yaml | 482 + .../specialisms/sales-broadcast-tv/index.yaml | 689 + .../sales-catalog-driven/index.yaml | 779 + .../specialisms/sales-guaranteed/index.yaml | 504 + .../sales-non-guaranteed/index.yaml | 428 + .../sales-proposal-mode/index.yaml | 520 + .../3.0.9/specialisms/sales-social/index.yaml | 584 + .../specialisms/signal-marketplace/index.yaml | 415 + .../scenarios/governance_denied.yaml | 207 + .../3.0.9/specialisms/signal-owned/index.yaml | 316 + .../3.0.9/test-kits/acme-outdoor.yaml | 210 + .../3.0.9/test-kits/bistro-oranje.yaml | 126 + .../3.0.9/test-kits/nova-motors.yaml | 262 + .../3.0.9/test-kits/osei-natural.yaml | 126 + .../test-kits/signed-requests-runner.yaml | 155 + .../substitution-observer-runner.yaml | 690 + .../3.0.9/test-kits/summit-foods.yaml | 125 + .../test-kits/webhook-receiver-runner.yaml | 265 + .../plan-hash/001-minimal-plan.json | 43 + .../test-vectors/plan-hash/002-full-plan.json | 217 + .../plan-hash/003-bookkeeping-stripped.json | 60 + .../plan-hash/004a-human-review-omitted.json | 43 + .../004b-human-review-explicit-null.json | 49 + .../005a-policy-categories-order-1.json | 53 + .../005b-policy-categories-order-2.json | 57 + .../plan-hash/006a-ext-trace-v1.json | 49 + .../plan-hash/006b-ext-trace-v2.json | 53 + .../plan-hash/007-unicode-objectives.json | 43 + .../008-numeric-canonicalization.json | 65 + .../test-vectors/request-signing/README.md | 219 + .../request-signing/canonicalization.json | 241 + .../test-vectors/request-signing/keys.json | 60 + .../negative/001-no-signature-header.json | 24 + .../negative/002-wrong-tag.json | 26 + .../negative/003-expired-signature.json | 26 + .../negative/004-window-too-long.json | 26 + .../negative/005-alg-not-allowed.json | 26 + .../006-missing-covered-component.json | 26 + .../negative/007-missing-content-digest.json | 26 + .../negative/008-unknown-keyid.json | 26 + .../negative/009-key-ops-missing-verify.json | 27 + .../negative/010-content-digest-mismatch.json | 33 + .../negative/011-malformed-header.json | 27 + .../negative/012-missing-expires-param.json | 26 + .../negative/013-expires-le-created.json | 27 + .../negative/014-missing-nonce-param.json | 27 + .../negative/015-signature-invalid.json | 28 + .../negative/016-replayed-nonce.json | 35 + .../negative/017-key-revoked.json | 38 + .../018-digest-covered-when-forbidden.json | 28 + ...019-signature-without-signature-input.json | 26 + .../negative/020-rate-abuse.json | 34 + .../021-duplicate-signature-input-label.json | 31 + .../022-multi-valued-content-type.json | 31 + .../023-multi-valued-content-digest.json | 32 + .../negative/024-unquoted-string-param.json | 31 + .../negative/025-jwk-alg-crv-mismatch.json | 43 + .../negative/026-non-ascii-host.json | 31 + ...-registration-authentication-unsigned.json | 25 + .../positive/001-basic-post.json | 30 + .../002-post-with-content-digest.json | 31 + .../positive/003-es256-post.json | 30 + .../004-multiple-signature-labels.json | 26 + .../positive/005-default-port-stripped.json | 30 + .../positive/006-dot-segment-path.json | 30 + .../positive/007-query-byte-preserved.json | 30 + .../positive/008-percent-encoded-path.json | 30 + ...09-percent-encoded-unreserved-decoded.json | 30 + .../010-percent-encoded-slash-preserved.json | 30 + .../positive/011-ipv6-authority.json | 30 + ...-ipv6-authority-default-port-stripped.json | 30 + .../test-vectors/webhook-signing/README.md | 211 + .../test-vectors/webhook-signing/keys.json | 61 + .../negative/001-wrong-tag.json | 26 + .../negative/002-expired-signature.json | 26 + .../negative/003-window-too-long.json | 26 + .../negative/004-alg-not-allowed.json | 26 + .../005-missing-authority-component.json | 26 + .../negative/006-missing-content-digest.json | 25 + .../negative/007-unknown-keyid.json | 26 + .../negative/008-wrong-adcp-use.json | 26 + .../negative/009-content-digest-mismatch.json | 26 + .../010-malformed-signature-input.json | 26 + .../negative/011-signature-without-input.json | 25 + .../negative/012-missing-expires-param.json | 26 + .../negative/013-expires-le-created.json | 26 + .../negative/014-missing-nonce-param.json | 26 + .../negative/015-signature-invalid.json | 26 + .../negative/016-replayed-nonce.json | 37 + .../negative/017-key-revoked.json | 32 + .../negative/018-rate-abuse.json | 33 + .../negative/019-revocation-stale.json | 32 + .../negative/020-key-ops-missing-verify.json | 41 + .../negative/021-base64-alphabet-mixing.json | 26 + .../positive/001-basic-post.json | 24 + .../positive/002-es256-post.json | 24 + .../003-multiple-signature-labels.json | 24 + .../positive/004-default-port-stripped.json | 24 + .../positive/005-percent-encoded-path.json | 24 + .../positive/006-query-byte-preserved.json | 24 + .../007-body-without-idempotency-key.json | 25 + .../3.0.9/universal/capability-discovery.yaml | 125 + ...collection-lists-pagination-integrity.yaml | 306 + ...ontent-standards-pagination-integrity.yaml | 326 + .../universal/deterministic-testing.yaml | 1343 ++ .../3.0.9/universal/error-compliance.yaml | 474 + .../3.0.9/universal/fictional-entities.yaml | 307 + .../get-media-buys-pagination-integrity.yaml | 160 + .../get-signals-pagination-integrity.yaml | 211 + .../3.0.9/universal/idempotency.yaml | 593 + ...pagination-integrity-creative-formats.yaml | 258 + .../pagination-integrity-list-accounts.yaml | 262 + .../3.0.9/universal/pagination-integrity.yaml | 263 + .../property-lists-pagination-integrity.yaml | 307 + .../universal/runner-output-contract.yaml | 358 + .../3.0.9/universal/schema-validation.yaml | 526 + dist/compliance/3.0.9/universal/security.yaml | 431 + .../3.0.9/universal/signed-requests.yaml | 205 + .../3.0.9/universal/storyboard-schema.yaml | 1176 ++ .../universal/v3-envelope-integrity.yaml | 106 + .../3.0.9/universal/webhook-emission.yaml | 337 + dist/protocol/3.0.0.tgz.crt | 42 +- dist/protocol/3.0.0.tgz.sig | 2 +- dist/protocol/3.0.1.tgz.crt | 42 +- dist/protocol/3.0.1.tgz.sig | 2 +- dist/protocol/3.0.2.tgz.crt | 42 +- dist/protocol/3.0.2.tgz.sig | 2 +- dist/protocol/3.0.3.tgz.crt | 42 +- dist/protocol/3.0.3.tgz.sig | 2 +- dist/protocol/3.0.4.tgz.crt | 40 +- dist/protocol/3.0.4.tgz.sig | 2 +- dist/protocol/3.0.5.tgz.crt | 40 +- dist/protocol/3.0.5.tgz.sig | 2 +- dist/protocol/3.0.6.tgz.crt | 42 +- dist/protocol/3.0.6.tgz.sig | 2 +- dist/protocol/3.0.7.tgz.crt | 42 +- dist/protocol/3.0.7.tgz.sig | 2 +- dist/protocol/3.0.8.tgz.crt | 42 +- dist/protocol/3.0.8.tgz.sig | 2 +- dist/protocol/3.0.9.tgz | Bin 0 -> 2244402 bytes dist/protocol/3.0.9.tgz.crt | 39 + dist/protocol/3.0.9.tgz.sha256 | 1 + dist/protocol/3.0.9.tgz.sig | 1 + dist/schemas/3.0.9/a2ui/bound-value.json | 70 + dist/schemas/3.0.9/a2ui/component.json | 29 + dist/schemas/3.0.9/a2ui/si-catalog.json | 342 + dist/schemas/3.0.9/a2ui/surface.json | 36 + dist/schemas/3.0.9/a2ui/user-action.json | 41 + .../get-account-financials-request.json | 70 + .../get-account-financials-response.json | 264 + .../3.0.9/account/list-accounts-request.json | 42 + .../3.0.9/account/list-accounts-response.json | 97 + .../3.0.9/account/report-usage-request.json | 168 + .../3.0.9/account/report-usage-response.json | 54 + .../3.0.9/account/sync-accounts-request.json | 201 + .../3.0.9/account/sync-accounts-response.json | 331 + .../account/sync-governance-request.json | 170 + .../account/sync-governance-response.json | 159 + dist/schemas/3.0.9/adagents.json | 1143 ++ dist/schemas/3.0.9/brand.json | 1784 ++ .../3.0.9/brand/acquire-rights-request.json | 114 + .../3.0.9/brand/acquire-rights-response.json | 178 + .../brand/creative-approval-request.json | 63 + .../brand/creative-approval-response.json | 155 + .../brand/get-brand-identity-request.json | 56 + .../brand/get-brand-identity-response.json | 346 + .../3.0.9/brand/get-rights-request.json | 70 + .../3.0.9/brand/get-rights-response.json | 175 + .../3.0.9/brand/revocation-notification.json | 51 + .../3.0.9/brand/rights-pricing-option.json | 59 + dist/schemas/3.0.9/brand/rights-terms.json | 56 + .../3.0.9/brand/update-rights-request.json | 63 + .../3.0.9/brand/update-rights-response.json | 77 + .../calibrate-content-request.json | 1712 ++ .../calibrate-content-response.json | 211 + .../create-content-standards-request.json | 3622 +++++ .../create-content-standards-response.json | 147 + .../get-content-standards-request.json | 38 + .../get-content-standards-response.json | 3882 +++++ .../get-media-buy-artifacts-request.json | 234 + .../get-media-buy-artifacts-response.json | 1911 +++ .../list-content-standards-request.json | 121 + .../list-content-standards-response.json | 3911 +++++ .../update-content-standards-request.json | 3611 ++++ .../update-content-standards-response.json | 162 + .../validate-content-delivery-request.json | 1766 ++ .../validate-content-delivery-response.json | 239 + .../3.0.9/bundled/core/tasks-get-request.json | 60 + .../bundled/core/tasks-get-response.json | 264 + .../bundled/core/tasks-list-request.json | 318 + .../bundled/core/tasks-list-response.json | 258 + .../get-creative-delivery-request.json | 245 + .../get-creative-delivery-response.json | 6001 +++++++ .../get-creative-features-request.json | 5094 ++++++ .../get-creative-features-response.json | 261 + .../list-creative-formats-request.json | 485 + .../list-creative-formats-response.json | 4107 +++++ .../creative/list-creatives-request.json | 687 + .../creative/list-creatives-response.json | 6011 +++++++ .../creative/preview-creative-request.json | 9630 +++++++++++ .../creative/preview-creative-response.json | 5965 +++++++ .../creative/sync-creatives-request.json | 5271 ++++++ .../creative/sync-creatives-response.json | 1239 ++ .../media-buy/build-creative-request.json | 5372 ++++++ .../media-buy/build-creative-response.json | 10385 ++++++++++++ .../media-buy/create-media-buy-request.json | 7896 +++++++++ .../media-buy/create-media-buy-response.json | 4232 +++++ .../get-media-buy-delivery-request.json | 494 + .../get-media-buy-delivery-response.json | 4287 +++++ .../media-buy/get-media-buys-request.json | 261 + .../media-buy/get-media-buys-response.json | 2388 +++ .../media-buy/get-products-request.json | 2046 +++ .../media-buy/get-products-response.json | 6840 ++++++++ .../list-creative-formats-request.json | 311 + .../list-creative-formats-response.json | 4111 +++++ .../bundled/media-buy/log-event-request.json | 394 + .../bundled/media-buy/log-event-response.json | 224 + .../bundled/media-buy/package-request.json | 6926 ++++++++ .../provide-performance-feedback-request.json | 119 + ...provide-performance-feedback-response.json | 173 + .../media-buy/sync-audiences-request.json | 454 + .../media-buy/sync-audiences-response.json | 397 + .../media-buy/sync-catalogs-request.json | 836 + .../media-buy/sync-catalogs-response.json | 406 + .../media-buy/sync-event-sources-request.json | 299 + .../sync-event-sources-response.json | 523 + .../media-buy/update-media-buy-request.json | 13554 ++++++++++++++++ .../media-buy/update-media-buy-response.json | 2830 ++++ .../create-property-list-request.json | 661 + .../create-property-list-response.json | 928 ++ .../delete-property-list-request.json | 198 + .../delete-property-list-response.json | 44 + .../property/get-property-list-request.json | 212 + .../property/get-property-list-response.json | 1001 ++ .../property/list-property-lists-request.json | 204 + .../list-property-lists-response.json | 946 ++ .../update-property-list-request.json | 670 + .../update-property-list-response.json | 923 ++ .../validate-property-delivery-request.json | 308 + .../validate-property-delivery-response.json | 418 + .../get-adcp-capabilities-request.json | 47 + .../get-adcp-capabilities-response.json | 1760 ++ .../signals/activate-signal-request.json | 269 + .../signals/activate-signal-response.json | 351 + .../bundled/signals/get-signals-request.json | 409 + .../bundled/signals/get-signals-response.json | 720 + .../si-get-offering-request.json | 57 + .../si-get-offering-response.json | 238 + .../si-initiate-session-request.json | 322 + .../si-initiate-session-response.json | 649 + .../si-send-message-request.json | 81 + .../si-send-message-response.json | 620 + .../si-terminate-session-request.json | 87 + .../si-terminate-session-response.json | 187 + .../collection/base-collection-source.json | 103 + .../collection-list-changed-webhook.json | 68 + .../collection/collection-list-filters.json | 82 + .../3.0.9/collection/collection-list.json | 68 + .../create-collection-list-request.json | 62 + .../create-collection-list-response.json | 33 + .../delete-collection-list-request.json | 43 + .../delete-collection-list-response.json | 34 + .../get-collection-list-request.json | 57 + .../get-collection-list-response.json | 122 + .../list-collection-lists-request.json | 33 + .../list-collection-lists-response.json | 29 + .../update-collection-list-request.json | 71 + .../update-collection-list-response.json | 28 + .../comply-test-controller-request.json | 304 + .../comply-test-controller-response.json | 228 + .../artifact-webhook-payload.json | 79 + .../3.0.9/content-standards/artifact.json | 326 + .../calibrate-content-request.json | 42 + .../calibrate-content-response.json | 84 + .../content-standards/content-standards.json | 71 + .../create-content-standards-request.json | 169 + .../create-content-standards-response.json | 47 + .../get-content-standards-request.json | 28 + .../get-content-standards-response.json | 41 + .../get-media-buy-artifacts-request.json | 79 + .../get-media-buy-artifacts-response.json | 122 + .../list-content-standards-request.json | 49 + .../list-content-standards-response.json | 47 + .../update-content-standards-request.json | 166 + .../update-content-standards-response.json | 60 + .../validate-content-delivery-request.json | 96 + .../validate-content-delivery-response.json | 98 + dist/schemas/3.0.9/core/account-ref.json | 47 + dist/schemas/3.0.9/core/account.json | 298 + dist/schemas/3.0.9/core/activation-key.json | 50 + .../3.0.9/core/ad-inventory-config.json | 37 + .../3.0.9/core/agent-encryption-key.json | 41 + .../schemas/3.0.9/core/agent-signing-key.json | 55 + dist/schemas/3.0.9/core/app-item.json | 151 + .../3.0.9/core/assets/asset-union.json | 25 + .../3.0.9/core/assets/audio-asset.json | 78 + .../3.0.9/core/assets/brief-asset.json | 20 + .../3.0.9/core/assets/catalog-asset.json | 20 + dist/schemas/3.0.9/core/assets/css-asset.json | 31 + .../3.0.9/core/assets/daast-asset.json | 88 + .../schemas/3.0.9/core/assets/html-asset.json | 54 + .../3.0.9/core/assets/image-asset.json | 49 + .../3.0.9/core/assets/javascript-asset.json | 54 + .../3.0.9/core/assets/markdown-asset.json | 37 + .../schemas/3.0.9/core/assets/text-asset.json | 31 + dist/schemas/3.0.9/core/assets/url-asset.json | 37 + .../schemas/3.0.9/core/assets/vast-asset.json | 94 + .../3.0.9/core/assets/video-asset.json | 155 + .../3.0.9/core/assets/webhook-asset.json | 87 + .../3.0.9/core/async-response-data.json | 128 + .../3.0.9/core/attribution-window.json | 23 + dist/schemas/3.0.9/core/audience-member.json | 53 + .../schemas/3.0.9/core/audience-selector.json | 117 + dist/schemas/3.0.9/core/brand-id.json | 16 + dist/schemas/3.0.9/core/brand-ref.json | 52 + dist/schemas/3.0.9/core/business-entity.json | 191 + .../3.0.9/core/cancellation-policy.json | 39 + .../3.0.9/core/catalog-field-mapping.json | 125 + dist/schemas/3.0.9/core/catalog.json | 202 + dist/schemas/3.0.9/core/catchment.json | 144 + .../3.0.9/core/collection-distribution.json | 35 + .../3.0.9/core/collection-list-ref.json | 26 + .../3.0.9/core/collection-selector.json | 27 + dist/schemas/3.0.9/core/collection.json | 110 + dist/schemas/3.0.9/core/content-rating.json | 19 + dist/schemas/3.0.9/core/context.json | 8 + dist/schemas/3.0.9/core/creative-asset.json | 104 + .../3.0.9/core/creative-assignment.json | 32 + dist/schemas/3.0.9/core/creative-brief.json | 126 + .../3.0.9/core/creative-consumption.json | 30 + dist/schemas/3.0.9/core/creative-filters.json | 119 + dist/schemas/3.0.9/core/creative-item.json | 79 + .../schemas/3.0.9/core/creative-manifest.json | 50 + dist/schemas/3.0.9/core/creative-policy.json | 31 + .../schemas/3.0.9/core/creative-variable.json | 33 + dist/schemas/3.0.9/core/creative-variant.json | 57 + .../core/data-provider-signal-selector.json | 94 + dist/schemas/3.0.9/core/date-range.json | 21 + dist/schemas/3.0.9/core/datetime-range.json | 21 + dist/schemas/3.0.9/core/daypart-target.json | 35 + dist/schemas/3.0.9/core/deadline-policy.json | 52 + .../schemas/3.0.9/core/delivery-forecast.json | 64 + dist/schemas/3.0.9/core/delivery-metrics.json | 321 + dist/schemas/3.0.9/core/deployment.json | 93 + dist/schemas/3.0.9/core/destination-item.json | 134 + dist/schemas/3.0.9/core/destination.json | 53 + dist/schemas/3.0.9/core/diagnostic-issue.json | 20 + dist/schemas/3.0.9/core/duration.json | 21 + dist/schemas/3.0.9/core/education-item.json | 135 + dist/schemas/3.0.9/core/error.json | 75 + .../schemas/3.0.9/core/event-custom-data.json | 84 + .../3.0.9/core/event-source-health.json | 65 + dist/schemas/3.0.9/core/event.json | 50 + dist/schemas/3.0.9/core/ext.json | 8 + .../3.0.9/core/feature-requirement.json | 40 + dist/schemas/3.0.9/core/flight-item.json | 127 + dist/schemas/3.0.9/core/forecast-point.json | 47 + dist/schemas/3.0.9/core/forecast-range.json | 29 + dist/schemas/3.0.9/core/format-id.json | 48 + dist/schemas/3.0.9/core/format.json | 640 + dist/schemas/3.0.9/core/frequency-cap.json | 42 + .../3.0.9/core/generation-credential.json | 40 + .../3.0.9/core/geo-breakdown-support.json | 30 + dist/schemas/3.0.9/core/hotel-item.json | 180 + dist/schemas/3.0.9/core/identifier.json | 19 + .../3.0.9/core/industry-identifier.json | 19 + dist/schemas/3.0.9/core/insertion-order.json | 79 + .../3.0.9/core/installment-deadlines.json | 29 + dist/schemas/3.0.9/core/installment.json | 103 + dist/schemas/3.0.9/core/job-item.json | 154 + dist/schemas/3.0.9/core/limited-series.json | 26 + .../schemas/3.0.9/core/material-deadline.json | 25 + .../3.0.9/core/mcp-webhook-payload.json | 163 + .../3.0.9/core/measurement-readiness.json | 41 + .../schemas/3.0.9/core/measurement-terms.json | 59 + .../3.0.9/core/measurement-window.json | 54 + .../3.0.9/core/media-buy-features.json | 24 + dist/schemas/3.0.9/core/media-buy.json | 98 + .../3.0.9/core/offering-asset-group.json | 71 + dist/schemas/3.0.9/core/offering.json | 219 + .../schemas/3.0.9/core/optimization-goal.json | 185 + .../3.0.9/core/outcome-measurement.json | 44 + dist/schemas/3.0.9/core/overlay.json | 73 + dist/schemas/3.0.9/core/package.json | 165 + .../3.0.9/core/pagination-request.json | 21 + .../3.0.9/core/pagination-response.json | 24 + .../3.0.9/core/performance-feedback.json | 90 + .../3.0.9/core/performance-standard.json | 29 + .../3.0.9/core/placement-definition.json | 77 + dist/schemas/3.0.9/core/placement.json | 42 + dist/schemas/3.0.9/core/planned-delivery.json | 76 + dist/schemas/3.0.9/core/price.json | 51 + dist/schemas/3.0.9/core/pricing-option.json | 35 + .../3.0.9/core/product-allocation.json | 69 + dist/schemas/3.0.9/core/product-filters.json | 376 + dist/schemas/3.0.9/core/product.json | 458 + dist/schemas/3.0.9/core/property-id.json | 15 + .../schemas/3.0.9/core/property-list-ref.json | 26 + dist/schemas/3.0.9/core/property-tag.json | 16 + dist/schemas/3.0.9/core/property.json | 70 + dist/schemas/3.0.9/core/proposal.json | 89 + .../schemas/3.0.9/core/protocol-envelope.json | 176 + dist/schemas/3.0.9/core/provenance.json | 209 + .../core/publisher-property-selector.json | 92 + .../3.0.9/core/push-notification-config.json | 47 + dist/schemas/3.0.9/core/real-estate-item.json | 199 + dist/schemas/3.0.9/core/reference-asset.json | 33 + .../3.0.9/core/reporting-capabilities.json | 115 + .../schemas/3.0.9/core/reporting-webhook.json | 67 + .../core/requirements/asset-requirements.json | 20 + .../audio-asset-requirements.json | 59 + .../requirements/catalog-field-binding.json | 131 + .../requirements/catalog-requirements.json | 66 + .../requirements/css-asset-requirements.json | 15 + .../daast-asset-requirements.json | 15 + .../requirements/html-asset-requirements.json | 33 + .../image-asset-requirements.json | 109 + .../javascript-asset-requirements.json | 36 + .../markdown-asset-requirements.json | 15 + .../offering-asset-constraint.json | 63 + .../requirements/text-asset-requirements.json | 41 + .../requirements/url-asset-requirements.json | 40 + .../requirements/vast-asset-requirements.json | 15 + .../video-asset-requirements.json | 150 + .../webhook-asset-requirements.json | 18 + dist/schemas/3.0.9/core/response.json | 24 + .../schemas/3.0.9/core/rights-constraint.json | 87 + dist/schemas/3.0.9/core/seller-agent-ref.json | 26 + .../schemas/3.0.9/core/signal-definition.json | 84 + dist/schemas/3.0.9/core/signal-filters.json | 43 + dist/schemas/3.0.9/core/signal-id.json | 58 + .../3.0.9/core/signal-pricing-option.json | 7 + dist/schemas/3.0.9/core/signal-pricing.json | 167 + dist/schemas/3.0.9/core/signal-targeting.json | 82 + dist/schemas/3.0.9/core/special.json | 29 + dist/schemas/3.0.9/core/start-timing.json | 18 + dist/schemas/3.0.9/core/store-item.json | 196 + dist/schemas/3.0.9/core/talent.json | 24 + dist/schemas/3.0.9/core/targeting.json | 441 + .../schemas/3.0.9/core/tasks-get-request.json | 50 + .../3.0.9/core/tasks-get-response.json | 159 + .../3.0.9/core/tasks-list-request.json | 185 + .../3.0.9/core/tasks-list-response.json | 153 + dist/schemas/3.0.9/core/user-match.json | 66 + dist/schemas/3.0.9/core/vehicle-item.json | 179 + .../3.0.9/core/vendor-pricing-option.json | 22 + dist/schemas/3.0.9/core/x-entity-types.json | 69 + .../3.0.9/creative/asset-types/index.json | 118 + .../creative/creative-feature-result.json | 60 + .../get-creative-delivery-request.json | 75 + .../get-creative-delivery-response.json | 143 + .../get-creative-features-request.json | 41 + .../get-creative-features-response.json | 70 + .../list-creative-formats-request.json | 150 + .../list-creative-formats-response.json | 64 + .../creative/list-creatives-request.json | 182 + .../creative/list-creatives-response.json | 390 + .../creative/preview-creative-request.json | 178 + .../creative/preview-creative-response.json | 307 + .../3.0.9/creative/preview-render.json | 225 + ...eatives-async-response-input-required.json | 25 + ...nc-creatives-async-response-submitted.json | 16 + ...sync-creatives-async-response-working.json | 46 + .../creative/sync-creatives-request.json | 242 + .../creative/sync-creatives-response.json | 270 + dist/schemas/3.0.9/enums/account-scope.json | 14 + dist/schemas/3.0.9/enums/account-status.json | 23 + dist/schemas/3.0.9/enums/action-source.json | 29 + dist/schemas/3.0.9/enums/adcp-protocol.json | 16 + dist/schemas/3.0.9/enums/adjustment-kind.json | 19 + .../3.0.9/enums/advertiser-industry.json | 175 + .../3.0.9/enums/age-verification-method.json | 14 + .../3.0.9/enums/assessment-status.json | 14 + .../3.0.9/enums/asset-content-type.json | 23 + .../3.0.9/enums/attribution-model.json | 14 + dist/schemas/3.0.9/enums/audience-source.json | 15 + dist/schemas/3.0.9/enums/audience-status.json | 17 + .../3.0.9/enums/audio-channel-layout.json | 8 + dist/schemas/3.0.9/enums/auth-scheme.json | 11 + .../schemas/3.0.9/enums/available-metric.json | 31 + dist/schemas/3.0.9/enums/billing-party.json | 13 + dist/schemas/3.0.9/enums/binary-verdict.json | 12 + .../schemas/3.0.9/enums/brand-agent-type.json | 27 + dist/schemas/3.0.9/enums/canceled-by.json | 15 + dist/schemas/3.0.9/enums/catalog-action.json | 14 + .../3.0.9/enums/catalog-item-status.json | 19 + dist/schemas/3.0.9/enums/catalog-type.json | 22 + dist/schemas/3.0.9/enums/channels.json | 51 + .../3.0.9/enums/cloud-storage-protocol.json | 12 + .../3.0.9/enums/co-branding-requirement.json | 12 + .../3.0.9/enums/collection-cadence.json | 23 + dist/schemas/3.0.9/enums/collection-kind.json | 14 + .../3.0.9/enums/collection-relationship.json | 21 + .../3.0.9/enums/collection-status.json | 19 + dist/schemas/3.0.9/enums/consent-basis.json | 8 + dist/schemas/3.0.9/enums/content-id-type.json | 21 + .../3.0.9/enums/content-rating-system.json | 33 + dist/schemas/3.0.9/enums/creative-action.json | 14 + .../enums/creative-agent-capability.json | 14 + .../3.0.9/enums/creative-approval-status.json | 17 + .../3.0.9/enums/creative-identifier-type.json | 22 + .../schemas/3.0.9/enums/creative-quality.json | 15 + .../3.0.9/enums/creative-sort-field.json | 14 + dist/schemas/3.0.9/enums/creative-status.json | 21 + .../3.0.9/enums/daast-tracking-event.json | 32 + dist/schemas/3.0.9/enums/daast-version.json | 11 + dist/schemas/3.0.9/enums/day-of-week.json | 8 + .../3.0.9/enums/delegation-authority.json | 17 + dist/schemas/3.0.9/enums/delivery-type.json | 15 + .../3.0.9/enums/demographic-system.json | 23 + dist/schemas/3.0.9/enums/derivative-type.json | 21 + dist/schemas/3.0.9/enums/device-platform.json | 21 + dist/schemas/3.0.9/enums/device-type.json | 15 + .../3.0.9/enums/digital-source-type.json | 29 + dist/schemas/3.0.9/enums/dimension-unit.json | 15 + .../3.0.9/enums/disclosure-persistence.json | 17 + .../3.0.9/enums/disclosure-position.json | 17 + dist/schemas/3.0.9/enums/distance-unit.json | 17 + .../enums/distribution-identifier-type.json | 53 + dist/schemas/3.0.9/enums/error-code.json | 284 + .../3.0.9/enums/escalation-severity.json | 17 + dist/schemas/3.0.9/enums/event-type.json | 67 + dist/schemas/3.0.9/enums/exclusivity.json | 17 + .../3.0.9/enums/feature-check-status.json | 14 + dist/schemas/3.0.9/enums/feed-format.json | 14 + dist/schemas/3.0.9/enums/feedback-source.json | 13 + dist/schemas/3.0.9/enums/forecast-method.json | 13 + .../3.0.9/enums/forecast-range-unit.json | 18 + .../3.0.9/enums/forecastable-metric.json | 43 + .../3.0.9/enums/format-id-parameter.json | 11 + dist/schemas/3.0.9/enums/frame-rate-type.json | 12 + .../3.0.9/enums/frequency-cap-scope.json | 13 + dist/schemas/3.0.9/enums/genre-taxonomy.json | 29 + dist/schemas/3.0.9/enums/geo-level.json | 13 + dist/schemas/3.0.9/enums/gop-type.json | 12 + .../3.0.9/enums/governance-decision.json | 14 + .../3.0.9/enums/governance-domain.json | 8 + dist/schemas/3.0.9/enums/governance-mode.json | 18 + .../schemas/3.0.9/enums/governance-phase.json | 8 + .../3.0.9/enums/history-entry-type.json | 11 + dist/schemas/3.0.9/enums/http-method.json | 11 + .../schemas/3.0.9/enums/identifier-types.json | 61 + .../3.0.9/enums/installment-status.json | 25 + .../3.0.9/enums/javascript-module-type.json | 12 + .../3.0.9/enums/landing-page-requirement.json | 12 + dist/schemas/3.0.9/enums/makegood-remedy.json | 17 + dist/schemas/3.0.9/enums/markdown-flavor.json | 11 + dist/schemas/3.0.9/enums/match-id-type.json | 29 + dist/schemas/3.0.9/enums/match-type.json | 13 + .../schemas/3.0.9/enums/media-buy-status.json | 25 + .../3.0.9/enums/media-buy-valid-action.json | 8 + dist/schemas/3.0.9/enums/metric-type.json | 17 + dist/schemas/3.0.9/enums/metro-system.json | 14 + .../3.0.9/enums/moov-atom-position.json | 12 + .../3.0.9/enums/notification-type.json | 13 + dist/schemas/3.0.9/enums/outcome-type.json | 17 + dist/schemas/3.0.9/enums/pacing.json | 17 + dist/schemas/3.0.9/enums/payment-terms.json | 16 + .../enums/performance-standard-metric.json | 21 + dist/schemas/3.0.9/enums/policy-category.json | 15 + .../3.0.9/enums/policy-enforcement.json | 17 + dist/schemas/3.0.9/enums/postal-system.json | 20 + .../3.0.9/enums/preview-output-format.json | 11 + dist/schemas/3.0.9/enums/pricing-model.json | 29 + .../3.0.9/enums/production-quality.json | 17 + dist/schemas/3.0.9/enums/property-type.json | 31 + dist/schemas/3.0.9/enums/proposal-status.json | 15 + .../enums/publisher-identifier-types.json | 19 + dist/schemas/3.0.9/enums/purchase-type.json | 8 + dist/schemas/3.0.9/enums/reach-unit.json | 23 + .../3.0.9/enums/reporting-frequency.json | 12 + dist/schemas/3.0.9/enums/response-type.json | 19 + .../3.0.9/enums/restricted-attribute.json | 31 + dist/schemas/3.0.9/enums/right-type.json | 22 + dist/schemas/3.0.9/enums/right-use.json | 36 + .../3.0.9/enums/rights-billing-period.json | 8 + dist/schemas/3.0.9/enums/scan-type.json | 12 + .../3.0.9/enums/si-session-status.json | 19 + .../3.0.9/enums/signal-catalog-type.json | 17 + dist/schemas/3.0.9/enums/signal-source.json | 12 + .../3.0.9/enums/signal-value-type.json | 17 + .../enums/snapshot-unavailable-reason.json | 13 + dist/schemas/3.0.9/enums/sort-direction.json | 11 + dist/schemas/3.0.9/enums/sort-metric.json | 31 + .../schemas/3.0.9/enums/special-category.json | 35 + dist/schemas/3.0.9/enums/specialism.json | 55 + dist/schemas/3.0.9/enums/talent-role.json | 29 + dist/schemas/3.0.9/enums/task-status.json | 29 + dist/schemas/3.0.9/enums/task-type.json | 57 + dist/schemas/3.0.9/enums/transport-mode.json | 19 + .../schemas/3.0.9/enums/travel-time-unit.json | 12 + dist/schemas/3.0.9/enums/uid-type.json | 31 + dist/schemas/3.0.9/enums/universal-macro.json | 149 + .../schemas/3.0.9/enums/update-frequency.json | 13 + dist/schemas/3.0.9/enums/url-asset-type.json | 12 + dist/schemas/3.0.9/enums/validation-mode.json | 11 + .../3.0.9/enums/vast-tracking-event.json | 41 + dist/schemas/3.0.9/enums/vast-version.json | 14 + .../3.0.9/enums/viewability-standard.json | 12 + dist/schemas/3.0.9/enums/wcag-level.json | 17 + .../3.0.9/enums/webhook-response-type.json | 13 + .../3.0.9/enums/webhook-security-method.json | 12 + .../error-details/account-setup-required.json | 20 + .../error-details/audience-too-small.json | 18 + .../3.0.9/error-details/budget-too-low.json | 18 + .../schemas/3.0.9/error-details/conflict.json | 22 + .../error-details/creative-rejected.json | 25 + .../3.0.9/error-details/policy-violation.json | 25 + .../3.0.9/error-details/rate-limited.json | 27 + .../error-details/vendor-error-codes.json | 43 + .../3.0.9/extensions/extension-meta.json | 59 + dist/schemas/3.0.9/extensions/index.json | 9 + .../governance/attribute-definition.json | 67 + .../governance/audience-constraints.json | 27 + .../governance/check-governance-request.json | 198 + .../governance/check-governance-response.json | 217 + .../get-plan-audit-logs-request.json | 79 + .../get-plan-audit-logs-response.json | 423 + .../policy-category-definition.json | 79 + .../3.0.9/governance/policy-entry.json | 141 + dist/schemas/3.0.9/governance/policy-ref.json | 25 + .../report-plan-outcome-request.json | 165 + .../report-plan-outcome-response.json | 89 + .../3.0.9/governance/sync-plans-request.json | 414 + .../3.0.9/governance/sync-plans-response.json | 119 + dist/schemas/3.0.9/index.json | 1678 ++ dist/schemas/3.0.9/manifest.json | 1190 ++ dist/schemas/3.0.9/manifest.schema.json | 162 + ...reative-async-response-input-required.json | 32 + ...ild-creative-async-response-submitted.json | 16 + ...build-creative-async-response-working.json | 36 + .../media-buy/build-creative-request.json | 138 + .../media-buy/build-creative-response.json | 293 + ...dia-buy-async-response-input-required.json | 31 + ...te-media-buy-async-response-submitted.json | 16 + ...eate-media-buy-async-response-working.json | 36 + .../media-buy/create-media-buy-request.json | 221 + .../media-buy/create-media-buy-response.json | 194 + .../get-media-buy-delivery-request.json | 205 + .../get-media-buy-delivery-response.json | 693 + .../media-buy/get-media-buys-request.json | 66 + .../media-buy/get-media-buys-response.json | 388 + ...roducts-async-response-input-required.json | 38 + ...get-products-async-response-submitted.json | 21 + .../get-products-async-response-working.json | 34 + .../3.0.9/media-buy/get-products-request.json | 227 + .../media-buy/get-products-response.json | 158 + .../list-creative-formats-request.json | 103 + .../list-creative-formats-response.json | 68 + .../3.0.9/media-buy/log-event-request.json | 53 + .../3.0.9/media-buy/log-event-response.json | 107 + .../3.0.9/media-buy/package-request.json | 133 + .../3.0.9/media-buy/package-update.json | 188 + .../provide-performance-feedback-request.json | 73 + ...provide-performance-feedback-response.json | 79 + .../media-buy/sync-audiences-request.json | 127 + .../media-buy/sync-audiences-response.json | 163 + ...atalogs-async-response-input-required.json | 26 + ...ync-catalogs-async-response-submitted.json | 16 + .../sync-catalogs-async-response-working.json | 56 + .../media-buy/sync-catalogs-request.json | 162 + .../media-buy/sync-catalogs-response.json | 210 + .../media-buy/sync-event-sources-request.json | 82 + .../sync-event-sources-response.json | 136 + ...dia-buy-async-response-input-required.json | 24 + ...te-media-buy-async-response-submitted.json | 16 + ...date-media-buy-async-response-working.json | 36 + .../media-buy/update-media-buy-request.json | 99 + .../media-buy/update-media-buy-response.json | 119 + .../3.0.9/pricing-options/cpa-option.json | 60 + .../3.0.9/pricing-options/cpc-option.json | 62 + .../3.0.9/pricing-options/cpcv-option.json | 62 + .../3.0.9/pricing-options/cpm-option.json | 62 + .../3.0.9/pricing-options/cpp-option.json | 78 + .../3.0.9/pricing-options/cpv-option.json | 93 + .../pricing-options/flat-rate-option.json | 105 + .../pricing-options/price-breakdown.json | 74 + .../3.0.9/pricing-options/price-guidance.json | 30 + .../3.0.9/pricing-options/time-option.json | 80 + .../3.0.9/pricing-options/vcpm-option.json | 62 + .../3.0.9/property/authorization-result.json | 41 + .../3.0.9/property/base-property-source.json | 87 + .../create-property-list-request.json | 62 + .../create-property-list-response.json | 33 + .../delete-property-list-request.json | 43 + .../delete-property-list-response.json | 34 + .../3.0.9/property/delivery-record.json | 32 + .../property/get-property-list-request.json | 57 + .../property/get-property-list-response.json | 53 + .../property/list-property-lists-request.json | 33 + .../list-property-lists-response.json | 29 + .../3.0.9/property/property-error.json | 31 + .../property/property-feature-definition.json | 84 + .../property/property-feature-result.json | 43 + .../property/property-feature-value.json | 51 + .../3.0.9/property/property-feature.json | 23 + .../property-list-changed-webhook.json | 68 + .../3.0.9/property/property-list-filters.json | 51 + .../schemas/3.0.9/property/property-list.json | 76 + .../update-property-list-request.json | 71 + .../update-property-list-response.json | 28 + .../validate-property-delivery-request.json | 49 + .../validate-property-delivery-response.json | 180 + .../3.0.9/property/validation-result.json | 89 + .../get-adcp-capabilities-request.json | 31 + .../get-adcp-capabilities-response.json | 1029 ++ .../signals/activate-signal-request.json | 66 + .../signals/activate-signal-response.json | 81 + .../3.0.9/signals/get-signals-request.json | 80 + .../3.0.9/signals/get-signals-response.json | 127 + .../si-capabilities.json | 137 + .../si-get-offering-request.json | 47 + .../si-get-offering-response.json | 150 + .../sponsored-intelligence/si-identity.json | 86 + .../si-initiate-session-request.json | 65 + .../si-initiate-session-response.json | 64 + .../si-send-message-request.json | 71 + .../si-send-message-response.json | 123 + .../si-terminate-session-request.json | 77 + .../si-terminate-session-response.json | 86 + .../sponsored-intelligence/si-ui-element.json | 191 + dist/schemas/3.0.9/tmp/available-package.json | 42 + .../3.0.9/tmp/context-match-request.json | 220 + .../3.0.9/tmp/context-match-response.json | 74 + dist/schemas/3.0.9/tmp/error.json | 42 + .../3.0.9/tmp/identity-match-request.json | 99 + .../3.0.9/tmp/identity-match-response.json | 43 + dist/schemas/3.0.9/tmp/offer-price.json | 31 + dist/schemas/3.0.9/tmp/offer.json | 43 + .../3.0.9/tmp/provider-registration.json | 88 + package.json | 2 +- static/schemas/source/index.json | 2 +- 794 files changed, 236664 insertions(+), 216 deletions(-) delete mode 100644 .changeset/account-discovery-must-normative.md delete mode 100644 .changeset/fix-hmac-framing-skill-9421-default.md delete mode 100644 .changeset/forward-merge-storyboard-schema-allowlist.md create mode 100644 dist/compliance/3.0.9/domains/brand/index.yaml create mode 100644 dist/compliance/3.0.9/domains/creative/index.yaml create mode 100644 dist/compliance/3.0.9/domains/governance/index.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/creative-reception.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/index.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/create_media_buy_async.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/delivery_reporting.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/governance_approved.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/governance_conditions.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied_recovery.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/invalid_transitions.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_no_match.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_targeting.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/measurement_terms_rejected.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/pending_creatives_to_start.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/proposal_finalize.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/scenarios/refine_products.yaml create mode 100644 dist/compliance/3.0.9/domains/media-buy/state-machine.yaml create mode 100644 dist/compliance/3.0.9/domains/signals/index.yaml create mode 100644 dist/compliance/3.0.9/domains/sponsored-intelligence/index.yaml create mode 100644 dist/compliance/3.0.9/index.json create mode 100644 dist/compliance/3.0.9/protocols/brand/index.yaml create mode 100644 dist/compliance/3.0.9/protocols/creative/index.yaml create mode 100644 dist/compliance/3.0.9/protocols/governance/index.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/creative-reception.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/index.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/create_media_buy_async.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/delivery_reporting.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_approved.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_conditions.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied_recovery.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/invalid_transitions.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_no_match.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_targeting.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/measurement_terms_rejected.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/pending_creatives_to_start.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/proposal_finalize.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/scenarios/refine_products.yaml create mode 100644 dist/compliance/3.0.9/protocols/media-buy/state-machine.yaml create mode 100644 dist/compliance/3.0.9/protocols/signals/index.yaml create mode 100644 dist/compliance/3.0.9/protocols/sponsored-intelligence/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/audience-sync/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/brand-rights/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/brand-rights/scenarios/governance_denied.yaml create mode 100644 dist/compliance/3.0.9/specialisms/collection-lists/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/content-standards/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/creative-ad-server/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/creative-generative/generative-seller.yaml create mode 100644 dist/compliance/3.0.9/specialisms/creative-generative/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/creative-template/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/governance-aware-seller/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/governance-delivery-monitor/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/governance-spend-authority/denied.yaml create mode 100644 dist/compliance/3.0.9/specialisms/governance-spend-authority/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/property-lists/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/sales-broadcast-tv/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/sales-catalog-driven/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/sales-guaranteed/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/sales-non-guaranteed/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/sales-proposal-mode/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/sales-social/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/signal-marketplace/index.yaml create mode 100644 dist/compliance/3.0.9/specialisms/signal-marketplace/scenarios/governance_denied.yaml create mode 100644 dist/compliance/3.0.9/specialisms/signal-owned/index.yaml create mode 100644 dist/compliance/3.0.9/test-kits/acme-outdoor.yaml create mode 100644 dist/compliance/3.0.9/test-kits/bistro-oranje.yaml create mode 100644 dist/compliance/3.0.9/test-kits/nova-motors.yaml create mode 100644 dist/compliance/3.0.9/test-kits/osei-natural.yaml create mode 100644 dist/compliance/3.0.9/test-kits/signed-requests-runner.yaml create mode 100644 dist/compliance/3.0.9/test-kits/substitution-observer-runner.yaml create mode 100644 dist/compliance/3.0.9/test-kits/summit-foods.yaml create mode 100644 dist/compliance/3.0.9/test-kits/webhook-receiver-runner.yaml create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/001-minimal-plan.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/002-full-plan.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/003-bookkeeping-stripped.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/004a-human-review-omitted.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/004b-human-review-explicit-null.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/005a-policy-categories-order-1.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/005b-policy-categories-order-2.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/006a-ext-trace-v1.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/006b-ext-trace-v2.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/007-unicode-objectives.json create mode 100644 dist/compliance/3.0.9/test-vectors/plan-hash/008-numeric-canonicalization.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/README.md create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/canonicalization.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/keys.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/001-no-signature-header.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/002-wrong-tag.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/003-expired-signature.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/004-window-too-long.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/005-alg-not-allowed.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/006-missing-covered-component.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/007-missing-content-digest.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/008-unknown-keyid.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/009-key-ops-missing-verify.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/010-content-digest-mismatch.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/011-malformed-header.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/012-missing-expires-param.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/013-expires-le-created.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/014-missing-nonce-param.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/015-signature-invalid.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/016-replayed-nonce.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/017-key-revoked.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/018-digest-covered-when-forbidden.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/019-signature-without-signature-input.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/020-rate-abuse.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/021-duplicate-signature-input-label.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/022-multi-valued-content-type.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/023-multi-valued-content-digest.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/024-unquoted-string-param.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/025-jwk-alg-crv-mismatch.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/026-non-ascii-host.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/negative/027-webhook-registration-authentication-unsigned.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/001-basic-post.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/002-post-with-content-digest.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/003-es256-post.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/004-multiple-signature-labels.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/005-default-port-stripped.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/006-dot-segment-path.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/007-query-byte-preserved.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/008-percent-encoded-path.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/009-percent-encoded-unreserved-decoded.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/010-percent-encoded-slash-preserved.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/011-ipv6-authority.json create mode 100644 dist/compliance/3.0.9/test-vectors/request-signing/positive/012-ipv6-authority-default-port-stripped.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/README.md create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/keys.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/001-wrong-tag.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/002-expired-signature.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/003-window-too-long.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/004-alg-not-allowed.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/005-missing-authority-component.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/006-missing-content-digest.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/007-unknown-keyid.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/008-wrong-adcp-use.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/009-content-digest-mismatch.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/010-malformed-signature-input.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/011-signature-without-input.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/012-missing-expires-param.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/013-expires-le-created.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/014-missing-nonce-param.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/015-signature-invalid.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/016-replayed-nonce.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/017-key-revoked.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/018-rate-abuse.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/019-revocation-stale.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/020-key-ops-missing-verify.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/negative/021-base64-alphabet-mixing.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/001-basic-post.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/002-es256-post.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/003-multiple-signature-labels.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/004-default-port-stripped.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/005-percent-encoded-path.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/006-query-byte-preserved.json create mode 100644 dist/compliance/3.0.9/test-vectors/webhook-signing/positive/007-body-without-idempotency-key.json create mode 100644 dist/compliance/3.0.9/universal/capability-discovery.yaml create mode 100644 dist/compliance/3.0.9/universal/collection-lists-pagination-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/content-standards-pagination-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/deterministic-testing.yaml create mode 100644 dist/compliance/3.0.9/universal/error-compliance.yaml create mode 100644 dist/compliance/3.0.9/universal/fictional-entities.yaml create mode 100644 dist/compliance/3.0.9/universal/get-media-buys-pagination-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/get-signals-pagination-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/idempotency.yaml create mode 100644 dist/compliance/3.0.9/universal/pagination-integrity-creative-formats.yaml create mode 100644 dist/compliance/3.0.9/universal/pagination-integrity-list-accounts.yaml create mode 100644 dist/compliance/3.0.9/universal/pagination-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/property-lists-pagination-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/runner-output-contract.yaml create mode 100644 dist/compliance/3.0.9/universal/schema-validation.yaml create mode 100644 dist/compliance/3.0.9/universal/security.yaml create mode 100644 dist/compliance/3.0.9/universal/signed-requests.yaml create mode 100644 dist/compliance/3.0.9/universal/storyboard-schema.yaml create mode 100644 dist/compliance/3.0.9/universal/v3-envelope-integrity.yaml create mode 100644 dist/compliance/3.0.9/universal/webhook-emission.yaml create mode 100644 dist/protocol/3.0.9.tgz create mode 100644 dist/protocol/3.0.9.tgz.crt create mode 100644 dist/protocol/3.0.9.tgz.sha256 create mode 100644 dist/protocol/3.0.9.tgz.sig create mode 100644 dist/schemas/3.0.9/a2ui/bound-value.json create mode 100644 dist/schemas/3.0.9/a2ui/component.json create mode 100644 dist/schemas/3.0.9/a2ui/si-catalog.json create mode 100644 dist/schemas/3.0.9/a2ui/surface.json create mode 100644 dist/schemas/3.0.9/a2ui/user-action.json create mode 100644 dist/schemas/3.0.9/account/get-account-financials-request.json create mode 100644 dist/schemas/3.0.9/account/get-account-financials-response.json create mode 100644 dist/schemas/3.0.9/account/list-accounts-request.json create mode 100644 dist/schemas/3.0.9/account/list-accounts-response.json create mode 100644 dist/schemas/3.0.9/account/report-usage-request.json create mode 100644 dist/schemas/3.0.9/account/report-usage-response.json create mode 100644 dist/schemas/3.0.9/account/sync-accounts-request.json create mode 100644 dist/schemas/3.0.9/account/sync-accounts-response.json create mode 100644 dist/schemas/3.0.9/account/sync-governance-request.json create mode 100644 dist/schemas/3.0.9/account/sync-governance-response.json create mode 100644 dist/schemas/3.0.9/adagents.json create mode 100644 dist/schemas/3.0.9/brand.json create mode 100644 dist/schemas/3.0.9/brand/acquire-rights-request.json create mode 100644 dist/schemas/3.0.9/brand/acquire-rights-response.json create mode 100644 dist/schemas/3.0.9/brand/creative-approval-request.json create mode 100644 dist/schemas/3.0.9/brand/creative-approval-response.json create mode 100644 dist/schemas/3.0.9/brand/get-brand-identity-request.json create mode 100644 dist/schemas/3.0.9/brand/get-brand-identity-response.json create mode 100644 dist/schemas/3.0.9/brand/get-rights-request.json create mode 100644 dist/schemas/3.0.9/brand/get-rights-response.json create mode 100644 dist/schemas/3.0.9/brand/revocation-notification.json create mode 100644 dist/schemas/3.0.9/brand/rights-pricing-option.json create mode 100644 dist/schemas/3.0.9/brand/rights-terms.json create mode 100644 dist/schemas/3.0.9/brand/update-rights-request.json create mode 100644 dist/schemas/3.0.9/brand/update-rights-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/calibrate-content-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/calibrate-content-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/create-content-standards-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/create-content-standards-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/get-content-standards-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/get-content-standards-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/get-media-buy-artifacts-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/get-media-buy-artifacts-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/list-content-standards-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/list-content-standards-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/update-content-standards-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/update-content-standards-response.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/validate-content-delivery-request.json create mode 100644 dist/schemas/3.0.9/bundled/content-standards/validate-content-delivery-response.json create mode 100644 dist/schemas/3.0.9/bundled/core/tasks-get-request.json create mode 100644 dist/schemas/3.0.9/bundled/core/tasks-get-response.json create mode 100644 dist/schemas/3.0.9/bundled/core/tasks-list-request.json create mode 100644 dist/schemas/3.0.9/bundled/core/tasks-list-response.json create mode 100644 dist/schemas/3.0.9/bundled/creative/get-creative-delivery-request.json create mode 100644 dist/schemas/3.0.9/bundled/creative/get-creative-delivery-response.json create mode 100644 dist/schemas/3.0.9/bundled/creative/get-creative-features-request.json create mode 100644 dist/schemas/3.0.9/bundled/creative/get-creative-features-response.json create mode 100644 dist/schemas/3.0.9/bundled/creative/list-creative-formats-request.json create mode 100644 dist/schemas/3.0.9/bundled/creative/list-creative-formats-response.json create mode 100644 dist/schemas/3.0.9/bundled/creative/list-creatives-request.json create mode 100644 dist/schemas/3.0.9/bundled/creative/list-creatives-response.json create mode 100644 dist/schemas/3.0.9/bundled/creative/preview-creative-request.json create mode 100644 dist/schemas/3.0.9/bundled/creative/preview-creative-response.json create mode 100644 dist/schemas/3.0.9/bundled/creative/sync-creatives-request.json create mode 100644 dist/schemas/3.0.9/bundled/creative/sync-creatives-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/build-creative-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/build-creative-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/create-media-buy-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/create-media-buy-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/get-media-buy-delivery-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/get-media-buy-delivery-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/get-media-buys-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/get-media-buys-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/get-products-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/get-products-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/list-creative-formats-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/list-creative-formats-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/log-event-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/log-event-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/package-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/provide-performance-feedback-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/provide-performance-feedback-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/sync-audiences-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/sync-audiences-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/sync-catalogs-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/sync-catalogs-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/sync-event-sources-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/sync-event-sources-response.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/update-media-buy-request.json create mode 100644 dist/schemas/3.0.9/bundled/media-buy/update-media-buy-response.json create mode 100644 dist/schemas/3.0.9/bundled/property/create-property-list-request.json create mode 100644 dist/schemas/3.0.9/bundled/property/create-property-list-response.json create mode 100644 dist/schemas/3.0.9/bundled/property/delete-property-list-request.json create mode 100644 dist/schemas/3.0.9/bundled/property/delete-property-list-response.json create mode 100644 dist/schemas/3.0.9/bundled/property/get-property-list-request.json create mode 100644 dist/schemas/3.0.9/bundled/property/get-property-list-response.json create mode 100644 dist/schemas/3.0.9/bundled/property/list-property-lists-request.json create mode 100644 dist/schemas/3.0.9/bundled/property/list-property-lists-response.json create mode 100644 dist/schemas/3.0.9/bundled/property/update-property-list-request.json create mode 100644 dist/schemas/3.0.9/bundled/property/update-property-list-response.json create mode 100644 dist/schemas/3.0.9/bundled/property/validate-property-delivery-request.json create mode 100644 dist/schemas/3.0.9/bundled/property/validate-property-delivery-response.json create mode 100644 dist/schemas/3.0.9/bundled/protocol/get-adcp-capabilities-request.json create mode 100644 dist/schemas/3.0.9/bundled/protocol/get-adcp-capabilities-response.json create mode 100644 dist/schemas/3.0.9/bundled/signals/activate-signal-request.json create mode 100644 dist/schemas/3.0.9/bundled/signals/activate-signal-response.json create mode 100644 dist/schemas/3.0.9/bundled/signals/get-signals-request.json create mode 100644 dist/schemas/3.0.9/bundled/signals/get-signals-response.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-get-offering-request.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-get-offering-response.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-initiate-session-request.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-initiate-session-response.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-send-message-request.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-send-message-response.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-terminate-session-request.json create mode 100644 dist/schemas/3.0.9/bundled/sponsored-intelligence/si-terminate-session-response.json create mode 100644 dist/schemas/3.0.9/collection/base-collection-source.json create mode 100644 dist/schemas/3.0.9/collection/collection-list-changed-webhook.json create mode 100644 dist/schemas/3.0.9/collection/collection-list-filters.json create mode 100644 dist/schemas/3.0.9/collection/collection-list.json create mode 100644 dist/schemas/3.0.9/collection/create-collection-list-request.json create mode 100644 dist/schemas/3.0.9/collection/create-collection-list-response.json create mode 100644 dist/schemas/3.0.9/collection/delete-collection-list-request.json create mode 100644 dist/schemas/3.0.9/collection/delete-collection-list-response.json create mode 100644 dist/schemas/3.0.9/collection/get-collection-list-request.json create mode 100644 dist/schemas/3.0.9/collection/get-collection-list-response.json create mode 100644 dist/schemas/3.0.9/collection/list-collection-lists-request.json create mode 100644 dist/schemas/3.0.9/collection/list-collection-lists-response.json create mode 100644 dist/schemas/3.0.9/collection/update-collection-list-request.json create mode 100644 dist/schemas/3.0.9/collection/update-collection-list-response.json create mode 100644 dist/schemas/3.0.9/compliance/comply-test-controller-request.json create mode 100644 dist/schemas/3.0.9/compliance/comply-test-controller-response.json create mode 100644 dist/schemas/3.0.9/content-standards/artifact-webhook-payload.json create mode 100644 dist/schemas/3.0.9/content-standards/artifact.json create mode 100644 dist/schemas/3.0.9/content-standards/calibrate-content-request.json create mode 100644 dist/schemas/3.0.9/content-standards/calibrate-content-response.json create mode 100644 dist/schemas/3.0.9/content-standards/content-standards.json create mode 100644 dist/schemas/3.0.9/content-standards/create-content-standards-request.json create mode 100644 dist/schemas/3.0.9/content-standards/create-content-standards-response.json create mode 100644 dist/schemas/3.0.9/content-standards/get-content-standards-request.json create mode 100644 dist/schemas/3.0.9/content-standards/get-content-standards-response.json create mode 100644 dist/schemas/3.0.9/content-standards/get-media-buy-artifacts-request.json create mode 100644 dist/schemas/3.0.9/content-standards/get-media-buy-artifacts-response.json create mode 100644 dist/schemas/3.0.9/content-standards/list-content-standards-request.json create mode 100644 dist/schemas/3.0.9/content-standards/list-content-standards-response.json create mode 100644 dist/schemas/3.0.9/content-standards/update-content-standards-request.json create mode 100644 dist/schemas/3.0.9/content-standards/update-content-standards-response.json create mode 100644 dist/schemas/3.0.9/content-standards/validate-content-delivery-request.json create mode 100644 dist/schemas/3.0.9/content-standards/validate-content-delivery-response.json create mode 100644 dist/schemas/3.0.9/core/account-ref.json create mode 100644 dist/schemas/3.0.9/core/account.json create mode 100644 dist/schemas/3.0.9/core/activation-key.json create mode 100644 dist/schemas/3.0.9/core/ad-inventory-config.json create mode 100644 dist/schemas/3.0.9/core/agent-encryption-key.json create mode 100644 dist/schemas/3.0.9/core/agent-signing-key.json create mode 100644 dist/schemas/3.0.9/core/app-item.json create mode 100644 dist/schemas/3.0.9/core/assets/asset-union.json create mode 100644 dist/schemas/3.0.9/core/assets/audio-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/brief-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/catalog-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/css-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/daast-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/html-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/image-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/javascript-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/markdown-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/text-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/url-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/vast-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/video-asset.json create mode 100644 dist/schemas/3.0.9/core/assets/webhook-asset.json create mode 100644 dist/schemas/3.0.9/core/async-response-data.json create mode 100644 dist/schemas/3.0.9/core/attribution-window.json create mode 100644 dist/schemas/3.0.9/core/audience-member.json create mode 100644 dist/schemas/3.0.9/core/audience-selector.json create mode 100644 dist/schemas/3.0.9/core/brand-id.json create mode 100644 dist/schemas/3.0.9/core/brand-ref.json create mode 100644 dist/schemas/3.0.9/core/business-entity.json create mode 100644 dist/schemas/3.0.9/core/cancellation-policy.json create mode 100644 dist/schemas/3.0.9/core/catalog-field-mapping.json create mode 100644 dist/schemas/3.0.9/core/catalog.json create mode 100644 dist/schemas/3.0.9/core/catchment.json create mode 100644 dist/schemas/3.0.9/core/collection-distribution.json create mode 100644 dist/schemas/3.0.9/core/collection-list-ref.json create mode 100644 dist/schemas/3.0.9/core/collection-selector.json create mode 100644 dist/schemas/3.0.9/core/collection.json create mode 100644 dist/schemas/3.0.9/core/content-rating.json create mode 100644 dist/schemas/3.0.9/core/context.json create mode 100644 dist/schemas/3.0.9/core/creative-asset.json create mode 100644 dist/schemas/3.0.9/core/creative-assignment.json create mode 100644 dist/schemas/3.0.9/core/creative-brief.json create mode 100644 dist/schemas/3.0.9/core/creative-consumption.json create mode 100644 dist/schemas/3.0.9/core/creative-filters.json create mode 100644 dist/schemas/3.0.9/core/creative-item.json create mode 100644 dist/schemas/3.0.9/core/creative-manifest.json create mode 100644 dist/schemas/3.0.9/core/creative-policy.json create mode 100644 dist/schemas/3.0.9/core/creative-variable.json create mode 100644 dist/schemas/3.0.9/core/creative-variant.json create mode 100644 dist/schemas/3.0.9/core/data-provider-signal-selector.json create mode 100644 dist/schemas/3.0.9/core/date-range.json create mode 100644 dist/schemas/3.0.9/core/datetime-range.json create mode 100644 dist/schemas/3.0.9/core/daypart-target.json create mode 100644 dist/schemas/3.0.9/core/deadline-policy.json create mode 100644 dist/schemas/3.0.9/core/delivery-forecast.json create mode 100644 dist/schemas/3.0.9/core/delivery-metrics.json create mode 100644 dist/schemas/3.0.9/core/deployment.json create mode 100644 dist/schemas/3.0.9/core/destination-item.json create mode 100644 dist/schemas/3.0.9/core/destination.json create mode 100644 dist/schemas/3.0.9/core/diagnostic-issue.json create mode 100644 dist/schemas/3.0.9/core/duration.json create mode 100644 dist/schemas/3.0.9/core/education-item.json create mode 100644 dist/schemas/3.0.9/core/error.json create mode 100644 dist/schemas/3.0.9/core/event-custom-data.json create mode 100644 dist/schemas/3.0.9/core/event-source-health.json create mode 100644 dist/schemas/3.0.9/core/event.json create mode 100644 dist/schemas/3.0.9/core/ext.json create mode 100644 dist/schemas/3.0.9/core/feature-requirement.json create mode 100644 dist/schemas/3.0.9/core/flight-item.json create mode 100644 dist/schemas/3.0.9/core/forecast-point.json create mode 100644 dist/schemas/3.0.9/core/forecast-range.json create mode 100644 dist/schemas/3.0.9/core/format-id.json create mode 100644 dist/schemas/3.0.9/core/format.json create mode 100644 dist/schemas/3.0.9/core/frequency-cap.json create mode 100644 dist/schemas/3.0.9/core/generation-credential.json create mode 100644 dist/schemas/3.0.9/core/geo-breakdown-support.json create mode 100644 dist/schemas/3.0.9/core/hotel-item.json create mode 100644 dist/schemas/3.0.9/core/identifier.json create mode 100644 dist/schemas/3.0.9/core/industry-identifier.json create mode 100644 dist/schemas/3.0.9/core/insertion-order.json create mode 100644 dist/schemas/3.0.9/core/installment-deadlines.json create mode 100644 dist/schemas/3.0.9/core/installment.json create mode 100644 dist/schemas/3.0.9/core/job-item.json create mode 100644 dist/schemas/3.0.9/core/limited-series.json create mode 100644 dist/schemas/3.0.9/core/material-deadline.json create mode 100644 dist/schemas/3.0.9/core/mcp-webhook-payload.json create mode 100644 dist/schemas/3.0.9/core/measurement-readiness.json create mode 100644 dist/schemas/3.0.9/core/measurement-terms.json create mode 100644 dist/schemas/3.0.9/core/measurement-window.json create mode 100644 dist/schemas/3.0.9/core/media-buy-features.json create mode 100644 dist/schemas/3.0.9/core/media-buy.json create mode 100644 dist/schemas/3.0.9/core/offering-asset-group.json create mode 100644 dist/schemas/3.0.9/core/offering.json create mode 100644 dist/schemas/3.0.9/core/optimization-goal.json create mode 100644 dist/schemas/3.0.9/core/outcome-measurement.json create mode 100644 dist/schemas/3.0.9/core/overlay.json create mode 100644 dist/schemas/3.0.9/core/package.json create mode 100644 dist/schemas/3.0.9/core/pagination-request.json create mode 100644 dist/schemas/3.0.9/core/pagination-response.json create mode 100644 dist/schemas/3.0.9/core/performance-feedback.json create mode 100644 dist/schemas/3.0.9/core/performance-standard.json create mode 100644 dist/schemas/3.0.9/core/placement-definition.json create mode 100644 dist/schemas/3.0.9/core/placement.json create mode 100644 dist/schemas/3.0.9/core/planned-delivery.json create mode 100644 dist/schemas/3.0.9/core/price.json create mode 100644 dist/schemas/3.0.9/core/pricing-option.json create mode 100644 dist/schemas/3.0.9/core/product-allocation.json create mode 100644 dist/schemas/3.0.9/core/product-filters.json create mode 100644 dist/schemas/3.0.9/core/product.json create mode 100644 dist/schemas/3.0.9/core/property-id.json create mode 100644 dist/schemas/3.0.9/core/property-list-ref.json create mode 100644 dist/schemas/3.0.9/core/property-tag.json create mode 100644 dist/schemas/3.0.9/core/property.json create mode 100644 dist/schemas/3.0.9/core/proposal.json create mode 100644 dist/schemas/3.0.9/core/protocol-envelope.json create mode 100644 dist/schemas/3.0.9/core/provenance.json create mode 100644 dist/schemas/3.0.9/core/publisher-property-selector.json create mode 100644 dist/schemas/3.0.9/core/push-notification-config.json create mode 100644 dist/schemas/3.0.9/core/real-estate-item.json create mode 100644 dist/schemas/3.0.9/core/reference-asset.json create mode 100644 dist/schemas/3.0.9/core/reporting-capabilities.json create mode 100644 dist/schemas/3.0.9/core/reporting-webhook.json create mode 100644 dist/schemas/3.0.9/core/requirements/asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/audio-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/catalog-field-binding.json create mode 100644 dist/schemas/3.0.9/core/requirements/catalog-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/css-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/daast-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/html-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/image-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/javascript-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/markdown-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/offering-asset-constraint.json create mode 100644 dist/schemas/3.0.9/core/requirements/text-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/url-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/vast-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/video-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/requirements/webhook-asset-requirements.json create mode 100644 dist/schemas/3.0.9/core/response.json create mode 100644 dist/schemas/3.0.9/core/rights-constraint.json create mode 100644 dist/schemas/3.0.9/core/seller-agent-ref.json create mode 100644 dist/schemas/3.0.9/core/signal-definition.json create mode 100644 dist/schemas/3.0.9/core/signal-filters.json create mode 100644 dist/schemas/3.0.9/core/signal-id.json create mode 100644 dist/schemas/3.0.9/core/signal-pricing-option.json create mode 100644 dist/schemas/3.0.9/core/signal-pricing.json create mode 100644 dist/schemas/3.0.9/core/signal-targeting.json create mode 100644 dist/schemas/3.0.9/core/special.json create mode 100644 dist/schemas/3.0.9/core/start-timing.json create mode 100644 dist/schemas/3.0.9/core/store-item.json create mode 100644 dist/schemas/3.0.9/core/talent.json create mode 100644 dist/schemas/3.0.9/core/targeting.json create mode 100644 dist/schemas/3.0.9/core/tasks-get-request.json create mode 100644 dist/schemas/3.0.9/core/tasks-get-response.json create mode 100644 dist/schemas/3.0.9/core/tasks-list-request.json create mode 100644 dist/schemas/3.0.9/core/tasks-list-response.json create mode 100644 dist/schemas/3.0.9/core/user-match.json create mode 100644 dist/schemas/3.0.9/core/vehicle-item.json create mode 100644 dist/schemas/3.0.9/core/vendor-pricing-option.json create mode 100644 dist/schemas/3.0.9/core/x-entity-types.json create mode 100644 dist/schemas/3.0.9/creative/asset-types/index.json create mode 100644 dist/schemas/3.0.9/creative/creative-feature-result.json create mode 100644 dist/schemas/3.0.9/creative/get-creative-delivery-request.json create mode 100644 dist/schemas/3.0.9/creative/get-creative-delivery-response.json create mode 100644 dist/schemas/3.0.9/creative/get-creative-features-request.json create mode 100644 dist/schemas/3.0.9/creative/get-creative-features-response.json create mode 100644 dist/schemas/3.0.9/creative/list-creative-formats-request.json create mode 100644 dist/schemas/3.0.9/creative/list-creative-formats-response.json create mode 100644 dist/schemas/3.0.9/creative/list-creatives-request.json create mode 100644 dist/schemas/3.0.9/creative/list-creatives-response.json create mode 100644 dist/schemas/3.0.9/creative/preview-creative-request.json create mode 100644 dist/schemas/3.0.9/creative/preview-creative-response.json create mode 100644 dist/schemas/3.0.9/creative/preview-render.json create mode 100644 dist/schemas/3.0.9/creative/sync-creatives-async-response-input-required.json create mode 100644 dist/schemas/3.0.9/creative/sync-creatives-async-response-submitted.json create mode 100644 dist/schemas/3.0.9/creative/sync-creatives-async-response-working.json create mode 100644 dist/schemas/3.0.9/creative/sync-creatives-request.json create mode 100644 dist/schemas/3.0.9/creative/sync-creatives-response.json create mode 100644 dist/schemas/3.0.9/enums/account-scope.json create mode 100644 dist/schemas/3.0.9/enums/account-status.json create mode 100644 dist/schemas/3.0.9/enums/action-source.json create mode 100644 dist/schemas/3.0.9/enums/adcp-protocol.json create mode 100644 dist/schemas/3.0.9/enums/adjustment-kind.json create mode 100644 dist/schemas/3.0.9/enums/advertiser-industry.json create mode 100644 dist/schemas/3.0.9/enums/age-verification-method.json create mode 100644 dist/schemas/3.0.9/enums/assessment-status.json create mode 100644 dist/schemas/3.0.9/enums/asset-content-type.json create mode 100644 dist/schemas/3.0.9/enums/attribution-model.json create mode 100644 dist/schemas/3.0.9/enums/audience-source.json create mode 100644 dist/schemas/3.0.9/enums/audience-status.json create mode 100644 dist/schemas/3.0.9/enums/audio-channel-layout.json create mode 100644 dist/schemas/3.0.9/enums/auth-scheme.json create mode 100644 dist/schemas/3.0.9/enums/available-metric.json create mode 100644 dist/schemas/3.0.9/enums/billing-party.json create mode 100644 dist/schemas/3.0.9/enums/binary-verdict.json create mode 100644 dist/schemas/3.0.9/enums/brand-agent-type.json create mode 100644 dist/schemas/3.0.9/enums/canceled-by.json create mode 100644 dist/schemas/3.0.9/enums/catalog-action.json create mode 100644 dist/schemas/3.0.9/enums/catalog-item-status.json create mode 100644 dist/schemas/3.0.9/enums/catalog-type.json create mode 100644 dist/schemas/3.0.9/enums/channels.json create mode 100644 dist/schemas/3.0.9/enums/cloud-storage-protocol.json create mode 100644 dist/schemas/3.0.9/enums/co-branding-requirement.json create mode 100644 dist/schemas/3.0.9/enums/collection-cadence.json create mode 100644 dist/schemas/3.0.9/enums/collection-kind.json create mode 100644 dist/schemas/3.0.9/enums/collection-relationship.json create mode 100644 dist/schemas/3.0.9/enums/collection-status.json create mode 100644 dist/schemas/3.0.9/enums/consent-basis.json create mode 100644 dist/schemas/3.0.9/enums/content-id-type.json create mode 100644 dist/schemas/3.0.9/enums/content-rating-system.json create mode 100644 dist/schemas/3.0.9/enums/creative-action.json create mode 100644 dist/schemas/3.0.9/enums/creative-agent-capability.json create mode 100644 dist/schemas/3.0.9/enums/creative-approval-status.json create mode 100644 dist/schemas/3.0.9/enums/creative-identifier-type.json create mode 100644 dist/schemas/3.0.9/enums/creative-quality.json create mode 100644 dist/schemas/3.0.9/enums/creative-sort-field.json create mode 100644 dist/schemas/3.0.9/enums/creative-status.json create mode 100644 dist/schemas/3.0.9/enums/daast-tracking-event.json create mode 100644 dist/schemas/3.0.9/enums/daast-version.json create mode 100644 dist/schemas/3.0.9/enums/day-of-week.json create mode 100644 dist/schemas/3.0.9/enums/delegation-authority.json create mode 100644 dist/schemas/3.0.9/enums/delivery-type.json create mode 100644 dist/schemas/3.0.9/enums/demographic-system.json create mode 100644 dist/schemas/3.0.9/enums/derivative-type.json create mode 100644 dist/schemas/3.0.9/enums/device-platform.json create mode 100644 dist/schemas/3.0.9/enums/device-type.json create mode 100644 dist/schemas/3.0.9/enums/digital-source-type.json create mode 100644 dist/schemas/3.0.9/enums/dimension-unit.json create mode 100644 dist/schemas/3.0.9/enums/disclosure-persistence.json create mode 100644 dist/schemas/3.0.9/enums/disclosure-position.json create mode 100644 dist/schemas/3.0.9/enums/distance-unit.json create mode 100644 dist/schemas/3.0.9/enums/distribution-identifier-type.json create mode 100644 dist/schemas/3.0.9/enums/error-code.json create mode 100644 dist/schemas/3.0.9/enums/escalation-severity.json create mode 100644 dist/schemas/3.0.9/enums/event-type.json create mode 100644 dist/schemas/3.0.9/enums/exclusivity.json create mode 100644 dist/schemas/3.0.9/enums/feature-check-status.json create mode 100644 dist/schemas/3.0.9/enums/feed-format.json create mode 100644 dist/schemas/3.0.9/enums/feedback-source.json create mode 100644 dist/schemas/3.0.9/enums/forecast-method.json create mode 100644 dist/schemas/3.0.9/enums/forecast-range-unit.json create mode 100644 dist/schemas/3.0.9/enums/forecastable-metric.json create mode 100644 dist/schemas/3.0.9/enums/format-id-parameter.json create mode 100644 dist/schemas/3.0.9/enums/frame-rate-type.json create mode 100644 dist/schemas/3.0.9/enums/frequency-cap-scope.json create mode 100644 dist/schemas/3.0.9/enums/genre-taxonomy.json create mode 100644 dist/schemas/3.0.9/enums/geo-level.json create mode 100644 dist/schemas/3.0.9/enums/gop-type.json create mode 100644 dist/schemas/3.0.9/enums/governance-decision.json create mode 100644 dist/schemas/3.0.9/enums/governance-domain.json create mode 100644 dist/schemas/3.0.9/enums/governance-mode.json create mode 100644 dist/schemas/3.0.9/enums/governance-phase.json create mode 100644 dist/schemas/3.0.9/enums/history-entry-type.json create mode 100644 dist/schemas/3.0.9/enums/http-method.json create mode 100644 dist/schemas/3.0.9/enums/identifier-types.json create mode 100644 dist/schemas/3.0.9/enums/installment-status.json create mode 100644 dist/schemas/3.0.9/enums/javascript-module-type.json create mode 100644 dist/schemas/3.0.9/enums/landing-page-requirement.json create mode 100644 dist/schemas/3.0.9/enums/makegood-remedy.json create mode 100644 dist/schemas/3.0.9/enums/markdown-flavor.json create mode 100644 dist/schemas/3.0.9/enums/match-id-type.json create mode 100644 dist/schemas/3.0.9/enums/match-type.json create mode 100644 dist/schemas/3.0.9/enums/media-buy-status.json create mode 100644 dist/schemas/3.0.9/enums/media-buy-valid-action.json create mode 100644 dist/schemas/3.0.9/enums/metric-type.json create mode 100644 dist/schemas/3.0.9/enums/metro-system.json create mode 100644 dist/schemas/3.0.9/enums/moov-atom-position.json create mode 100644 dist/schemas/3.0.9/enums/notification-type.json create mode 100644 dist/schemas/3.0.9/enums/outcome-type.json create mode 100644 dist/schemas/3.0.9/enums/pacing.json create mode 100644 dist/schemas/3.0.9/enums/payment-terms.json create mode 100644 dist/schemas/3.0.9/enums/performance-standard-metric.json create mode 100644 dist/schemas/3.0.9/enums/policy-category.json create mode 100644 dist/schemas/3.0.9/enums/policy-enforcement.json create mode 100644 dist/schemas/3.0.9/enums/postal-system.json create mode 100644 dist/schemas/3.0.9/enums/preview-output-format.json create mode 100644 dist/schemas/3.0.9/enums/pricing-model.json create mode 100644 dist/schemas/3.0.9/enums/production-quality.json create mode 100644 dist/schemas/3.0.9/enums/property-type.json create mode 100644 dist/schemas/3.0.9/enums/proposal-status.json create mode 100644 dist/schemas/3.0.9/enums/publisher-identifier-types.json create mode 100644 dist/schemas/3.0.9/enums/purchase-type.json create mode 100644 dist/schemas/3.0.9/enums/reach-unit.json create mode 100644 dist/schemas/3.0.9/enums/reporting-frequency.json create mode 100644 dist/schemas/3.0.9/enums/response-type.json create mode 100644 dist/schemas/3.0.9/enums/restricted-attribute.json create mode 100644 dist/schemas/3.0.9/enums/right-type.json create mode 100644 dist/schemas/3.0.9/enums/right-use.json create mode 100644 dist/schemas/3.0.9/enums/rights-billing-period.json create mode 100644 dist/schemas/3.0.9/enums/scan-type.json create mode 100644 dist/schemas/3.0.9/enums/si-session-status.json create mode 100644 dist/schemas/3.0.9/enums/signal-catalog-type.json create mode 100644 dist/schemas/3.0.9/enums/signal-source.json create mode 100644 dist/schemas/3.0.9/enums/signal-value-type.json create mode 100644 dist/schemas/3.0.9/enums/snapshot-unavailable-reason.json create mode 100644 dist/schemas/3.0.9/enums/sort-direction.json create mode 100644 dist/schemas/3.0.9/enums/sort-metric.json create mode 100644 dist/schemas/3.0.9/enums/special-category.json create mode 100644 dist/schemas/3.0.9/enums/specialism.json create mode 100644 dist/schemas/3.0.9/enums/talent-role.json create mode 100644 dist/schemas/3.0.9/enums/task-status.json create mode 100644 dist/schemas/3.0.9/enums/task-type.json create mode 100644 dist/schemas/3.0.9/enums/transport-mode.json create mode 100644 dist/schemas/3.0.9/enums/travel-time-unit.json create mode 100644 dist/schemas/3.0.9/enums/uid-type.json create mode 100644 dist/schemas/3.0.9/enums/universal-macro.json create mode 100644 dist/schemas/3.0.9/enums/update-frequency.json create mode 100644 dist/schemas/3.0.9/enums/url-asset-type.json create mode 100644 dist/schemas/3.0.9/enums/validation-mode.json create mode 100644 dist/schemas/3.0.9/enums/vast-tracking-event.json create mode 100644 dist/schemas/3.0.9/enums/vast-version.json create mode 100644 dist/schemas/3.0.9/enums/viewability-standard.json create mode 100644 dist/schemas/3.0.9/enums/wcag-level.json create mode 100644 dist/schemas/3.0.9/enums/webhook-response-type.json create mode 100644 dist/schemas/3.0.9/enums/webhook-security-method.json create mode 100644 dist/schemas/3.0.9/error-details/account-setup-required.json create mode 100644 dist/schemas/3.0.9/error-details/audience-too-small.json create mode 100644 dist/schemas/3.0.9/error-details/budget-too-low.json create mode 100644 dist/schemas/3.0.9/error-details/conflict.json create mode 100644 dist/schemas/3.0.9/error-details/creative-rejected.json create mode 100644 dist/schemas/3.0.9/error-details/policy-violation.json create mode 100644 dist/schemas/3.0.9/error-details/rate-limited.json create mode 100644 dist/schemas/3.0.9/error-details/vendor-error-codes.json create mode 100644 dist/schemas/3.0.9/extensions/extension-meta.json create mode 100644 dist/schemas/3.0.9/extensions/index.json create mode 100644 dist/schemas/3.0.9/governance/attribute-definition.json create mode 100644 dist/schemas/3.0.9/governance/audience-constraints.json create mode 100644 dist/schemas/3.0.9/governance/check-governance-request.json create mode 100644 dist/schemas/3.0.9/governance/check-governance-response.json create mode 100644 dist/schemas/3.0.9/governance/get-plan-audit-logs-request.json create mode 100644 dist/schemas/3.0.9/governance/get-plan-audit-logs-response.json create mode 100644 dist/schemas/3.0.9/governance/policy-category-definition.json create mode 100644 dist/schemas/3.0.9/governance/policy-entry.json create mode 100644 dist/schemas/3.0.9/governance/policy-ref.json create mode 100644 dist/schemas/3.0.9/governance/report-plan-outcome-request.json create mode 100644 dist/schemas/3.0.9/governance/report-plan-outcome-response.json create mode 100644 dist/schemas/3.0.9/governance/sync-plans-request.json create mode 100644 dist/schemas/3.0.9/governance/sync-plans-response.json create mode 100644 dist/schemas/3.0.9/index.json create mode 100644 dist/schemas/3.0.9/manifest.json create mode 100644 dist/schemas/3.0.9/manifest.schema.json create mode 100644 dist/schemas/3.0.9/media-buy/build-creative-async-response-input-required.json create mode 100644 dist/schemas/3.0.9/media-buy/build-creative-async-response-submitted.json create mode 100644 dist/schemas/3.0.9/media-buy/build-creative-async-response-working.json create mode 100644 dist/schemas/3.0.9/media-buy/build-creative-request.json create mode 100644 dist/schemas/3.0.9/media-buy/build-creative-response.json create mode 100644 dist/schemas/3.0.9/media-buy/create-media-buy-async-response-input-required.json create mode 100644 dist/schemas/3.0.9/media-buy/create-media-buy-async-response-submitted.json create mode 100644 dist/schemas/3.0.9/media-buy/create-media-buy-async-response-working.json create mode 100644 dist/schemas/3.0.9/media-buy/create-media-buy-request.json create mode 100644 dist/schemas/3.0.9/media-buy/create-media-buy-response.json create mode 100644 dist/schemas/3.0.9/media-buy/get-media-buy-delivery-request.json create mode 100644 dist/schemas/3.0.9/media-buy/get-media-buy-delivery-response.json create mode 100644 dist/schemas/3.0.9/media-buy/get-media-buys-request.json create mode 100644 dist/schemas/3.0.9/media-buy/get-media-buys-response.json create mode 100644 dist/schemas/3.0.9/media-buy/get-products-async-response-input-required.json create mode 100644 dist/schemas/3.0.9/media-buy/get-products-async-response-submitted.json create mode 100644 dist/schemas/3.0.9/media-buy/get-products-async-response-working.json create mode 100644 dist/schemas/3.0.9/media-buy/get-products-request.json create mode 100644 dist/schemas/3.0.9/media-buy/get-products-response.json create mode 100644 dist/schemas/3.0.9/media-buy/list-creative-formats-request.json create mode 100644 dist/schemas/3.0.9/media-buy/list-creative-formats-response.json create mode 100644 dist/schemas/3.0.9/media-buy/log-event-request.json create mode 100644 dist/schemas/3.0.9/media-buy/log-event-response.json create mode 100644 dist/schemas/3.0.9/media-buy/package-request.json create mode 100644 dist/schemas/3.0.9/media-buy/package-update.json create mode 100644 dist/schemas/3.0.9/media-buy/provide-performance-feedback-request.json create mode 100644 dist/schemas/3.0.9/media-buy/provide-performance-feedback-response.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-audiences-request.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-audiences-response.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-catalogs-async-response-input-required.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-catalogs-async-response-submitted.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-catalogs-async-response-working.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-catalogs-request.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-catalogs-response.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-event-sources-request.json create mode 100644 dist/schemas/3.0.9/media-buy/sync-event-sources-response.json create mode 100644 dist/schemas/3.0.9/media-buy/update-media-buy-async-response-input-required.json create mode 100644 dist/schemas/3.0.9/media-buy/update-media-buy-async-response-submitted.json create mode 100644 dist/schemas/3.0.9/media-buy/update-media-buy-async-response-working.json create mode 100644 dist/schemas/3.0.9/media-buy/update-media-buy-request.json create mode 100644 dist/schemas/3.0.9/media-buy/update-media-buy-response.json create mode 100644 dist/schemas/3.0.9/pricing-options/cpa-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/cpc-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/cpcv-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/cpm-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/cpp-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/cpv-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/flat-rate-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/price-breakdown.json create mode 100644 dist/schemas/3.0.9/pricing-options/price-guidance.json create mode 100644 dist/schemas/3.0.9/pricing-options/time-option.json create mode 100644 dist/schemas/3.0.9/pricing-options/vcpm-option.json create mode 100644 dist/schemas/3.0.9/property/authorization-result.json create mode 100644 dist/schemas/3.0.9/property/base-property-source.json create mode 100644 dist/schemas/3.0.9/property/create-property-list-request.json create mode 100644 dist/schemas/3.0.9/property/create-property-list-response.json create mode 100644 dist/schemas/3.0.9/property/delete-property-list-request.json create mode 100644 dist/schemas/3.0.9/property/delete-property-list-response.json create mode 100644 dist/schemas/3.0.9/property/delivery-record.json create mode 100644 dist/schemas/3.0.9/property/get-property-list-request.json create mode 100644 dist/schemas/3.0.9/property/get-property-list-response.json create mode 100644 dist/schemas/3.0.9/property/list-property-lists-request.json create mode 100644 dist/schemas/3.0.9/property/list-property-lists-response.json create mode 100644 dist/schemas/3.0.9/property/property-error.json create mode 100644 dist/schemas/3.0.9/property/property-feature-definition.json create mode 100644 dist/schemas/3.0.9/property/property-feature-result.json create mode 100644 dist/schemas/3.0.9/property/property-feature-value.json create mode 100644 dist/schemas/3.0.9/property/property-feature.json create mode 100644 dist/schemas/3.0.9/property/property-list-changed-webhook.json create mode 100644 dist/schemas/3.0.9/property/property-list-filters.json create mode 100644 dist/schemas/3.0.9/property/property-list.json create mode 100644 dist/schemas/3.0.9/property/update-property-list-request.json create mode 100644 dist/schemas/3.0.9/property/update-property-list-response.json create mode 100644 dist/schemas/3.0.9/property/validate-property-delivery-request.json create mode 100644 dist/schemas/3.0.9/property/validate-property-delivery-response.json create mode 100644 dist/schemas/3.0.9/property/validation-result.json create mode 100644 dist/schemas/3.0.9/protocol/get-adcp-capabilities-request.json create mode 100644 dist/schemas/3.0.9/protocol/get-adcp-capabilities-response.json create mode 100644 dist/schemas/3.0.9/signals/activate-signal-request.json create mode 100644 dist/schemas/3.0.9/signals/activate-signal-response.json create mode 100644 dist/schemas/3.0.9/signals/get-signals-request.json create mode 100644 dist/schemas/3.0.9/signals/get-signals-response.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-capabilities.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-get-offering-request.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-get-offering-response.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-identity.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-initiate-session-request.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-initiate-session-response.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-send-message-request.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-send-message-response.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-terminate-session-request.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-terminate-session-response.json create mode 100644 dist/schemas/3.0.9/sponsored-intelligence/si-ui-element.json create mode 100644 dist/schemas/3.0.9/tmp/available-package.json create mode 100644 dist/schemas/3.0.9/tmp/context-match-request.json create mode 100644 dist/schemas/3.0.9/tmp/context-match-response.json create mode 100644 dist/schemas/3.0.9/tmp/error.json create mode 100644 dist/schemas/3.0.9/tmp/identity-match-request.json create mode 100644 dist/schemas/3.0.9/tmp/identity-match-response.json create mode 100644 dist/schemas/3.0.9/tmp/offer-price.json create mode 100644 dist/schemas/3.0.9/tmp/offer.json create mode 100644 dist/schemas/3.0.9/tmp/provider-registration.json diff --git a/.changeset/account-discovery-must-normative.md b/.changeset/account-discovery-must-normative.md deleted file mode 100644 index cd4a85d93c..0000000000 --- a/.changeset/account-discovery-must-normative.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Propagate account discovery MUST from `required-tasks.mdx` into `accounts/overview.mdx`. Every seller agent must expose at least one of `list_accounts` or `sync_accounts` — this restates the existing `required-tasks.mdx` MUST in the surface-level overview where implementors look first. No wire shape change. diff --git a/.changeset/fix-hmac-framing-skill-9421-default.md b/.changeset/fix-hmac-framing-skill-9421-default.md deleted file mode 100644 index 6aaf4f9669..0000000000 --- a/.changeset/fix-hmac-framing-skill-9421-default.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"adcontextprotocol": patch ---- - -Fix stale HMAC-as-recommended framing in reporting-webhook.json, auth-scheme.json, and create-media-buy-request.json's artifact_webhook; add RFC 9421 default guidance to call-adcp-agent SKILL.md. Description-only fixes aligning these surfaces with the existing push-notification-config.json framing (HMAC is the deprecated fallback, RFC 9421 is the default). No wire format changes. diff --git a/.changeset/forward-merge-storyboard-schema-allowlist.md b/.changeset/forward-merge-storyboard-schema-allowlist.md deleted file mode 100644 index 9e5f9ffe25..0000000000 --- a/.changeset/forward-merge-storyboard-schema-allowlist.md +++ /dev/null @@ -1,8 +0,0 @@ ---- ---- - -ci(release): forward-merge auto-resolves `storyboard-schema.yaml` on 3.0.x - -Backports the `.github/workflows/forward-merge-3.0.yml` allowlist update from #4229 — the workflow runs from the pushed branch, so 3.0.x needs its own copy. Promotes `static/compliance/source/universal/storyboard-schema.yaml` to the 3.1-track-divergence allowlist with `--ours` (keep main's superset), unsticking the post-Version-Packages forward-merge that's been failing since the storyboard-schema divergence emerged. - -Verified manually on PRs #3902 (3.0.5), the unmerged 3.0.6 forward-merge attempts, #4225 (3.0.7), and the 3.0.8 cut. Workflow file is taken verbatim from main's #4229; no other surface changes. diff --git a/CHANGELOG.md b/CHANGELOG.md index e207e7fa6c..1826b61fae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 3.0.9 + +### Patch Changes + +- 753dbe3: Propagate account discovery MUST from `required-tasks.mdx` into `accounts/overview.mdx`. Every seller agent must expose at least one of `list_accounts` or `sync_accounts` — this restates the existing `required-tasks.mdx` MUST in the surface-level overview where implementors look first. No wire shape change. +- 5d2e7be: Fix stale HMAC-as-recommended framing in reporting-webhook.json, auth-scheme.json, and create-media-buy-request.json's artifact_webhook; add RFC 9421 default guidance to call-adcp-agent SKILL.md. Description-only fixes aligning these surfaces with the existing push-notification-config.json framing (HMAC is the deprecated fallback, RFC 9421 is the default). No wire format changes. + ## 3.0.8 ### Patch Changes diff --git a/dist/compliance/3.0.9/domains/brand/index.yaml b/dist/compliance/3.0.9/domains/brand/index.yaml new file mode 100644 index 0000000000..9598013fe3 --- /dev/null +++ b/dist/compliance/3.0.9/domains/brand/index.yaml @@ -0,0 +1,163 @@ +id: brand_baseline +version: "1.0.0" +title: "Brand baseline" +protocol: brand +category: brand_baseline +summary: "Baseline protocol storyboard — every brand agent must declare the brand protocol in capabilities and return a schema-valid brand identity." +track: brand +required_tools: + - get_brand_identity + +narrative: | + Brand protocol agents are the identity layer of AdCP. Their job is to hold + brand identity data (names, logos, colors, fonts, tone) and expose it to + other agents — buyer agents, creative agents, DSPs — that need to render + on-brand creative or verify who a campaign is for. + + The baseline tests the minimum contract that every brand agent honors, + regardless of what additional capabilities (rights licensing, creative + approval) it layers on top: + + 1. Declare `brand` in `supported_protocols` on `get_adcp_capabilities`. + 2. Respond to `get_brand_identity` with a schema-valid identity manifest. + 3. Reject unknown `brand_id` values with a structured error. + + Rights licensing (`get_rights`, `acquire_rights`, `update_rights`, + `creative_approval`) ships experimentally in 3.0 and is covered by the + `brand-rights` specialism storyboard, not this baseline. + +agent: + interaction_model: brand_agent + capabilities: [] + examples: + - "Any brand agent (simple identity host or full rights platform)" + - "Brand-owned agents (Acme Outdoor)" + - "Third-party brand identity platforms" + - "Agency-hosted brand agents" + +caller: + role: buyer_agent + example: "Any buyer, creative agent, or DSP needing brand identity" + +prerequisites: + description: | + The test kit provides a sample brand (Nova Motors) that any brand agent + can serve identity for. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls `get_adcp_capabilities` to confirm the agent declares + the brand protocol before issuing any brand-identity call. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares `brand` in `supported_protocols`. + Without this claim the buyer MUST NOT send `get_brand_identity`. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring `brand` in `supported_protocols`. + + sample_request: + context: + correlation_id: "brand_baseline--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Response declares supported_protocols" + + - id: brand_identity_retrieval + title: "Brand identity retrieval" + narrative: | + The buyer calls `get_brand_identity` to retrieve the brand's identity + manifest. The minimum contract is a schema-valid response that echoes + the requested `brand_id` and carries at least one name. + + steps: + - id: get_brand_identity + title: "Retrieve brand identity" + narrative: | + The buyer calls `get_brand_identity` with a known `brand_id`. The + response MUST match the brand-identity schema and echo the + requested `brand_id`. Rich fields (logos, colors, fonts, tone, + visual_guidelines) are optional at the baseline level — the + minimum bar is that identity resolution works and is schema-valid. + task: get_brand_identity + schema_ref: "brand/get-brand-identity-request.json" + response_schema_ref: "brand/get-brand-identity-response.json" + doc_ref: "/brand-protocol/tasks/get_brand_identity" + stateful: false + expected: | + Return a schema-valid brand identity that echoes the requested + brand_id and includes at least one name. + + sample_request: + brand_id: "nova_motors" + context: + correlation_id: "brand_baseline--get_brand_identity" + context_outputs: + - path: "brand_id" + key: "brand_id" + + validations: + - check: response_schema + description: "Response matches get-brand-identity-response.json schema" + - check: field_present + path: "brand_id" + description: "Response includes brand_id" + - check: field_value + path: "brand_id" + value: "nova_motors" + description: "Returned brand_id echoes the requested brand" + - check: field_present + path: "names" + description: "Response includes brand names" + + - id: unknown_brand_rejection + title: "Unknown brand rejection" + narrative: | + Agents MUST reject unknown `brand_id` values with a structured + AdCP error rather than returning an empty or fabricated manifest. + + steps: + - id: get_brand_identity_unknown + title: "Reject unknown brand ID" + narrative: | + The buyer calls `get_brand_identity` with a `brand_id` the agent + does not serve. The response MUST be a structured error with a + recovery classification — not a success response with empty + fields. + task: get_brand_identity + schema_ref: "brand/get-brand-identity-request.json" + response_schema_ref: "brand/get-brand-identity-response.json" + doc_ref: "/brand-protocol/tasks/get_brand_identity" + stateful: false + expected: | + Return an AdCP error response indicating the brand is not known + to this agent. + + sample_request: + brand_id: "brand_that_does_not_exist_12345" + context: + correlation_id: "brand_baseline--get_brand_identity_unknown" + + expect_error: true + negative_path: payload_well_formed + validations: + - check: error_code + allowed_values: + - "brand_not_found" + - "BRAND_NOT_FOUND" + - "NOT_FOUND" + description: "Error code indicates brand-not-found" diff --git a/dist/compliance/3.0.9/domains/creative/index.yaml b/dist/compliance/3.0.9/domains/creative/index.yaml new file mode 100644 index 0000000000..cc6efd4f20 --- /dev/null +++ b/dist/compliance/3.0.9/domains/creative/index.yaml @@ -0,0 +1,412 @@ +id: creative_lifecycle +version: "1.0.0" +title: "Creative lifecycle" +category: creative_lifecycle +summary: "Full creative lifecycle on a stateful platform: sync multiple creatives, list with filtering, build and preview across formats, observe status transitions." +track: creative +required_tools: + - list_creative_formats + +narrative: | + You run a creative platform with a persistent library — an ad server, creative management + platform, or publisher that accepts and stores creative assets. A buyer agent pushes + multiple creatives in different formats, queries the library, builds serving tags, previews + renderings, and monitors creative status as assets move through your review pipeline. + + This storyboard covers the complete creative lifecycle from the buyer's perspective: + uploading assets, browsing the library, building deliverables across formats, and + observing status transitions as creatives move from pending_review through to accepted. + + The individual creative storyboards (template, ad server, sales agent) cover specific + interaction models. This storyboard tests the full lifecycle across multiple creatives + and formats on a single platform. + +agent: + interaction_model: stateful_preloaded + capabilities: + - has_creative_library + - supports_transformation + examples: + - "Innovid" + - "Flashtalking" + - "CM360" + - "Creative management platforms" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs creative assets in multiple formats (display, video, native) and + a brand identity. The test kit provides sample assets at standard ad dimensions. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_lifecycle--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: discover_formats + title: "Discover accepted formats" + narrative: | + Before pushing any creatives, the buyer discovers what formats your platform accepts. + This determines which assets to prepare and what dimensions and specs to target. + + steps: + - id: list_formats + title: "List creative formats" + narrative: | + The buyer calls list_creative_formats to discover what your platform accepts. + The response defines format specs: dimensions, asset requirements, mime types, + and any platform-specific constraints. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return all creative formats your platform accepts: + - format_id with your agent_url and unique id + - Asset requirements (dimensions, file sizes, mime types) + - Render dimensions + - At least two formats (e.g., display and video) + + sample_request: + context: + correlation_id: "creative_lifecycle--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--list_formats" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - id: sync_multiple + title: "Sync multiple creatives" + narrative: | + The buyer pushes three creatives in different formats to your platform: a display + banner, a video spot, and a native card. Your platform validates each creative + against its format specs and returns per-creative status. + + steps: + - id: sync_creatives + title: "Push three creatives in different formats" + narrative: | + The buyer syncs three creatives simultaneously: a 300x250 display banner, a 30s + video spot, and a native content card. Your platform validates each against its + format specs and returns per-creative action and status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + Accept and validate all three creatives: + - Per-creative action: created + - Per-creative status: accepted or pending_review + - Validation results for each creative + - Platform-assigned IDs if applicable + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "display_trail_pro_300x250" + name: "Trail Pro 3000 - Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + width: 300 + height: 250 + mime_type: "image/png" + - creative_id: "video_30s_trail_pro" + name: "Trail Pro 3000 - 30s Video" + format_id: + agent_url: "https://your-platform.example.com" + id: "video_30s" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + mime_type: "video/mp4" + - creative_id: "native_trail_pro" + name: "Trail Pro 3000 - Native Card" + format_id: + agent_url: "https://your-platform.example.com" + id: "native_content" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-native.png" + width: 1200 + height: 628 + mime_type: "image/png" + headline: + asset_type: "text" + content: "Trail Pro 3000 — Built for the Summit" + + idempotency_key: "$generate:uuid_v4#creative_lifecycle_sync_multiple_sync_creatives" + context: + correlation_id: "creative_lifecycle--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives" + description: "Response contains per-creative results" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: list_and_filter + title: "List creatives with filtering" + narrative: | + The buyer queries the creative library to see their synced creatives. First a broad + list, then filtered by format. This verifies the library correctly stores and indexes + the pushed creatives. + + steps: + - id: list_all + title: "List all creatives in library" + narrative: | + The buyer calls list_creatives with no filters to see all creatives in the + library for their account. The response includes the three creatives synced + in the previous phase. + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + Return creatives in the library: + - creatives array containing the synced items + - Each creative includes: creative_id, name, format_id, status + - At least three creatives from the sync phase + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "creative_lifecycle--list_all" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives" + description: "Response contains creatives array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--list_all" + description: "Context correlation_id returned unchanged" + - id: list_filtered + title: "List creatives filtered by format" + narrative: | + The buyer lists creatives filtered to a specific format (display only). The + response should only include creatives matching that format. + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + Return only creatives matching the format filter: + - creatives array filtered to display format + - Should include display_trail_pro_300x250 but not video or native + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + filters: + format_ids: + - agent_url: "https://your-platform.example.com" + id: "display_300x250" + + context: + correlation_id: "creative_lifecycle--list_filtered" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives" + description: "Response contains filtered creatives" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--list_filtered" + description: "Context correlation_id returned unchanged" + - id: build_and_preview + title: "Build and preview across formats" + narrative: | + The buyer builds serving tags and previews renderings for the synced creatives. + This tests multi-format output: a display tag, a VAST tag for video, and a + native rendering preview. + + steps: + - id: preview_display + title: "Preview the display creative" + narrative: | + The buyer calls preview_creative for the display banner to see how it renders + in the platform's environment before going live. + task: preview_creative + schema_ref: "creative/preview-creative-request.json" + response_schema_ref: "creative/preview-creative-response.json" + doc_ref: "/creative/task-reference/preview_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a preview of the display creative: + - preview_url: rendered preview the buyer can inspect + - render_dimensions: matches the 300x250 format + - status: preview available + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + request_type: "single" + creative_manifest: + creative_id: "display_trail_pro_300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/banner_300x250.jpg" + width: 300 + height: 250 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/trail-pro" + + context: + correlation_id: "creative_lifecycle--preview_display" + validations: + - check: response_schema + description: "Response matches preview-creative-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--preview_display" + description: "Context correlation_id returned unchanged" + - id: build_video_tag + title: "Build a VAST tag for the video creative" + narrative: | + The buyer builds a serving tag for the video creative. The platform produces + a VAST-compatible tag that the buyer can traffic to ad servers. + task: build_creative + schema_ref: "creative/build-creative-request.json" + response_schema_ref: "creative/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a built serving tag for the video creative: + - tag: VAST-compatible serving tag or URL + - format: matches the video format + - creative_id: matches the requested creative + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creative_id: "video_30s_trail_pro" + target_format_id: + agent_url: "https://your-platform.example.com" + id: "vast_30s" + idempotency_key: "$generate:uuid_v4#creative_lifecycle_build_video_tag" + + context: + correlation_id: "creative_lifecycle--build_video_tag" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--build_video_tag" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/governance/index.yaml b/dist/compliance/3.0.9/domains/governance/index.yaml new file mode 100644 index 0000000000..8839db4847 --- /dev/null +++ b/dist/compliance/3.0.9/domains/governance/index.yaml @@ -0,0 +1,683 @@ +id: media_buy_governance_escalation +version: "1.0.0" +title: "Governance denial and human escalation" +category: media_buy_governance_escalation +summary: "Buyer's governance agent denies a media buy that exceeds spending authority, escalates to a human who approves with conditions." +track: campaign_governance +required_tools: + - sync_plans + - check_governance + +narrative: | + The buyer's governance agent denies a media buy because it exceeds the agent's spending + authority. The governance check escalates to a human reviewer who approves with conditions. + + This storyboard shows the full governance loop: plan registration with spending authority + limits, product discovery, pre-buy governance check that gets denied, human escalation + that results in conditional approval, media buy creation with the approved governance + context, outcome reporting, and a complete audit trail. + + Governance exists to ensure that automated agents operate within defined boundaries. When + those boundaries are exceeded, the system escalates to humans rather than blocking + entirely. The audit trail provides accountability for every decision in the chain. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Publisher platform integrated with buyer governance" + - "SSP that respects governance checks" + - "Retail media network with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity, operator credentials, and a governance agent + URL. The test kit provides a sample brand (Acme Outdoor) with campaign parameters + and a governance configuration with spending authority limits. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_ctv_q2" + delivery_type: "guaranteed" + channels: ["ctv"] + format_ids: + - id: "video_30s" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "sports_ctv_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 45.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + plans: + - plan_id: "gov_acme_q2_2027" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 outdoor lifestyle campaign — display and video, Adults 25-54, US" + budget: + total: 50000 + currency: "USD" + reallocation_threshold: 20000 + flight: + start: "2027-04-01T00:00:00Z" + end: "2027-06-30T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "weekly_reporting_over_10k" + enforcement: "must" + policy: "Weekly reporting required for buys over $10K." + - policy_id: "seller_concentration_cap" + enforcement: "must" + policy: "No single-seller concentration above 60% of total budget." + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "media_buy_governance_escalation--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account and governance setup" + narrative: | + The buyer establishes an account and registers their governance agent with the + seller. The governance agent will be called before media buys are confirmed to + validate spending authority, brand safety, and compliance. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier + - action: created or updated + - status: active or pending_approval + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_account_setup_sync_accounts" + context: + correlation_id: "media_buy_governance_escalation--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: sync_governance + title: "Register governance agent" + narrative: | + The buyer tells your platform: "Before you confirm any media buy for this + account, call this governance agent to validate it." Your platform stores + the governance agent URL and will call it during create_media_buy. + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agents. Your platform should: + - Store the governance agent URLs for the specified accounts + - Return confirmation that agents were registered + - Use these agents during create_media_buy validation + + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "https://governance.pinnacle-agency.example" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_account_setup_sync_governance" + context: + correlation_id: "media_buy_governance_escalation--sync_governance" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--sync_governance" + description: "Context correlation_id returned unchanged" + - id: register_plan + title: "Register governance plan with spending limits" + narrative: | + The buyer registers a governance plan that defines the agent's spending authority. + The plan sets a per-transaction threshold below the intended media buy amount. + This ensures the governance check will trigger an escalation when the buy exceeds + the agent's authority. + + steps: + - id: sync_plans + title: "Register a governance plan with agent-limited authority" + narrative: | + The buyer's governance agent registers a plan with spending authority limits. + The plan sets a per-transaction threshold of $20K via reallocation_threshold + on the budget. Any media buy above this amount requires human approval. + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + comply_scenario: campaign_governance + stateful: true + expected: | + Acknowledge the governance plan: + - plan_id: identifier for this governance plan + - budget.reallocation_threshold: spending limit before re-evaluation + - human_review_required: set when the plan mandates escalation + + sample_request: + idempotency_key: "media-buy-governance-escalation-sync-plans-v1" + plans: + - plan_id: "gov_acme_q2_2027" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 outdoor lifestyle campaign — display and video, Adults 25-54, US" + budget: + total: 50000 + currency: "USD" + reallocation_threshold: 20000 + flight: + start: "2027-04-01T00:00:00Z" + end: "2027-06-30T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "weekly_reporting_over_10k" + enforcement: "must" + policy: "Weekly reporting required for buys over $10K." + - policy_id: "seller_concentration_cap" + enforcement: "must" + policy: "No single-seller concentration above 60% of total budget." + + context: + correlation_id: "media_buy_governance_escalation--sync_plans" + context_outputs: + - name: plan_id + path: 'plans[0].plan_id' + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--sync_plans" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "plans[0].plan_id" + description: "Governance agent assigns plan_id — must be echoed in check_governance" + - id: discover_products + title: "Product discovery" + narrative: | + The buyer sends a brief to discover products. The products returned will exceed + the governance agent's spending authority when assembled into a media buy. + + steps: + - id: get_products_brief + title: "Send a brief" + narrative: | + The buyer describes what they want. Your platform returns products with + pricing that, when combined, will exceed the $20K per-transaction threshold + set in the governance plan. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief with pricing that totals above $20K: + - product_id, name, description + - delivery_type: guaranteed or non_guaranteed + - pricing_models with CPM and budget recommendations + - forecast: estimated delivery + + sample_request: + buying_mode: "brief" + brief: "Premium CTV and video on sports publishers. Q2 flight, $50K budget. Adults 25-54, US." + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "media_buy_governance_escalation--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: governance_check_denied + title: "Governance check — denied" + narrative: | + Before creating the media buy, the buyer's governance agent runs a pre-buy check. + The proposed buy totals $50K, which exceeds the agent's $20K per-transaction + authority. The governance agent denies the buy with a must-severity finding and + returns escalation instructions for human review. + + steps: + - id: check_governance_denied + title: "Pre-buy governance check (denied)" + narrative: | + The buyer calls check_governance with the proposed media buy binding. The + governance agent evaluates the buy against the registered plan and finds that + the total exceeds the agent's spending authority. The check returns denied + with a must-severity finding and instructions for escalating to a human. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_spend_authority/denied + stateful: true + expected: | + Return a denied governance decision: + - decision: denied + - findings: array with at least one must-severity finding + - severity: must + - code: SPENDING_AUTHORITY_EXCEEDED + - message: explains the agent's authority limit and how much the buy exceeds it + - escalation: instructions for human review + - governance_context: token/ID the buyer passes to the next check after escalation + - plan_id: the governance plan that triggered the denial + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + tool: "create_media_buy" + payload: + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_check_governance_denied_payload" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2027-01-01T00:00:00Z" + end_time: "2027-03-31T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + + context: + correlation_id: "media_buy_governance_escalation--check_governance_denied" + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains a governance decision" + - check: field_present + path: "findings" + description: "Response contains findings explaining the denial" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--check_governance_denied" + description: "Context correlation_id returned unchanged" + - id: human_escalation + title: "Human escalation — conditional approval" + narrative: | + The governance denial triggers human escalation. A human reviewer at the agency + reviews the proposed buy, the denial reason, and the governance plan. The human + approves the buy with conditions — for example, requiring weekly reporting. The + buyer calls check_governance again with the human's approval, and this time + the check returns approved with conditions. + + steps: + - id: check_governance_approved + title: "Re-check governance after human approval (approved with conditions)" + narrative: | + After the human reviewer approves the buy, the buyer calls check_governance + again with the governance_context from the prior denial and the human's + approval. The governance agent returns approved with conditions that the + buyer must honor during the campaign. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_spend_authority + stateful: true + expected: | + Return an approved governance decision with conditions: + - decision: approved + - conditions: array of requirements the buyer must honor + - e.g., "Weekly delivery reporting required" + - e.g., "Human review required for any budget increase" + - governance_context: updated token the buyer passes to create_media_buy + - approved_by: identifier of the human who approved + - approved_at: timestamp of approval + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + governance_context: "gov_ctx_acme_q2_escalated" + tool: "create_media_buy" + payload: + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_check_governance_approved_payload" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2027-01-01T00:00:00Z" + end_time: "2027-03-31T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + + context: + correlation_id: "media_buy_governance_escalation--check_governance_approved" + context_outputs: + - name: check_id + path: 'check_id' + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains an approved governance decision" + - check: field_present + path: "conditions" + description: "Approval includes conditions" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--check_governance_approved" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "check_id" + description: "Check ID for audit trail — must be echoed in report_plan_outcome" + - id: create_buy_with_governance + title: "Create media buy with governance context" + narrative: | + The buyer creates the media buy, passing the governance_context from the approved + governance check. The seller's platform verifies the governance approval before + confirming the buy. + + steps: + - id: create_media_buy + title: "Create a media buy with governance approval" + narrative: | + The buyer creates the media buy with the governance_context token from the + approved check. The seller's platform validates the governance approval and + confirms the buy. Without a valid governance_context, the platform would + reject the buy because the governance agent is registered for this account. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Confirm the media buy with governance approval: + - media_buy_id: your platform's identifier + - status: active + - confirmed_at: timestamp + - governance_context: echoed back confirming governance was validated + - packages: confirmed line items + - valid_actions: creative sync, get_delivery, etc. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + governance_context: "gov_ctx_acme_q2_approved" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_guaranteed" + creative_assignments: + - creative_id: "video_30s_trail_pro" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + creative_assignments: + - creative_id: "video_15s_trail_pro" + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_create_buy_with_governance_create_media_buy" + context: + correlation_id: "media_buy_governance_escalation--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: report_outcome + title: "Report governance outcome" + narrative: | + After the media buy is created, the buyer reports the outcome back to the + governance agent. This closes the governance loop by linking the actual media + buy to the governance check that authorized it. + + steps: + - id: report_plan_outcome + title: "Report media buy outcome to governance" + narrative: | + The buyer reports the media buy creation back to the governance agent, + linking the media_buy_id to the governance check. This enables the governance + agent to track what was actually purchased against what was approved. + task: report_plan_outcome + schema_ref: "governance/report-plan-outcome-request.json" + response_schema_ref: "governance/report-plan-outcome-response.json" + doc_ref: "/governance/campaign/tasks/report_plan_outcome" + comply_scenario: campaign_governance + stateful: true + expected: | + Acknowledge the outcome report: + - outcome_id: identifier for this outcome record + - plan_id: the governance plan + - media_buy_id: the buy that was created + - governance_context: the approval that authorized it + - status: recorded + + sample_request: + plan_id: "$context.plan_id" + governance_context: "gov_ctx_acme_q2_approved" + outcome: "completed" + seller_response: + seller_reference: "$context.media_buy_id" + committed_budget: 50000 + packages: + - package_id: "pkg_sports_ctv_q2" + committed_budget: 30000 + - package_id: "pkg_outdoor_video_q2" + committed_budget: 20000 + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_report_outcome_report_plan_outcome" + context: + correlation_id: "media_buy_governance_escalation--report_plan_outcome" + validations: + - check: response_schema + description: "Response matches report-plan-outcome-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--report_plan_outcome" + description: "Context correlation_id returned unchanged" + - id: audit_trail + title: "Governance audit trail" + narrative: | + The buyer (or an auditor) retrieves the full audit trail for the governance + plan. This shows every decision in the chain: the initial denial, the human + escalation, the conditional approval, the media buy creation, and the outcome + report. The audit trail provides accountability for automated spending decisions. + + steps: + - id: get_plan_audit_logs + title: "Retrieve the full governance audit trail" + narrative: | + The buyer requests the audit log for the governance plan. The log shows + every governance event: check requests, denials, escalations, approvals, + and outcome reports. Each entry has a timestamp, actor, and decision. + task: get_plan_audit_logs + schema_ref: "governance/get-plan-audit-logs-request.json" + response_schema_ref: "governance/get-plan-audit-logs-response.json" + doc_ref: "/governance/campaign/tasks/get_plan_audit_logs" + comply_scenario: campaign_governance + stateful: false + expected: | + Return the complete audit trail for the governance plan: + - plan_id: the governance plan + - entries: ordered list of governance events, including: + 1. Plan registered with $20K per-transaction threshold + 2. Governance check denied — spending authority exceeded ($50K > $20K) + 3. Human escalation initiated + 4. Human approved with conditions (weekly reporting, budget increase review) + 5. Media buy created with governance approval + 6. Outcome reported linking media_buy_id to governance context + - Each entry includes: timestamp, event_type, actor, decision, details + + sample_request: + plan_ids: + - "$context.plan_id" + + context: + correlation_id: "media_buy_governance_escalation--get_plan_audit_logs" + validations: + - check: response_schema + description: "Response matches get-plan-audit-logs-response.json schema" + - check: field_present + path: "plans" + description: "Response contains audit log entries" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--get_plan_audit_logs" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/creative-reception.yaml b/dist/compliance/3.0.9/domains/media-buy/creative-reception.yaml new file mode 100644 index 0000000000..56b4b5fb59 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/creative-reception.yaml @@ -0,0 +1,247 @@ +id: creative_sales_agent +version: "1.0.0" +title: "Sales agent with creative capabilities" +category: creative_sales_agent +summary: "Stateful sales agent that accepts pushed creative assets and renders them in its environment." +track: creative +required_tools: + - sync_creatives + +narrative: | + You run a publisher platform, retail media network, or other sell-side system that + accepts creative assets from buyers. The buyer pushes assets or catalog items to your + platform, and you render them in your environment. + + Your agent is stateful: buyers push creatives to you via sync_creatives, and you + persist them for rendering. This is where catalogs get interesting — the buyer might + push product feeds (flights, hotels, retail products) that your platform renders as + native ads. + + This storyboard walks through the push-and-preview flow from the buyer's perspective. + +agent: + interaction_model: stateful_push + capabilities: + - has_creative_library + examples: + - "Publisher platforms" + - "Retail media networks" + - "Native ad platforms" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The buyer has creative assets (images, catalog feeds, or ad tags) ready to push. + The test kit provides sample assets compatible with common publisher formats. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_sales_agent--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_sales_agent--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: discover_accepted_formats + title: "Discover accepted formats" + narrative: | + The buyer first needs to know what creative formats your platform accepts. + For a publisher, this includes your native ad formats, display placements, + and any custom units. For retail media, this might include product listing + formats or sponsored product cards. + + steps: + - id: list_formats + title: "Discover accepted creative formats" + narrative: | + The buyer asks: "What creative formats does your platform accept?" Your + platform returns the formats you support — native post formats, display + units, video slots, or product listing formats. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_sync + stateful: false + expected: | + Return the creative formats your platform accepts. Each format should define: + - Asset requirements (what the buyer needs to provide) + - Render dimensions + - Any catalog requirements (for product-feed formats) + + - id: push_creatives + title: "Push creative assets" + narrative: | + The buyer pushes their creative assets to your platform. This could be: + - Standard display assets (images, HTML tags) + - Catalog items (product feeds, flight listings, hotel inventory) + - Native ad content (headlines, descriptions, images) + + Your platform validates the assets against your format specs and stores them. + + steps: + - id: sync_creatives + title: "Push creatives to the platform" + narrative: | + The buyer uploads creative assets to your platform. For standard ads, this + is images and copy. For catalog-driven formats, this is a product feed or + set of catalog items. Your platform validates each creative against the + format's asset requirements and returns a per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept the creatives, validate against format specifications, and return: + - Per-creative action (created or updated) + - Per-creative status (accepted, pending_review, rejected) + - Platform-assigned IDs if applicable + - Validation errors for rejected creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme_summer_native_001" + name: "Acme Summer Sale — native 2026" + format_id: + agent_url: "https://your-platform.example.com" + id: "native_post" + assets: + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-master.jpg" + width: 1200 + height: 628 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + + idempotency_key: "$generate:uuid_v4#creative_sales_agent_push_creatives_sync_creatives" + context: + correlation_id: "creative_sales_agent--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_sales_agent--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: preview + title: "Preview pushed creatives" + narrative: | + After pushing assets, the buyer wants to see how their creatives will render + in your platform's environment. For a publisher, this shows the ad in the + publication's native chrome — with engagement buttons, community badges, and + platform-specific styling that the buyer can't preview elsewhere. + + steps: + - id: preview_synced + title: "Preview a pushed creative" + narrative: | + The buyer asks to see how a synced creative will look in your environment. + Your platform renders the creative with its native chrome — the surrounding + UI, engagement buttons, and platform-specific styling. + task: preview_creative + schema_ref: "creative/preview-creative-request.json" + response_schema_ref: "creative/preview-creative-response.json" + doc_ref: "/creative/task-reference/preview_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a preview showing the creative in your platform's environment. + The preview should include your platform's native chrome — not just the + raw assets, but how they'll actually appear to users. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + request_type: "single" + creative_manifest: + creative_id: "acme_summer_native_001" + format_id: + agent_url: "https://your-platform.example.com" + id: "native_post" + assets: + headline: + asset_type: "text" + content: "Summer Sale — 40% Off" + body: + asset_type: "text" + content: "Top-rated outdoor gear. This weekend only." + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero.jpg" + width: 1200 + height: 628 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + output_format: "url" + quality: "draft" + + context: + correlation_id: "creative_sales_agent--preview_synced" + validations: + - check: response_schema + description: "Response matches preview-creative-response.json schema" + - check: field_present + path: "previews[0].renders[0].preview_url" + description: "Preview includes a renderable URL" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_sales_agent--preview_synced" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/index.yaml b/dist/compliance/3.0.9/domains/media-buy/index.yaml new file mode 100644 index 0000000000..38c421a694 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/index.yaml @@ -0,0 +1,769 @@ +id: media_buy_seller +version: "1.0.0" +title: "Media buy seller agent" +category: media_buy_seller +summary: "Seller agent that receives briefs, returns products, accepts media buys, and reports delivery." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/refine_products + - media_buy_seller/delivery_reporting + - media_buy_seller/measurement_terms_rejected + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/inventory_list_targeting + - media_buy_seller/inventory_list_no_match + - media_buy_seller/invalid_transitions + - media_buy_seller/creative_fate_after_cancellation + - media_buy_seller/create_media_buy_async + +narrative: | + You run a sell-side platform — a publisher, SSP, retail media network, or any system that + sells advertising inventory. A buyer agent connects to discover your products, create + media buys, sync creatives, and monitor delivery. Your agent handles the full lifecycle + from brief to reporting. + + This storyboard walks through the core media buy flow: account setup, product discovery, + buy creation, creative sync, and delivery monitoring. + + Governance integration, product refinement, and proposal finalization are tested by + required scenarios that run alongside this storyboard. See requires_scenarios for the + full set of seller behaviors validated. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - supports_guaranteed + - supports_non_guaranteed + examples: + - "Yahoo" + - "Retail media networks" + - "Publisher platforms" + - "SSPs" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials for account setup. + The test kit provides a sample brand (Acme Outdoor) with campaign parameters + suitable for testing the full media buy flow. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_preroll_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_30s" + - product_id: "lifestyle_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + pricing_options: + - product_id: "sports_preroll_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 22.0 + - product_id: "lifestyle_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "media_buy_seller--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--get_capabilities" + description: "Context correlation_id returned unchanged" + + - id: account_setup + title: "Account setup" + narrative: | + Before buying anything, the buyer establishes an account relationship with + your platform. This is the handshake: the buyer tells you which brand and + agency (operator) they represent, and you return an account ID, status, and + any setup requirements. + + Some platforms approve accounts instantly. Others require human review — the + buyer gets back a pending_approval status and a URL to complete setup. The + buyer polls or waits for a webhook until the account is active. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. This is + the first call in any new relationship. Your platform validates the request, + provisions the account, and returns its status. + + If your platform requires manual approval (credit checks, sales team review), + return the account with status pending_approval and account.setup.url populated. + The buyer directs the human to that URL to complete setup, then polls list_accounts + until the account status changes to active. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + # No TestScenario exists for account setup + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier for this relationship + - action: created or updated + - status: active (instant approval) or pending_approval (requires human review) + - account_scope: operator, brand, operator_brand, or agent + - setup.url and setup.message: populated on the account when status is pending_approval (where the human completes onboarding) + - rate_card: pricing tiers if applicable + - payment_terms: net_30, prepay, etc. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_account_setup_sync_accounts" + context: + correlation_id: "media_buy_seller--sync_accounts" + + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + - check: field_present + path: "accounts[0].status" + description: "Account has a status (active or pending_approval)" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--sync_accounts" + description: "Context correlation_id returned unchanged" + + - id: governance_setup + title: "Governance agent registration" + narrative: | + The buyer registers their governance agent with your platform. This tells your + platform where to call check_governance before confirming media buys. + + steps: + - id: sync_governance + title: "Register governance agents" + narrative: | + The buyer tells your platform: "Before you confirm any media buy for this + account, call this governance agent to validate it." + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + requires_tool: sync_governance + stateful: true + expected: | + Acknowledge the governance agents. Your platform should: + - Store the governance agent URLs for the specified accounts + - Return confirmation that agents were registered + + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "https://governance.pinnacle-agency.example" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_setup_sync_governance" + context: + correlation_id: "media_buy_seller--sync_governance" + ext: + test_platform: + governance_tier: "standard" + + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--sync_governance" + description: "Context correlation_id returned unchanged" + + - id: product_discovery + title: "Product discovery" + narrative: | + The buyer sends a natural-language brief describing what they want to buy. + Your platform interprets the brief against your inventory and returns products — + structured representations of what you can sell, with pricing, delivery forecasts, + targeting options, and creative requirements. + + This is where seller differentiation happens. The same brief sent to three sellers + produces three different product sets. Your AI interprets "premium video on sports + and outdoor lifestyle" against your specific inventory, audiences, and pricing. + + steps: + - id: get_products_brief + title: "Send a brief" + narrative: | + The buyer describes what they want in natural language. Your platform returns + products that match the brief, including pricing options, delivery forecasts, + and creative format requirements. + + This call may take up to 60 seconds — your platform is running AI inference + against your inventory catalog. If the brief is ambiguous, you can return + input-required to ask clarifying questions before producing results. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. Each product should include: + - product_id: unique identifier + - name and description + - delivery_type: guaranteed or non_guaranteed + - pricing_models: available pricing options (CPM, CPC, etc.) + - forecast: estimated impressions, reach + - creative_format_ids: what creative formats this product requires + - targeting: what audiences or contexts this product reaches + + Optionally return proposals — curated media plans that bundle products + with budget allocations the buyer can accept or refine. + + If the brief is unclear, return input-required with clarifying questions. + + sample_request: + buying_mode: "brief" + brief: "Premium video inventory on sports and outdoor lifestyle publishers. Q2 flight, $50K budget. Adults 25-54, US and Canada." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context: + correlation_id: "media_buy_seller--get_products_brief" + + context_outputs: + - name: product_format_id + path: 'products[0].format_ids[0]' + - name: product_id + path: 'products[0].product_id' + - name: pricing_option_id + path: 'products[0].pricing_options[0].pricing_option_id' + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + - check: field_present + path: "products[0].delivery_type" + description: "Each product declares guaranteed or non_guaranteed delivery" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--get_products_brief" + description: "Context correlation_id returned unchanged" + + - check: field_present + path: "products[0].publisher_properties" + description: "Products include publisher_properties" + - id: list_formats_integrity + title: "Verify format_ids on products resolve to real formats" + narrative: | + The buyer asks the sales agent to filter `list_creative_formats` by + `products[0].format_ids[0]`. The sales agent MUST return the format + it advertised on its own product — whether it hosts that format + directly or proxies to the creative agent named in + `format_ids[0].agent_url`. An empty `formats[]` means the sales + agent's product catalog references a format that does not resolve — + a stale or typo'd entry that would have failed silently at + `sync_creatives` after the media buy was already committed. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + The sales agent resolves `products[0].format_ids[0]` and returns + the matching format entry: + - formats[] contains at least one entry + - formats[0].format_id matches the id captured from get_products + + An empty formats[] means the sales agent's product catalog references + a format that does not resolve — a common production failure mode + when creative agents deprecate formats without sellers updating + their product catalog. + sample_request: + format_ids: + - "$context.product_format_id" + context: + correlation_id: "media_buy_seller--list_formats_integrity" + # The @adcp/client `list_creative_formats` request builder up + # through 5.10.0 (the currently-published release) returns `{}`, + # and the runner's post-builder merge only forwards envelope + # fields (context / ext / idempotency_key / + # push_notification_config) from sample_request — so `format_ids` + # above never reaches the wire and the seller answers with its + # full format catalog. `context_inputs` is applied after the + # builder runs, so this injects the captured `product_format_id` + # (the `{agent_url, id}` object from `products[0].format_ids[0]`) + # at `format_ids[0]` and lets the round-trip invariant actually + # grade. Drop once we bump past the @adcp/client release that + # ships adcontextprotocol/adcp-client#789. + context_inputs: + - key: product_format_id + inject_at: "format_ids[0]" + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats[0]" + description: "Sales agent resolves the format_id — products[0].format_ids[0] exists in the catalog" + - check: field_value + path: "formats[0].format_id" + value: "$context.product_format_id" + description: "Returned format_id round-trips verbatim — the agent cannot substitute a different format in response to the filter" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--list_formats_integrity" + description: "Context correlation_id returned unchanged" + - id: create_buy + title: "Create the media buy" + narrative: | + The buyer is satisfied with the products and creates a media buy. This is the + equivalent of signing an IO — the buyer commits to specific products, budgets, + and flight dates. + + This operation may be synchronous (completed immediately), short-async (working + while your platform processes), or long-async (task stays submitted while a human + signs the IO internally; task completion delivers the final media_buy_id). There + is no "pending_approval" media buy status — IO review is modelled at the task + layer, not as a MediaBuy.status value. + + If the buyer registered governance agents in Phase 2, your platform calls + check_governance before confirming the buy. The governance agent validates budget + authority, brand safety, and compliance. If governance denies the buy, return the + denial — don't override it. + + steps: + - id: create_media_buy + title: "Create a media buy" + narrative: | + The buyer commits to specific products with budgets and flight dates. Your + platform validates the request, optionally calls governance, and either confirms + the buy or sends it through an approval workflow. + + Two creation modes: + - Manual: buyer specifies packages array with explicit product selections + - Proposal: buyer passes a proposal_id from get_products to execute a proposal + + The response status tells the buyer what happens next: + - completed: buy is active and live + - working: your platform is processing (poll or wait for webhook) + - submitted: long-running async — approval workflow, IO signing, etc. + - input-required: need more information (budget clarification, approval) + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Process the media buy request and return one of: + + Synchronous (completed): + - media_buy_id: your platform's identifier + - status: active or pending_creatives + - packages: line items with pricing + - confirmed_at: timestamp + - valid_actions: what the buyer can do next + + Asynchronous (working): + - percentage: 0-100 completion + - current_step: what's happening ("Validating inventory", "Checking governance") + + Async with human approval (submitted): + - task_id / taskId: handle the buyer polls or receives webhooks on + - message (optional): explanation of what the seller is waiting on (e.g., "Awaiting IO signature from sales team; typical turnaround 2–4 hours") + - No media_buy_id yet — it is issued on task completion + - Seller-side IO signing is modelled here (task stays submitted until signed). Do not emit a "pending_approval" media buy status — that value is not in MediaBuy.status + + Needs input (input-required): + - reason: APPROVAL_REQUIRED, BUDGET_EXCEEDS_LIMIT, CLARIFICATION_NEEDED + - errors: what needs to be resolved + - Used only when the seller needs the buyer to respond (e.g., confirm a budget). If the blocker is account-level (credit application, funding), the resolution path is list_accounts / sync_accounts, where account.setup.url surfaces on the pending_approval account + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_preroll_q2" + budget: 25000 + pricing_option_id: "cpm_guaranteed" + creative_assignments: + - creative_id: "video_30s_trail_pro" + - product_id: "lifestyle_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + push_notification_config: + url: "https://buyer.example/webhooks/adcp" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "media-buy-seller-webhook-secret-token" + idempotency_key: "$generate:uuid_v4#media_buy_seller_create_buy_create_media_buy" + context: + correlation_id: "media_buy_seller--create_media_buy" + + context_outputs: + - name: media_buy_id + path: 'media_buy_id' + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--create_media_buy" + description: "Context correlation_id returned unchanged" + + - id: check_buy_status + title: "Check media buy status" + narrative: | + Once create_media_buy has completed and a media_buy_id is issued, the buyer + calls get_media_buys to read current state — pending_creatives, pending_start, + active, paused, completed, rejected, or canceled. + + While the create_media_buy task is still submitted (e.g., waiting on internal + IO signing), the media buy does not exist as a queryable MediaBuy yet. IO + review is tracked at the task layer, not as a MediaBuy.status value. The buyer + polls tasks/get or waits on the webhook until the task completes and a + media_buy_id is delivered. + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the current state of the media buy: + - media_buy_id: matches what was returned from create_media_buy + - status: pending_creatives, pending_start, active, paused, completed, rejected, canceled + - packages: line items with current delivery status + - valid_actions: what operations are available in this state + + If pending_creatives: + - Include message explaining what creatives are needed + - valid_actions should include sync_creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + context: + correlation_id: "media_buy_seller--check_buy_status" + + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_present + path: "media_buys[0].status" + description: "Each media buy has a status" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--check_buy_status" + description: "Context correlation_id returned unchanged" + + - id: creative_sync + title: "Creative sync" + narrative: | + With the media buy confirmed, the buyer syncs creative assets to your platform. + Each package in the buy has creative format requirements — the buyer discovered + these during product discovery and now pushes matching assets. + + The format_ids used in sync_creatives must match those returned by your platform + in get_products and list_creative_formats. If your platform returns a format_id + in a product but rejects it when the buyer echoes it back in sync_creatives, the + buyer cannot fulfill the creative requirements. This is a common compliance failure. + + Your platform validates each creative against the format specs and returns + per-creative status. If assets need review or transcoding, the operation may + go async. + + steps: + - id: list_formats + title: "Check creative format requirements" + narrative: | + The buyer confirms what creative formats the confirmed packages require. + Your platform returns format specs with asset requirements, dimensions, + and constraints. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return creative formats your platform accepts. Each format should define: + - format_id with your agent_url and unique id + - Asset requirements (dimensions, file sizes, mime types) + - Render dimensions + + sample_request: + context: + correlation_id: "media_buy_seller--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--list_formats" + description: "Context correlation_id returned unchanged" + - check: refs_resolve + description: | + Every format_id returned on products resolves to a format in this + agent's list_creative_formats. Broken references here surface as a + grading failure instead of a silent mismatch that only breaks at + sync_creatives time, after the buy is committed. Third-party + format_ids (agent_url pointing at a different creative agent) + can't be verified without calling that agent and are reported as + observations rather than failures. + source: + from: context + path: "products[*].format_ids[*]" + target: + from: current_step + path: "formats[*].format_id" + match_keys: [agent_url, id] + scope: + key: agent_url + equals: $agent_url + on_out_of_scope: warn + + - id: sync_creatives + title: "Push creative assets (format_id roundtrip)" + narrative: | + The buyer uploads creative assets for the confirmed packages. The first + creative uses $context.product_format_id — the exact format_id object + returned by get_products. This is the roundtrip test: the seller must + accept its own format_ids without modification. If the seller's validation + rejects a format_id that it returned in products, this step fails. + + Your platform validates each creative against the format specs, transcodes + if necessary, and returns per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept and validate creatives: + - Per-creative action: created or updated + - Per-creative status: accepted, pending_review, or rejected + - Validation errors for rejected creatives + - Platform-assigned IDs if applicable + + The first creative uses a format_id extracted from get_products. + If this is rejected, your format_ids do not roundtrip correctly. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "video_30s_trail_pro" + name: "Trail Pro 3000 - 30s CTV Spot" + format_id: "$context.product_format_id" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + mime_type: "video/mp4" + - creative_id: "display_trail_pro_300x250" + name: "Trail Pro 3000 - Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + width: 300 + height: 250 + mime_type: "image/png" + idempotency_key: "$generate:uuid_v4#media_buy_seller_creative_sync_sync_creatives" + context: + correlation_id: "media_buy_seller--sync_creatives" + + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--sync_creatives" + description: "Context correlation_id returned unchanged" + + - id: delivery_monitoring + title: "Delivery and reporting" + narrative: | + The campaign is live. The buyer monitors delivery through two tasks: + get_media_buys for status and get_media_buy_delivery for performance metrics. + + Your platform reports in a standard format — impressions, clicks, spend, + completion rates — so the buyer can compare delivery across multiple sellers + in a single view. + + steps: + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data for the active media buy. Your platform + returns performance metrics — impressions, clicks, spend, completion rates — + broken down by package and optionally by day. + + This call may take up to 60 seconds as your platform aggregates reporting + data across delivery systems. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the media buy: + - Per-package delivery: impressions, clicks, spend, completion rates + - Daily breakdown if requested (include_package_daily_breakdown) + - Pacing information: on track, ahead, behind + - Budget utilization: spent vs. committed + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + context: + correlation_id: "media_buy_seller--get_delivery" + + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--get_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/create_media_buy_async.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/create_media_buy_async.yaml new file mode 100644 index 0000000000..c2ba4c3ec0 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/create_media_buy_async.yaml @@ -0,0 +1,232 @@ +id: media_buy_seller/create_media_buy_async +version: "1.0.0" +title: "Seller returns submitted task envelope when create_media_buy goes async" +category: media_buy_seller +summary: "Verifies the AdCP-payload wire shape of the submitted-arm response from create_media_buy: status='submitted', task_id present, no media_buy_id and no packages on the envelope." +track: media_buy +required_tools: + - create_media_buy + - comply_test_controller + +narrative: | + When create_media_buy cannot confirm the buy synchronously — e.g., the seller is + routing the request through IO signing, batch processing, or any out-of-band human + workflow — the task layer carries the result, not the response. The seller emits the + submitted task envelope: status='submitted', task_id present, no media_buy_id, no + packages. The buyer then polls tasks/get with task_id (or waits for a webhook) until + the task completes and the media_buy_id arrives on the completion artifact. + + This scenario anchors the AdCP-payload-level invariant for that envelope. Three things + matter and are easy to regress: + + 1. status MUST be the literal string 'submitted' (not 'pending', not a MediaBuyStatus + value, not omitted) + 2. task_id MUST be present at the top of the payload, snake_case (A2A adapters MAY + surface it as taskId on the wire, but the payload field emitted by the agent is + task_id) + 3. media_buy_id and packages MUST NOT appear on the envelope — they land on the task's + completion artifact, not here. Sellers that return media_buy_id with status='submitted' + break the buyer's polling contract; buyers cannot tell whether the buy is queued or + confirmed. + + Determinism. The submitted arm is implementation-dependent — most sellers route most + buys synchronously. To make this storyboard runnable across implementations, the test + harness uses comply_test_controller force_create_media_buy_arm to drive the next + create_media_buy call into the submitted arm. The directive is keyed to the caller's + authenticated sandbox account (account + principal pair); sellers that do not implement + the controller scenario return UNKNOWN_SCENARIO and the runner grades this storyboard + not_applicable rather than failed. + + Round-trip integrity. The deterministic task_id is captured from the controller + response and reused as the expected task_id on the create_media_buy assertion, so the + storyboard catches sellers that fabricate a fresh task_id instead of honoring the + registered directive. + + Out of scope (by design). Transport-level wire-shape assertions — A2A Task.state and + artifact.metadata.adcp_task_id placement, MCP structuredContent envelope details — are + runner-side concerns, not storyboard assertions. The runner exercises this scenario + against both transports and probes the transport envelope independently. See + adcp-client#904 for the runner-side probes; this storyboard provides the deterministic + driver. + + The submitted → completed transition (forcing the task to resolve and asserting the + completion artifact carries media_buy_id) is deferred to a follow-up scenario. It needs + a force_task_completion controller scenario that does not exist yet. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_test_controller + examples: + - "Sellers that route some create_media_buy calls through IO signing or batch processing" + - "Any seller exposing comply_test_controller in sandbox mode" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller exposes comply_test_controller in sandbox mode and supports + force_create_media_buy_arm. The directive is keyed to the caller's + authenticated sandbox account; without controller support, the storyboard + grades not_applicable — sellers cannot be deterministically driven into + the submitted arm by buyer-initiated requests alone. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "async_signed_io_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_30s" + pricing_options: + - product_id: "async_signed_io_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 18.0 + +phases: + - id: force_submitted_arm + title: "Drive next create_media_buy into submitted arm" + narrative: | + Tell the controller that the next create_media_buy call from the caller's + authenticated sandbox account should return the submitted task envelope. + The controller stores the directive against the (account, principal) pair + and consumes it on the next create_media_buy call. Sellers that do not + implement force_create_media_buy_arm return UNKNOWN_SCENARIO and the + runner grades this storyboard not_applicable. + + steps: + - id: force_arm_submitted + title: "Force submitted arm on next create_media_buy" + requires_tool: comply_test_controller + narrative: | + Direct the controller to return the submitted envelope with a + deterministic task_id on the buyer's next create_media_buy call. The + message field is set to a representative IO-signing explanation so + buyers exercising prompt-injection sanitization on submitted.message + have a stable string to assert against. + task: comply_test_controller + comply_scenario: create_media_buy + stateful: true + context_outputs: + - name: forced_task_id + path: "forced.task_id" + expected: | + Return a successful directive: + - success: true + - forced.arm: submitted + - forced.task_id: deterministic task handle the next create_media_buy will return + + sample_request: + scenario: "force_create_media_buy_arm" + params: + arm: "submitted" + task_id: "task_async_signed_io_q2" + message: "Awaiting IO signature from sales team; typical turnaround 2–4 hours" + context: + correlation_id: "create_media_buy_async--force_arm_submitted" + validations: + - check: field_value + path: "success" + allowed_values: [true] + description: "Controller accepts the directive" + - check: field_value + path: "forced.arm" + value: "submitted" + description: "Controller echoes the forced arm" + - check: field_present + path: "forced.task_id" + description: "Controller echoes the deterministic task_id" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "create_media_buy_async--force_arm_submitted" + description: "Context correlation_id returned unchanged" + + - id: submitted_arm_response + title: "create_media_buy returns submitted task envelope" + narrative: | + The buyer makes a normal create_media_buy call. Because the controller + registered a directive against this sandbox account, the seller MUST emit + the submitted task envelope: status='submitted', task_id matching the + forced value, no media_buy_id, no packages. + + The response_schema check carries the absence invariant — the submitted + arm in create-media-buy-response.json has not.anyOf clauses for both + media_buy_id and packages, so a seller that emits either under + status='submitted' fails schema validation. The explicit field_value + check on status pins the literal 'submitted' value, since a malformed + seller might omit the discriminator and still satisfy the parent oneOf + via the error or success branch. The task_id check uses the captured + $context.forced_task_id so the storyboard fails if the seller ignores + the registered directive and fabricates its own task_id. + + steps: + - id: create_media_buy_submitted + title: "Call create_media_buy and observe the submitted envelope" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Return the submitted task envelope: + - status: 'submitted' (literal, not a MediaBuyStatus value) + - task_id: matches the value registered by force_create_media_buy_arm + - no media_buy_id (issued on task completion, not here) + - no packages (issued on task completion, not here) + - message (optional): seller's explanation of the wait + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + start_time: "2026-07-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "async_signed_io_q2" + budget: 30000 + pricing_option_id: "cpm_guaranteed" + push_notification_config: + url: "https://buyer.example/webhooks/adcp" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "media-buy-seller-webhook-secret-token" + idempotency_key: "$generate:uuid_v4#media_buy_seller_create_media_buy_async_submitted_arm_response_create_media_buy_submitted" + context: + correlation_id: "create_media_buy_async--create_media_buy_submitted" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json — submitted-arm not.required clauses block media_buy_id and packages" + - check: field_value + path: "status" + value: "submitted" + description: "Status is the literal 'submitted' task-status value, not a MediaBuyStatus" + - check: field_present + path: "task_id" + description: "task_id is present at the top of the envelope (snake_case payload field, even when the A2A adapter surfaces it as taskId on the wire)" + - check: field_value + path: "task_id" + value: "$context.forced_task_id" + description: "task_id matches the captured value from the controller directive — sellers that fabricate a fresh task_id instead of honoring the registered one fail here" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "create_media_buy_async--create_media_buy_submitted" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml new file mode 100644 index 0000000000..92d747dcd3 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml @@ -0,0 +1,414 @@ +id: media_buy_seller/creative_fate_after_cancellation +version: "1.0.0" +title: "Creative lifecycle is decoupled from media buy lifecycle" +category: media_buy_seller +summary: "Validates that canceling a media buy releases package-creative assignments but leaves the underlying creatives in the library with their review state intact, and that buyers can reuse released creatives on a new buy." +track: media_buy +required_tools: + - get_products + - create_media_buy + - update_media_buy + - sync_creatives + - list_creatives + +narrative: | + Per the creative library model (docs/creative/creative-libraries#creative-state-and-assignment-state-are-separate) + and the Media Buy State Transitions rule, canceling or rejecting a media buy + releases its package-creative assignments but leaves the creatives themselves + in the library. The creatives remain reusable by `creative_id` in a subsequent + `create_media_buy` or `sync_creatives` call, and a seller MUST NOT implicitly + reject a creative because its containing buy was canceled — a creative + rejection MUST be a deliberate review decision with its own `rejection_reason`. + + This scenario walks the whole flow end-to-end: + + 1. Create a buy, sync a creative, assign it to a package (setup) + 2. Confirm the creative is in the library with a non-terminal review state (pre-cancel) + 3. Cancel the buy + 4. Confirm the creative is STILL in the library with its review state intact — + not archived, not auto-rejected as a side effect of the cancel + 5. Reuse the same `creative_id` on a new buy via `sync_creatives` assignment + + A seller that evaporates library creatives on buy cancellation, or that flips + `status: rejected` on creatives whose only assignment was released by a cancel, + fails this scenario. A seller that correctly decouples the two lifecycles + passes. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any media buy seller with a creative library" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports create_media_buy, update_media_buy with cancellation, sync_creatives, + and list_creatives. Catalog-driven sellers and sellers without a creative library + grade this scenario not_applicable (creative lifecycle assumes a library surface). + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Create a buy and assign a creative" + narrative: | + Discover a product, create a media buy, sync one creative with an inline + assignment to the buy's first package. Capture the media_buy_id, package_id, + and creative_id for subsequent phases. + + steps: + - id: get_products_brief + title: "Discover a product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product with a pricing option and at least one format_id. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content for creative-reuse testing." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context: + correlation_id: "creative_fate--get_products" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + - path: "products[0].format_ids[0].agent_url" + key: "format_agent_url" + - path: "products[0].format_ids[0].id" + key: "format_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Product exposes at least one format_id for creative sync" + + - id: create_buy + title: "Create the initial media buy" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Media buy created with media_buy_id and at least one package. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_create_buy" + start_time: "2026-09-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 5000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "creative_fate--create_buy" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns media_buy_id" + - check: field_present + path: "packages[0].package_id" + description: "Seller returns package_id for the newly created package" + + - id: sync_creative_with_assignment + title: "Sync a creative and assign to the package" + narrative: | + Sync one creative into the library and assign it to the package in a single + sync_creatives call. The creative enters the library with the library's + review flow; the assignment binds it to the media buy's package. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Creative accepted into the library (action: created), assignment acknowledged. + Creative status is one of: pending_review, approved, processing. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme_reuse_banner_001" + name: "Acme Outdoor reuse banner" + format_id: + agent_url: "$context.format_agent_url" + id: "$context.format_id" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/acme-reuse-banner.png" + width: 300 + height: 250 + mime_type: "image/png" + assignments: + - creative_id: "acme_reuse_banner_001" + package_id: "$context.package_id" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_sync_creative_with_assignment" + context: + correlation_id: "creative_fate--sync_creative_with_assignment" + context_outputs: + - path: "creatives[0].creative_id" + key: "creative_id" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].creative_id" + description: "Response echoes back the buyer-supplied creative_id" + + - id: verify_creative_in_library_pre_cancel + title: "Baseline: creative is in the library with a non-terminal review state" + narrative: | + Before canceling the buy, list the creative via list_creatives and record the + review state. The purpose of this phase is to establish the baseline the + post-cancel phase compares against: the creative exists and has a status + that is NOT `rejected` or `archived`. + + steps: + - id: list_creatives_before_cancel + title: "Look up the creative in the library" + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_library + stateful: true + expected: | + list_creatives returns the creative with a non-terminal review state + (processing, pending_review, or approved). + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + filters: + creative_ids: + - "$context.creative_id" + context: + correlation_id: "creative_fate--list_creatives_before_cancel" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives[0].creative_id" + description: "Creative is present in the library" + - check: field_value + path: "creatives[0].status" + allowed_values: ["processing", "pending_review", "approved"] + description: "Creative status is non-terminal (not rejected or archived) before cancel" + + - id: cancel_buy + title: "Cancel the media buy" + narrative: | + Cancel the media buy with `canceled: true`. The seller MUST transition the buy + to `canceled` and release the creative's package assignment per + docs/media-buy/specification#media-buy-state-transitions. + + steps: + - id: update_media_buy_canceled + title: "update_media_buy with canceled: true" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Seller acknowledges the cancellation and transitions the buy to `canceled`. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + cancellation_reason: "Creative-fate scenario: releasing assignment to verify library persistence." + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_update_media_buy_canceled" + context: + correlation_id: "creative_fate--update_media_buy_canceled" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - id: verify_creative_persists_post_cancel + title: "Creative remains in the library with review state intact" + narrative: | + After the cancel, the creative's library entry MUST still exist and its review + state MUST NOT be `rejected` (which would indicate implicit-reject-on-cancel, + forbidden by the spec) and SHOULD NOT be `archived` (which would indicate the + seller evaporated library state on buy cancellation, inconsistent with the + decoupled-lifecycle contract). Allowed terminal-adjacent states are + `processing`, `pending_review`, `approved` — whatever the review flow produced. + + steps: + - id: list_creatives_after_cancel + title: "Look up the creative again, post-cancel" + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_library + stateful: true + expected: | + Creative still present with non-rejected, non-archived status. A seller + that returns an empty list, or that has flipped the creative to + `rejected` or `archived` as a side effect of the cancel, fails. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + filters: + creative_ids: + - "$context.creative_id" + context: + correlation_id: "creative_fate--list_creatives_after_cancel" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives[0].creative_id" + description: "Creative still in the library after buy cancellation" + - check: field_value + path: "creatives[0].creative_id" + value: "acme_reuse_banner_001" + description: "Creative ID is unchanged (not re-keyed on cancel)" + - check: field_value + path: "creatives[0].status" + allowed_values: ["processing", "pending_review", "approved"] + description: "Creative status is NOT rejected and NOT archived — no implicit review cascade from the buy cancel" + + - id: reuse_creative_on_new_buy + title: "Reuse the released creative on a new media buy" + narrative: | + With the old buy canceled and the assignment released, the buyer creates a NEW + media buy and references the same creative_id in a fresh assignment. The seller + MUST accept the assignment — the creative_id resolves to the persisted library + entry, demonstrating end-to-end reusability. + + steps: + - id: create_second_buy + title: "Create a second media buy" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Second media buy created successfully with a new media_buy_id and package_id. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_create_second_buy" + start_time: "2026-10-01T00:00:00Z" + end_time: "2026-10-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 5000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "creative_fate--create_second_buy" + context_outputs: + - path: "media_buy_id" + key: "second_media_buy_id" + - path: "packages[0].package_id" + key: "second_package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Second media_buy_id returned" + - check: field_present + path: "packages[0].package_id" + description: "Second package_id returned" + + - id: reassign_creative + title: "Assign the original creative_id to the new package" + narrative: | + Reference the original creative by creative_id only (no assets, no re-upload) + and assign it to the new package. The seller resolves the creative_id from + the library; if the creative was evaporated on cancel, this call fails. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Assignment accepted. No creative-not-found or similar error. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + # Buyer-authoritative id set in sync_creative_with_assignment — + # use it literally instead of round-tripping through + # `$context.creative_id`. That context key is populated from + # the seller's response at `creatives[0].creative_id`; sellers + # whose envelope doesn't surface that exact path resolve to + # undefined and the template engine strips the creative, + # leaving `creatives: undefined` which fails pre-flight zod. + - creative_id: "acme_reuse_banner_001" + name: "Reassigned creative" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/banner_300x250.jpg" + width: 300 + height: 250 + assignments: + - creative_id: "acme_reuse_banner_001" + package_id: "$context.second_package_id" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_reassign_creative" + context: + correlation_id: "creative_fate--reassign_creative" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/delivery_reporting.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/delivery_reporting.yaml new file mode 100644 index 0000000000..75596a2306 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/delivery_reporting.yaml @@ -0,0 +1,205 @@ +id: media_buy_seller/delivery_reporting +version: "1.0.0" +title: "Seller returns valid delivery reporting" +category: media_buy_seller +summary: "Verifies that get_media_buy_delivery returns schema-compliant delivery data after simulated delivery via the test controller." +track: reporting +required_tools: + - get_products + - create_media_buy + - get_media_buy_delivery + - comply_test_controller + +narrative: | + Delivery reporting is how buyers know if their campaign is working. The seller must + return schema-compliant delivery data from get_media_buy_delivery with per-package + metrics (impressions, spend, pacing). + + This scenario creates a media buy, injects delivery data via the test controller's + simulate_delivery scenario, then calls get_media_buy_delivery and validates the + response against the schema. Without this test, sellers can return arbitrary formats + and still pass certification. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any media buy seller" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller must implement comply_test_controller with the simulate_delivery + scenario. This allows the test harness to inject delivery metrics into a + media buy so get_media_buy_delivery has data to return. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "outdoor_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "outdoor_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + +phases: + - id: setup + title: "Create a media buy for delivery testing" + steps: + - id: sync_accounts + title: "Establish account" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_delivery_reporting_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Display and video inventory on outdoor lifestyle content. Q2 flight, $25K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy + title: "Create media buy" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Create the media buy. We need a media_buy_id to simulate delivery against. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 10000 + pricing_option_id: "cpm_standard" + idempotency_key: "$generate:uuid_v4#media_buy_seller_delivery_reporting_setup_create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - id: simulate_and_verify + title: "Simulate delivery and validate reporting" + narrative: | + Inject delivery metrics via the test controller, then call get_media_buy_delivery + and validate the response format. This is the core test — does the seller return + schema-compliant delivery data with per-package metrics? + + steps: + - id: simulate_delivery + title: "Inject simulated delivery metrics" + task: comply_test_controller + requires_tool: comply_test_controller + stateful: true + expected: | + The test controller acknowledges the simulated delivery data. + sample_request: + scenario: "simulate_delivery" + params: + media_buy_id: "$context.media_buy_id" + impressions: 5000 + clicks: 150 + reported_spend: + amount: 250.00 + currency: "USD" + validations: + - check: field_value + path: "success" + allowed_values: [true] + description: "Delivery simulation succeeds" + + - id: get_delivery + title: "Get delivery report and validate schema" + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics reflecting the simulated data: + - media_buy_deliveries array with at least one entry + - Per-package breakdown with impressions, spend + - Response matches the get-media-buy-delivery-response.json schema + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains delivery data" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_approved.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_approved.yaml new file mode 100644 index 0000000000..fd8e15b634 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_approved.yaml @@ -0,0 +1,211 @@ +id: media_buy_seller/governance_approved +version: "1.0.0" +title: "Seller creates buy when governance approves" +category: media_buy_seller +summary: "Verifies that the seller creates a media buy when governance approves the transaction." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + This is a multi-agent test. The test harness sets up a permissive governance plan on + a governance agent with a $100K budget, then registers that agent with the seller. + The buyer creates a $25K buy which falls within limits. + + When the seller calls check_governance during create_media_buy, the governance agent + approves. The seller must confirm the buy normally. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "outdoor_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "outdoor_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + +phases: + - id: governance_plan_setup + title: "Set up permissive governance plan" + narrative: | + Create a governance plan on the governance agent with a high budget ($100K). + The subsequent $25K buy will fall within these limits and should be approved. + + steps: + - id: sync_plans + title: "Create permissive governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan with a plan_id. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_approved_sync_plans" + plans: + - plan_id: "comply-gov-approved-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 outdoor lifestyle campaign — display and video" + budget: + total: 100000 + currency: "USD" + reallocation_threshold: 100000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US", "CA"] + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_approved_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_approved_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_approved + title: "Create buy within governance limits" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Display and video inventory on outdoor lifestyle content. Q2 flight, $25K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy + title: "Create buy (governance approves)" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds — governance approved because the $25K buy is within + the plan's $100K budget. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 10000 + pricing_option_id: "cpm_standard" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_approved_buy_approved_create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_conditions.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_conditions.yaml new file mode 100644 index 0000000000..e094f2f9ea --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_conditions.yaml @@ -0,0 +1,196 @@ +id: media_buy_seller/governance_conditions +version: "1.0.0" +title: "Seller attaches conditions when governance approves with conditions" +category: media_buy_seller +summary: "Verifies that the seller attaches governance conditions to the buy when governance approves with conditions." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + This is a multi-agent test. The test harness sets up a governance plan on a governance + agent with custom policies that trigger conditions (e.g., "CTV buys require weekly + delivery reporting"). The buyer creates a CTV buy within budget but matching a policy. + + When the seller calls check_governance, the governance agent approves with conditions. + The seller must create the buy and include the governance conditions and context token + in its response. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up conditional governance plan" + narrative: | + Create a governance plan on the governance agent with custom policies that + trigger conditions. The plan has sufficient budget but policies that require + conditions on CTV buys. + + steps: + - id: sync_plans + title: "Create conditional governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan with a plan_id. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_conditions_sync_plans" + plans: + - plan_id: "comply-gov-conditions-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 CTV campaign with reporting requirements" + budget: + total: 100000 + currency: "USD" + reallocation_threshold: 100000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US", "CA"] + custom_policies: + - policy_id: "ctv_weekly_reporting" + enforcement: "must" + policy: "CTV buys require weekly delivery reporting." + - policy_id: "ugc_brand_safety" + enforcement: "must" + policy: "UGC placements require brand safety review before activation." + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_conditions_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_conditions_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_with_conditions + title: "Create CTV buy triggering governance conditions" + steps: + - id: get_products_brief + title: "Discover CTV products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return CTV/video products matching the brief. + sample_request: + buying_mode: "brief" + brief: "CTV and connected TV inventory on outdoor lifestyle content. Q2 flight, $25K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy_conditions + title: "Create CTV buy (governance approves with conditions)" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds with governance conditions attached: + - media_buy_id: present + - status: active or pending_creatives + - governance_context: token from the governance agent + - conditions visible to the buyer + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_ctv_q2" + budget: 25000 + pricing_option_id: "cpm_standard" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_conditions_buy_with_conditions_create_media_buy_conditions" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied.yaml new file mode 100644 index 0000000000..b7ce2987d2 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied.yaml @@ -0,0 +1,192 @@ +id: media_buy_seller/governance_denied +version: "1.0.0" +title: "Seller rejects buy when governance denies" +category: media_buy_seller +summary: "Verifies that the seller rejects a media buy and propagates the denial when governance denies the transaction." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + This is a multi-agent test. The test harness sets up a governance plan on a governance + agent with a strict $10K budget, then registers that agent with the seller. The buyer + attempts a $50K media buy which exceeds the plan's budget. + + When the seller calls check_governance, the governance agent denies because the + requested budget exceeds the plan total. The seller must reject the buy and propagate + the denial back to the buyer. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up strict governance plan" + narrative: | + Create a governance plan on the governance agent with a strict $10K budget + and agent_limited authority. The subsequent $50K buy will exceed the plan + budget and should be denied. + + steps: + - id: sync_plans + title: "Create strict governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan with a plan_id. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_denied_sync_plans" + plans: + - plan_id: "comply-gov-denied-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Small test campaign — limited budget authority" + budget: + total: 10000 + currency: "USD" + reallocation_threshold: 5000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US"] + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_denied + title: "Create buy exceeding governance limits" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Premium video and display on outdoor lifestyle. Q2 flight, $50K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy_denied + title: "Create buy (governance denies — should fail)" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expect_error: true + negative_path: payload_well_formed + expected: | + The buy is rejected because governance denied — the $50K buy exceeds + the plan's $10K budget. The seller propagates the denial with findings. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + idempotency_key: "$generate:uuid_v4#governance_denied_create_media_buy" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + validations: + - check: error_code + value: "GOVERNANCE_DENIED" + description: "Error code indicates governance denial" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied_recovery.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied_recovery.yaml new file mode 100644 index 0000000000..b24a7c263c --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/governance_denied_recovery.yaml @@ -0,0 +1,244 @@ +id: media_buy_seller/governance_denied_recovery +version: "1.0.0" +title: "Seller accepts corrected buy after governance denial" +category: media_buy_seller +summary: "Verifies that a buyer can recover from GOVERNANCE_DENIED by shrinking the buy to within plan limits and retrying." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + GOVERNANCE_DENIED is a correctable error, not a dead end. The buyer must be able to + read the denial findings, adjust the media buy, and retry. This scenario exercises the + full loop: strict governance plan ($10K), failed $50K buy that is denied, then a + corrected $8K buy that fits within the plan and is approved. + + The seller must propagate the governance findings unchanged so the buyer can identify + exactly which constraint was violated (budget authority, brand policy, etc.) and + correct it. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up strict governance plan" + narrative: | + Create a governance plan with a $10K budget. The initial $50K buy will exceed this + limit; the retry at $8K will fit. + + steps: + - id: sync_plans + title: "Create strict governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_denied_recovery_sync_plans" + plans: + - plan_id: "comply-gov-recovery-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Strict-budget plan for denial + recovery validation" + budget: + total: 10000 + currency: "USD" + reallocation_threshold: 5000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US"] + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_recovery_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_recovery_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_denied + title: "Initial buy exceeds plan — governance denies" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content. Q2 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy_denied + title: "Attempt $50K buy — denied" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + The buy is rejected with GOVERNANCE_DENIED. The response includes findings + from the governance agent explaining which constraint was exceeded. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#governance_denied_recovery_create_media_buy_denied" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 50000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "governance_denied_recovery--create_media_buy_denied" + validations: + - check: error_code + value: "GOVERNANCE_DENIED" + description: "Error code is GOVERNANCE_DENIED" + - check: field_value + path: "context.correlation_id" + value: "governance_denied_recovery--create_media_buy_denied" + description: "Response echoes context.correlation_id verbatim on error responses (echo contract applies to both success and failure)" + + - id: buy_retried + title: "Corrected buy within plan — governance approves" + narrative: | + The buyer reads the denial findings, shrinks the budget to $8K (within the $10K + plan), and retries with a fresh idempotency_key. The seller consults governance + again, which now approves. + + steps: + - id: create_media_buy_retry + title: "Retry with $8K — approved" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds. Governance approves because $8K fits within the $10K plan + budget. The seller returns a media_buy_id. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#governance_denied_recovery_create_media_buy_retry" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 8000 + pricing_option_id: "$context.pricing_option_id" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns a media_buy_id after governance approves" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/invalid_transitions.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/invalid_transitions.yaml new file mode 100644 index 0000000000..112336abc5 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/invalid_transitions.yaml @@ -0,0 +1,284 @@ +id: media_buy_seller/invalid_transitions +version: "1.0.0" +title: "Seller rejects illegal state transitions and unknown references" +category: media_buy_seller +summary: "Validates that the seller returns structured AdCP errors (MEDIA_BUY_NOT_FOUND, PACKAGE_NOT_FOUND, NOT_CANCELLABLE) rather than 500s or undefined behavior when the buyer references missing entities or attempts forbidden state transitions." +track: media_buy +required_tools: + - get_products + - create_media_buy + - update_media_buy + +narrative: | + Error-code coverage is the most common compliance gap: sellers accept malformed input + silently or return generic 500s rather than the specific AdCP codes the spec defines. + This scenario forces three of the most-referenced media_buy error codes with + deterministic input: + + - MEDIA_BUY_NOT_FOUND — buyer references a media_buy_id that does not exist. + - PACKAGE_NOT_FOUND — media_buy_id is valid but package_id inside it is not. + - NOT_CANCELLABLE — buyer cancels the same media buy twice; the second cancel must + be rejected because canceled is terminal. + + All three probes use hard `check: error_code` assertions so a seller that returns + a 500 or swallows the error fails the scenario outright. Recovery hints, correlation + echo, and context preservation are also checked on every error response. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any media buy seller" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports create_media_buy and update_media_buy with cancellation via + the canceled field. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: unknown_media_buy + title: "Reference an unknown media_buy_id" + narrative: | + Before any state is set up, call update_media_buy with a fabricated media_buy_id. + The seller must return MEDIA_BUY_NOT_FOUND with a correctable recovery hint — not + a 500 and not a silent success. + + steps: + - id: update_unknown_media_buy + title: "update_media_buy with bogus media_buy_id" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + expect_error: true + negative_path: payload_well_formed + stateful: false + expected: | + Reject with: + - code: MEDIA_BUY_NOT_FOUND + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "does-not-exist-invalid-transitions-v1" + paused: true + idempotency_key: "$generate:uuid_v4#update_unknown_media_buy" + context: + correlation_id: "invalid_transitions--update_unknown_media_buy" + validations: + - check: error_code + value: "MEDIA_BUY_NOT_FOUND" + description: "Error code is MEDIA_BUY_NOT_FOUND" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "invalid_transitions--update_unknown_media_buy" + description: "Context correlation_id returned unchanged" + + - id: setup + title: "Create a real media buy for follow-up probes" + narrative: | + The remaining probes need a real media_buy_id and package_id. Discover a product + and create a plain buy — no targeting tricks, no governance — so we have known-good + IDs for the error cases. + + steps: + - id: get_products_brief + title: "Discover a product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product with pricing. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content. Q3 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: create_buy + title: "Create a buy for the error probes" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Media buy created with media_buy_id and at least one package. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#invalid_transitions_create_buy" + start_time: "2026-08-01T00:00:00Z" + end_time: "2026-08-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 5000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "invalid_transitions--create_buy" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns media_buy_id" + + - id: unknown_package + title: "Reference an unknown package_id on a real buy" + narrative: | + The media_buy_id is real, but the package_id is fabricated. The seller must + return PACKAGE_NOT_FOUND — distinct from MEDIA_BUY_NOT_FOUND — because the + lookup succeeds at the buy level and only fails at the package lookup. + + steps: + - id: update_unknown_package + title: "update_media_buy with bogus package_id" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with: + - code: PACKAGE_NOT_FOUND + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + idempotency_key: "$generate:uuid_v4#update_unknown_package" + packages: + - package_id: "does-not-exist-package-invalid-transitions-v1" + paused: true + context: + correlation_id: "invalid_transitions--update_unknown_package" + validations: + - check: error_code + value: "PACKAGE_NOT_FOUND" + description: "Error code is PACKAGE_NOT_FOUND" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "invalid_transitions--update_unknown_package" + description: "Context correlation_id returned unchanged" + + - id: double_cancel + title: "Cancel twice — second cancel is not valid" + narrative: | + Cancel the buy (success), then try to cancel the same buy again. canceled is + terminal per the AdCP spec, so the second cancel cannot succeed. The seller + must return NOT_CANCELLABLE — the schema specifically reserves this code for + "media buy cannot be canceled in its current state." + + steps: + - id: first_cancel + title: "Cancel the media buy" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Seller acknowledges the cancellation and transitions the buy to canceled. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + cancellation_reason: "Testing NOT_CANCELLABLE on re-cancel" + idempotency_key: "$generate:uuid_v4#media_buy_seller_invalid_transitions_double_cancel_first_cancel" + context: + correlation_id: "invalid_transitions--first_cancel" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - id: second_cancel + title: "Re-cancel the canceled buy" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with: + - code: NOT_CANCELLABLE + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + cancellation_reason: "Deliberate re-cancel to force NOT_CANCELLABLE" + idempotency_key: "$generate:uuid_v4#media_buy_seller_invalid_transitions_double_cancel_second_cancel" + context: + correlation_id: "invalid_transitions--second_cancel" + validations: + - check: error_code + value: "NOT_CANCELLABLE" + description: "Error code is NOT_CANCELLABLE on re-cancel of canceled buy" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "invalid_transitions--second_cancel" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_no_match.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_no_match.yaml new file mode 100644 index 0000000000..183da2616d --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_no_match.yaml @@ -0,0 +1,143 @@ +id: media_buy_seller/inventory_list_no_match +version: "1.0.0" +title: "Seller handles property_list / collection_list references that match zero inventory" +category: media_buy_seller +summary: "Verifies a seller returns a zero-forecast product or a clear error — not a crash — when a buyer references inventory lists that resolve to nothing in the seller's catalog." +track: media_buy +required_tools: + - get_products + - create_media_buy + +narrative: | + Not every buyer list matches every seller's inventory. A buyer may point at a + property_list of domains this seller does not carry, or a collection_list of + programs this seller does not syndicate. The seller must handle that gracefully: + + - Return a product with a zero forecast and explain the mismatch, OR + - Return an informative error on create_media_buy (INSUFFICIENT_INVENTORY or + similar) with findings the buyer can act on. + + What the seller must NOT do: crash, return a misleading non-zero forecast, or + silently drop the targeting and deliver against unintended inventory. Each of + those is a real failure mode we've seen in adapter integrations. + + This scenario exercises the no-match path using the test kit's pre-populated + no_match_properties and no_match_collections lists. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_property_list_targeting + - supports_collection_list_targeting + examples: + - "Sellers that validate inventory against buyer-supplied lists" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller must resolve PropertyListReference / CollectionListReference against + its own inventory and report a truthful outcome when nothing matches. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: discover + title: "Discover products" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products the seller carries. + sample_request: + buying_mode: "brief" + brief: "Video inventory on outdoor lifestyle programming. Q3 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "inventory_list_no_match--get_products_brief" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: no_match_attempt + title: "Create buy with lists that match no inventory" + narrative: | + The buyer references the no-match property list and the no-match collection + list. Neither list resolves to anything in the seller's catalog. The seller + must either (a) accept the buy and surface a zero-delivery expectation, or + (b) reject it with an informative error. Silent success with undefined + delivery behaviour is not acceptable. + + steps: + - id: create_buy_no_match + title: "create_media_buy against empty intersections" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + One of two acceptable outcomes: + + 1. Buy accepted with zero-forecast reporting — status may be + pending_creatives/pending_start/active, but the seller returns + packages with forecast indicating zero deliverable inventory and + a message explaining the list mismatch. + + 2. Buy rejected with an informative error — typically + INSUFFICIENT_INVENTORY or INVALID_TARGETING — including findings + that identify which list(s) matched nothing. + + What is NOT acceptable: a silently-successful buy with normal forecast + numbers, or a crash / non-AdCP error shape. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#inventory_list_no_match_create_buy" + start_time: "2026-07-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 10000 + pricing_option_id: "$context.pricing_option_id" + targeting_overlay: + property_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_v1" + collection_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_collections_v1" + + context: + correlation_id: "inventory_list_no_match--create_buy_no_match" + validations: + - check: field_present + path: "context" + description: "Response echoes back the context object (success or error)" + - check: field_value + path: "context.correlation_id" + value: "inventory_list_no_match--create_buy_no_match" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_targeting.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_targeting.yaml new file mode 100644 index 0000000000..d55802ce69 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/inventory_list_targeting.yaml @@ -0,0 +1,271 @@ +id: media_buy_seller/inventory_list_targeting +version: "1.0.0" +title: "Seller honors property_list and collection_list targeting on create and update" +category: media_buy_seller +summary: "Verifies that a seller accepts PropertyListReference and CollectionListReference in package targeting on create_media_buy AND update_media_buy, with parity between both paths." +track: media_buy +required_tools: + - get_products + - create_media_buy + - update_media_buy + +narrative: | + AdCP 3.0 targets inventory through agent-hosted reference lists rather than inline + property arrays. Buyers point a package's targeting at a `PropertyListReference` + and/or `CollectionListReference`; the seller resolves the list against its own + inventory at serve time. + + A common integration regression is create/update parity: a seller accepts list + references on create_media_buy but silently drops them on update_media_buy, so a + buyer who edits a live buy loses their list targeting. This scenario writes both + list types on create, then swaps both on update, and finally reads the buy back + to confirm the updated references are what the seller persisted. + + The buyer references pre-populated inventory lists from the test kit — one for + matching properties and one for matching collections — so the seller's test + engine can resolve them without needing a live governance/inventory agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_property_list_targeting + - supports_collection_list_targeting + examples: + - "Sellers that accept PropertyListReference / CollectionListReference in package targeting" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller must accept PropertyListReference and CollectionListReference in + package targeting on both create_media_buy and update_media_buy. List contents + come from test-kits/acme-outdoor.yaml → inventory_targets. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: discover_product + title: "Discover a product that supports list targeting" + steps: + - id: get_products_brief + title: "Discover product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product whose targeting supports property_list and + collection_list references. + + sample_request: + buying_mode: "brief" + brief: "Video inventory on outdoor lifestyle programming. Q3 flight, $30K budget." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + + context: + correlation_id: "inventory_list_targeting--get_products_brief" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "Product has a product_id" + + - id: create_with_both_lists + title: "Create media buy with property_list AND collection_list targeting" + narrative: | + The buyer references the matching-property list and the matching-collection + list from the test kit. The seller accepts both references, creates the buy, + and echoes the resolved targeting back on the package. + + steps: + - id: create_buy_with_lists + title: "create_media_buy with both list references" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The seller accepts the list references without error. The response + includes a media_buy_id and the package retains both the property_list + and collection_list targeting fields so subsequent get / update calls + can read them back. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + idempotency_key: "$generate:uuid_v4#inventory_list_targeting_create_buy" + start_time: "2026-07-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 20000 + pricing_option_id: "$context.pricing_option_id" + targeting_overlay: + property_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_allowlist_v1" + collection_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_collections_v1" + + context: + correlation_id: "inventory_list_targeting--create_buy_with_lists" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller assigns a media_buy_id" + - check: field_present + path: "context" + description: "Response echoes back the context object" + + - id: verify_create_persisted + title: "Verify list targeting persisted after create" + narrative: | + Read the buy back to confirm the seller stored both list references — not just + echoed them back in the create response. This catches sellers that parse list + references on create but never persist them to their internal package model. + + steps: + - id: get_after_create + title: "get_media_buys after create" + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the media buy with packages[0].targeting_overlay.property_list + and .collection_list populated with the list_id values sent on create. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "inventory_list_targeting--get_after_create" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.property_list.list_id" + value: "acme_outdoor_allowlist_v1" + description: "property_list.list_id persisted after create" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.collection_list.list_id" + value: "acme_outdoor_collections_v1" + description: "collection_list.list_id persisted after create" + + - id: update_swap_lists + title: "Swap property_list and collection_list via update_media_buy" + narrative: | + The buyer swaps both list references. This is the create/update parity check: + a seller that accepts list references on create but silently drops them on + update would fail to update the persisted targeting. We exercise update + explicitly to catch that class of regression. + + steps: + - id: update_buy_swap_lists + title: "update_media_buy replaces both list references" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The seller acknowledges the updated targeting. Both property_list and + collection_list are replaced (not merged) with the new list_id values. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + media_buy_id: "$context.media_buy_id" + packages: + - package_id: "$context.package_id" + targeting_overlay: + property_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_v1" + collection_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_collections_v1" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_inventory_list_targeting_update_swap_lists_update_buy_swap_lists" + context: + correlation_id: "inventory_list_targeting--update_buy_swap_lists" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - id: get_after_update + title: "Confirm swap persisted" + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The persisted package targeting reflects the new list_id values. This is + the create/update parity guarantee: edits through update_media_buy land + in persistent state just like fields on create_media_buy. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "inventory_list_targeting--get_after_update" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.property_list.list_id" + value: "acme_outdoor_no_match_v1" + description: "property_list.list_id updated after update_media_buy" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.collection_list.list_id" + value: "acme_outdoor_no_match_collections_v1" + description: "collection_list.list_id updated after update_media_buy" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/measurement_terms_rejected.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/measurement_terms_rejected.yaml new file mode 100644 index 0000000000..650db4c9bc --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/measurement_terms_rejected.yaml @@ -0,0 +1,196 @@ +id: media_buy_seller/measurement_terms_rejected +version: "1.0.0" +title: "Seller rejects unworkable measurement_terms" +category: media_buy_seller +summary: "Buyer proposes measurement_terms the seller will not accept; seller returns TERMS_REJECTED; buyer retries with seller-compatible terms." +track: media_buy +required_tools: + - get_products + - create_media_buy + +narrative: | + measurement_terms negotiation is a round-trip. The buyer proposes a measurement vendor, + window, and variance tolerance on each package; the seller either accepts (returning + measurement_terms echoed in the response) or rejects with TERMS_REJECTED and a message + explaining what is unacceptable. The buyer mints a fresh idempotency_key and retries + create_media_buy with the adjusted payload — reusing the prior key against a different + body MUST be rejected with IDEMPOTENCY_CONFLICT per spec. + + This scenario sends an intentionally aggressive first proposal (max_variance_percent: 0 + with a window the seller does not guarantee), verifies the seller rejects with + TERMS_REJECTED, then retries with a relaxed proposal that falls inside the seller's + stated tolerance and expects a successful create_media_buy. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - measurement_terms + examples: + - "Broadcast sellers" + - "CTV sellers with C3/C7 guarantees" + - "Any seller that negotiates measurement terms" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports measurement_terms on create_media_buy packages. Buyer knows the + seller's declared measurement capabilities from get_adcp_capabilities. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: discover_products + title: "Discover products" + steps: + - id: get_products_brief + title: "Find a product that supports measurement_terms" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products with pricing_options. At least one product should advertise + measurement support in the response. + sample_request: + buying_mode: "brief" + brief: "Premium video inventory with measurement guarantees. Q2 flight, $50K." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "At least one product returned" + + - id: reject_terms + title: "Seller rejects unworkable terms" + narrative: | + The buyer proposes max_variance_percent: 0 — a tolerance almost no seller can + honor against third-party measurement. The seller must reject with TERMS_REJECTED + and indicate what would be acceptable. + + steps: + - id: create_media_buy_aggressive_terms + title: "Propose aggressive measurement_terms" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + expect_error: true + negative_path: payload_well_formed + stateful: false + expected: | + Reject with: + - code: TERMS_REJECTED + - recovery: correctable + - message indicating which terms are unworkable (vendor, window, or variance) + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#measurement_terms_rejected_aggressive_terms" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 25000 + pricing_option_id: "$context.pricing_option_id" + measurement_terms: + billing_measurement: + vendor: + domain: "videoamp.example" + measurement_window: "c30" + max_variance_percent: 0 + makegood_policy: + available_remedies: ["credit"] + + context: + correlation_id: "measurement_terms_rejected--aggressive" + validations: + - check: error_code + value: "TERMS_REJECTED" + description: "Error code is TERMS_REJECTED" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "measurement_terms_rejected--aggressive" + description: "Context correlation_id returned unchanged" + + - id: accept_terms + title: "Buyer retries with acceptable terms" + narrative: | + The buyer relaxes measurement_terms to match the seller's stated capability + (c7 window, 10% variance, makegood remedies) and retries. The seller accepts and + returns the confirmed terms echoed in the response. + + steps: + - id: create_media_buy_relaxed_terms + title: "Propose seller-compatible measurement_terms" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds. The response echoes the accepted measurement_terms (possibly + normalized by the seller). + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#measurement_terms_rejected_relaxed_terms" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 25000 + pricing_option_id: "$context.pricing_option_id" + measurement_terms: + billing_measurement: + vendor: + domain: "videoamp.example" + measurement_window: "c7" + max_variance_percent: 10 + makegood_policy: + available_remedies: ["additional_delivery", "credit"] + + context: + correlation_id: "measurement_terms_rejected--relaxed" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns a media_buy_id after accepting terms" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "measurement_terms_rejected--relaxed" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/pending_creatives_to_start.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/pending_creatives_to_start.yaml new file mode 100644 index 0000000000..b5dd9230db --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/pending_creatives_to_start.yaml @@ -0,0 +1,250 @@ +id: media_buy_seller/pending_creatives_to_start +version: "1.0.0" +title: "Creative sync unblocks pending_creatives → pending_start" +category: media_buy_seller +summary: "Verifies that a media buy created without creatives sits in pending_creatives until sync_creatives completes, then transitions to pending_start." +track: media_buy +required_tools: + - get_products + - create_media_buy + - sync_creatives + - get_media_buys + +narrative: | + When a buyer creates a media buy without creative_assignments, the seller cannot start + delivery until creatives are supplied. The seller must report status: pending_creatives + and, after sync_creatives attaches the required assets, transition the buy to + pending_start (awaiting flight start) or active (if already in flight). + + This scenario walks the transition end-to-end: create the buy, confirm pending_creatives, + sync a creative, and confirm the status advances to pending_start. The transition + proves that the creative pipeline and the buy state machine are wired together — a + common integration gap when creative and media buy systems live on different backends. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_creatives + examples: + - "Any seller that requires creatives before starting delivery" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports create_media_buy without creative_assignments and honors the + pending_creatives → pending_start transition when creatives are synced. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Discover products and formats" + steps: + - id: get_products_brief + title: "Discover a product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product with format_ids and pricing options. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content. Q3 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + - path: "products[0].format_ids[0]" + key: "format_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Product declares a format_id for creative sync" + + - id: create_without_creatives + title: "Create media buy without creative_assignments" + narrative: | + The buyer intentionally omits creative_assignments. The seller must accept the + buy, persist it, and return status: pending_creatives with valid_actions + including sync_creatives. + + steps: + - id: create_buy_no_creatives + title: "Create buy in pending_creatives" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Media buy created with: + - media_buy_id assigned + - status: pending_creatives + - valid_actions including sync_creatives + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#pending_creatives_to_start_create_buy_no_creatives" + start_time: "2026-08-01T00:00:00Z" + end_time: "2026-08-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 10000 + pricing_option_id: "$context.pricing_option_id" + + context: + correlation_id: "pending_creatives_to_start--create_buy_no_creatives" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller assigns a media_buy_id" + - check: field_value + path: "status" + value: "pending_creatives" + description: "Status is pending_creatives because no creatives supplied" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "pending_creatives_to_start--create_buy_no_creatives" + description: "Context correlation_id returned unchanged" + + - id: supply_creatives + title: "Supply creatives and assign to the package" + narrative: | + The buyer syncs a creative with the format the product requires, then updates the + media buy with creative_assignments so the seller can attach the asset to the + package and advance state. + + steps: + - id: sync_creative + title: "Sync the creative asset" + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + The seller ingests the creative and returns status active (or approved). + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme-outdoor-display-q3" + name: "Acme Outdoor Q3 display" + format_id: "$context.format_id" + assets: + image: + asset_type: "image" + url: "https://creative.acmeoutdoor.example/q3/display-300x250.jpg" + width: 300 + height: 250 + mime_type: "image/jpeg" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_pending_creatives_to_start_supply_creatives_sync_creative" + context: + correlation_id: "pending_creatives_to_start--sync_creative" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + + - id: assign_creative_to_package + title: "Assign the creative to the package" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The seller acknowledges the assignment and advances the buy state. The response + status should be pending_start (or active if the flight has started). + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + packages: + - package_id: "$context.package_id" + creative_assignments: + - creative_id: "acme-outdoor-display-q3" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_pending_creatives_to_start_supply_creatives_assign_creative_to_package" + context: + correlation_id: "pending_creatives_to_start--assign_creative_to_package" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + - check: field_value + path: "status" + allowed_values: ["pending_start", "active"] + description: "Status advances out of pending_creatives once creatives attached" + + - id: verify_transition + title: "Verify the status transition" + narrative: | + Independently of the update_media_buy response, call get_media_buys to confirm the + seller's stored state has advanced. This protects against sellers that return the + updated status in response but do not persist it. + + steps: + - id: get_media_buy_after_sync + title: "Confirm pending_start after creative sync" + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The media buy's persisted status is pending_start (or active). valid_actions + no longer includes sync_creatives as a required next step. + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "pending_creatives_to_start--get_media_buy_after_sync" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_value + path: "media_buys[0].status" + allowed_values: ["pending_start", "active"] + description: "Persisted status is past pending_creatives" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/proposal_finalize.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/proposal_finalize.yaml new file mode 100644 index 0000000000..68fac08bb7 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/proposal_finalize.yaml @@ -0,0 +1,247 @@ +id: media_buy_seller/proposal_finalize +version: "1.0.0" +title: "Seller handles proposal refinement and finalize" +category: media_buy_seller +summary: "Verifies the full proposal lifecycle: brief with proposals, refine a proposal, finalize to committed, and accept via create_media_buy." +track: media_buy +required_tools: + - get_products + - create_media_buy + +narrative: | + Proposals are curated media plans that the seller generates alongside products. The buyer + reviews proposals, refines them (adjust budget splits, swap products, add constraints), + and then finalizes a proposal to get firm pricing and an inventory hold. Once committed, + the buyer accepts the proposal via create_media_buy. + + The proposal lifecycle is: draft (indicative pricing) -> refine -> finalize (firm pricing, + inventory hold) -> create_media_buy (accept and go live). + + This scenario exercises the complete proposal flow including the finalize action, which + transitions a proposal from draft to committed status with an expires_at hold window. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - generates_proposals + examples: + - "Full-service publisher with proposal engine" + - "Retail media network with curated packages" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials. The seller must support + proposal generation (return proposals alongside products in get_products responses). + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Account setup" + steps: + - id: sync_accounts + title: "Establish account" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_proposal_finalize_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: brief_with_proposals + title: "Brief with proposals" + narrative: | + Send a brief and receive proposals alongside products. Proposals are curated + media plans with budget allocations the buyer can review and refine. + + steps: + - id: get_products_brief + title: "Send a brief and receive proposals" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products and proposals matching the brief: + - products: individual products with pricing and forecasts + - proposals: curated media plans with proposal_id, budget_allocations, rationale + + sample_request: + buying_mode: "brief" + brief: "Premium video and display across outdoor lifestyle and sports. Q2 flight, $50K budget. Adults 25-54, US and Canada." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context_outputs: + - path: "proposals[0].proposal_id" + key: "proposal_id" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + - check: field_present + path: "proposals" + description: "Response contains proposals" + - check: field_present + path: "proposals[0].proposal_id" + description: "Proposals have IDs" + + - id: refine_proposal + title: "Refine the proposal" + narrative: | + The buyer reviews the proposals and wants to adjust one. They call get_products in + refine mode targeting a specific proposal_id with changes. The seller applies the + refinements and returns the updated proposal. + + steps: + - id: get_products_refine + title: "Refine a specific proposal" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return the refined proposal: + - proposals: updated proposal reflecting the requested changes + - refinement_applied: how each refinement was handled + - Updated budget allocations, product selections, and forecasts + + sample_request: + buying_mode: "refine" + refine: + - scope: "proposal" + proposal_id: "$context.proposal_id" + ask: "Shift 60% of budget to CTV. Drop the display product and redistribute that budget to video." + - scope: "request" + ask: "All products must support frequency capping at 3 per day." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "proposals" + description: "Response contains updated proposals" + + - id: finalize_proposal + title: "Finalize the proposal" + narrative: | + The buyer is satisfied with the refined proposal and requests finalization. This + triggers the transition from draft (indicative pricing) to committed (firm pricing + with inventory hold). The seller may need time to process this — the buyer should + not set a time_budget to signal willingness to wait. + + After finalization, the proposal has firm pricing and an expires_at timestamp. + The buyer must create the media buy before the hold expires. + + steps: + - id: get_products_finalize + title: "Finalize the proposal" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return the finalized proposal with committed status: + - proposals[0].proposal_status: committed + - proposals[0].expires_at: timestamp for the inventory hold window + - Firm pricing (not indicative) + - The proposal is ready to accept via create_media_buy + + sample_request: + buying_mode: "refine" + refine: + - scope: "proposal" + proposal_id: "$context.proposal_id" + action: "finalize" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "proposals" + description: "Response contains the finalized proposal" + + - id: accept_proposal + title: "Accept the committed proposal" + narrative: | + The buyer accepts the committed proposal by creating a media buy with the + proposal_id. The seller converts the proposal's product selections and budget + allocations into active packages. + + steps: + - id: create_media_buy + title: "Create media buy from proposal" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Convert the committed proposal into an active media buy: + - media_buy_id: the seller's identifier + - status: active + - confirmed_at: timestamp + - packages: line items derived from the proposal's budget allocations + - proposal_id: echoed back to confirm which proposal was accepted + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + proposal_id: "$context.proposal_id" + total_budget: + amount: 50000 + currency: "USD" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_proposal_finalize_accept_proposal_create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" diff --git a/dist/compliance/3.0.9/domains/media-buy/scenarios/refine_products.yaml b/dist/compliance/3.0.9/domains/media-buy/scenarios/refine_products.yaml new file mode 100644 index 0000000000..f47c311c2c --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/scenarios/refine_products.yaml @@ -0,0 +1,148 @@ +id: media_buy_seller/refine_products +version: "1.0.0" +title: "Seller handles product refinement" +category: media_buy_seller +summary: "Verifies that a media buy seller supports buying_mode: refine with product-level and request-level changes." +track: media_buy +required_tools: + - get_products + +narrative: | + After a buyer receives products from a brief, they refine the results without starting + over. The buyer calls get_products with buying_mode: refine and a refine array describing + changes at the request level ("only guaranteed") or product level ("increase this product's + budget"). + + The seller must apply each refinement, return updated products, and include + refinement_applied showing how each request was handled. This is the standard negotiation + loop — brief, review, refine, repeat until satisfied. + + Every media buy seller that supports negotiation (not just wholesale) must handle + product-level refinement. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + examples: + - "Any media buy seller that accepts briefs" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Account setup" + steps: + - id: sync_accounts + title: "Establish account" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_refine_products_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: brief + title: "Initial brief" + narrative: | + Send a brief to get the initial product set that the buyer will refine. + + steps: + - id: get_products_brief + title: "Send a brief" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. Each product should include product_id, + delivery_type, pricing_models, and forecast. + + sample_request: + buying_mode: "brief" + brief: "Premium video and display on sports and outdoor lifestyle. Q2 flight, $50K budget. Adults 25-54, US and Canada." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + - check: field_present + path: "products[0].product_id" + description: "Products have IDs" + + - id: refine + title: "Refine products" + narrative: | + The buyer has reviewed the initial products and wants to narrow down. They call + get_products with buying_mode: refine and a refine array with request-level and + product-level changes. The seller applies each refinement and returns the updated + product set. + + steps: + - id: get_products_refine + title: "Refine with request-level and product-level changes" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return updated products reflecting the refinements: + - Apply each refinement to the relevant scope (request or product) + - Include refinement_applied showing how each request was handled + - Preserve products that weren't targeted by refinements + - Update pricing and forecasts to reflect the changes + + sample_request: + buying_mode: "refine" + refine: + - scope: "request" + ask: "Only guaranteed packages. Must include completion rate SLA above 80%." + - scope: "product" + product_id: "sports_preroll_q2" + ask: "Increase budget allocation to $30K" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains updated products" diff --git a/dist/compliance/3.0.9/domains/media-buy/state-machine.yaml b/dist/compliance/3.0.9/domains/media-buy/state-machine.yaml new file mode 100644 index 0000000000..ddc67b9a33 --- /dev/null +++ b/dist/compliance/3.0.9/domains/media-buy/state-machine.yaml @@ -0,0 +1,442 @@ +id: media_buy_state_machine +version: "1.0.0" +title: "Media buy state machine lifecycle" +category: media_buy_state_machine +summary: "Validates media buy state transitions: create, pause, resume, cancel, and terminal state enforcement." +track: media_buy +required_tools: + - create_media_buy + - update_media_buy + +narrative: | + A media buy has a well-defined state machine: pending_creatives, pending_start, active, + paused, completed, rejected, canceled. Transitions between states must follow the spec — you cannot + resume a canceled buy or pause a completed one. + + This storyboard creates a media buy, walks it through pause/resume/cancel transitions, then + verifies that the agent rejects updates to a buy in a terminal state (canceled or completed). + It also tests package-level pause/resume independent of the media buy status. + + The state machine is the backbone of media buy reliability. If an agent allows invalid + transitions, buyers cannot trust the status field and automation breaks down. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + examples: + - "Any AdCP seller with media buy support" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity for account setup. The test creates a media buy + using discovered products, then exercises state transitions. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "media_buy_state_machine--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: setup + title: "Create a media buy" + narrative: | + Discover products and create a media buy to use for state transition testing. + The media buy ID is captured and passed to subsequent phases. + + steps: + - id: discover_products + title: "Discover products for media buy" + narrative: | + Send a brief to get available products with pricing options. The first product + with a pricing option will be used to create the test media buy. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products with: + - product_id + - pricing_options with pricing_option_id + + sample_request: + buying_mode: "brief" + brief: "Display advertising products for state machine testing" + brand: + domain: "acmeoutdoor.example" + + context: + correlation_id: "media_buy_state_machine--discover_products" + context_outputs: + - name: product_id + path: 'products[0].product_id' + - name: pricing_option_id + path: 'products[0].pricing_options[0].pricing_option_id' + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "At least one product with a product_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--discover_products" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_buy + title: "Create the test media buy" + narrative: | + Create a media buy using the discovered product. This buy will be used for + all subsequent state transition tests. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Return an active media buy with: + - media_buy_id + - status: active + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "test-product" + budget: 10000 + pricing_option_id: "test-pricing" + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_setup_create_buy" + context: + correlation_id: "media_buy_state_machine--create_buy" + context_outputs: + - name: media_buy_id + path: 'media_buy_id' + - name: package_id + path: 'packages[0].package_id' + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Response includes a media_buy_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--create_buy" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "packages[0].package_id" + description: "Seller assigns package_id — must be echoed in update_media_buy" + - id: state_transitions + title: "Valid state transitions" + narrative: | + Exercise the valid state transitions: pause, resume, and cancel. Each transition + must update the status field correctly. + + steps: + - id: pause_buy + title: "Pause the media buy" + narrative: | + Pause the active media buy. The status should transition to paused. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions to paused. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: true + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_state_transitions_pause_buy" + context: + correlation_id: "media_buy_state_machine--pause_buy" + validations: + - check: field_present + path: "status" + description: "Response includes updated status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--pause_buy" + description: "Context correlation_id returned unchanged" + - id: resume_buy + title: "Resume the media buy" + narrative: | + Resume the paused media buy. The status should transition back to active. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions back to active. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: false + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_state_transitions_resume_buy" + context: + correlation_id: "media_buy_state_machine--resume_buy" + validations: + - check: field_present + path: "status" + description: "Response includes updated status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--resume_buy" + description: "Context correlation_id returned unchanged" + - id: cancel_buy + title: "Cancel the media buy" + narrative: | + Cancel the media buy. This is a terminal transition — the buy cannot be + resumed or modified after cancellation. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions to canceled. This is terminal. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_state_transitions_cancel_buy" + context: + correlation_id: "media_buy_state_machine--cancel_buy" + validations: + - check: field_present + path: "status" + description: "Response includes canceled status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--cancel_buy" + description: "Context correlation_id returned unchanged" + - id: terminal_enforcement + title: "Terminal state enforcement" + narrative: | + Verify that the agent rejects state transitions on a canceled media buy. Pausing + or resuming a terminal buy returns INVALID_STATE (generic terminal-state rule). + Re-canceling a terminal buy returns NOT_CANCELLABLE — the cancellation-specific + code takes precedence over the generic INVALID_STATE when the attempted + transition is itself a cancellation. Non-cancellation illegal transitions into + or out of terminal states still return INVALID_STATE. + + steps: + - id: pause_canceled_buy + title: "Reject pause on canceled buy" + narrative: | + Attempt to pause a canceled media buy. The agent must reject this with + INVALID_STATE. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with INVALID_STATE — cannot pause a canceled buy. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: true + idempotency_key: "$generate:uuid_v4#pause_canceled_buy" + + context: + correlation_id: "media_buy_state_machine--pause_canceled_buy" + validations: + - check: error_code + allowed_values: ["INVALID_STATE"] + description: "Invalid transition rejected with INVALID_STATE" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--pause_canceled_buy" + description: "Context correlation_id returned unchanged" + - id: resume_canceled_buy + title: "Reject resume on canceled buy" + narrative: | + Attempt to resume a canceled media buy. The agent must reject this with + INVALID_STATE. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with INVALID_STATE — cannot resume a canceled buy. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: false + idempotency_key: "$generate:uuid_v4#resume_canceled_buy" + + context: + correlation_id: "media_buy_state_machine--resume_canceled_buy" + validations: + - check: error_code + allowed_values: ["INVALID_STATE"] + description: "Invalid transition rejected with INVALID_STATE" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--resume_canceled_buy" + description: "Context correlation_id returned unchanged" + - id: recancel_buy + title: "Reject re-cancel of canceled buy with NOT_CANCELLABLE" + narrative: | + Attempt to cancel an already-canceled media buy. The agent MUST reject + this with NOT_CANCELLABLE. The cancellation-specific code takes + precedence over the generic terminal-state INVALID_STATE when the + terminal update is itself a cancellation attempt — see §128/§129 of + the media-buy specification and media_buy_seller/invalid_transitions + for the canonical vector. Idempotent acceptance is NOT conformant for + this case. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with: + - code: NOT_CANCELLABLE + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_terminal_enforcement_recancel_buy" + context: + correlation_id: "media_buy_state_machine--recancel_buy" + validations: + - check: error_code + value: "NOT_CANCELLABLE" + description: "Error code is NOT_CANCELLABLE on re-cancel of canceled buy" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--recancel_buy" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/signals/index.yaml b/dist/compliance/3.0.9/domains/signals/index.yaml new file mode 100644 index 0000000000..8301cc20f2 --- /dev/null +++ b/dist/compliance/3.0.9/domains/signals/index.yaml @@ -0,0 +1,266 @@ +id: signals_baseline +version: "1.1.0" +title: "Signals baseline" +protocol: signals +category: signals_baseline +summary: "Baseline domain storyboard — every signals agent must discover signals and return an activation, regardless of whether they are owned or marketplace." +track: signals +required_tools: + - get_signals + - activate_signal + +narrative: | + Signals domain agents expose audience and contextual signals to buyers and + activate them on downstream destinations (DSPs, sales agents, clean rooms). + Every signals agent — whether a first-party owned platform or a third-party + marketplace — must support the same three-call flow at the protocol level: + + 1. Declare the signals protocol in get_adcp_capabilities. + 2. Respond to get_signals with a schema-valid signals array. + 3. Respond to activate_signal with a schema-valid deployments array. + + This baseline tests those three calls and nothing beyond them. Specialism + storyboards (signal-owned, signal-marketplace) exercise the richer flows + specific to each model — pricing option selection, source/provenance + discriminators, agent-destination vs. platform-destination activation, + and deactivation for compliance. + + Agents declaring supported_protocols: ["signals"] MUST pass this baseline + even before claiming a specialism. Declaring signals without exposing + get_signals or activate_signal fails the baseline with missing_tool. + +agent: + interaction_model: owned_signals + capabilities: [] + examples: + - "Any signals agent (owned or marketplace)" + - "Retailer CDPs" + - "Publisher contextual platforms" + - "Data provider marketplaces" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The buyer has a campaign brief with broad targeting objectives. The test + kit provides a sample brand (Nova Motors) with a signal description that + any signals agent should be able to interpret. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent serves signals + before issuing discovery or activation calls. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares `signals` in supported_protocols. + Without this claim the buyer will not send get_signals or + activate_signal. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring `signals` in supported_protocols. + + sample_request: + context: + correlation_id: "signals_baseline--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--get_capabilities" + description: "Context correlation_id returned unchanged" + + - id: discovery + title: "Signal discovery" + narrative: | + The buyer calls get_signals with a natural language signal_spec. Every + signals agent — owned or marketplace — must return a schema-valid + response. The buyer uses the returned signal_agent_segment_id values + to drive activation in the next phase. + + steps: + - id: search_signals + title: "Discover signals matching a spec" + narrative: | + The buyer describes a target audience in natural language. The agent + returns a list of signals from its catalog that match. Each signal + must include the fields the buyer needs to proceed to activation. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + comply_scenario: signals_flow + stateful: false + expected: | + Return a signals array with at least one entry. Each signal must + carry a signal_agent_segment_id that the buyer can pass to + activate_signal, along with pricing_options and a signal_id that + includes a source discriminator. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_spec: "Adults interested in electric vehicles" + context: + correlation_id: "signals_baseline--search_signals" + context_outputs: + - name: signal_agent_segment_id + path: "signals[0].signal_agent_segment_id" + - name: pricing_option_id + path: "signals[0].pricing_options[0].pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "First signal carries a signal_agent_segment_id" + - check: field_present + path: "signals[0].signal_id.source" + description: "Signal ID carries a source discriminator (agent_native or data_provider)" + - check: field_present + path: "signals[0].pricing_options" + description: "Signal carries pricing options the buyer can select" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--search_signals" + description: "Context correlation_id returned unchanged" + + - id: activation + title: "Signal activation" + narrative: | + The buyer activates one of the signals returned from discovery against + a destination. The protocol baseline tests only that the agent accepts + the call and returns a schema-valid deployments array — specialisms + exercise owned vs. marketplace activation patterns in depth. + + steps: + - id: activate_on_agent + title: "Activate on a sales agent destination" + narrative: | + Using the signal_agent_segment_id and pricing_option_id captured + from the previous step, the buyer activates the signal on a sales + agent destination. Every signals agent MUST accept `type: agent` + per the signals specification — the SA records the activation + internally and applies targeting in subsequent media-buy calls. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployments array with at least one entry carrying a + `type` discriminator and, for live deployments, an + `activation_key`. Agents MAY return an async deployment with + `is_live: false` and `estimated_activation_duration_minutes`. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "$context.signal_agent_segment_id" + pricing_option_id: "$context.pricing_option_id" + destinations: + - type: "agent" + agent_url: "https://wonderstruck.salesagents.example" + idempotency_key: "$generate:uuid_v4#signals_baseline_activate_agent" + + context: + correlation_id: "signals_baseline--activate_on_agent" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].type" + description: "Deployment carries a type discriminator" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--activate_on_agent" + description: "Context correlation_id returned unchanged" + + - id: activate_on_platform + title: "Activate on a platform destination" + narrative: | + The buyer re-activates the same signal against a platform + destination (a DSP). Signal agents MUST accept `type: platform` + per the signals specification — the agent pushes the segment to + the platform and returns a deployment record. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployments array with at least one entry whose + `type: "platform"` and, for live deployments, an + `activation_key` with `type: "segment_id"`. Async deployments + MAY report `is_live: false` with + `estimated_activation_duration_minutes`. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "$context.signal_agent_segment_id" + pricing_option_id: "$context.pricing_option_id" + destinations: + - type: "platform" + platform: "the-trade-desk" + account: "agency-123-ttd" + idempotency_key: "$generate:uuid_v4#signals_baseline_activate_platform" + + context: + correlation_id: "signals_baseline--activate_on_platform" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].type" + description: "Deployment carries a type discriminator" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--activate_on_platform" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/domains/sponsored-intelligence/index.yaml b/dist/compliance/3.0.9/domains/sponsored-intelligence/index.yaml new file mode 100644 index 0000000000..9846ad7bcc --- /dev/null +++ b/dist/compliance/3.0.9/domains/sponsored-intelligence/index.yaml @@ -0,0 +1,256 @@ +id: si_baseline +version: "1.0.0" +title: "Sponsored intelligence baseline" +protocol: sponsored-intelligence +category: si_baseline +summary: "Baseline domain storyboard — every SI agent must discover offerings, initiate a session, exchange messages, and terminate cleanly." +track: si +required_tools: + - si_initiate_session + +narrative: | + You run an AI platform that supports sponsored intelligence — conversational ad experiences + embedded in AI-powered search, chat, or assistant products. A buyer agent connects to + discover what SI offerings are available, initiate a session, send messages within the + conversation, and cleanly terminate when done. + + Sponsored intelligence is fundamentally different from display or video advertising. The + ad experience is conversational — the user asks a question, the AI responds, and sponsored + content is woven into the response in a way that is transparent and relevant. + + This storyboard covers the SI session lifecycle from the buyer's perspective: discovering + what the platform offers, starting a conversation, exchanging messages, and ending the + session. + +agent: + interaction_model: si_platform + capabilities: + - sponsored_intelligence + examples: + - "Perplexity" + - "ChatGPT Search" + - "Arc Browser" + - "AI assistants with ad support" + +caller: + role: buyer_agent + example: "Nova Motors (advertiser)" + +prerequisites: + description: | + The caller needs brand context and campaign parameters for SI. The test kit provides + a sample brand (Nova Motors) with signal definitions suitable for conversational + ad experiences. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports sponsored intelligence before initiating sessions. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring sponsored_intelligence in supported_protocols, confirming the agent supports conversational ad experiences. + sample_request: + context: + correlation_id: "si_session--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: offering_discovery + title: "Discover SI offerings" + narrative: | + Before initiating any session, the buyer discovers what sponsored intelligence + offerings the platform has available. This determines what kinds of conversational + experiences can be sponsored and at what pricing. + + steps: + - id: si_get_offering + title: "Get available SI offerings" + narrative: | + The buyer calls si_get_offering to learn what conversational ad experiences + the platform supports. The response describes available offerings with pricing, + targeting options, and format specifications. + task: si_get_offering + schema_ref: "sponsored-intelligence/si-get-offering-request.json" + response_schema_ref: "sponsored-intelligence/si-get-offering-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_get_offering" + comply_scenario: si_availability + stateful: false + expected: | + Return available SI offerings: + - Offering descriptions with pricing + - Supported conversation types + - Targeting and context options + - Format specifications for sponsored content + + sample_request: + offering_id: "novamotors_conversational_v1" + context: + correlation_id: "si_session--si_get_offering" + + context_outputs: + - name: offering_id + path: 'offering_id' + validations: + - check: response_schema + description: "Response matches si-get-offering-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_get_offering" + description: "Context correlation_id returned unchanged" + - id: session_lifecycle + title: "Session lifecycle" + narrative: | + The buyer initiates a session, exchanges messages within it, and terminates + cleanly. Each session represents a single conversational ad experience — the + buyer provides context and the platform weaves sponsored content into the + conversation. + + steps: + - id: si_initiate_session + title: "Start a conversation session" + narrative: | + The buyer initiates a new SI session with campaign context. The platform + creates a session and returns a session ID that the buyer uses for subsequent + messages. + task: si_initiate_session + schema_ref: "sponsored-intelligence/si-initiate-session-request.json" + response_schema_ref: "sponsored-intelligence/si-initiate-session-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_initiate_session" + comply_scenario: si_session_lifecycle + stateful: true + expected: | + Return a new session: + - session_id: platform-assigned session identifier + - status: active + - Initial context acknowledgment + - Available interaction modes + + sample_request: + intent: "User is researching electric vehicles for long road trips and wants to talk to Nova Motors" + identity: + consent_granted: true + consent_timestamp: "2026-04-22T14:00:00Z" + user: + locale: "en-US" + + idempotency_key: "$generate:uuid_v4#si_baseline_session_lifecycle_si_initiate_session" + context: + correlation_id: "si_session--si_initiate_session" + context_outputs: + - name: session_id + path: 'session_id' + validations: + - check: response_schema + description: "Response matches si-initiate-session-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_initiate_session" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "session_id" + description: "Platform assigns session_id — must be echoed in si_send_message and si_terminate_session" + - id: si_send_message + title: "Exchange messages" + narrative: | + The buyer sends a message within the active session. The platform processes + the message and returns a response that may include sponsored content woven + into the conversational experience. + task: si_send_message + schema_ref: "sponsored-intelligence/si-send-message-request.json" + response_schema_ref: "sponsored-intelligence/si-send-message-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_send_message" + comply_scenario: si_session_lifecycle + stateful: true + expected: | + Process the message and return a response: + - Message acknowledgment + - Response content (may include sponsored elements) + - Session state (active, waiting, etc.) + + sample_request: + session_id: "$context.session_id" + message: "What are the best electric vehicles for long road trips?" + + idempotency_key: "$generate:uuid_v4#si_baseline_session_lifecycle_si_send_message" + context: + correlation_id: "si_session--si_send_message" + validations: + - check: response_schema + description: "Response matches si-send-message-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_send_message" + description: "Context correlation_id returned unchanged" + - id: si_terminate_session + title: "End the session" + narrative: | + The buyer terminates the SI session. The platform records session metrics + and returns a summary of the conversation including any sponsored content + that was delivered. + task: si_terminate_session + schema_ref: "sponsored-intelligence/si-terminate-session-request.json" + response_schema_ref: "sponsored-intelligence/si-terminate-session-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_terminate_session" + comply_scenario: si_handoff + stateful: true + expected: | + Terminate the session and return a summary: + - session_id: confirms which session was terminated + - status: terminated + - Session metrics (duration, messages exchanged) + - Sponsored content delivery summary + + sample_request: + session_id: "$context.session_id" + reason: "handoff_complete" + + context: + correlation_id: "si_session--si_terminate_session" + validations: + - check: response_schema + description: "Response matches si-terminate-session-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_terminate_session" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/index.json b/dist/compliance/3.0.9/index.json new file mode 100644 index 0000000000..5b65155f11 --- /dev/null +++ b/dist/compliance/3.0.9/index.json @@ -0,0 +1,324 @@ +{ + "adcp_version": "3.0.9", + "generated_at": "2026-05-09T20:40:15.272Z", + "universal": [ + "capability-discovery", + "collection-lists-pagination-integrity", + "content-standards-pagination-integrity", + "deterministic-testing", + "error-compliance", + "fictional-entities", + "get-media-buys-pagination-integrity", + "get-signals-pagination-integrity", + "idempotency", + "pagination-integrity", + "pagination-integrity-creative-formats", + "pagination-integrity-list-accounts", + "property-lists-pagination-integrity", + "runner-output-contract", + "schema-validation", + "security", + "signed-requests", + "storyboard-schema", + "v3-envelope-integrity", + "webhook-emission" + ], + "protocols": [ + { + "id": "brand", + "title": "Brand baseline", + "has_baseline": true, + "path": "protocols/brand/" + }, + { + "id": "creative", + "title": "Creative lifecycle", + "has_baseline": true, + "path": "protocols/creative/" + }, + { + "id": "governance", + "title": "Governance denial and human escalation", + "has_baseline": true, + "path": "protocols/governance/" + }, + { + "id": "media-buy", + "title": "Media buy seller agent", + "has_baseline": true, + "path": "protocols/media-buy/" + }, + { + "id": "signals", + "title": "Signals baseline", + "has_baseline": true, + "path": "protocols/signals/" + }, + { + "id": "sponsored-intelligence", + "title": "Sponsored intelligence baseline", + "has_baseline": true, + "path": "protocols/sponsored-intelligence/" + } + ], + "domains": [ + { + "id": "brand", + "title": "Brand baseline", + "has_baseline": true, + "path": "domains/brand/" + }, + { + "id": "creative", + "title": "Creative lifecycle", + "has_baseline": true, + "path": "domains/creative/" + }, + { + "id": "governance", + "title": "Governance denial and human escalation", + "has_baseline": true, + "path": "domains/governance/" + }, + { + "id": "media-buy", + "title": "Media buy seller agent", + "has_baseline": true, + "path": "domains/media-buy/" + }, + { + "id": "signals", + "title": "Signals baseline", + "has_baseline": true, + "path": "domains/signals/" + }, + { + "id": "sponsored-intelligence", + "title": "Sponsored intelligence baseline", + "has_baseline": true, + "path": "domains/sponsored-intelligence/" + } + ], + "specialisms": [ + { + "id": "audience-sync", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Audience sync", + "status": "stable", + "required_tools": [ + "list_accounts", + "sync_audiences" + ], + "path": "specialisms/audience-sync/" + }, + { + "id": "brand-rights", + "protocol": "brand", + "domain": "brand", + "title": "Brand identity and rights licensing", + "status": "stable", + "required_tools": [ + "get_brand_identity" + ], + "path": "specialisms/brand-rights/" + }, + { + "id": "collection-lists", + "protocol": "governance", + "domain": "governance", + "title": "Collection lists", + "status": "stable", + "required_tools": [ + "create_collection_list" + ], + "path": "specialisms/collection-lists/" + }, + { + "id": "content-standards", + "protocol": "governance", + "domain": "governance", + "title": "Content standards", + "status": "stable", + "required_tools": [ + "list_content_standards" + ], + "path": "specialisms/content-standards/" + }, + { + "id": "creative-ad-server", + "protocol": "creative", + "domain": "creative", + "title": "Creative ad server", + "status": "stable", + "required_tools": [ + "build_creative" + ], + "path": "specialisms/creative-ad-server/" + }, + { + "id": "creative-generative", + "protocol": "creative", + "domain": "creative", + "title": "Generative creative agent", + "status": "stable", + "required_tools": [ + "build_creative" + ], + "path": "specialisms/creative-generative/" + }, + { + "id": "creative-template", + "protocol": "creative", + "domain": "creative", + "title": "Creative template and transformation agent", + "status": "stable", + "required_tools": [ + "build_creative" + ], + "path": "specialisms/creative-template/" + }, + { + "id": "governance-aware-seller", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Governance-aware seller", + "status": "stable", + "required_tools": [ + "sync_governance", + "create_media_buy" + ], + "path": "specialisms/governance-aware-seller/" + }, + { + "id": "governance-delivery-monitor", + "protocol": "governance", + "domain": "governance", + "title": "Campaign governance — delivery monitoring with drift detection", + "status": "stable", + "required_tools": [ + "check_governance" + ], + "path": "specialisms/governance-delivery-monitor/" + }, + { + "id": "governance-spend-authority", + "protocol": "governance", + "domain": "governance", + "title": "Campaign governance — conditional approval", + "status": "stable", + "required_tools": [ + "sync_plans", + "check_governance" + ], + "path": "specialisms/governance-spend-authority/" + }, + { + "id": "property-lists", + "protocol": "governance", + "domain": "governance", + "title": "Property lists", + "status": "stable", + "required_tools": [ + "create_property_list" + ], + "path": "specialisms/property-lists/" + }, + { + "id": "sales-broadcast-tv", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Broadcast linear TV seller agent", + "status": "stable", + "required_tools": [ + "get_products", + "create_media_buy" + ], + "path": "specialisms/sales-broadcast-tv/" + }, + { + "id": "sales-catalog-driven", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Catalog-driven creative and conversion tracking", + "status": "stable", + "required_tools": [ + "get_products", + "create_media_buy" + ], + "path": "specialisms/sales-catalog-driven/" + }, + { + "id": "sales-guaranteed", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Guaranteed media buy with human IO approval", + "status": "stable", + "required_tools": [ + "get_products", + "create_media_buy" + ], + "path": "specialisms/sales-guaranteed/" + }, + { + "id": "sales-non-guaranteed", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Non-guaranteed auction-based media buy", + "status": "stable", + "required_tools": [ + "get_products", + "create_media_buy" + ], + "path": "specialisms/sales-non-guaranteed/" + }, + { + "id": "sales-proposal-mode", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Media buy via proposal acceptance", + "status": "stable", + "required_tools": [ + "get_products", + "create_media_buy" + ], + "path": "specialisms/sales-proposal-mode/" + }, + { + "id": "sales-social", + "protocol": "media-buy", + "domain": "media-buy", + "title": "Social platform", + "status": "stable", + "required_tools": [ + "sync_audiences", + "sync_catalogs", + "sync_creatives", + "sync_event_sources" + ], + "path": "specialisms/sales-social/" + }, + { + "id": "signal-marketplace", + "protocol": "signals", + "domain": "signals", + "title": "Marketplace signal agent", + "status": "stable", + "required_tools": [ + "get_signals" + ], + "path": "specialisms/signal-marketplace/" + }, + { + "id": "signal-owned", + "protocol": "signals", + "domain": "signals", + "title": "Owned signal agent", + "status": "stable", + "required_tools": [ + "get_signals" + ], + "path": "specialisms/signal-owned/" + } + ] +} diff --git a/dist/compliance/3.0.9/protocols/brand/index.yaml b/dist/compliance/3.0.9/protocols/brand/index.yaml new file mode 100644 index 0000000000..9598013fe3 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/brand/index.yaml @@ -0,0 +1,163 @@ +id: brand_baseline +version: "1.0.0" +title: "Brand baseline" +protocol: brand +category: brand_baseline +summary: "Baseline protocol storyboard — every brand agent must declare the brand protocol in capabilities and return a schema-valid brand identity." +track: brand +required_tools: + - get_brand_identity + +narrative: | + Brand protocol agents are the identity layer of AdCP. Their job is to hold + brand identity data (names, logos, colors, fonts, tone) and expose it to + other agents — buyer agents, creative agents, DSPs — that need to render + on-brand creative or verify who a campaign is for. + + The baseline tests the minimum contract that every brand agent honors, + regardless of what additional capabilities (rights licensing, creative + approval) it layers on top: + + 1. Declare `brand` in `supported_protocols` on `get_adcp_capabilities`. + 2. Respond to `get_brand_identity` with a schema-valid identity manifest. + 3. Reject unknown `brand_id` values with a structured error. + + Rights licensing (`get_rights`, `acquire_rights`, `update_rights`, + `creative_approval`) ships experimentally in 3.0 and is covered by the + `brand-rights` specialism storyboard, not this baseline. + +agent: + interaction_model: brand_agent + capabilities: [] + examples: + - "Any brand agent (simple identity host or full rights platform)" + - "Brand-owned agents (Acme Outdoor)" + - "Third-party brand identity platforms" + - "Agency-hosted brand agents" + +caller: + role: buyer_agent + example: "Any buyer, creative agent, or DSP needing brand identity" + +prerequisites: + description: | + The test kit provides a sample brand (Nova Motors) that any brand agent + can serve identity for. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls `get_adcp_capabilities` to confirm the agent declares + the brand protocol before issuing any brand-identity call. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares `brand` in `supported_protocols`. + Without this claim the buyer MUST NOT send `get_brand_identity`. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring `brand` in `supported_protocols`. + + sample_request: + context: + correlation_id: "brand_baseline--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Response declares supported_protocols" + + - id: brand_identity_retrieval + title: "Brand identity retrieval" + narrative: | + The buyer calls `get_brand_identity` to retrieve the brand's identity + manifest. The minimum contract is a schema-valid response that echoes + the requested `brand_id` and carries at least one name. + + steps: + - id: get_brand_identity + title: "Retrieve brand identity" + narrative: | + The buyer calls `get_brand_identity` with a known `brand_id`. The + response MUST match the brand-identity schema and echo the + requested `brand_id`. Rich fields (logos, colors, fonts, tone, + visual_guidelines) are optional at the baseline level — the + minimum bar is that identity resolution works and is schema-valid. + task: get_brand_identity + schema_ref: "brand/get-brand-identity-request.json" + response_schema_ref: "brand/get-brand-identity-response.json" + doc_ref: "/brand-protocol/tasks/get_brand_identity" + stateful: false + expected: | + Return a schema-valid brand identity that echoes the requested + brand_id and includes at least one name. + + sample_request: + brand_id: "nova_motors" + context: + correlation_id: "brand_baseline--get_brand_identity" + context_outputs: + - path: "brand_id" + key: "brand_id" + + validations: + - check: response_schema + description: "Response matches get-brand-identity-response.json schema" + - check: field_present + path: "brand_id" + description: "Response includes brand_id" + - check: field_value + path: "brand_id" + value: "nova_motors" + description: "Returned brand_id echoes the requested brand" + - check: field_present + path: "names" + description: "Response includes brand names" + + - id: unknown_brand_rejection + title: "Unknown brand rejection" + narrative: | + Agents MUST reject unknown `brand_id` values with a structured + AdCP error rather than returning an empty or fabricated manifest. + + steps: + - id: get_brand_identity_unknown + title: "Reject unknown brand ID" + narrative: | + The buyer calls `get_brand_identity` with a `brand_id` the agent + does not serve. The response MUST be a structured error with a + recovery classification — not a success response with empty + fields. + task: get_brand_identity + schema_ref: "brand/get-brand-identity-request.json" + response_schema_ref: "brand/get-brand-identity-response.json" + doc_ref: "/brand-protocol/tasks/get_brand_identity" + stateful: false + expected: | + Return an AdCP error response indicating the brand is not known + to this agent. + + sample_request: + brand_id: "brand_that_does_not_exist_12345" + context: + correlation_id: "brand_baseline--get_brand_identity_unknown" + + expect_error: true + negative_path: payload_well_formed + validations: + - check: error_code + allowed_values: + - "brand_not_found" + - "BRAND_NOT_FOUND" + - "NOT_FOUND" + description: "Error code indicates brand-not-found" diff --git a/dist/compliance/3.0.9/protocols/creative/index.yaml b/dist/compliance/3.0.9/protocols/creative/index.yaml new file mode 100644 index 0000000000..cc6efd4f20 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/creative/index.yaml @@ -0,0 +1,412 @@ +id: creative_lifecycle +version: "1.0.0" +title: "Creative lifecycle" +category: creative_lifecycle +summary: "Full creative lifecycle on a stateful platform: sync multiple creatives, list with filtering, build and preview across formats, observe status transitions." +track: creative +required_tools: + - list_creative_formats + +narrative: | + You run a creative platform with a persistent library — an ad server, creative management + platform, or publisher that accepts and stores creative assets. A buyer agent pushes + multiple creatives in different formats, queries the library, builds serving tags, previews + renderings, and monitors creative status as assets move through your review pipeline. + + This storyboard covers the complete creative lifecycle from the buyer's perspective: + uploading assets, browsing the library, building deliverables across formats, and + observing status transitions as creatives move from pending_review through to accepted. + + The individual creative storyboards (template, ad server, sales agent) cover specific + interaction models. This storyboard tests the full lifecycle across multiple creatives + and formats on a single platform. + +agent: + interaction_model: stateful_preloaded + capabilities: + - has_creative_library + - supports_transformation + examples: + - "Innovid" + - "Flashtalking" + - "CM360" + - "Creative management platforms" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs creative assets in multiple formats (display, video, native) and + a brand identity. The test kit provides sample assets at standard ad dimensions. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_lifecycle--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: discover_formats + title: "Discover accepted formats" + narrative: | + Before pushing any creatives, the buyer discovers what formats your platform accepts. + This determines which assets to prepare and what dimensions and specs to target. + + steps: + - id: list_formats + title: "List creative formats" + narrative: | + The buyer calls list_creative_formats to discover what your platform accepts. + The response defines format specs: dimensions, asset requirements, mime types, + and any platform-specific constraints. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return all creative formats your platform accepts: + - format_id with your agent_url and unique id + - Asset requirements (dimensions, file sizes, mime types) + - Render dimensions + - At least two formats (e.g., display and video) + + sample_request: + context: + correlation_id: "creative_lifecycle--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--list_formats" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - id: sync_multiple + title: "Sync multiple creatives" + narrative: | + The buyer pushes three creatives in different formats to your platform: a display + banner, a video spot, and a native card. Your platform validates each creative + against its format specs and returns per-creative status. + + steps: + - id: sync_creatives + title: "Push three creatives in different formats" + narrative: | + The buyer syncs three creatives simultaneously: a 300x250 display banner, a 30s + video spot, and a native content card. Your platform validates each against its + format specs and returns per-creative action and status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + Accept and validate all three creatives: + - Per-creative action: created + - Per-creative status: accepted or pending_review + - Validation results for each creative + - Platform-assigned IDs if applicable + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "display_trail_pro_300x250" + name: "Trail Pro 3000 - Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + width: 300 + height: 250 + mime_type: "image/png" + - creative_id: "video_30s_trail_pro" + name: "Trail Pro 3000 - 30s Video" + format_id: + agent_url: "https://your-platform.example.com" + id: "video_30s" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + mime_type: "video/mp4" + - creative_id: "native_trail_pro" + name: "Trail Pro 3000 - Native Card" + format_id: + agent_url: "https://your-platform.example.com" + id: "native_content" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-native.png" + width: 1200 + height: 628 + mime_type: "image/png" + headline: + asset_type: "text" + content: "Trail Pro 3000 — Built for the Summit" + + idempotency_key: "$generate:uuid_v4#creative_lifecycle_sync_multiple_sync_creatives" + context: + correlation_id: "creative_lifecycle--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives" + description: "Response contains per-creative results" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: list_and_filter + title: "List creatives with filtering" + narrative: | + The buyer queries the creative library to see their synced creatives. First a broad + list, then filtered by format. This verifies the library correctly stores and indexes + the pushed creatives. + + steps: + - id: list_all + title: "List all creatives in library" + narrative: | + The buyer calls list_creatives with no filters to see all creatives in the + library for their account. The response includes the three creatives synced + in the previous phase. + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + Return creatives in the library: + - creatives array containing the synced items + - Each creative includes: creative_id, name, format_id, status + - At least three creatives from the sync phase + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "creative_lifecycle--list_all" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives" + description: "Response contains creatives array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--list_all" + description: "Context correlation_id returned unchanged" + - id: list_filtered + title: "List creatives filtered by format" + narrative: | + The buyer lists creatives filtered to a specific format (display only). The + response should only include creatives matching that format. + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + Return only creatives matching the format filter: + - creatives array filtered to display format + - Should include display_trail_pro_300x250 but not video or native + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + filters: + format_ids: + - agent_url: "https://your-platform.example.com" + id: "display_300x250" + + context: + correlation_id: "creative_lifecycle--list_filtered" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives" + description: "Response contains filtered creatives" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--list_filtered" + description: "Context correlation_id returned unchanged" + - id: build_and_preview + title: "Build and preview across formats" + narrative: | + The buyer builds serving tags and previews renderings for the synced creatives. + This tests multi-format output: a display tag, a VAST tag for video, and a + native rendering preview. + + steps: + - id: preview_display + title: "Preview the display creative" + narrative: | + The buyer calls preview_creative for the display banner to see how it renders + in the platform's environment before going live. + task: preview_creative + schema_ref: "creative/preview-creative-request.json" + response_schema_ref: "creative/preview-creative-response.json" + doc_ref: "/creative/task-reference/preview_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a preview of the display creative: + - preview_url: rendered preview the buyer can inspect + - render_dimensions: matches the 300x250 format + - status: preview available + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + request_type: "single" + creative_manifest: + creative_id: "display_trail_pro_300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/banner_300x250.jpg" + width: 300 + height: 250 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/trail-pro" + + context: + correlation_id: "creative_lifecycle--preview_display" + validations: + - check: response_schema + description: "Response matches preview-creative-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--preview_display" + description: "Context correlation_id returned unchanged" + - id: build_video_tag + title: "Build a VAST tag for the video creative" + narrative: | + The buyer builds a serving tag for the video creative. The platform produces + a VAST-compatible tag that the buyer can traffic to ad servers. + task: build_creative + schema_ref: "creative/build-creative-request.json" + response_schema_ref: "creative/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a built serving tag for the video creative: + - tag: VAST-compatible serving tag or URL + - format: matches the video format + - creative_id: matches the requested creative + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creative_id: "video_30s_trail_pro" + target_format_id: + agent_url: "https://your-platform.example.com" + id: "vast_30s" + idempotency_key: "$generate:uuid_v4#creative_lifecycle_build_video_tag" + + context: + correlation_id: "creative_lifecycle--build_video_tag" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_lifecycle--build_video_tag" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/governance/index.yaml b/dist/compliance/3.0.9/protocols/governance/index.yaml new file mode 100644 index 0000000000..8839db4847 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/governance/index.yaml @@ -0,0 +1,683 @@ +id: media_buy_governance_escalation +version: "1.0.0" +title: "Governance denial and human escalation" +category: media_buy_governance_escalation +summary: "Buyer's governance agent denies a media buy that exceeds spending authority, escalates to a human who approves with conditions." +track: campaign_governance +required_tools: + - sync_plans + - check_governance + +narrative: | + The buyer's governance agent denies a media buy because it exceeds the agent's spending + authority. The governance check escalates to a human reviewer who approves with conditions. + + This storyboard shows the full governance loop: plan registration with spending authority + limits, product discovery, pre-buy governance check that gets denied, human escalation + that results in conditional approval, media buy creation with the approved governance + context, outcome reporting, and a complete audit trail. + + Governance exists to ensure that automated agents operate within defined boundaries. When + those boundaries are exceeded, the system escalates to humans rather than blocking + entirely. The audit trail provides accountability for every decision in the chain. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Publisher platform integrated with buyer governance" + - "SSP that respects governance checks" + - "Retail media network with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity, operator credentials, and a governance agent + URL. The test kit provides a sample brand (Acme Outdoor) with campaign parameters + and a governance configuration with spending authority limits. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_ctv_q2" + delivery_type: "guaranteed" + channels: ["ctv"] + format_ids: + - id: "video_30s" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "sports_ctv_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 45.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + plans: + - plan_id: "gov_acme_q2_2027" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 outdoor lifestyle campaign — display and video, Adults 25-54, US" + budget: + total: 50000 + currency: "USD" + reallocation_threshold: 20000 + flight: + start: "2027-04-01T00:00:00Z" + end: "2027-06-30T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "weekly_reporting_over_10k" + enforcement: "must" + policy: "Weekly reporting required for buys over $10K." + - policy_id: "seller_concentration_cap" + enforcement: "must" + policy: "No single-seller concentration above 60% of total budget." + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "media_buy_governance_escalation--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account and governance setup" + narrative: | + The buyer establishes an account and registers their governance agent with the + seller. The governance agent will be called before media buys are confirmed to + validate spending authority, brand safety, and compliance. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier + - action: created or updated + - status: active or pending_approval + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_account_setup_sync_accounts" + context: + correlation_id: "media_buy_governance_escalation--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: sync_governance + title: "Register governance agent" + narrative: | + The buyer tells your platform: "Before you confirm any media buy for this + account, call this governance agent to validate it." Your platform stores + the governance agent URL and will call it during create_media_buy. + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agents. Your platform should: + - Store the governance agent URLs for the specified accounts + - Return confirmation that agents were registered + - Use these agents during create_media_buy validation + + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "https://governance.pinnacle-agency.example" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_account_setup_sync_governance" + context: + correlation_id: "media_buy_governance_escalation--sync_governance" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--sync_governance" + description: "Context correlation_id returned unchanged" + - id: register_plan + title: "Register governance plan with spending limits" + narrative: | + The buyer registers a governance plan that defines the agent's spending authority. + The plan sets a per-transaction threshold below the intended media buy amount. + This ensures the governance check will trigger an escalation when the buy exceeds + the agent's authority. + + steps: + - id: sync_plans + title: "Register a governance plan with agent-limited authority" + narrative: | + The buyer's governance agent registers a plan with spending authority limits. + The plan sets a per-transaction threshold of $20K via reallocation_threshold + on the budget. Any media buy above this amount requires human approval. + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + comply_scenario: campaign_governance + stateful: true + expected: | + Acknowledge the governance plan: + - plan_id: identifier for this governance plan + - budget.reallocation_threshold: spending limit before re-evaluation + - human_review_required: set when the plan mandates escalation + + sample_request: + idempotency_key: "media-buy-governance-escalation-sync-plans-v1" + plans: + - plan_id: "gov_acme_q2_2027" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 outdoor lifestyle campaign — display and video, Adults 25-54, US" + budget: + total: 50000 + currency: "USD" + reallocation_threshold: 20000 + flight: + start: "2027-04-01T00:00:00Z" + end: "2027-06-30T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "weekly_reporting_over_10k" + enforcement: "must" + policy: "Weekly reporting required for buys over $10K." + - policy_id: "seller_concentration_cap" + enforcement: "must" + policy: "No single-seller concentration above 60% of total budget." + + context: + correlation_id: "media_buy_governance_escalation--sync_plans" + context_outputs: + - name: plan_id + path: 'plans[0].plan_id' + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--sync_plans" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "plans[0].plan_id" + description: "Governance agent assigns plan_id — must be echoed in check_governance" + - id: discover_products + title: "Product discovery" + narrative: | + The buyer sends a brief to discover products. The products returned will exceed + the governance agent's spending authority when assembled into a media buy. + + steps: + - id: get_products_brief + title: "Send a brief" + narrative: | + The buyer describes what they want. Your platform returns products with + pricing that, when combined, will exceed the $20K per-transaction threshold + set in the governance plan. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief with pricing that totals above $20K: + - product_id, name, description + - delivery_type: guaranteed or non_guaranteed + - pricing_models with CPM and budget recommendations + - forecast: estimated delivery + + sample_request: + buying_mode: "brief" + brief: "Premium CTV and video on sports publishers. Q2 flight, $50K budget. Adults 25-54, US." + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "media_buy_governance_escalation--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: governance_check_denied + title: "Governance check — denied" + narrative: | + Before creating the media buy, the buyer's governance agent runs a pre-buy check. + The proposed buy totals $50K, which exceeds the agent's $20K per-transaction + authority. The governance agent denies the buy with a must-severity finding and + returns escalation instructions for human review. + + steps: + - id: check_governance_denied + title: "Pre-buy governance check (denied)" + narrative: | + The buyer calls check_governance with the proposed media buy binding. The + governance agent evaluates the buy against the registered plan and finds that + the total exceeds the agent's spending authority. The check returns denied + with a must-severity finding and instructions for escalating to a human. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_spend_authority/denied + stateful: true + expected: | + Return a denied governance decision: + - decision: denied + - findings: array with at least one must-severity finding + - severity: must + - code: SPENDING_AUTHORITY_EXCEEDED + - message: explains the agent's authority limit and how much the buy exceeds it + - escalation: instructions for human review + - governance_context: token/ID the buyer passes to the next check after escalation + - plan_id: the governance plan that triggered the denial + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + tool: "create_media_buy" + payload: + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_check_governance_denied_payload" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2027-01-01T00:00:00Z" + end_time: "2027-03-31T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + + context: + correlation_id: "media_buy_governance_escalation--check_governance_denied" + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains a governance decision" + - check: field_present + path: "findings" + description: "Response contains findings explaining the denial" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--check_governance_denied" + description: "Context correlation_id returned unchanged" + - id: human_escalation + title: "Human escalation — conditional approval" + narrative: | + The governance denial triggers human escalation. A human reviewer at the agency + reviews the proposed buy, the denial reason, and the governance plan. The human + approves the buy with conditions — for example, requiring weekly reporting. The + buyer calls check_governance again with the human's approval, and this time + the check returns approved with conditions. + + steps: + - id: check_governance_approved + title: "Re-check governance after human approval (approved with conditions)" + narrative: | + After the human reviewer approves the buy, the buyer calls check_governance + again with the governance_context from the prior denial and the human's + approval. The governance agent returns approved with conditions that the + buyer must honor during the campaign. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_spend_authority + stateful: true + expected: | + Return an approved governance decision with conditions: + - decision: approved + - conditions: array of requirements the buyer must honor + - e.g., "Weekly delivery reporting required" + - e.g., "Human review required for any budget increase" + - governance_context: updated token the buyer passes to create_media_buy + - approved_by: identifier of the human who approved + - approved_at: timestamp of approval + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + governance_context: "gov_ctx_acme_q2_escalated" + tool: "create_media_buy" + payload: + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_check_governance_approved_payload" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2027-01-01T00:00:00Z" + end_time: "2027-03-31T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + + context: + correlation_id: "media_buy_governance_escalation--check_governance_approved" + context_outputs: + - name: check_id + path: 'check_id' + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains an approved governance decision" + - check: field_present + path: "conditions" + description: "Approval includes conditions" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--check_governance_approved" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "check_id" + description: "Check ID for audit trail — must be echoed in report_plan_outcome" + - id: create_buy_with_governance + title: "Create media buy with governance context" + narrative: | + The buyer creates the media buy, passing the governance_context from the approved + governance check. The seller's platform verifies the governance approval before + confirming the buy. + + steps: + - id: create_media_buy + title: "Create a media buy with governance approval" + narrative: | + The buyer creates the media buy with the governance_context token from the + approved check. The seller's platform validates the governance approval and + confirms the buy. Without a valid governance_context, the platform would + reject the buy because the governance agent is registered for this account. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Confirm the media buy with governance approval: + - media_buy_id: your platform's identifier + - status: active + - confirmed_at: timestamp + - governance_context: echoed back confirming governance was validated + - packages: confirmed line items + - valid_actions: creative sync, get_delivery, etc. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + governance_context: "gov_ctx_acme_q2_approved" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_guaranteed" + creative_assignments: + - creative_id: "video_30s_trail_pro" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + creative_assignments: + - creative_id: "video_15s_trail_pro" + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_create_buy_with_governance_create_media_buy" + context: + correlation_id: "media_buy_governance_escalation--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: report_outcome + title: "Report governance outcome" + narrative: | + After the media buy is created, the buyer reports the outcome back to the + governance agent. This closes the governance loop by linking the actual media + buy to the governance check that authorized it. + + steps: + - id: report_plan_outcome + title: "Report media buy outcome to governance" + narrative: | + The buyer reports the media buy creation back to the governance agent, + linking the media_buy_id to the governance check. This enables the governance + agent to track what was actually purchased against what was approved. + task: report_plan_outcome + schema_ref: "governance/report-plan-outcome-request.json" + response_schema_ref: "governance/report-plan-outcome-response.json" + doc_ref: "/governance/campaign/tasks/report_plan_outcome" + comply_scenario: campaign_governance + stateful: true + expected: | + Acknowledge the outcome report: + - outcome_id: identifier for this outcome record + - plan_id: the governance plan + - media_buy_id: the buy that was created + - governance_context: the approval that authorized it + - status: recorded + + sample_request: + plan_id: "$context.plan_id" + governance_context: "gov_ctx_acme_q2_approved" + outcome: "completed" + seller_response: + seller_reference: "$context.media_buy_id" + committed_budget: 50000 + packages: + - package_id: "pkg_sports_ctv_q2" + committed_budget: 30000 + - package_id: "pkg_outdoor_video_q2" + committed_budget: 20000 + + idempotency_key: "$generate:uuid_v4#media_buy_governance_escalation_report_outcome_report_plan_outcome" + context: + correlation_id: "media_buy_governance_escalation--report_plan_outcome" + validations: + - check: response_schema + description: "Response matches report-plan-outcome-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--report_plan_outcome" + description: "Context correlation_id returned unchanged" + - id: audit_trail + title: "Governance audit trail" + narrative: | + The buyer (or an auditor) retrieves the full audit trail for the governance + plan. This shows every decision in the chain: the initial denial, the human + escalation, the conditional approval, the media buy creation, and the outcome + report. The audit trail provides accountability for automated spending decisions. + + steps: + - id: get_plan_audit_logs + title: "Retrieve the full governance audit trail" + narrative: | + The buyer requests the audit log for the governance plan. The log shows + every governance event: check requests, denials, escalations, approvals, + and outcome reports. Each entry has a timestamp, actor, and decision. + task: get_plan_audit_logs + schema_ref: "governance/get-plan-audit-logs-request.json" + response_schema_ref: "governance/get-plan-audit-logs-response.json" + doc_ref: "/governance/campaign/tasks/get_plan_audit_logs" + comply_scenario: campaign_governance + stateful: false + expected: | + Return the complete audit trail for the governance plan: + - plan_id: the governance plan + - entries: ordered list of governance events, including: + 1. Plan registered with $20K per-transaction threshold + 2. Governance check denied — spending authority exceeded ($50K > $20K) + 3. Human escalation initiated + 4. Human approved with conditions (weekly reporting, budget increase review) + 5. Media buy created with governance approval + 6. Outcome reported linking media_buy_id to governance context + - Each entry includes: timestamp, event_type, actor, decision, details + + sample_request: + plan_ids: + - "$context.plan_id" + + context: + correlation_id: "media_buy_governance_escalation--get_plan_audit_logs" + validations: + - check: response_schema + description: "Response matches get-plan-audit-logs-response.json schema" + - check: field_present + path: "plans" + description: "Response contains audit log entries" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_governance_escalation--get_plan_audit_logs" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/creative-reception.yaml b/dist/compliance/3.0.9/protocols/media-buy/creative-reception.yaml new file mode 100644 index 0000000000..56b4b5fb59 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/creative-reception.yaml @@ -0,0 +1,247 @@ +id: creative_sales_agent +version: "1.0.0" +title: "Sales agent with creative capabilities" +category: creative_sales_agent +summary: "Stateful sales agent that accepts pushed creative assets and renders them in its environment." +track: creative +required_tools: + - sync_creatives + +narrative: | + You run a publisher platform, retail media network, or other sell-side system that + accepts creative assets from buyers. The buyer pushes assets or catalog items to your + platform, and you render them in your environment. + + Your agent is stateful: buyers push creatives to you via sync_creatives, and you + persist them for rendering. This is where catalogs get interesting — the buyer might + push product feeds (flights, hotels, retail products) that your platform renders as + native ads. + + This storyboard walks through the push-and-preview flow from the buyer's perspective. + +agent: + interaction_model: stateful_push + capabilities: + - has_creative_library + examples: + - "Publisher platforms" + - "Retail media networks" + - "Native ad platforms" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The buyer has creative assets (images, catalog feeds, or ad tags) ready to push. + The test kit provides sample assets compatible with common publisher formats. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_sales_agent--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_sales_agent--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: discover_accepted_formats + title: "Discover accepted formats" + narrative: | + The buyer first needs to know what creative formats your platform accepts. + For a publisher, this includes your native ad formats, display placements, + and any custom units. For retail media, this might include product listing + formats or sponsored product cards. + + steps: + - id: list_formats + title: "Discover accepted creative formats" + narrative: | + The buyer asks: "What creative formats does your platform accept?" Your + platform returns the formats you support — native post formats, display + units, video slots, or product listing formats. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_sync + stateful: false + expected: | + Return the creative formats your platform accepts. Each format should define: + - Asset requirements (what the buyer needs to provide) + - Render dimensions + - Any catalog requirements (for product-feed formats) + + - id: push_creatives + title: "Push creative assets" + narrative: | + The buyer pushes their creative assets to your platform. This could be: + - Standard display assets (images, HTML tags) + - Catalog items (product feeds, flight listings, hotel inventory) + - Native ad content (headlines, descriptions, images) + + Your platform validates the assets against your format specs and stores them. + + steps: + - id: sync_creatives + title: "Push creatives to the platform" + narrative: | + The buyer uploads creative assets to your platform. For standard ads, this + is images and copy. For catalog-driven formats, this is a product feed or + set of catalog items. Your platform validates each creative against the + format's asset requirements and returns a per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept the creatives, validate against format specifications, and return: + - Per-creative action (created or updated) + - Per-creative status (accepted, pending_review, rejected) + - Platform-assigned IDs if applicable + - Validation errors for rejected creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme_summer_native_001" + name: "Acme Summer Sale — native 2026" + format_id: + agent_url: "https://your-platform.example.com" + id: "native_post" + assets: + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-master.jpg" + width: 1200 + height: 628 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + + idempotency_key: "$generate:uuid_v4#creative_sales_agent_push_creatives_sync_creatives" + context: + correlation_id: "creative_sales_agent--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_sales_agent--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: preview + title: "Preview pushed creatives" + narrative: | + After pushing assets, the buyer wants to see how their creatives will render + in your platform's environment. For a publisher, this shows the ad in the + publication's native chrome — with engagement buttons, community badges, and + platform-specific styling that the buyer can't preview elsewhere. + + steps: + - id: preview_synced + title: "Preview a pushed creative" + narrative: | + The buyer asks to see how a synced creative will look in your environment. + Your platform renders the creative with its native chrome — the surrounding + UI, engagement buttons, and platform-specific styling. + task: preview_creative + schema_ref: "creative/preview-creative-request.json" + response_schema_ref: "creative/preview-creative-response.json" + doc_ref: "/creative/task-reference/preview_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a preview showing the creative in your platform's environment. + The preview should include your platform's native chrome — not just the + raw assets, but how they'll actually appear to users. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + request_type: "single" + creative_manifest: + creative_id: "acme_summer_native_001" + format_id: + agent_url: "https://your-platform.example.com" + id: "native_post" + assets: + headline: + asset_type: "text" + content: "Summer Sale — 40% Off" + body: + asset_type: "text" + content: "Top-rated outdoor gear. This weekend only." + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero.jpg" + width: 1200 + height: 628 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + output_format: "url" + quality: "draft" + + context: + correlation_id: "creative_sales_agent--preview_synced" + validations: + - check: response_schema + description: "Response matches preview-creative-response.json schema" + - check: field_present + path: "previews[0].renders[0].preview_url" + description: "Preview includes a renderable URL" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_sales_agent--preview_synced" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/index.yaml b/dist/compliance/3.0.9/protocols/media-buy/index.yaml new file mode 100644 index 0000000000..38c421a694 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/index.yaml @@ -0,0 +1,769 @@ +id: media_buy_seller +version: "1.0.0" +title: "Media buy seller agent" +category: media_buy_seller +summary: "Seller agent that receives briefs, returns products, accepts media buys, and reports delivery." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/refine_products + - media_buy_seller/delivery_reporting + - media_buy_seller/measurement_terms_rejected + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/inventory_list_targeting + - media_buy_seller/inventory_list_no_match + - media_buy_seller/invalid_transitions + - media_buy_seller/creative_fate_after_cancellation + - media_buy_seller/create_media_buy_async + +narrative: | + You run a sell-side platform — a publisher, SSP, retail media network, or any system that + sells advertising inventory. A buyer agent connects to discover your products, create + media buys, sync creatives, and monitor delivery. Your agent handles the full lifecycle + from brief to reporting. + + This storyboard walks through the core media buy flow: account setup, product discovery, + buy creation, creative sync, and delivery monitoring. + + Governance integration, product refinement, and proposal finalization are tested by + required scenarios that run alongside this storyboard. See requires_scenarios for the + full set of seller behaviors validated. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - supports_guaranteed + - supports_non_guaranteed + examples: + - "Yahoo" + - "Retail media networks" + - "Publisher platforms" + - "SSPs" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials for account setup. + The test kit provides a sample brand (Acme Outdoor) with campaign parameters + suitable for testing the full media buy flow. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_preroll_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_30s" + - product_id: "lifestyle_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + pricing_options: + - product_id: "sports_preroll_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 22.0 + - product_id: "lifestyle_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "media_buy_seller--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--get_capabilities" + description: "Context correlation_id returned unchanged" + + - id: account_setup + title: "Account setup" + narrative: | + Before buying anything, the buyer establishes an account relationship with + your platform. This is the handshake: the buyer tells you which brand and + agency (operator) they represent, and you return an account ID, status, and + any setup requirements. + + Some platforms approve accounts instantly. Others require human review — the + buyer gets back a pending_approval status and a URL to complete setup. The + buyer polls or waits for a webhook until the account is active. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. This is + the first call in any new relationship. Your platform validates the request, + provisions the account, and returns its status. + + If your platform requires manual approval (credit checks, sales team review), + return the account with status pending_approval and account.setup.url populated. + The buyer directs the human to that URL to complete setup, then polls list_accounts + until the account status changes to active. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + # No TestScenario exists for account setup + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier for this relationship + - action: created or updated + - status: active (instant approval) or pending_approval (requires human review) + - account_scope: operator, brand, operator_brand, or agent + - setup.url and setup.message: populated on the account when status is pending_approval (where the human completes onboarding) + - rate_card: pricing tiers if applicable + - payment_terms: net_30, prepay, etc. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_account_setup_sync_accounts" + context: + correlation_id: "media_buy_seller--sync_accounts" + + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + - check: field_present + path: "accounts[0].status" + description: "Account has a status (active or pending_approval)" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--sync_accounts" + description: "Context correlation_id returned unchanged" + + - id: governance_setup + title: "Governance agent registration" + narrative: | + The buyer registers their governance agent with your platform. This tells your + platform where to call check_governance before confirming media buys. + + steps: + - id: sync_governance + title: "Register governance agents" + narrative: | + The buyer tells your platform: "Before you confirm any media buy for this + account, call this governance agent to validate it." + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + requires_tool: sync_governance + stateful: true + expected: | + Acknowledge the governance agents. Your platform should: + - Store the governance agent URLs for the specified accounts + - Return confirmation that agents were registered + + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "https://governance.pinnacle-agency.example" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_setup_sync_governance" + context: + correlation_id: "media_buy_seller--sync_governance" + ext: + test_platform: + governance_tier: "standard" + + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--sync_governance" + description: "Context correlation_id returned unchanged" + + - id: product_discovery + title: "Product discovery" + narrative: | + The buyer sends a natural-language brief describing what they want to buy. + Your platform interprets the brief against your inventory and returns products — + structured representations of what you can sell, with pricing, delivery forecasts, + targeting options, and creative requirements. + + This is where seller differentiation happens. The same brief sent to three sellers + produces three different product sets. Your AI interprets "premium video on sports + and outdoor lifestyle" against your specific inventory, audiences, and pricing. + + steps: + - id: get_products_brief + title: "Send a brief" + narrative: | + The buyer describes what they want in natural language. Your platform returns + products that match the brief, including pricing options, delivery forecasts, + and creative format requirements. + + This call may take up to 60 seconds — your platform is running AI inference + against your inventory catalog. If the brief is ambiguous, you can return + input-required to ask clarifying questions before producing results. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. Each product should include: + - product_id: unique identifier + - name and description + - delivery_type: guaranteed or non_guaranteed + - pricing_models: available pricing options (CPM, CPC, etc.) + - forecast: estimated impressions, reach + - creative_format_ids: what creative formats this product requires + - targeting: what audiences or contexts this product reaches + + Optionally return proposals — curated media plans that bundle products + with budget allocations the buyer can accept or refine. + + If the brief is unclear, return input-required with clarifying questions. + + sample_request: + buying_mode: "brief" + brief: "Premium video inventory on sports and outdoor lifestyle publishers. Q2 flight, $50K budget. Adults 25-54, US and Canada." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context: + correlation_id: "media_buy_seller--get_products_brief" + + context_outputs: + - name: product_format_id + path: 'products[0].format_ids[0]' + - name: product_id + path: 'products[0].product_id' + - name: pricing_option_id + path: 'products[0].pricing_options[0].pricing_option_id' + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + - check: field_present + path: "products[0].delivery_type" + description: "Each product declares guaranteed or non_guaranteed delivery" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--get_products_brief" + description: "Context correlation_id returned unchanged" + + - check: field_present + path: "products[0].publisher_properties" + description: "Products include publisher_properties" + - id: list_formats_integrity + title: "Verify format_ids on products resolve to real formats" + narrative: | + The buyer asks the sales agent to filter `list_creative_formats` by + `products[0].format_ids[0]`. The sales agent MUST return the format + it advertised on its own product — whether it hosts that format + directly or proxies to the creative agent named in + `format_ids[0].agent_url`. An empty `formats[]` means the sales + agent's product catalog references a format that does not resolve — + a stale or typo'd entry that would have failed silently at + `sync_creatives` after the media buy was already committed. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + The sales agent resolves `products[0].format_ids[0]` and returns + the matching format entry: + - formats[] contains at least one entry + - formats[0].format_id matches the id captured from get_products + + An empty formats[] means the sales agent's product catalog references + a format that does not resolve — a common production failure mode + when creative agents deprecate formats without sellers updating + their product catalog. + sample_request: + format_ids: + - "$context.product_format_id" + context: + correlation_id: "media_buy_seller--list_formats_integrity" + # The @adcp/client `list_creative_formats` request builder up + # through 5.10.0 (the currently-published release) returns `{}`, + # and the runner's post-builder merge only forwards envelope + # fields (context / ext / idempotency_key / + # push_notification_config) from sample_request — so `format_ids` + # above never reaches the wire and the seller answers with its + # full format catalog. `context_inputs` is applied after the + # builder runs, so this injects the captured `product_format_id` + # (the `{agent_url, id}` object from `products[0].format_ids[0]`) + # at `format_ids[0]` and lets the round-trip invariant actually + # grade. Drop once we bump past the @adcp/client release that + # ships adcontextprotocol/adcp-client#789. + context_inputs: + - key: product_format_id + inject_at: "format_ids[0]" + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats[0]" + description: "Sales agent resolves the format_id — products[0].format_ids[0] exists in the catalog" + - check: field_value + path: "formats[0].format_id" + value: "$context.product_format_id" + description: "Returned format_id round-trips verbatim — the agent cannot substitute a different format in response to the filter" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--list_formats_integrity" + description: "Context correlation_id returned unchanged" + - id: create_buy + title: "Create the media buy" + narrative: | + The buyer is satisfied with the products and creates a media buy. This is the + equivalent of signing an IO — the buyer commits to specific products, budgets, + and flight dates. + + This operation may be synchronous (completed immediately), short-async (working + while your platform processes), or long-async (task stays submitted while a human + signs the IO internally; task completion delivers the final media_buy_id). There + is no "pending_approval" media buy status — IO review is modelled at the task + layer, not as a MediaBuy.status value. + + If the buyer registered governance agents in Phase 2, your platform calls + check_governance before confirming the buy. The governance agent validates budget + authority, brand safety, and compliance. If governance denies the buy, return the + denial — don't override it. + + steps: + - id: create_media_buy + title: "Create a media buy" + narrative: | + The buyer commits to specific products with budgets and flight dates. Your + platform validates the request, optionally calls governance, and either confirms + the buy or sends it through an approval workflow. + + Two creation modes: + - Manual: buyer specifies packages array with explicit product selections + - Proposal: buyer passes a proposal_id from get_products to execute a proposal + + The response status tells the buyer what happens next: + - completed: buy is active and live + - working: your platform is processing (poll or wait for webhook) + - submitted: long-running async — approval workflow, IO signing, etc. + - input-required: need more information (budget clarification, approval) + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Process the media buy request and return one of: + + Synchronous (completed): + - media_buy_id: your platform's identifier + - status: active or pending_creatives + - packages: line items with pricing + - confirmed_at: timestamp + - valid_actions: what the buyer can do next + + Asynchronous (working): + - percentage: 0-100 completion + - current_step: what's happening ("Validating inventory", "Checking governance") + + Async with human approval (submitted): + - task_id / taskId: handle the buyer polls or receives webhooks on + - message (optional): explanation of what the seller is waiting on (e.g., "Awaiting IO signature from sales team; typical turnaround 2–4 hours") + - No media_buy_id yet — it is issued on task completion + - Seller-side IO signing is modelled here (task stays submitted until signed). Do not emit a "pending_approval" media buy status — that value is not in MediaBuy.status + + Needs input (input-required): + - reason: APPROVAL_REQUIRED, BUDGET_EXCEEDS_LIMIT, CLARIFICATION_NEEDED + - errors: what needs to be resolved + - Used only when the seller needs the buyer to respond (e.g., confirm a budget). If the blocker is account-level (credit application, funding), the resolution path is list_accounts / sync_accounts, where account.setup.url surfaces on the pending_approval account + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_preroll_q2" + budget: 25000 + pricing_option_id: "cpm_guaranteed" + creative_assignments: + - creative_id: "video_30s_trail_pro" + - product_id: "lifestyle_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + push_notification_config: + url: "https://buyer.example/webhooks/adcp" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "media-buy-seller-webhook-secret-token" + idempotency_key: "$generate:uuid_v4#media_buy_seller_create_buy_create_media_buy" + context: + correlation_id: "media_buy_seller--create_media_buy" + + context_outputs: + - name: media_buy_id + path: 'media_buy_id' + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--create_media_buy" + description: "Context correlation_id returned unchanged" + + - id: check_buy_status + title: "Check media buy status" + narrative: | + Once create_media_buy has completed and a media_buy_id is issued, the buyer + calls get_media_buys to read current state — pending_creatives, pending_start, + active, paused, completed, rejected, or canceled. + + While the create_media_buy task is still submitted (e.g., waiting on internal + IO signing), the media buy does not exist as a queryable MediaBuy yet. IO + review is tracked at the task layer, not as a MediaBuy.status value. The buyer + polls tasks/get or waits on the webhook until the task completes and a + media_buy_id is delivered. + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the current state of the media buy: + - media_buy_id: matches what was returned from create_media_buy + - status: pending_creatives, pending_start, active, paused, completed, rejected, canceled + - packages: line items with current delivery status + - valid_actions: what operations are available in this state + + If pending_creatives: + - Include message explaining what creatives are needed + - valid_actions should include sync_creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + context: + correlation_id: "media_buy_seller--check_buy_status" + + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_present + path: "media_buys[0].status" + description: "Each media buy has a status" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--check_buy_status" + description: "Context correlation_id returned unchanged" + + - id: creative_sync + title: "Creative sync" + narrative: | + With the media buy confirmed, the buyer syncs creative assets to your platform. + Each package in the buy has creative format requirements — the buyer discovered + these during product discovery and now pushes matching assets. + + The format_ids used in sync_creatives must match those returned by your platform + in get_products and list_creative_formats. If your platform returns a format_id + in a product but rejects it when the buyer echoes it back in sync_creatives, the + buyer cannot fulfill the creative requirements. This is a common compliance failure. + + Your platform validates each creative against the format specs and returns + per-creative status. If assets need review or transcoding, the operation may + go async. + + steps: + - id: list_formats + title: "Check creative format requirements" + narrative: | + The buyer confirms what creative formats the confirmed packages require. + Your platform returns format specs with asset requirements, dimensions, + and constraints. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return creative formats your platform accepts. Each format should define: + - format_id with your agent_url and unique id + - Asset requirements (dimensions, file sizes, mime types) + - Render dimensions + + sample_request: + context: + correlation_id: "media_buy_seller--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--list_formats" + description: "Context correlation_id returned unchanged" + - check: refs_resolve + description: | + Every format_id returned on products resolves to a format in this + agent's list_creative_formats. Broken references here surface as a + grading failure instead of a silent mismatch that only breaks at + sync_creatives time, after the buy is committed. Third-party + format_ids (agent_url pointing at a different creative agent) + can't be verified without calling that agent and are reported as + observations rather than failures. + source: + from: context + path: "products[*].format_ids[*]" + target: + from: current_step + path: "formats[*].format_id" + match_keys: [agent_url, id] + scope: + key: agent_url + equals: $agent_url + on_out_of_scope: warn + + - id: sync_creatives + title: "Push creative assets (format_id roundtrip)" + narrative: | + The buyer uploads creative assets for the confirmed packages. The first + creative uses $context.product_format_id — the exact format_id object + returned by get_products. This is the roundtrip test: the seller must + accept its own format_ids without modification. If the seller's validation + rejects a format_id that it returned in products, this step fails. + + Your platform validates each creative against the format specs, transcodes + if necessary, and returns per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept and validate creatives: + - Per-creative action: created or updated + - Per-creative status: accepted, pending_review, or rejected + - Validation errors for rejected creatives + - Platform-assigned IDs if applicable + + The first creative uses a format_id extracted from get_products. + If this is rejected, your format_ids do not roundtrip correctly. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "video_30s_trail_pro" + name: "Trail Pro 3000 - 30s CTV Spot" + format_id: "$context.product_format_id" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + mime_type: "video/mp4" + - creative_id: "display_trail_pro_300x250" + name: "Trail Pro 3000 - Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + width: 300 + height: 250 + mime_type: "image/png" + idempotency_key: "$generate:uuid_v4#media_buy_seller_creative_sync_sync_creatives" + context: + correlation_id: "media_buy_seller--sync_creatives" + + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--sync_creatives" + description: "Context correlation_id returned unchanged" + + - id: delivery_monitoring + title: "Delivery and reporting" + narrative: | + The campaign is live. The buyer monitors delivery through two tasks: + get_media_buys for status and get_media_buy_delivery for performance metrics. + + Your platform reports in a standard format — impressions, clicks, spend, + completion rates — so the buyer can compare delivery across multiple sellers + in a single view. + + steps: + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data for the active media buy. Your platform + returns performance metrics — impressions, clicks, spend, completion rates — + broken down by package and optionally by day. + + This call may take up to 60 seconds as your platform aggregates reporting + data across delivery systems. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the media buy: + - Per-package delivery: impressions, clicks, spend, completion rates + - Daily breakdown if requested (include_package_daily_breakdown) + - Pacing information: on track, ahead, behind + - Budget utilization: spent vs. committed + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + context: + correlation_id: "media_buy_seller--get_delivery" + + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_seller--get_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/create_media_buy_async.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/create_media_buy_async.yaml new file mode 100644 index 0000000000..c2ba4c3ec0 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/create_media_buy_async.yaml @@ -0,0 +1,232 @@ +id: media_buy_seller/create_media_buy_async +version: "1.0.0" +title: "Seller returns submitted task envelope when create_media_buy goes async" +category: media_buy_seller +summary: "Verifies the AdCP-payload wire shape of the submitted-arm response from create_media_buy: status='submitted', task_id present, no media_buy_id and no packages on the envelope." +track: media_buy +required_tools: + - create_media_buy + - comply_test_controller + +narrative: | + When create_media_buy cannot confirm the buy synchronously — e.g., the seller is + routing the request through IO signing, batch processing, or any out-of-band human + workflow — the task layer carries the result, not the response. The seller emits the + submitted task envelope: status='submitted', task_id present, no media_buy_id, no + packages. The buyer then polls tasks/get with task_id (or waits for a webhook) until + the task completes and the media_buy_id arrives on the completion artifact. + + This scenario anchors the AdCP-payload-level invariant for that envelope. Three things + matter and are easy to regress: + + 1. status MUST be the literal string 'submitted' (not 'pending', not a MediaBuyStatus + value, not omitted) + 2. task_id MUST be present at the top of the payload, snake_case (A2A adapters MAY + surface it as taskId on the wire, but the payload field emitted by the agent is + task_id) + 3. media_buy_id and packages MUST NOT appear on the envelope — they land on the task's + completion artifact, not here. Sellers that return media_buy_id with status='submitted' + break the buyer's polling contract; buyers cannot tell whether the buy is queued or + confirmed. + + Determinism. The submitted arm is implementation-dependent — most sellers route most + buys synchronously. To make this storyboard runnable across implementations, the test + harness uses comply_test_controller force_create_media_buy_arm to drive the next + create_media_buy call into the submitted arm. The directive is keyed to the caller's + authenticated sandbox account (account + principal pair); sellers that do not implement + the controller scenario return UNKNOWN_SCENARIO and the runner grades this storyboard + not_applicable rather than failed. + + Round-trip integrity. The deterministic task_id is captured from the controller + response and reused as the expected task_id on the create_media_buy assertion, so the + storyboard catches sellers that fabricate a fresh task_id instead of honoring the + registered directive. + + Out of scope (by design). Transport-level wire-shape assertions — A2A Task.state and + artifact.metadata.adcp_task_id placement, MCP structuredContent envelope details — are + runner-side concerns, not storyboard assertions. The runner exercises this scenario + against both transports and probes the transport envelope independently. See + adcp-client#904 for the runner-side probes; this storyboard provides the deterministic + driver. + + The submitted → completed transition (forcing the task to resolve and asserting the + completion artifact carries media_buy_id) is deferred to a follow-up scenario. It needs + a force_task_completion controller scenario that does not exist yet. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_test_controller + examples: + - "Sellers that route some create_media_buy calls through IO signing or batch processing" + - "Any seller exposing comply_test_controller in sandbox mode" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller exposes comply_test_controller in sandbox mode and supports + force_create_media_buy_arm. The directive is keyed to the caller's + authenticated sandbox account; without controller support, the storyboard + grades not_applicable — sellers cannot be deterministically driven into + the submitted arm by buyer-initiated requests alone. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "async_signed_io_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_30s" + pricing_options: + - product_id: "async_signed_io_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 18.0 + +phases: + - id: force_submitted_arm + title: "Drive next create_media_buy into submitted arm" + narrative: | + Tell the controller that the next create_media_buy call from the caller's + authenticated sandbox account should return the submitted task envelope. + The controller stores the directive against the (account, principal) pair + and consumes it on the next create_media_buy call. Sellers that do not + implement force_create_media_buy_arm return UNKNOWN_SCENARIO and the + runner grades this storyboard not_applicable. + + steps: + - id: force_arm_submitted + title: "Force submitted arm on next create_media_buy" + requires_tool: comply_test_controller + narrative: | + Direct the controller to return the submitted envelope with a + deterministic task_id on the buyer's next create_media_buy call. The + message field is set to a representative IO-signing explanation so + buyers exercising prompt-injection sanitization on submitted.message + have a stable string to assert against. + task: comply_test_controller + comply_scenario: create_media_buy + stateful: true + context_outputs: + - name: forced_task_id + path: "forced.task_id" + expected: | + Return a successful directive: + - success: true + - forced.arm: submitted + - forced.task_id: deterministic task handle the next create_media_buy will return + + sample_request: + scenario: "force_create_media_buy_arm" + params: + arm: "submitted" + task_id: "task_async_signed_io_q2" + message: "Awaiting IO signature from sales team; typical turnaround 2–4 hours" + context: + correlation_id: "create_media_buy_async--force_arm_submitted" + validations: + - check: field_value + path: "success" + allowed_values: [true] + description: "Controller accepts the directive" + - check: field_value + path: "forced.arm" + value: "submitted" + description: "Controller echoes the forced arm" + - check: field_present + path: "forced.task_id" + description: "Controller echoes the deterministic task_id" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "create_media_buy_async--force_arm_submitted" + description: "Context correlation_id returned unchanged" + + - id: submitted_arm_response + title: "create_media_buy returns submitted task envelope" + narrative: | + The buyer makes a normal create_media_buy call. Because the controller + registered a directive against this sandbox account, the seller MUST emit + the submitted task envelope: status='submitted', task_id matching the + forced value, no media_buy_id, no packages. + + The response_schema check carries the absence invariant — the submitted + arm in create-media-buy-response.json has not.anyOf clauses for both + media_buy_id and packages, so a seller that emits either under + status='submitted' fails schema validation. The explicit field_value + check on status pins the literal 'submitted' value, since a malformed + seller might omit the discriminator and still satisfy the parent oneOf + via the error or success branch. The task_id check uses the captured + $context.forced_task_id so the storyboard fails if the seller ignores + the registered directive and fabricates its own task_id. + + steps: + - id: create_media_buy_submitted + title: "Call create_media_buy and observe the submitted envelope" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Return the submitted task envelope: + - status: 'submitted' (literal, not a MediaBuyStatus value) + - task_id: matches the value registered by force_create_media_buy_arm + - no media_buy_id (issued on task completion, not here) + - no packages (issued on task completion, not here) + - message (optional): seller's explanation of the wait + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + start_time: "2026-07-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "async_signed_io_q2" + budget: 30000 + pricing_option_id: "cpm_guaranteed" + push_notification_config: + url: "https://buyer.example/webhooks/adcp" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "media-buy-seller-webhook-secret-token" + idempotency_key: "$generate:uuid_v4#media_buy_seller_create_media_buy_async_submitted_arm_response_create_media_buy_submitted" + context: + correlation_id: "create_media_buy_async--create_media_buy_submitted" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json — submitted-arm not.required clauses block media_buy_id and packages" + - check: field_value + path: "status" + value: "submitted" + description: "Status is the literal 'submitted' task-status value, not a MediaBuyStatus" + - check: field_present + path: "task_id" + description: "task_id is present at the top of the envelope (snake_case payload field, even when the A2A adapter surfaces it as taskId on the wire)" + - check: field_value + path: "task_id" + value: "$context.forced_task_id" + description: "task_id matches the captured value from the controller directive — sellers that fabricate a fresh task_id instead of honoring the registered one fail here" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "create_media_buy_async--create_media_buy_submitted" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml new file mode 100644 index 0000000000..92d747dcd3 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml @@ -0,0 +1,414 @@ +id: media_buy_seller/creative_fate_after_cancellation +version: "1.0.0" +title: "Creative lifecycle is decoupled from media buy lifecycle" +category: media_buy_seller +summary: "Validates that canceling a media buy releases package-creative assignments but leaves the underlying creatives in the library with their review state intact, and that buyers can reuse released creatives on a new buy." +track: media_buy +required_tools: + - get_products + - create_media_buy + - update_media_buy + - sync_creatives + - list_creatives + +narrative: | + Per the creative library model (docs/creative/creative-libraries#creative-state-and-assignment-state-are-separate) + and the Media Buy State Transitions rule, canceling or rejecting a media buy + releases its package-creative assignments but leaves the creatives themselves + in the library. The creatives remain reusable by `creative_id` in a subsequent + `create_media_buy` or `sync_creatives` call, and a seller MUST NOT implicitly + reject a creative because its containing buy was canceled — a creative + rejection MUST be a deliberate review decision with its own `rejection_reason`. + + This scenario walks the whole flow end-to-end: + + 1. Create a buy, sync a creative, assign it to a package (setup) + 2. Confirm the creative is in the library with a non-terminal review state (pre-cancel) + 3. Cancel the buy + 4. Confirm the creative is STILL in the library with its review state intact — + not archived, not auto-rejected as a side effect of the cancel + 5. Reuse the same `creative_id` on a new buy via `sync_creatives` assignment + + A seller that evaporates library creatives on buy cancellation, or that flips + `status: rejected` on creatives whose only assignment was released by a cancel, + fails this scenario. A seller that correctly decouples the two lifecycles + passes. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any media buy seller with a creative library" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports create_media_buy, update_media_buy with cancellation, sync_creatives, + and list_creatives. Catalog-driven sellers and sellers without a creative library + grade this scenario not_applicable (creative lifecycle assumes a library surface). + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Create a buy and assign a creative" + narrative: | + Discover a product, create a media buy, sync one creative with an inline + assignment to the buy's first package. Capture the media_buy_id, package_id, + and creative_id for subsequent phases. + + steps: + - id: get_products_brief + title: "Discover a product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product with a pricing option and at least one format_id. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content for creative-reuse testing." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context: + correlation_id: "creative_fate--get_products" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + - path: "products[0].format_ids[0].agent_url" + key: "format_agent_url" + - path: "products[0].format_ids[0].id" + key: "format_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Product exposes at least one format_id for creative sync" + + - id: create_buy + title: "Create the initial media buy" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Media buy created with media_buy_id and at least one package. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_create_buy" + start_time: "2026-09-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 5000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "creative_fate--create_buy" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns media_buy_id" + - check: field_present + path: "packages[0].package_id" + description: "Seller returns package_id for the newly created package" + + - id: sync_creative_with_assignment + title: "Sync a creative and assign to the package" + narrative: | + Sync one creative into the library and assign it to the package in a single + sync_creatives call. The creative enters the library with the library's + review flow; the assignment binds it to the media buy's package. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Creative accepted into the library (action: created), assignment acknowledged. + Creative status is one of: pending_review, approved, processing. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme_reuse_banner_001" + name: "Acme Outdoor reuse banner" + format_id: + agent_url: "$context.format_agent_url" + id: "$context.format_id" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/acme-reuse-banner.png" + width: 300 + height: 250 + mime_type: "image/png" + assignments: + - creative_id: "acme_reuse_banner_001" + package_id: "$context.package_id" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_sync_creative_with_assignment" + context: + correlation_id: "creative_fate--sync_creative_with_assignment" + context_outputs: + - path: "creatives[0].creative_id" + key: "creative_id" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].creative_id" + description: "Response echoes back the buyer-supplied creative_id" + + - id: verify_creative_in_library_pre_cancel + title: "Baseline: creative is in the library with a non-terminal review state" + narrative: | + Before canceling the buy, list the creative via list_creatives and record the + review state. The purpose of this phase is to establish the baseline the + post-cancel phase compares against: the creative exists and has a status + that is NOT `rejected` or `archived`. + + steps: + - id: list_creatives_before_cancel + title: "Look up the creative in the library" + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_library + stateful: true + expected: | + list_creatives returns the creative with a non-terminal review state + (processing, pending_review, or approved). + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + filters: + creative_ids: + - "$context.creative_id" + context: + correlation_id: "creative_fate--list_creatives_before_cancel" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives[0].creative_id" + description: "Creative is present in the library" + - check: field_value + path: "creatives[0].status" + allowed_values: ["processing", "pending_review", "approved"] + description: "Creative status is non-terminal (not rejected or archived) before cancel" + + - id: cancel_buy + title: "Cancel the media buy" + narrative: | + Cancel the media buy with `canceled: true`. The seller MUST transition the buy + to `canceled` and release the creative's package assignment per + docs/media-buy/specification#media-buy-state-transitions. + + steps: + - id: update_media_buy_canceled + title: "update_media_buy with canceled: true" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Seller acknowledges the cancellation and transitions the buy to `canceled`. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + cancellation_reason: "Creative-fate scenario: releasing assignment to verify library persistence." + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_update_media_buy_canceled" + context: + correlation_id: "creative_fate--update_media_buy_canceled" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - id: verify_creative_persists_post_cancel + title: "Creative remains in the library with review state intact" + narrative: | + After the cancel, the creative's library entry MUST still exist and its review + state MUST NOT be `rejected` (which would indicate implicit-reject-on-cancel, + forbidden by the spec) and SHOULD NOT be `archived` (which would indicate the + seller evaporated library state on buy cancellation, inconsistent with the + decoupled-lifecycle contract). Allowed terminal-adjacent states are + `processing`, `pending_review`, `approved` — whatever the review flow produced. + + steps: + - id: list_creatives_after_cancel + title: "Look up the creative again, post-cancel" + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_library + stateful: true + expected: | + Creative still present with non-rejected, non-archived status. A seller + that returns an empty list, or that has flipped the creative to + `rejected` or `archived` as a side effect of the cancel, fails. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + filters: + creative_ids: + - "$context.creative_id" + context: + correlation_id: "creative_fate--list_creatives_after_cancel" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema" + - check: field_present + path: "creatives[0].creative_id" + description: "Creative still in the library after buy cancellation" + - check: field_value + path: "creatives[0].creative_id" + value: "acme_reuse_banner_001" + description: "Creative ID is unchanged (not re-keyed on cancel)" + - check: field_value + path: "creatives[0].status" + allowed_values: ["processing", "pending_review", "approved"] + description: "Creative status is NOT rejected and NOT archived — no implicit review cascade from the buy cancel" + + - id: reuse_creative_on_new_buy + title: "Reuse the released creative on a new media buy" + narrative: | + With the old buy canceled and the assignment released, the buyer creates a NEW + media buy and references the same creative_id in a fresh assignment. The seller + MUST accept the assignment — the creative_id resolves to the persisted library + entry, demonstrating end-to-end reusability. + + steps: + - id: create_second_buy + title: "Create a second media buy" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Second media buy created successfully with a new media_buy_id and package_id. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_create_second_buy" + start_time: "2026-10-01T00:00:00Z" + end_time: "2026-10-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 5000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "creative_fate--create_second_buy" + context_outputs: + - path: "media_buy_id" + key: "second_media_buy_id" + - path: "packages[0].package_id" + key: "second_package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Second media_buy_id returned" + - check: field_present + path: "packages[0].package_id" + description: "Second package_id returned" + + - id: reassign_creative + title: "Assign the original creative_id to the new package" + narrative: | + Reference the original creative by creative_id only (no assets, no re-upload) + and assign it to the new package. The seller resolves the creative_id from + the library; if the creative was evaporated on cancel, this call fails. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Assignment accepted. No creative-not-found or similar error. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + # Buyer-authoritative id set in sync_creative_with_assignment — + # use it literally instead of round-tripping through + # `$context.creative_id`. That context key is populated from + # the seller's response at `creatives[0].creative_id`; sellers + # whose envelope doesn't surface that exact path resolve to + # undefined and the template engine strips the creative, + # leaving `creatives: undefined` which fails pre-flight zod. + - creative_id: "acme_reuse_banner_001" + name: "Reassigned creative" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/banner_300x250.jpg" + width: 300 + height: 250 + assignments: + - creative_id: "acme_reuse_banner_001" + package_id: "$context.second_package_id" + idempotency_key: "$generate:uuid_v4#creative_fate_after_cancellation_reassign_creative" + context: + correlation_id: "creative_fate--reassign_creative" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/delivery_reporting.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/delivery_reporting.yaml new file mode 100644 index 0000000000..75596a2306 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/delivery_reporting.yaml @@ -0,0 +1,205 @@ +id: media_buy_seller/delivery_reporting +version: "1.0.0" +title: "Seller returns valid delivery reporting" +category: media_buy_seller +summary: "Verifies that get_media_buy_delivery returns schema-compliant delivery data after simulated delivery via the test controller." +track: reporting +required_tools: + - get_products + - create_media_buy + - get_media_buy_delivery + - comply_test_controller + +narrative: | + Delivery reporting is how buyers know if their campaign is working. The seller must + return schema-compliant delivery data from get_media_buy_delivery with per-package + metrics (impressions, spend, pacing). + + This scenario creates a media buy, injects delivery data via the test controller's + simulate_delivery scenario, then calls get_media_buy_delivery and validates the + response against the schema. Without this test, sellers can return arbitrary formats + and still pass certification. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any media buy seller" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller must implement comply_test_controller with the simulate_delivery + scenario. This allows the test harness to inject delivery metrics into a + media buy so get_media_buy_delivery has data to return. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "outdoor_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "outdoor_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + +phases: + - id: setup + title: "Create a media buy for delivery testing" + steps: + - id: sync_accounts + title: "Establish account" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_delivery_reporting_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Display and video inventory on outdoor lifestyle content. Q2 flight, $25K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy + title: "Create media buy" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Create the media buy. We need a media_buy_id to simulate delivery against. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 10000 + pricing_option_id: "cpm_standard" + idempotency_key: "$generate:uuid_v4#media_buy_seller_delivery_reporting_setup_create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - id: simulate_and_verify + title: "Simulate delivery and validate reporting" + narrative: | + Inject delivery metrics via the test controller, then call get_media_buy_delivery + and validate the response format. This is the core test — does the seller return + schema-compliant delivery data with per-package metrics? + + steps: + - id: simulate_delivery + title: "Inject simulated delivery metrics" + task: comply_test_controller + requires_tool: comply_test_controller + stateful: true + expected: | + The test controller acknowledges the simulated delivery data. + sample_request: + scenario: "simulate_delivery" + params: + media_buy_id: "$context.media_buy_id" + impressions: 5000 + clicks: 150 + reported_spend: + amount: 250.00 + currency: "USD" + validations: + - check: field_value + path: "success" + allowed_values: [true] + description: "Delivery simulation succeeds" + + - id: get_delivery + title: "Get delivery report and validate schema" + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics reflecting the simulated data: + - media_buy_deliveries array with at least one entry + - Per-package breakdown with impressions, spend + - Response matches the get-media-buy-delivery-response.json schema + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains delivery data" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_approved.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_approved.yaml new file mode 100644 index 0000000000..fd8e15b634 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_approved.yaml @@ -0,0 +1,211 @@ +id: media_buy_seller/governance_approved +version: "1.0.0" +title: "Seller creates buy when governance approves" +category: media_buy_seller +summary: "Verifies that the seller creates a media buy when governance approves the transaction." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + This is a multi-agent test. The test harness sets up a permissive governance plan on + a governance agent with a $100K budget, then registers that agent with the seller. + The buyer creates a $25K buy which falls within limits. + + When the seller calls check_governance during create_media_buy, the governance agent + approves. The seller must confirm the buy normally. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "outdoor_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "outdoor_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + +phases: + - id: governance_plan_setup + title: "Set up permissive governance plan" + narrative: | + Create a governance plan on the governance agent with a high budget ($100K). + The subsequent $25K buy will fall within these limits and should be approved. + + steps: + - id: sync_plans + title: "Create permissive governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan with a plan_id. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_approved_sync_plans" + plans: + - plan_id: "comply-gov-approved-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 outdoor lifestyle campaign — display and video" + budget: + total: 100000 + currency: "USD" + reallocation_threshold: 100000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US", "CA"] + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_approved_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_approved_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_approved + title: "Create buy within governance limits" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Display and video inventory on outdoor lifestyle content. Q2 flight, $25K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy + title: "Create buy (governance approves)" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds — governance approved because the $25K buy is within + the plan's $100K budget. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 10000 + pricing_option_id: "cpm_standard" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_approved_buy_approved_create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_conditions.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_conditions.yaml new file mode 100644 index 0000000000..e094f2f9ea --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_conditions.yaml @@ -0,0 +1,196 @@ +id: media_buy_seller/governance_conditions +version: "1.0.0" +title: "Seller attaches conditions when governance approves with conditions" +category: media_buy_seller +summary: "Verifies that the seller attaches governance conditions to the buy when governance approves with conditions." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + This is a multi-agent test. The test harness sets up a governance plan on a governance + agent with custom policies that trigger conditions (e.g., "CTV buys require weekly + delivery reporting"). The buyer creates a CTV buy within budget but matching a policy. + + When the seller calls check_governance, the governance agent approves with conditions. + The seller must create the buy and include the governance conditions and context token + in its response. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up conditional governance plan" + narrative: | + Create a governance plan on the governance agent with custom policies that + trigger conditions. The plan has sufficient budget but policies that require + conditions on CTV buys. + + steps: + - id: sync_plans + title: "Create conditional governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan with a plan_id. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_conditions_sync_plans" + plans: + - plan_id: "comply-gov-conditions-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Q2 CTV campaign with reporting requirements" + budget: + total: 100000 + currency: "USD" + reallocation_threshold: 100000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US", "CA"] + custom_policies: + - policy_id: "ctv_weekly_reporting" + enforcement: "must" + policy: "CTV buys require weekly delivery reporting." + - policy_id: "ugc_brand_safety" + enforcement: "must" + policy: "UGC placements require brand safety review before activation." + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_conditions_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_conditions_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_with_conditions + title: "Create CTV buy triggering governance conditions" + steps: + - id: get_products_brief + title: "Discover CTV products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return CTV/video products matching the brief. + sample_request: + buying_mode: "brief" + brief: "CTV and connected TV inventory on outdoor lifestyle content. Q2 flight, $25K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy_conditions + title: "Create CTV buy (governance approves with conditions)" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds with governance conditions attached: + - media_buy_id: present + - status: active or pending_creatives + - governance_context: token from the governance agent + - conditions visible to the buyer + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_ctv_q2" + budget: 25000 + pricing_option_id: "cpm_standard" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_conditions_buy_with_conditions_create_media_buy_conditions" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied.yaml new file mode 100644 index 0000000000..b7ce2987d2 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied.yaml @@ -0,0 +1,192 @@ +id: media_buy_seller/governance_denied +version: "1.0.0" +title: "Seller rejects buy when governance denies" +category: media_buy_seller +summary: "Verifies that the seller rejects a media buy and propagates the denial when governance denies the transaction." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + This is a multi-agent test. The test harness sets up a governance plan on a governance + agent with a strict $10K budget, then registers that agent with the seller. The buyer + attempts a $50K media buy which exceeds the plan's budget. + + When the seller calls check_governance, the governance agent denies because the + requested budget exceeds the plan total. The seller must reject the buy and propagate + the denial back to the buyer. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up strict governance plan" + narrative: | + Create a governance plan on the governance agent with a strict $10K budget + and agent_limited authority. The subsequent $50K buy will exceed the plan + budget and should be denied. + + steps: + - id: sync_plans + title: "Create strict governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan with a plan_id. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_denied_sync_plans" + plans: + - plan_id: "comply-gov-denied-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Small test campaign — limited budget authority" + budget: + total: 10000 + currency: "USD" + reallocation_threshold: 5000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US"] + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority", "brand_policy"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_denied + title: "Create buy exceeding governance limits" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Premium video and display on outdoor lifestyle. Q2 flight, $50K budget. Adults 25-54, US." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy_denied + title: "Create buy (governance denies — should fail)" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expect_error: true + negative_path: payload_well_formed + expected: | + The buy is rejected because governance denied — the $50K buy exceeds + the plan's $10K budget. The seller propagates the denial with findings. + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + idempotency_key: "$generate:uuid_v4#governance_denied_create_media_buy" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + validations: + - check: error_code + value: "GOVERNANCE_DENIED" + description: "Error code indicates governance denial" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied_recovery.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied_recovery.yaml new file mode 100644 index 0000000000..b24a7c263c --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/governance_denied_recovery.yaml @@ -0,0 +1,244 @@ +id: media_buy_seller/governance_denied_recovery +version: "1.0.0" +title: "Seller accepts corrected buy after governance denial" +category: media_buy_seller +summary: "Verifies that a buyer can recover from GOVERNANCE_DENIED by shrinking the buy to within plan limits and retrying." +track: media_buy +required_tools: + - sync_governance + - get_products + - create_media_buy + +narrative: | + GOVERNANCE_DENIED is a correctable error, not a dead end. The buyer must be able to + read the denial findings, adjust the media buy, and retry. This scenario exercises the + full loop: strict governance plan ($10K), failed $50K buy that is denied, then a + corrected $8K buy that fits within the plan and is approved. + + The seller must propagate the governance findings unchanged so the buyer can identify + exactly which constraint was violated (budget authority, brand policy, etc.) and + correct it. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Any media buy seller with governance support" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up strict governance plan" + narrative: | + Create a governance plan with a $10K budget. The initial $50K buy will exceed this + limit; the retry at $8K will fit. + + steps: + - id: sync_plans + title: "Create strict governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan. + sample_request: + idempotency_key: "$generate:uuid_v4#governance_denied_recovery_sync_plans" + plans: + - plan_id: "comply-gov-recovery-plan" + brand: + domain: "acmeoutdoor.example" + objectives: "Strict-budget plan for denial + recovery validation" + budget: + total: 10000 + currency: "USD" + reallocation_threshold: 5000 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US"] + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: seller_setup + title: "Account and governance registration on seller" + steps: + - id: sync_accounts + title: "Establish account with seller" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_recovery_seller_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with seller" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Acknowledge the governance agent registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority"] + idempotency_key: "$generate:uuid_v4#media_buy_seller_governance_denied_recovery_seller_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: buy_denied + title: "Initial buy exceeds plan — governance denies" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content. Q2 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - id: create_media_buy_denied + title: "Attempt $50K buy — denied" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + The buy is rejected with GOVERNANCE_DENIED. The response includes findings + from the governance agent explaining which constraint was exceeded. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#governance_denied_recovery_create_media_buy_denied" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 50000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "governance_denied_recovery--create_media_buy_denied" + validations: + - check: error_code + value: "GOVERNANCE_DENIED" + description: "Error code is GOVERNANCE_DENIED" + - check: field_value + path: "context.correlation_id" + value: "governance_denied_recovery--create_media_buy_denied" + description: "Response echoes context.correlation_id verbatim on error responses (echo contract applies to both success and failure)" + + - id: buy_retried + title: "Corrected buy within plan — governance approves" + narrative: | + The buyer reads the denial findings, shrinks the budget to $8K (within the $10K + plan), and retries with a fresh idempotency_key. The seller consults governance + again, which now approves. + + steps: + - id: create_media_buy_retry + title: "Retry with $8K — approved" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds. Governance approves because $8K fits within the $10K plan + budget. The seller returns a media_buy_id. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#governance_denied_recovery_create_media_buy_retry" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 8000 + pricing_option_id: "$context.pricing_option_id" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns a media_buy_id after governance approves" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/invalid_transitions.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/invalid_transitions.yaml new file mode 100644 index 0000000000..112336abc5 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/invalid_transitions.yaml @@ -0,0 +1,284 @@ +id: media_buy_seller/invalid_transitions +version: "1.0.0" +title: "Seller rejects illegal state transitions and unknown references" +category: media_buy_seller +summary: "Validates that the seller returns structured AdCP errors (MEDIA_BUY_NOT_FOUND, PACKAGE_NOT_FOUND, NOT_CANCELLABLE) rather than 500s or undefined behavior when the buyer references missing entities or attempts forbidden state transitions." +track: media_buy +required_tools: + - get_products + - create_media_buy + - update_media_buy + +narrative: | + Error-code coverage is the most common compliance gap: sellers accept malformed input + silently or return generic 500s rather than the specific AdCP codes the spec defines. + This scenario forces three of the most-referenced media_buy error codes with + deterministic input: + + - MEDIA_BUY_NOT_FOUND — buyer references a media_buy_id that does not exist. + - PACKAGE_NOT_FOUND — media_buy_id is valid but package_id inside it is not. + - NOT_CANCELLABLE — buyer cancels the same media buy twice; the second cancel must + be rejected because canceled is terminal. + + All three probes use hard `check: error_code` assertions so a seller that returns + a 500 or swallows the error fails the scenario outright. Recovery hints, correlation + echo, and context preservation are also checked on every error response. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + examples: + - "Any media buy seller" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports create_media_buy and update_media_buy with cancellation via + the canceled field. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: unknown_media_buy + title: "Reference an unknown media_buy_id" + narrative: | + Before any state is set up, call update_media_buy with a fabricated media_buy_id. + The seller must return MEDIA_BUY_NOT_FOUND with a correctable recovery hint — not + a 500 and not a silent success. + + steps: + - id: update_unknown_media_buy + title: "update_media_buy with bogus media_buy_id" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + expect_error: true + negative_path: payload_well_formed + stateful: false + expected: | + Reject with: + - code: MEDIA_BUY_NOT_FOUND + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "does-not-exist-invalid-transitions-v1" + paused: true + idempotency_key: "$generate:uuid_v4#update_unknown_media_buy" + context: + correlation_id: "invalid_transitions--update_unknown_media_buy" + validations: + - check: error_code + value: "MEDIA_BUY_NOT_FOUND" + description: "Error code is MEDIA_BUY_NOT_FOUND" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "invalid_transitions--update_unknown_media_buy" + description: "Context correlation_id returned unchanged" + + - id: setup + title: "Create a real media buy for follow-up probes" + narrative: | + The remaining probes need a real media_buy_id and package_id. Discover a product + and create a plain buy — no targeting tricks, no governance — so we have known-good + IDs for the error cases. + + steps: + - id: get_products_brief + title: "Discover a product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product with pricing. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content. Q3 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: create_buy + title: "Create a buy for the error probes" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Media buy created with media_buy_id and at least one package. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#invalid_transitions_create_buy" + start_time: "2026-08-01T00:00:00Z" + end_time: "2026-08-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 5000 + pricing_option_id: "$context.pricing_option_id" + context: + correlation_id: "invalid_transitions--create_buy" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns media_buy_id" + + - id: unknown_package + title: "Reference an unknown package_id on a real buy" + narrative: | + The media_buy_id is real, but the package_id is fabricated. The seller must + return PACKAGE_NOT_FOUND — distinct from MEDIA_BUY_NOT_FOUND — because the + lookup succeeds at the buy level and only fails at the package lookup. + + steps: + - id: update_unknown_package + title: "update_media_buy with bogus package_id" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with: + - code: PACKAGE_NOT_FOUND + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + idempotency_key: "$generate:uuid_v4#update_unknown_package" + packages: + - package_id: "does-not-exist-package-invalid-transitions-v1" + paused: true + context: + correlation_id: "invalid_transitions--update_unknown_package" + validations: + - check: error_code + value: "PACKAGE_NOT_FOUND" + description: "Error code is PACKAGE_NOT_FOUND" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "invalid_transitions--update_unknown_package" + description: "Context correlation_id returned unchanged" + + - id: double_cancel + title: "Cancel twice — second cancel is not valid" + narrative: | + Cancel the buy (success), then try to cancel the same buy again. canceled is + terminal per the AdCP spec, so the second cancel cannot succeed. The seller + must return NOT_CANCELLABLE — the schema specifically reserves this code for + "media buy cannot be canceled in its current state." + + steps: + - id: first_cancel + title: "Cancel the media buy" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Seller acknowledges the cancellation and transitions the buy to canceled. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + cancellation_reason: "Testing NOT_CANCELLABLE on re-cancel" + idempotency_key: "$generate:uuid_v4#media_buy_seller_invalid_transitions_double_cancel_first_cancel" + context: + correlation_id: "invalid_transitions--first_cancel" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - id: second_cancel + title: "Re-cancel the canceled buy" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with: + - code: NOT_CANCELLABLE + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + cancellation_reason: "Deliberate re-cancel to force NOT_CANCELLABLE" + idempotency_key: "$generate:uuid_v4#media_buy_seller_invalid_transitions_double_cancel_second_cancel" + context: + correlation_id: "invalid_transitions--second_cancel" + validations: + - check: error_code + value: "NOT_CANCELLABLE" + description: "Error code is NOT_CANCELLABLE on re-cancel of canceled buy" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "invalid_transitions--second_cancel" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_no_match.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_no_match.yaml new file mode 100644 index 0000000000..183da2616d --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_no_match.yaml @@ -0,0 +1,143 @@ +id: media_buy_seller/inventory_list_no_match +version: "1.0.0" +title: "Seller handles property_list / collection_list references that match zero inventory" +category: media_buy_seller +summary: "Verifies a seller returns a zero-forecast product or a clear error — not a crash — when a buyer references inventory lists that resolve to nothing in the seller's catalog." +track: media_buy +required_tools: + - get_products + - create_media_buy + +narrative: | + Not every buyer list matches every seller's inventory. A buyer may point at a + property_list of domains this seller does not carry, or a collection_list of + programs this seller does not syndicate. The seller must handle that gracefully: + + - Return a product with a zero forecast and explain the mismatch, OR + - Return an informative error on create_media_buy (INSUFFICIENT_INVENTORY or + similar) with findings the buyer can act on. + + What the seller must NOT do: crash, return a misleading non-zero forecast, or + silently drop the targeting and deliver against unintended inventory. Each of + those is a real failure mode we've seen in adapter integrations. + + This scenario exercises the no-match path using the test kit's pre-populated + no_match_properties and no_match_collections lists. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_property_list_targeting + - supports_collection_list_targeting + examples: + - "Sellers that validate inventory against buyer-supplied lists" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller must resolve PropertyListReference / CollectionListReference against + its own inventory and report a truthful outcome when nothing matches. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: discover + title: "Discover products" + steps: + - id: get_products_brief + title: "Discover products" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products the seller carries. + sample_request: + buying_mode: "brief" + brief: "Video inventory on outdoor lifestyle programming. Q3 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "inventory_list_no_match--get_products_brief" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + + - id: no_match_attempt + title: "Create buy with lists that match no inventory" + narrative: | + The buyer references the no-match property list and the no-match collection + list. Neither list resolves to anything in the seller's catalog. The seller + must either (a) accept the buy and surface a zero-delivery expectation, or + (b) reject it with an informative error. Silent success with undefined + delivery behaviour is not acceptable. + + steps: + - id: create_buy_no_match + title: "create_media_buy against empty intersections" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + One of two acceptable outcomes: + + 1. Buy accepted with zero-forecast reporting — status may be + pending_creatives/pending_start/active, but the seller returns + packages with forecast indicating zero deliverable inventory and + a message explaining the list mismatch. + + 2. Buy rejected with an informative error — typically + INSUFFICIENT_INVENTORY or INVALID_TARGETING — including findings + that identify which list(s) matched nothing. + + What is NOT acceptable: a silently-successful buy with normal forecast + numbers, or a crash / non-AdCP error shape. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#inventory_list_no_match_create_buy" + start_time: "2026-07-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 10000 + pricing_option_id: "$context.pricing_option_id" + targeting_overlay: + property_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_v1" + collection_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_collections_v1" + + context: + correlation_id: "inventory_list_no_match--create_buy_no_match" + validations: + - check: field_present + path: "context" + description: "Response echoes back the context object (success or error)" + - check: field_value + path: "context.correlation_id" + value: "inventory_list_no_match--create_buy_no_match" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_targeting.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_targeting.yaml new file mode 100644 index 0000000000..d55802ce69 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/inventory_list_targeting.yaml @@ -0,0 +1,271 @@ +id: media_buy_seller/inventory_list_targeting +version: "1.0.0" +title: "Seller honors property_list and collection_list targeting on create and update" +category: media_buy_seller +summary: "Verifies that a seller accepts PropertyListReference and CollectionListReference in package targeting on create_media_buy AND update_media_buy, with parity between both paths." +track: media_buy +required_tools: + - get_products + - create_media_buy + - update_media_buy + +narrative: | + AdCP 3.0 targets inventory through agent-hosted reference lists rather than inline + property arrays. Buyers point a package's targeting at a `PropertyListReference` + and/or `CollectionListReference`; the seller resolves the list against its own + inventory at serve time. + + A common integration regression is create/update parity: a seller accepts list + references on create_media_buy but silently drops them on update_media_buy, so a + buyer who edits a live buy loses their list targeting. This scenario writes both + list types on create, then swaps both on update, and finally reads the buy back + to confirm the updated references are what the seller persisted. + + The buyer references pre-populated inventory lists from the test kit — one for + matching properties and one for matching collections — so the seller's test + engine can resolve them without needing a live governance/inventory agent. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_property_list_targeting + - supports_collection_list_targeting + examples: + - "Sellers that accept PropertyListReference / CollectionListReference in package targeting" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The seller must accept PropertyListReference and CollectionListReference in + package targeting on both create_media_buy and update_media_buy. List contents + come from test-kits/acme-outdoor.yaml → inventory_targets. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: discover_product + title: "Discover a product that supports list targeting" + steps: + - id: get_products_brief + title: "Discover product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product whose targeting supports property_list and + collection_list references. + + sample_request: + buying_mode: "brief" + brief: "Video inventory on outdoor lifestyle programming. Q3 flight, $30K budget." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + + context: + correlation_id: "inventory_list_targeting--get_products_brief" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "Product has a product_id" + + - id: create_with_both_lists + title: "Create media buy with property_list AND collection_list targeting" + narrative: | + The buyer references the matching-property list and the matching-collection + list from the test kit. The seller accepts both references, creates the buy, + and echoes the resolved targeting back on the package. + + steps: + - id: create_buy_with_lists + title: "create_media_buy with both list references" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The seller accepts the list references without error. The response + includes a media_buy_id and the package retains both the property_list + and collection_list targeting fields so subsequent get / update calls + can read them back. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + idempotency_key: "$generate:uuid_v4#inventory_list_targeting_create_buy" + start_time: "2026-07-01T00:00:00Z" + end_time: "2026-09-30T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 20000 + pricing_option_id: "$context.pricing_option_id" + targeting_overlay: + property_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_allowlist_v1" + collection_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_collections_v1" + + context: + correlation_id: "inventory_list_targeting--create_buy_with_lists" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller assigns a media_buy_id" + - check: field_present + path: "context" + description: "Response echoes back the context object" + + - id: verify_create_persisted + title: "Verify list targeting persisted after create" + narrative: | + Read the buy back to confirm the seller stored both list references — not just + echoed them back in the create response. This catches sellers that parse list + references on create but never persist them to their internal package model. + + steps: + - id: get_after_create + title: "get_media_buys after create" + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the media buy with packages[0].targeting_overlay.property_list + and .collection_list populated with the list_id values sent on create. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "inventory_list_targeting--get_after_create" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.property_list.list_id" + value: "acme_outdoor_allowlist_v1" + description: "property_list.list_id persisted after create" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.collection_list.list_id" + value: "acme_outdoor_collections_v1" + description: "collection_list.list_id persisted after create" + + - id: update_swap_lists + title: "Swap property_list and collection_list via update_media_buy" + narrative: | + The buyer swaps both list references. This is the create/update parity check: + a seller that accepts list references on create but silently drops them on + update would fail to update the persisted targeting. We exercise update + explicitly to catch that class of regression. + + steps: + - id: update_buy_swap_lists + title: "update_media_buy replaces both list references" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The seller acknowledges the updated targeting. Both property_list and + collection_list are replaced (not merged) with the new list_id values. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + media_buy_id: "$context.media_buy_id" + packages: + - package_id: "$context.package_id" + targeting_overlay: + property_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_v1" + collection_list: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_collections_v1" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_inventory_list_targeting_update_swap_lists_update_buy_swap_lists" + context: + correlation_id: "inventory_list_targeting--update_buy_swap_lists" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - id: get_after_update + title: "Confirm swap persisted" + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The persisted package targeting reflects the new list_id values. This is + the create/update parity guarantee: edits through update_media_buy land + in persistent state just like fields on create_media_buy. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "inventory_list_targeting--get_after_update" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.property_list.list_id" + value: "acme_outdoor_no_match_v1" + description: "property_list.list_id updated after update_media_buy" + - check: field_value + path: "media_buys[0].packages[0].targeting_overlay.collection_list.list_id" + value: "acme_outdoor_no_match_collections_v1" + description: "collection_list.list_id updated after update_media_buy" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/measurement_terms_rejected.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/measurement_terms_rejected.yaml new file mode 100644 index 0000000000..650db4c9bc --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/measurement_terms_rejected.yaml @@ -0,0 +1,196 @@ +id: media_buy_seller/measurement_terms_rejected +version: "1.0.0" +title: "Seller rejects unworkable measurement_terms" +category: media_buy_seller +summary: "Buyer proposes measurement_terms the seller will not accept; seller returns TERMS_REJECTED; buyer retries with seller-compatible terms." +track: media_buy +required_tools: + - get_products + - create_media_buy + +narrative: | + measurement_terms negotiation is a round-trip. The buyer proposes a measurement vendor, + window, and variance tolerance on each package; the seller either accepts (returning + measurement_terms echoed in the response) or rejects with TERMS_REJECTED and a message + explaining what is unacceptable. The buyer mints a fresh idempotency_key and retries + create_media_buy with the adjusted payload — reusing the prior key against a different + body MUST be rejected with IDEMPOTENCY_CONFLICT per spec. + + This scenario sends an intentionally aggressive first proposal (max_variance_percent: 0 + with a window the seller does not guarantee), verifies the seller rejects with + TERMS_REJECTED, then retries with a relaxed proposal that falls inside the seller's + stated tolerance and expects a successful create_media_buy. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - measurement_terms + examples: + - "Broadcast sellers" + - "CTV sellers with C3/C7 guarantees" + - "Any seller that negotiates measurement terms" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports measurement_terms on create_media_buy packages. Buyer knows the + seller's declared measurement capabilities from get_adcp_capabilities. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: discover_products + title: "Discover products" + steps: + - id: get_products_brief + title: "Find a product that supports measurement_terms" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products with pricing_options. At least one product should advertise + measurement support in the response. + sample_request: + buying_mode: "brief" + brief: "Premium video inventory with measurement guarantees. Q2 flight, $50K." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "At least one product returned" + + - id: reject_terms + title: "Seller rejects unworkable terms" + narrative: | + The buyer proposes max_variance_percent: 0 — a tolerance almost no seller can + honor against third-party measurement. The seller must reject with TERMS_REJECTED + and indicate what would be acceptable. + + steps: + - id: create_media_buy_aggressive_terms + title: "Propose aggressive measurement_terms" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + expect_error: true + negative_path: payload_well_formed + stateful: false + expected: | + Reject with: + - code: TERMS_REJECTED + - recovery: correctable + - message indicating which terms are unworkable (vendor, window, or variance) + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#measurement_terms_rejected_aggressive_terms" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 25000 + pricing_option_id: "$context.pricing_option_id" + measurement_terms: + billing_measurement: + vendor: + domain: "videoamp.example" + measurement_window: "c30" + max_variance_percent: 0 + makegood_policy: + available_remedies: ["credit"] + + context: + correlation_id: "measurement_terms_rejected--aggressive" + validations: + - check: error_code + value: "TERMS_REJECTED" + description: "Error code is TERMS_REJECTED" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "measurement_terms_rejected--aggressive" + description: "Context correlation_id returned unchanged" + + - id: accept_terms + title: "Buyer retries with acceptable terms" + narrative: | + The buyer relaxes measurement_terms to match the seller's stated capability + (c7 window, 10% variance, makegood remedies) and retries. The seller accepts and + returns the confirmed terms echoed in the response. + + steps: + - id: create_media_buy_relaxed_terms + title: "Propose seller-compatible measurement_terms" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + The buy succeeds. The response echoes the accepted measurement_terms (possibly + normalized by the seller). + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#measurement_terms_rejected_relaxed_terms" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 25000 + pricing_option_id: "$context.pricing_option_id" + measurement_terms: + billing_measurement: + vendor: + domain: "videoamp.example" + measurement_window: "c7" + max_variance_percent: 10 + makegood_policy: + available_remedies: ["additional_delivery", "credit"] + + context: + correlation_id: "measurement_terms_rejected--relaxed" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller returns a media_buy_id after accepting terms" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "measurement_terms_rejected--relaxed" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/pending_creatives_to_start.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/pending_creatives_to_start.yaml new file mode 100644 index 0000000000..b5dd9230db --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/pending_creatives_to_start.yaml @@ -0,0 +1,250 @@ +id: media_buy_seller/pending_creatives_to_start +version: "1.0.0" +title: "Creative sync unblocks pending_creatives → pending_start" +category: media_buy_seller +summary: "Verifies that a media buy created without creatives sits in pending_creatives until sync_creatives completes, then transitions to pending_start." +track: media_buy +required_tools: + - get_products + - create_media_buy + - sync_creatives + - get_media_buys + +narrative: | + When a buyer creates a media buy without creative_assignments, the seller cannot start + delivery until creatives are supplied. The seller must report status: pending_creatives + and, after sync_creatives attaches the required assets, transition the buy to + pending_start (awaiting flight start) or active (if already in flight). + + This scenario walks the transition end-to-end: create the buy, confirm pending_creatives, + sync a creative, and confirm the status advances to pending_start. The transition + proves that the creative pipeline and the buy state machine are wired together — a + common integration gap when creative and media buy systems live on different backends. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_creatives + examples: + - "Any seller that requires creatives before starting delivery" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + Seller supports create_media_buy without creative_assignments and honors the + pending_creatives → pending_start transition when creatives are synced. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Discover products and formats" + steps: + - id: get_products_brief + title: "Discover a product" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return at least one product with format_ids and pricing options. + sample_request: + buying_mode: "brief" + brief: "Display inventory on outdoor lifestyle content. Q3 flight." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "products[0].product_id" + key: "product_id" + - path: "products[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + - path: "products[0].format_ids[0]" + key: "format_id" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Product declares a format_id for creative sync" + + - id: create_without_creatives + title: "Create media buy without creative_assignments" + narrative: | + The buyer intentionally omits creative_assignments. The seller must accept the + buy, persist it, and return status: pending_creatives with valid_actions + including sync_creatives. + + steps: + - id: create_buy_no_creatives + title: "Create buy in pending_creatives" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Media buy created with: + - media_buy_id assigned + - status: pending_creatives + - valid_actions including sync_creatives + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "$generate:uuid_v4#pending_creatives_to_start_create_buy_no_creatives" + start_time: "2026-08-01T00:00:00Z" + end_time: "2026-08-31T23:59:59Z" + packages: + - product_id: "$context.product_id" + budget: 10000 + pricing_option_id: "$context.pricing_option_id" + + context: + correlation_id: "pending_creatives_to_start--create_buy_no_creatives" + context_outputs: + - path: "media_buy_id" + key: "media_buy_id" + - path: "packages[0].package_id" + key: "package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Seller assigns a media_buy_id" + - check: field_value + path: "status" + value: "pending_creatives" + description: "Status is pending_creatives because no creatives supplied" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "pending_creatives_to_start--create_buy_no_creatives" + description: "Context correlation_id returned unchanged" + + - id: supply_creatives + title: "Supply creatives and assign to the package" + narrative: | + The buyer syncs a creative with the format the product requires, then updates the + media buy with creative_assignments so the seller can attach the asset to the + package and advance state. + + steps: + - id: sync_creative + title: "Sync the creative asset" + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_lifecycle + stateful: true + expected: | + The seller ingests the creative and returns status active (or approved). + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme-outdoor-display-q3" + name: "Acme Outdoor Q3 display" + format_id: "$context.format_id" + assets: + image: + asset_type: "image" + url: "https://creative.acmeoutdoor.example/q3/display-300x250.jpg" + width: 300 + height: 250 + mime_type: "image/jpeg" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_pending_creatives_to_start_supply_creatives_sync_creative" + context: + correlation_id: "pending_creatives_to_start--sync_creative" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + + - id: assign_creative_to_package + title: "Assign the creative to the package" + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The seller acknowledges the assignment and advances the buy state. The response + status should be pending_start (or active if the flight has started). + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + packages: + - package_id: "$context.package_id" + creative_assignments: + - creative_id: "acme-outdoor-display-q3" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_pending_creatives_to_start_supply_creatives_assign_creative_to_package" + context: + correlation_id: "pending_creatives_to_start--assign_creative_to_package" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + - check: field_value + path: "status" + allowed_values: ["pending_start", "active"] + description: "Status advances out of pending_creatives once creatives attached" + + - id: verify_transition + title: "Verify the status transition" + narrative: | + Independently of the update_media_buy response, call get_media_buys to confirm the + seller's stored state has advanced. This protects against sellers that return the + updated status in response but do not persist it. + + steps: + - id: get_media_buy_after_sync + title: "Confirm pending_start after creative sync" + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + The media buy's persisted status is pending_start (or active). valid_actions + no longer includes sync_creatives as a required next step. + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "pending_creatives_to_start--get_media_buy_after_sync" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_value + path: "media_buys[0].status" + allowed_values: ["pending_start", "active"] + description: "Persisted status is past pending_creatives" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/proposal_finalize.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/proposal_finalize.yaml new file mode 100644 index 0000000000..68fac08bb7 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/proposal_finalize.yaml @@ -0,0 +1,247 @@ +id: media_buy_seller/proposal_finalize +version: "1.0.0" +title: "Seller handles proposal refinement and finalize" +category: media_buy_seller +summary: "Verifies the full proposal lifecycle: brief with proposals, refine a proposal, finalize to committed, and accept via create_media_buy." +track: media_buy +required_tools: + - get_products + - create_media_buy + +narrative: | + Proposals are curated media plans that the seller generates alongside products. The buyer + reviews proposals, refines them (adjust budget splits, swap products, add constraints), + and then finalizes a proposal to get firm pricing and an inventory hold. Once committed, + the buyer accepts the proposal via create_media_buy. + + The proposal lifecycle is: draft (indicative pricing) -> refine -> finalize (firm pricing, + inventory hold) -> create_media_buy (accept and go live). + + This scenario exercises the complete proposal flow including the finalize action, which + transitions a proposal from draft to committed status with an expires_at hold window. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - generates_proposals + examples: + - "Full-service publisher with proposal engine" + - "Retail media network with curated packages" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials. The seller must support + proposal generation (return proposals alongside products in get_products responses). + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Account setup" + steps: + - id: sync_accounts + title: "Establish account" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_proposal_finalize_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: brief_with_proposals + title: "Brief with proposals" + narrative: | + Send a brief and receive proposals alongside products. Proposals are curated + media plans with budget allocations the buyer can review and refine. + + steps: + - id: get_products_brief + title: "Send a brief and receive proposals" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products and proposals matching the brief: + - products: individual products with pricing and forecasts + - proposals: curated media plans with proposal_id, budget_allocations, rationale + + sample_request: + buying_mode: "brief" + brief: "Premium video and display across outdoor lifestyle and sports. Q2 flight, $50K budget. Adults 25-54, US and Canada." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context_outputs: + - path: "proposals[0].proposal_id" + key: "proposal_id" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + - check: field_present + path: "proposals" + description: "Response contains proposals" + - check: field_present + path: "proposals[0].proposal_id" + description: "Proposals have IDs" + + - id: refine_proposal + title: "Refine the proposal" + narrative: | + The buyer reviews the proposals and wants to adjust one. They call get_products in + refine mode targeting a specific proposal_id with changes. The seller applies the + refinements and returns the updated proposal. + + steps: + - id: get_products_refine + title: "Refine a specific proposal" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return the refined proposal: + - proposals: updated proposal reflecting the requested changes + - refinement_applied: how each refinement was handled + - Updated budget allocations, product selections, and forecasts + + sample_request: + buying_mode: "refine" + refine: + - scope: "proposal" + proposal_id: "$context.proposal_id" + ask: "Shift 60% of budget to CTV. Drop the display product and redistribute that budget to video." + - scope: "request" + ask: "All products must support frequency capping at 3 per day." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "proposals" + description: "Response contains updated proposals" + + - id: finalize_proposal + title: "Finalize the proposal" + narrative: | + The buyer is satisfied with the refined proposal and requests finalization. This + triggers the transition from draft (indicative pricing) to committed (firm pricing + with inventory hold). The seller may need time to process this — the buyer should + not set a time_budget to signal willingness to wait. + + After finalization, the proposal has firm pricing and an expires_at timestamp. + The buyer must create the media buy before the hold expires. + + steps: + - id: get_products_finalize + title: "Finalize the proposal" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return the finalized proposal with committed status: + - proposals[0].proposal_status: committed + - proposals[0].expires_at: timestamp for the inventory hold window + - Firm pricing (not indicative) + - The proposal is ready to accept via create_media_buy + + sample_request: + buying_mode: "refine" + refine: + - scope: "proposal" + proposal_id: "$context.proposal_id" + action: "finalize" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "proposals" + description: "Response contains the finalized proposal" + + - id: accept_proposal + title: "Accept the committed proposal" + narrative: | + The buyer accepts the committed proposal by creating a media buy with the + proposal_id. The seller converts the proposal's product selections and budget + allocations into active packages. + + steps: + - id: create_media_buy + title: "Create media buy from proposal" + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Convert the committed proposal into an active media buy: + - media_buy_id: the seller's identifier + - status: active + - confirmed_at: timestamp + - packages: line items derived from the proposal's budget allocations + - proposal_id: echoed back to confirm which proposal was accepted + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + sandbox: true + proposal_id: "$context.proposal_id" + total_budget: + amount: 50000 + currency: "USD" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_proposal_finalize_accept_proposal_create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" diff --git a/dist/compliance/3.0.9/protocols/media-buy/scenarios/refine_products.yaml b/dist/compliance/3.0.9/protocols/media-buy/scenarios/refine_products.yaml new file mode 100644 index 0000000000..f47c311c2c --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/scenarios/refine_products.yaml @@ -0,0 +1,148 @@ +id: media_buy_seller/refine_products +version: "1.0.0" +title: "Seller handles product refinement" +category: media_buy_seller +summary: "Verifies that a media buy seller supports buying_mode: refine with product-level and request-level changes." +track: media_buy +required_tools: + - get_products + +narrative: | + After a buyer receives products from a brief, they refine the results without starting + over. The buyer calls get_products with buying_mode: refine and a refine array describing + changes at the request level ("only guaranteed") or product level ("increase this product's + budget"). + + The seller must apply each refinement, return updated products, and include + refinement_applied showing how each request was handled. This is the standard negotiation + loop — brief, review, refine, repeat until satisfied. + + Every media buy seller that supports negotiation (not just wholesale) must handle + product-level refinement. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + examples: + - "Any media buy seller that accepts briefs" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: setup + title: "Account setup" + steps: + - id: sync_accounts + title: "Establish account" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id and status active. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#media_buy_seller_refine_products_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: brief + title: "Initial brief" + narrative: | + Send a brief to get the initial product set that the buyer will refine. + + steps: + - id: get_products_brief + title: "Send a brief" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. Each product should include product_id, + delivery_type, pricing_models, and forecast. + + sample_request: + buying_mode: "brief" + brief: "Premium video and display on sports and outdoor lifestyle. Q2 flight, $50K budget. Adults 25-54, US and Canada." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + - check: field_present + path: "products[0].product_id" + description: "Products have IDs" + + - id: refine + title: "Refine products" + narrative: | + The buyer has reviewed the initial products and wants to narrow down. They call + get_products with buying_mode: refine and a refine array with request-level and + product-level changes. The seller applies each refinement and returns the updated + product set. + + steps: + - id: get_products_refine + title: "Refine with request-level and product-level changes" + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return updated products reflecting the refinements: + - Apply each refinement to the relevant scope (request or product) + - Include refinement_applied showing how each request was handled + - Preserve products that weren't targeted by refinements + - Update pricing and forecasts to reflect the changes + + sample_request: + buying_mode: "refine" + refine: + - scope: "request" + ask: "Only guaranteed packages. Must include completion rate SLA above 80%." + - scope: "product" + product_id: "sports_preroll_q2" + ask: "Increase budget allocation to $30K" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains updated products" diff --git a/dist/compliance/3.0.9/protocols/media-buy/state-machine.yaml b/dist/compliance/3.0.9/protocols/media-buy/state-machine.yaml new file mode 100644 index 0000000000..ddc67b9a33 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/media-buy/state-machine.yaml @@ -0,0 +1,442 @@ +id: media_buy_state_machine +version: "1.0.0" +title: "Media buy state machine lifecycle" +category: media_buy_state_machine +summary: "Validates media buy state transitions: create, pause, resume, cancel, and terminal state enforcement." +track: media_buy +required_tools: + - create_media_buy + - update_media_buy + +narrative: | + A media buy has a well-defined state machine: pending_creatives, pending_start, active, + paused, completed, rejected, canceled. Transitions between states must follow the spec — you cannot + resume a canceled buy or pause a completed one. + + This storyboard creates a media buy, walks it through pause/resume/cancel transitions, then + verifies that the agent rejects updates to a buy in a terminal state (canceled or completed). + It also tests package-level pause/resume independent of the media buy status. + + The state machine is the backbone of media buy reliability. If an agent allows invalid + transitions, buyers cannot trust the status field and automation breaks down. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + examples: + - "Any AdCP seller with media buy support" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity for account setup. The test creates a media buy + using discovered products, then exercises state transitions. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "media_buy_state_machine--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: setup + title: "Create a media buy" + narrative: | + Discover products and create a media buy to use for state transition testing. + The media buy ID is captured and passed to subsequent phases. + + steps: + - id: discover_products + title: "Discover products for media buy" + narrative: | + Send a brief to get available products with pricing options. The first product + with a pricing option will be used to create the test media buy. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products with: + - product_id + - pricing_options with pricing_option_id + + sample_request: + buying_mode: "brief" + brief: "Display advertising products for state machine testing" + brand: + domain: "acmeoutdoor.example" + + context: + correlation_id: "media_buy_state_machine--discover_products" + context_outputs: + - name: product_id + path: 'products[0].product_id' + - name: pricing_option_id + path: 'products[0].pricing_options[0].pricing_option_id' + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products[0].product_id" + description: "At least one product with a product_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--discover_products" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_buy + title: "Create the test media buy" + narrative: | + Create a media buy using the discovered product. This buy will be used for + all subsequent state transition tests. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Return an active media buy with: + - media_buy_id + - status: active + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2026-05-01T00:00:00Z" + end_time: "2026-05-31T23:59:59Z" + packages: + - product_id: "test-product" + budget: 10000 + pricing_option_id: "test-pricing" + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_setup_create_buy" + context: + correlation_id: "media_buy_state_machine--create_buy" + context_outputs: + - name: media_buy_id + path: 'media_buy_id' + - name: package_id + path: 'packages[0].package_id' + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Response includes a media_buy_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--create_buy" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "packages[0].package_id" + description: "Seller assigns package_id — must be echoed in update_media_buy" + - id: state_transitions + title: "Valid state transitions" + narrative: | + Exercise the valid state transitions: pause, resume, and cancel. Each transition + must update the status field correctly. + + steps: + - id: pause_buy + title: "Pause the media buy" + narrative: | + Pause the active media buy. The status should transition to paused. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions to paused. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: true + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_state_transitions_pause_buy" + context: + correlation_id: "media_buy_state_machine--pause_buy" + validations: + - check: field_present + path: "status" + description: "Response includes updated status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--pause_buy" + description: "Context correlation_id returned unchanged" + - id: resume_buy + title: "Resume the media buy" + narrative: | + Resume the paused media buy. The status should transition back to active. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions back to active. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: false + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_state_transitions_resume_buy" + context: + correlation_id: "media_buy_state_machine--resume_buy" + validations: + - check: field_present + path: "status" + description: "Response includes updated status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--resume_buy" + description: "Context correlation_id returned unchanged" + - id: cancel_buy + title: "Cancel the media buy" + narrative: | + Cancel the media buy. This is a terminal transition — the buy cannot be + resumed or modified after cancellation. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Media buy status transitions to canceled. This is terminal. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_state_transitions_cancel_buy" + context: + correlation_id: "media_buy_state_machine--cancel_buy" + validations: + - check: field_present + path: "status" + description: "Response includes canceled status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--cancel_buy" + description: "Context correlation_id returned unchanged" + - id: terminal_enforcement + title: "Terminal state enforcement" + narrative: | + Verify that the agent rejects state transitions on a canceled media buy. Pausing + or resuming a terminal buy returns INVALID_STATE (generic terminal-state rule). + Re-canceling a terminal buy returns NOT_CANCELLABLE — the cancellation-specific + code takes precedence over the generic INVALID_STATE when the attempted + transition is itself a cancellation. Non-cancellation illegal transitions into + or out of terminal states still return INVALID_STATE. + + steps: + - id: pause_canceled_buy + title: "Reject pause on canceled buy" + narrative: | + Attempt to pause a canceled media buy. The agent must reject this with + INVALID_STATE. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with INVALID_STATE — cannot pause a canceled buy. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: true + idempotency_key: "$generate:uuid_v4#pause_canceled_buy" + + context: + correlation_id: "media_buy_state_machine--pause_canceled_buy" + validations: + - check: error_code + allowed_values: ["INVALID_STATE"] + description: "Invalid transition rejected with INVALID_STATE" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--pause_canceled_buy" + description: "Context correlation_id returned unchanged" + - id: resume_canceled_buy + title: "Reject resume on canceled buy" + narrative: | + Attempt to resume a canceled media buy. The agent must reject this with + INVALID_STATE. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with INVALID_STATE — cannot resume a canceled buy. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + paused: false + idempotency_key: "$generate:uuid_v4#resume_canceled_buy" + + context: + correlation_id: "media_buy_state_machine--resume_canceled_buy" + validations: + - check: error_code + allowed_values: ["INVALID_STATE"] + description: "Invalid transition rejected with INVALID_STATE" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--resume_canceled_buy" + description: "Context correlation_id returned unchanged" + - id: recancel_buy + title: "Reject re-cancel of canceled buy with NOT_CANCELLABLE" + narrative: | + Attempt to cancel an already-canceled media buy. The agent MUST reject + this with NOT_CANCELLABLE. The cancellation-specific code takes + precedence over the generic terminal-state INVALID_STATE when the + terminal update is itself a cancellation attempt — see §128/§129 of + the media-buy specification and media_buy_seller/invalid_transitions + for the canonical vector. Idempotent acceptance is NOT conformant for + this case. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: terminal_state_enforcement + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Reject with: + - code: NOT_CANCELLABLE + - recovery: correctable + - context echoed unchanged + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + canceled: true + + idempotency_key: "$generate:uuid_v4#media_buy_state_machine_terminal_enforcement_recancel_buy" + context: + correlation_id: "media_buy_state_machine--recancel_buy" + validations: + - check: error_code + value: "NOT_CANCELLABLE" + description: "Error code is NOT_CANCELLABLE on re-cancel of canceled buy" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" + - check: field_value + path: "context.correlation_id" + value: "media_buy_state_machine--recancel_buy" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/signals/index.yaml b/dist/compliance/3.0.9/protocols/signals/index.yaml new file mode 100644 index 0000000000..8301cc20f2 --- /dev/null +++ b/dist/compliance/3.0.9/protocols/signals/index.yaml @@ -0,0 +1,266 @@ +id: signals_baseline +version: "1.1.0" +title: "Signals baseline" +protocol: signals +category: signals_baseline +summary: "Baseline domain storyboard — every signals agent must discover signals and return an activation, regardless of whether they are owned or marketplace." +track: signals +required_tools: + - get_signals + - activate_signal + +narrative: | + Signals domain agents expose audience and contextual signals to buyers and + activate them on downstream destinations (DSPs, sales agents, clean rooms). + Every signals agent — whether a first-party owned platform or a third-party + marketplace — must support the same three-call flow at the protocol level: + + 1. Declare the signals protocol in get_adcp_capabilities. + 2. Respond to get_signals with a schema-valid signals array. + 3. Respond to activate_signal with a schema-valid deployments array. + + This baseline tests those three calls and nothing beyond them. Specialism + storyboards (signal-owned, signal-marketplace) exercise the richer flows + specific to each model — pricing option selection, source/provenance + discriminators, agent-destination vs. platform-destination activation, + and deactivation for compliance. + + Agents declaring supported_protocols: ["signals"] MUST pass this baseline + even before claiming a specialism. Declaring signals without exposing + get_signals or activate_signal fails the baseline with missing_tool. + +agent: + interaction_model: owned_signals + capabilities: [] + examples: + - "Any signals agent (owned or marketplace)" + - "Retailer CDPs" + - "Publisher contextual platforms" + - "Data provider marketplaces" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The buyer has a campaign brief with broad targeting objectives. The test + kit provides a sample brand (Nova Motors) with a signal description that + any signals agent should be able to interpret. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent serves signals + before issuing discovery or activation calls. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares `signals` in supported_protocols. + Without this claim the buyer will not send get_signals or + activate_signal. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring `signals` in supported_protocols. + + sample_request: + context: + correlation_id: "signals_baseline--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--get_capabilities" + description: "Context correlation_id returned unchanged" + + - id: discovery + title: "Signal discovery" + narrative: | + The buyer calls get_signals with a natural language signal_spec. Every + signals agent — owned or marketplace — must return a schema-valid + response. The buyer uses the returned signal_agent_segment_id values + to drive activation in the next phase. + + steps: + - id: search_signals + title: "Discover signals matching a spec" + narrative: | + The buyer describes a target audience in natural language. The agent + returns a list of signals from its catalog that match. Each signal + must include the fields the buyer needs to proceed to activation. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + comply_scenario: signals_flow + stateful: false + expected: | + Return a signals array with at least one entry. Each signal must + carry a signal_agent_segment_id that the buyer can pass to + activate_signal, along with pricing_options and a signal_id that + includes a source discriminator. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_spec: "Adults interested in electric vehicles" + context: + correlation_id: "signals_baseline--search_signals" + context_outputs: + - name: signal_agent_segment_id + path: "signals[0].signal_agent_segment_id" + - name: pricing_option_id + path: "signals[0].pricing_options[0].pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "First signal carries a signal_agent_segment_id" + - check: field_present + path: "signals[0].signal_id.source" + description: "Signal ID carries a source discriminator (agent_native or data_provider)" + - check: field_present + path: "signals[0].pricing_options" + description: "Signal carries pricing options the buyer can select" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--search_signals" + description: "Context correlation_id returned unchanged" + + - id: activation + title: "Signal activation" + narrative: | + The buyer activates one of the signals returned from discovery against + a destination. The protocol baseline tests only that the agent accepts + the call and returns a schema-valid deployments array — specialisms + exercise owned vs. marketplace activation patterns in depth. + + steps: + - id: activate_on_agent + title: "Activate on a sales agent destination" + narrative: | + Using the signal_agent_segment_id and pricing_option_id captured + from the previous step, the buyer activates the signal on a sales + agent destination. Every signals agent MUST accept `type: agent` + per the signals specification — the SA records the activation + internally and applies targeting in subsequent media-buy calls. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployments array with at least one entry carrying a + `type` discriminator and, for live deployments, an + `activation_key`. Agents MAY return an async deployment with + `is_live: false` and `estimated_activation_duration_minutes`. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "$context.signal_agent_segment_id" + pricing_option_id: "$context.pricing_option_id" + destinations: + - type: "agent" + agent_url: "https://wonderstruck.salesagents.example" + idempotency_key: "$generate:uuid_v4#signals_baseline_activate_agent" + + context: + correlation_id: "signals_baseline--activate_on_agent" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].type" + description: "Deployment carries a type discriminator" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--activate_on_agent" + description: "Context correlation_id returned unchanged" + + - id: activate_on_platform + title: "Activate on a platform destination" + narrative: | + The buyer re-activates the same signal against a platform + destination (a DSP). Signal agents MUST accept `type: platform` + per the signals specification — the agent pushes the segment to + the platform and returns a deployment record. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployments array with at least one entry whose + `type: "platform"` and, for live deployments, an + `activation_key` with `type: "segment_id"`. Async deployments + MAY report `is_live: false` with + `estimated_activation_duration_minutes`. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "$context.signal_agent_segment_id" + pricing_option_id: "$context.pricing_option_id" + destinations: + - type: "platform" + platform: "the-trade-desk" + account: "agency-123-ttd" + idempotency_key: "$generate:uuid_v4#signals_baseline_activate_platform" + + context: + correlation_id: "signals_baseline--activate_on_platform" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].type" + description: "Deployment carries a type discriminator" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signals_baseline--activate_on_platform" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/protocols/sponsored-intelligence/index.yaml b/dist/compliance/3.0.9/protocols/sponsored-intelligence/index.yaml new file mode 100644 index 0000000000..9846ad7bcc --- /dev/null +++ b/dist/compliance/3.0.9/protocols/sponsored-intelligence/index.yaml @@ -0,0 +1,256 @@ +id: si_baseline +version: "1.0.0" +title: "Sponsored intelligence baseline" +protocol: sponsored-intelligence +category: si_baseline +summary: "Baseline domain storyboard — every SI agent must discover offerings, initiate a session, exchange messages, and terminate cleanly." +track: si +required_tools: + - si_initiate_session + +narrative: | + You run an AI platform that supports sponsored intelligence — conversational ad experiences + embedded in AI-powered search, chat, or assistant products. A buyer agent connects to + discover what SI offerings are available, initiate a session, send messages within the + conversation, and cleanly terminate when done. + + Sponsored intelligence is fundamentally different from display or video advertising. The + ad experience is conversational — the user asks a question, the AI responds, and sponsored + content is woven into the response in a way that is transparent and relevant. + + This storyboard covers the SI session lifecycle from the buyer's perspective: discovering + what the platform offers, starting a conversation, exchanging messages, and ending the + session. + +agent: + interaction_model: si_platform + capabilities: + - sponsored_intelligence + examples: + - "Perplexity" + - "ChatGPT Search" + - "Arc Browser" + - "AI assistants with ad support" + +caller: + role: buyer_agent + example: "Nova Motors (advertiser)" + +prerequisites: + description: | + The caller needs brand context and campaign parameters for SI. The test kit provides + a sample brand (Nova Motors) with signal definitions suitable for conversational + ad experiences. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports sponsored intelligence before initiating sessions. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring sponsored_intelligence in supported_protocols, confirming the agent supports conversational ad experiences. + sample_request: + context: + correlation_id: "si_session--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: offering_discovery + title: "Discover SI offerings" + narrative: | + Before initiating any session, the buyer discovers what sponsored intelligence + offerings the platform has available. This determines what kinds of conversational + experiences can be sponsored and at what pricing. + + steps: + - id: si_get_offering + title: "Get available SI offerings" + narrative: | + The buyer calls si_get_offering to learn what conversational ad experiences + the platform supports. The response describes available offerings with pricing, + targeting options, and format specifications. + task: si_get_offering + schema_ref: "sponsored-intelligence/si-get-offering-request.json" + response_schema_ref: "sponsored-intelligence/si-get-offering-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_get_offering" + comply_scenario: si_availability + stateful: false + expected: | + Return available SI offerings: + - Offering descriptions with pricing + - Supported conversation types + - Targeting and context options + - Format specifications for sponsored content + + sample_request: + offering_id: "novamotors_conversational_v1" + context: + correlation_id: "si_session--si_get_offering" + + context_outputs: + - name: offering_id + path: 'offering_id' + validations: + - check: response_schema + description: "Response matches si-get-offering-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_get_offering" + description: "Context correlation_id returned unchanged" + - id: session_lifecycle + title: "Session lifecycle" + narrative: | + The buyer initiates a session, exchanges messages within it, and terminates + cleanly. Each session represents a single conversational ad experience — the + buyer provides context and the platform weaves sponsored content into the + conversation. + + steps: + - id: si_initiate_session + title: "Start a conversation session" + narrative: | + The buyer initiates a new SI session with campaign context. The platform + creates a session and returns a session ID that the buyer uses for subsequent + messages. + task: si_initiate_session + schema_ref: "sponsored-intelligence/si-initiate-session-request.json" + response_schema_ref: "sponsored-intelligence/si-initiate-session-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_initiate_session" + comply_scenario: si_session_lifecycle + stateful: true + expected: | + Return a new session: + - session_id: platform-assigned session identifier + - status: active + - Initial context acknowledgment + - Available interaction modes + + sample_request: + intent: "User is researching electric vehicles for long road trips and wants to talk to Nova Motors" + identity: + consent_granted: true + consent_timestamp: "2026-04-22T14:00:00Z" + user: + locale: "en-US" + + idempotency_key: "$generate:uuid_v4#si_baseline_session_lifecycle_si_initiate_session" + context: + correlation_id: "si_session--si_initiate_session" + context_outputs: + - name: session_id + path: 'session_id' + validations: + - check: response_schema + description: "Response matches si-initiate-session-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_initiate_session" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "session_id" + description: "Platform assigns session_id — must be echoed in si_send_message and si_terminate_session" + - id: si_send_message + title: "Exchange messages" + narrative: | + The buyer sends a message within the active session. The platform processes + the message and returns a response that may include sponsored content woven + into the conversational experience. + task: si_send_message + schema_ref: "sponsored-intelligence/si-send-message-request.json" + response_schema_ref: "sponsored-intelligence/si-send-message-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_send_message" + comply_scenario: si_session_lifecycle + stateful: true + expected: | + Process the message and return a response: + - Message acknowledgment + - Response content (may include sponsored elements) + - Session state (active, waiting, etc.) + + sample_request: + session_id: "$context.session_id" + message: "What are the best electric vehicles for long road trips?" + + idempotency_key: "$generate:uuid_v4#si_baseline_session_lifecycle_si_send_message" + context: + correlation_id: "si_session--si_send_message" + validations: + - check: response_schema + description: "Response matches si-send-message-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_send_message" + description: "Context correlation_id returned unchanged" + - id: si_terminate_session + title: "End the session" + narrative: | + The buyer terminates the SI session. The platform records session metrics + and returns a summary of the conversation including any sponsored content + that was delivered. + task: si_terminate_session + schema_ref: "sponsored-intelligence/si-terminate-session-request.json" + response_schema_ref: "sponsored-intelligence/si-terminate-session-response.json" + doc_ref: "/sponsored-intelligence/tasks/si_terminate_session" + comply_scenario: si_handoff + stateful: true + expected: | + Terminate the session and return a summary: + - session_id: confirms which session was terminated + - status: terminated + - Session metrics (duration, messages exchanged) + - Sponsored content delivery summary + + sample_request: + session_id: "$context.session_id" + reason: "handoff_complete" + + context: + correlation_id: "si_session--si_terminate_session" + validations: + - check: response_schema + description: "Response matches si-terminate-session-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "si_session--si_terminate_session" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/audience-sync/index.yaml b/dist/compliance/3.0.9/specialisms/audience-sync/index.yaml new file mode 100644 index 0000000000..92a87ec3a7 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/audience-sync/index.yaml @@ -0,0 +1,280 @@ +id: audience_sync +version: "1.0.0" +title: "Audience sync" +protocol: media-buy +category: audience_sync +summary: "Full audience lifecycle: account discovery, audience creation with hashed identifiers, and audience deletion." +track: audiences +required_tools: + - list_accounts + - sync_audiences + +# Cross-step assertion (adcp#2664, extended in adcp-client#782). status.monotonic +# rejects audience status transitions observed across steps that aren't on +# the spec lifecycle graph. Audience lifecycle is fully bidirectional across +# processing / ready / too_small — the graph allows ready ↔ processing on +# re-sync, ready ↔ too_small as counts cross minimum_size, and the re-sync +# path too_small → processing → ready. Off-graph regressions (e.g. a seller +# silently downgrading from ready to an unknown enum value) fail here rather +# than slipping past per-step schema checks. +invariants: + - status.monotonic + +platform_types: + - display_ad_server + - social_platform + - retail_media + - audio_platform + - dsp + - pmax_platform + +narrative: | + You support audience syncing via the sync_audiences task. Buyers push first-party + audience segments to your platform using hashed identifiers (emails, phones). Your + platform matches those identifiers against your user graph and returns match rates. + + This storyboard verifies the full audience lifecycle: discover an account to sync + against, create a test audience with hashed identifiers, and clean up by deleting it. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_audiences + examples: + - "Retail media networks" + - "Social platforms" + - "DSPs with audience onboarding" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs an active account on the seller platform. The account_setup phase + discovers or creates the account relationship before syncing audiences. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before syncing audiences. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "audience_sync--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "audience_sync--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account setup" + narrative: | + Before syncing audiences, the buyer needs an account on the seller platform. + This phase discovers an existing account via list_accounts or establishes one + via sync_accounts. + + steps: + - id: discover_account + title: "Discover or create account" + narrative: | + List existing accounts to find one suitable for audience sync. The account + must already exist and be active. The account_id is captured for use in + subsequent audience operations. + task: list_accounts + schema_ref: "account/list-accounts-request.json" + response_schema_ref: "account/list-accounts-response.json" + doc_ref: "/accounts/tasks/list_accounts" + stateful: true + expected: | + Return at least one account with: + - account_id: platform-assigned identifier + - status: active (required for audience sync) + + sample_request: + context: + correlation_id: "audience_sync--discover_account" + + validations: + - check: response_schema + description: "Response matches list-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "At least one account exists with an account_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "audience_sync--discover_account" + description: "Context correlation_id returned unchanged" + - id: audience_sync + title: "Audience sync" + narrative: | + The buyer syncs a test audience containing hashed email and phone identifiers. + The seller platform matches those identifiers and returns per-audience results + including action (created/updated), status, and match rates. + + After verifying creation, the test audience is deleted to clean up. + + steps: + - id: discover_audiences + title: "Discover existing audiences" + narrative: | + Call sync_audiences with no audiences array to discover what audiences + already exist on the platform for this account. This is a read-only + discovery call. + task: sync_audiences + schema_ref: "media-buy/sync-audiences-request.json" + response_schema_ref: "media-buy/sync-audiences-response.json" + doc_ref: "/media-buy/task-reference/sync_audiences" + comply_scenario: sync_audiences + stateful: false + expected: | + Return existing audiences for the account (may be empty). + Each audience should have an audience_id and status. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + idempotency_key: "$generate:uuid_v4#audience_sync_audience_sync_discover_audiences" + context: + correlation_id: "audience_sync--discover_audiences" + validations: + - check: response_schema + description: "Response matches sync-audiences-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "audience_sync--discover_audiences" + description: "Context correlation_id returned unchanged" + - id: create_audience + title: "Create test audience" + narrative: | + Push a test audience with hashed email and phone identifiers. The seller + matches identifiers against their user graph and returns the audience with + action: created, match counts, and match rate. + task: sync_audiences + schema_ref: "media-buy/sync-audiences-request.json" + response_schema_ref: "media-buy/sync-audiences-response.json" + doc_ref: "/media-buy/task-reference/sync_audiences" + comply_scenario: sync_audiences + stateful: true + expected: | + Accept the audience and return: + - audience_id: matches the submitted ID + - action: created + - status: active or processing + - uploaded_count: number of identifiers received + - matched_count: number of identifiers matched + - effective_match_rate: ratio of matched to uploaded + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + audiences: + - audience_id: "adcp-test-audience-001" + name: "AdCP test audience" + add: + - external_id: "adcp-user-0001" + hashed_email: "a000000000000000000000000000000000000000000000000000000000000000" + - external_id: "adcp-user-0002" + hashed_phone: "b000000000000000000000000000000000000000000000000000000000000000" + + idempotency_key: "$generate:uuid_v4#audience_sync_audience_sync_create_audience" + context: + correlation_id: "audience_sync--create_audience" + validations: + - check: response_schema + description: "Response matches sync-audiences-response.json schema" + - check: field_present + path: "audiences[0].audience_id" + description: "Audience has an audience_id" + - check: field_present + path: "audiences[0].action" + description: "Audience has an action (created or updated)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "audience_sync--create_audience" + description: "Context correlation_id returned unchanged" + - id: delete_audience + title: "Delete test audience" + narrative: | + Clean up by deleting the test audience. The seller should acknowledge the + deletion with action: deleted. + task: sync_audiences + schema_ref: "media-buy/sync-audiences-request.json" + response_schema_ref: "media-buy/sync-audiences-response.json" + doc_ref: "/media-buy/task-reference/sync_audiences" + comply_scenario: sync_audiences + stateful: true + expected: | + Delete the test audience and return: + - audience_id: matches the test audience + - action: deleted + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + audiences: + - audience_id: "adcp-test-audience-001" + delete: true + + idempotency_key: "$generate:uuid_v4#audience_sync_audience_sync_delete_audience" + context: + correlation_id: "audience_sync--delete_audience" + validations: + - check: response_schema + description: "Response matches sync-audiences-response.json schema" + - check: field_present + path: "audiences[0].action" + description: "Audience deletion acknowledged with action field" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "audience_sync--delete_audience" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/brand-rights/index.yaml b/dist/compliance/3.0.9/specialisms/brand-rights/index.yaml new file mode 100644 index 0000000000..b219781f71 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/brand-rights/index.yaml @@ -0,0 +1,350 @@ +id: brand_rights +version: "1.0.0" +title: "Brand identity and rights licensing" +protocol: brand +category: brand_rights +summary: "Brand agent that serves identity assets and licenses rights for AI-generated content." +track: core +required_tools: + - get_brand_identity +requires_scenarios: + - brand_rights/governance_denied + +narrative: | + You run a brand agent — a system that holds brand identity data (logos, colors, fonts, + tone of voice) and licenses rights for AI-generated content. A buyer agent connects to + discover your brand identity, browse available rights, acquire licenses, manage them over + time, and submit generated creatives for brand approval. + + Brand agents are the bridge between brand owners and generative AI. When a DSP or + creative platform wants to generate an ad for your brand, they call your brand agent + to get the identity guidelines, license the right to generate content, and then submit + the result for approval before it goes live. + + This storyboard covers the full brand rights lifecycle: discovering the brand, browsing + rights, acquiring a license, managing it, and approving generated content. + +agent: + interaction_model: brand_rights_holder + capabilities: + - brand_identity + - rights_licensing + examples: + - "Rights management platforms" + - "Talent agencies" + - "Brand licensing services" + - "Enterprise brand portals" + +caller: + role: buyer_agent + example: "Pinnacle Agency (creative buyer)" + +prerequisites: + description: | + The caller needs brand context for identity discovery and campaign parameters for + rights acquisition. The test kit provides a sample brand with visual identity assets. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports brand identity and rights before requesting brand data. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring brand in supported_protocols, confirming the agent serves brand identity and rights. + sample_request: + context: + correlation_id: "brand_rights--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "brand_rights--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: identity_discovery + title: "Brand identity discovery" + narrative: | + The buyer retrieves the brand's public identity — logos, colors, fonts, tone of + voice, and visual guidelines. This is the foundation for any generative content: + the buyer needs to know what the brand looks like and sounds like before generating + anything. + + steps: + - id: get_brand_identity + title: "Retrieve brand identity" + narrative: | + The buyer calls get_brand_identity to retrieve the brand's visual and verbal + identity. Public-tier access returns basic assets. Authorized access (after + account linking) provides high-resolution assets, voice configs, and detailed + tone guidelines. + task: get_brand_identity + schema_ref: "brand/get-brand-identity-request.json" + response_schema_ref: "brand/get-brand-identity-response.json" + doc_ref: "/brand-protocol/tasks/get_brand_identity" + stateful: false + expected: | + Return brand identity data: + - Logos at multiple resolutions + - Brand colors (primary, secondary, accent) + - Typography (fonts, weights, sizes) + - Tone of voice guidelines + - Visual style guidelines + + sample_request: + brand_id: "acme_outdoor" + + context: + correlation_id: "brand_rights--get_brand_identity" + + validations: + - check: response_schema + description: "Response matches brand identity schema" + - check: field_present + path: "brand_id" + description: "Response includes brand_id" + - check: field_value + path: "brand_id" + value: "acme_outdoor" + description: "Returned brand_id matches the requested brand" + - check: field_present + path: "names" + description: "Response includes brand names" + + - id: get_brand_identity_invalid + title: "Reject invalid brand ID" + narrative: | + The buyer requests identity for a brand that does not exist. The agent must + return an error rather than generating default identity data — an agent that + silently returns placeholder identity would pass schema checks but fail this + behavioral test. + task: get_brand_identity + schema_ref: "brand/get-brand-identity-request.json" + response_schema_ref: "brand/get-brand-identity-response.json" + doc_ref: "/brand-protocol/tasks/get_brand_identity" + stateful: false + expect_error: true + negative_path: payload_well_formed + expected: | + Reject the request with an error: + - Error indicates the brand was not found + - No default or placeholder identity data returned + + sample_request: + brand_id: "nonexistent_brand_xyz" + + validations: + - check: error_code + description: "Error code present for unknown brand" + + - id: rights_search + title: "Browse available rights" + narrative: | + The buyer discovers what rights are available for licensing. Rights define what + generative content can be created — image generation, video synthesis, voice + cloning, copy writing — and at what terms. + + steps: + - id: get_rights + title: "Discover available rights" + narrative: | + The buyer calls get_rights to search across the agent's full rights + roster (talent, music, stock media, etc.). `buyer_brand` identifies + the advertiser so the agent can apply compatibility filtering + (competitor exclusions, dietary conflicts from the buyer's + brand.json). The optional `brand_id` filter — which scopes results + to a specific rights-holder brand (e.g., a named athlete) — is + omitted here so the agent returns its full catalog. + task: get_rights + schema_ref: "brand/get-rights-request.json" + response_schema_ref: "brand/get-rights-response.json" + doc_ref: "/brand-protocol/tasks/get_rights" + stateful: false + expected: | + Return available rights with pricing: + - Right types (image_generation, video_synthesis, copy_writing, etc.) + - Pricing options per right + - Duration and renewal terms + - Usage constraints (impression caps, geo restrictions) + + sample_request: + query: "image generation rights for advertising" + uses: + - "ai_generated_image" + buyer_brand: + domain: "acmeoutdoor.example" + + context: + correlation_id: "brand_rights--get_rights" + context_outputs: + - path: "rights.0.rights_id" + key: "rights_id" + - path: "rights.0.pricing_options.0.pricing_option_id" + key: "pricing_option_id" + + validations: + - check: response_schema + description: "Response matches get_rights schema" + - check: field_present + path: "rights" + description: "Response includes rights array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "brand_rights--get_rights" + description: "Context correlation_id returned unchanged" + - id: rights_acquisition + title: "Acquire a rights license" + narrative: | + The buyer selects a right and acquires a license. This is the contractual commitment — + the buyer pays for the right to generate content of a specific type for a defined + period. The brand agent issues generation credentials. + + steps: + - id: acquire_rights + title: "Purchase a rights license" + narrative: | + The buyer acquires a specific right by selecting a pricing option and providing + campaign context. The brand agent validates the request, processes payment, + and returns a rights grant with generation credentials. + task: acquire_rights + schema_ref: "brand/acquire-rights-request.json" + response_schema_ref: "brand/acquire-rights-response.json" + doc_ref: "/brand-protocol/tasks/acquire_rights" + stateful: true + expected: | + Return the acquired rights grant: + - rights_id: unique identifier + - status: acquired + - Generation credentials + - Expiration date and usage limits + - Terms and constraints + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + rights_id: "$context.rights_id" + pricing_option_id: "$context.pricing_option_id" + buyer: + domain: "pinnacle-agency.example" + campaign: + description: "AI-generated display ads for Acme Outdoor summer trail gear campaign" + uses: + - "likeness" + - "commercial" + start_date: "2026-04-01" + end_date: "2026-06-30" + revocation_webhook: + url: "https://pinnacle-agency.example/webhooks/revocation" + authentication: + schemes: + - "Bearer" + credentials: "pinnacle-revocation-webhook-secret-token" + + idempotency_key: "$generate:uuid_v4#brand_rights_rights_acquisition_acquire_rights" + context: + correlation_id: "brand_rights--acquire_rights" + context_outputs: + - path: "rights_id" + key: "rights_grant_id" + + validations: + - check: response_schema + description: "Response matches acquire_rights schema" + - check: field_present + path: "rights_id" + description: "Response includes rights_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "brand_rights--acquire_rights" + description: "Context correlation_id returned unchanged" + # NOTE: update_rights and creative_approval are intentionally absent. + # - update_rights has no published JSON request/response schemas yet (spec + # describes it conceptually in rights-terms.json). + # - creative_approval is modeled in the spec as a webhook (POST to the + # approval_webhook returned from acquire_rights), not an AdCP tool. Agents + # expose it as a regular HTTP endpoint outside the MCP surface. + # Both will be added back as storyboard phases once upstream schemas land + # (see https://github.com/adcontextprotocol/adcp for tracking). + + - id: rights_enforcement + title: "Rights enforcement" + narrative: | + These steps test that the brand agent enforces rights constraints — expired + licenses, impression cap violations, and geographic restrictions. An agent that + only implements the happy path would pass the phases above but fail here. + + steps: + - id: acquire_expired_rights + title: "Reject acquisition with expired campaign dates" + narrative: | + The buyer attempts to acquire rights for a campaign with dates entirely in + the past. The brand agent must reject this — a compliant agent does not issue + licenses for expired campaigns. + task: acquire_rights + schema_ref: "brand/acquire-rights-request.json" + response_schema_ref: "brand/acquire-rights-response.json" + doc_ref: "/brand-protocol/tasks/acquire_rights" + stateful: true + expect_error: true + negative_path: payload_well_formed + expected: | + Reject the request: + - Campaign dates are in the past + - Error indicates the rights period is invalid or expired + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + rights_id: "$context.rights_id" + pricing_option_id: "$context.pricing_option_id" + buyer: + domain: "pinnacle-agency.example" + campaign: + description: "Expired campaign test" + uses: + - "commercial" + start_date: "2024-01-01" + end_date: "2024-03-31" + revocation_webhook: + url: "https://pinnacle-agency.example/webhooks/revocation" + authentication: + schemes: + - "Bearer" + credentials: "pinnacle-expired-rights-revocation-webhook-token" + idempotency_key: "$generate:uuid_v4#brand_rights_rights_enforcement_acquire_expired_rights" + + validations: + - check: error_code + description: "Error code present for expired campaign dates" diff --git a/dist/compliance/3.0.9/specialisms/brand-rights/scenarios/governance_denied.yaml b/dist/compliance/3.0.9/specialisms/brand-rights/scenarios/governance_denied.yaml new file mode 100644 index 0000000000..e65c6547e9 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/brand-rights/scenarios/governance_denied.yaml @@ -0,0 +1,204 @@ +id: brand_rights/governance_denied +version: "1.0.0" +title: "Brand agent rejects rights acquisition when governance denies" +category: brand_rights +summary: "Verifies that a brand agent propagates GOVERNANCE_DENIED when the buyer's governance plan denies a rights license." +track: brand +required_tools: + - sync_governance + - get_rights + - acquire_rights + +narrative: | + Acquiring rights is a spending event. The brand agent must consult the buyer's + governance agent before issuing a license and deny the acquisition when governance + returns denied. This keeps licensing authority consistent with media buy spending + authority — a buyer cannot commit to a generative campaign budget that exceeds the + plan even when the spending flows to a brand agent instead of a seller. + + This scenario sets up a strict $50 governance plan, registers governance with the + brand agent via sync_governance, then attempts to acquire rights whose pricing + exceeds the plan. The brand agent must return GOVERNANCE_DENIED with findings + propagated from the governance agent. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: brand_rights_holder + capabilities: + - rights_licensing + - governance_aware + examples: + - "Any brand rights agent that honors governance before licensing" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance, and a brand + agent that supports sync_governance + acquire_rights. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up strict governance plan" + steps: + - id: sync_plans + title: "Create strict governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan. + sample_request: + plans: + - plan_id: "comply-rights-gov-denied" + brand: + domain: "acmeoutdoor.example" + objectives: "Restricted plan — rights licensing test" + budget: + total: 50 + currency: "USD" + reallocation_threshold: 25 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US"] + idempotency_key: "$generate:uuid_v4#brand_rights_governance_denied_governance_plan_setup_sync_plans" + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: brand_agent_setup + title: "Register account and governance with the brand agent" + steps: + - id: sync_accounts + title: "Establish account with brand agent" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Brand agent returns the account with account_id active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#brand_rights_governance_denied_brand_agent_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with brand agent" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Brand agent acknowledges governance registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority"] + idempotency_key: "$generate:uuid_v4#brand_rights_governance_denied_brand_agent_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: rights_denied + title: "Attempt rights acquisition — governance denies" + steps: + - id: get_rights_catalog + title: "Discover rights to license" + task: get_rights + schema_ref: "brand/get-rights-request.json" + response_schema_ref: "brand/get-rights-response.json" + doc_ref: "/brand-protocol/tasks/get_rights" + stateful: false + expected: | + Return rights available for licensing, each with pricing_options. + sample_request: + buyer: + domain: "pinnacle-agency.example" + query: "licensed commercial rights for a regional outdoor retail campaign" + uses: + - "commercial" + - "endorsement" + context_outputs: + - path: "rights[0].rights_id" + key: "rights_id" + - path: "rights[0].pricing_options[0].pricing_option_id" + key: "pricing_option_id" + validations: + - check: response_schema + description: "Response matches get-rights-response.json schema" + + - id: acquire_rights_denied + title: "acquire_rights — governance denies" + task: acquire_rights + schema_ref: "brand/acquire-rights-request.json" + response_schema_ref: "brand/acquire-rights-response.json" + doc_ref: "/brand-protocol/tasks/acquire_rights" + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Brand agent rejects with: + - code: GOVERNANCE_DENIED + - findings propagated from the governance agent + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + rights_id: "$context.rights_id" + pricing_option_id: "$context.pricing_option_id" + buyer: + domain: "pinnacle-agency.example" + campaign: + description: "Governance-denied rights acquisition probe" + uses: + - "likeness" + - "commercial" + start_date: "2026-04-01" + end_date: "2026-06-30" + revocation_webhook: + url: "https://pinnacle-agency.example/webhooks/revocation" + authentication: + schemes: + - "Bearer" + credentials: "pinnacle-revocation-webhook-secret-token" + idempotency_key: "$generate:uuid_v4#brand_rights_governance_denied_acquire_rights_denied" + + context: + correlation_id: "brand_rights--governance_denied--acquire" + validations: + - check: error_code + value: "GOVERNANCE_DENIED" + description: "Error code is GOVERNANCE_DENIED" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" diff --git a/dist/compliance/3.0.9/specialisms/collection-lists/index.yaml b/dist/compliance/3.0.9/specialisms/collection-lists/index.yaml new file mode 100644 index 0000000000..770d89e341 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/collection-lists/index.yaml @@ -0,0 +1,359 @@ +id: collection_lists +version: "1.0.0" +title: "Collection lists" +protocol: governance +category: collection_lists +summary: "Curated collection lists for program-level brand safety and content targeting — create, query, update, and delete lists of content programs (shows, series, podcasts)." +track: governance +required_tools: + - create_collection_list + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph. Silent on collection-list-only runs (no tracked +# lifecycle resource), but wired so phases that touch media_buy / +# account status are automatically gated. +invariants: + - status.monotonic + +narrative: | + You run a governance agent that manages collection lists for brand safety. Unlike + property lists which operate on technical surfaces (domains, apps), collection lists + operate on content programs (shows, series, podcasts) identified by platform-independent + IDs like IMDb, Gracenote, or EIDR. + + Buyers create inclusion and exclusion lists that define which programs their ads can + and cannot appear against. Your agent resolves the base collections and filters into a + concrete list of program identifiers that sellers can cache and enforce at bid time. + + Collection lists are setup-time resources: the governance agent resolves them once and + sellers cache the result. Unlike property lists, there is no post-delivery validation + task yet — enforcement happens at serve time via the cached list, not via after-the-fact + compliance checks. This storyboard therefore exercises the full CRUD lifecycle (create, + query, update, delete) but does not test delivery validation. + +agent: + interaction_model: governance_agent + capabilities: + - collection_lists + - brand_safety + examples: + - "IAS" + - "DoubleVerify" + - "GARM-aligned platforms" + - "Brand safety services" + +caller: + role: buyer_agent + example: "Nova Motors (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and content-program knowledge (distribution IDs, + publisher identifiers, or genre taxonomies). The test kit provides a sample brand + with campaign context. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports governance + before creating or fetching collection lists. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring governance in supported_protocols, confirming + the agent provides governance services. + sample_request: + context: + correlation_id: "collection_lists--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "collection_lists--get_capabilities" + description: "Context correlation_id returned unchanged" + + - id: create_list + title: "Create collection lists" + narrative: | + The buyer creates an inclusion collection list for the campaign. The list defines + which content programs the brand is willing to advertise against, selected by + platform-independent distribution identifiers. + + steps: + - id: create_inclusion_list + title: "Create an inclusion collection list" + narrative: | + The buyer creates an inclusion list of programs the brand considers safe to + advertise against, referenced by distribution IDs (e.g. IMDb) so the selection + is portable across publishers. + task: create_collection_list + schema_ref: "collection/create-collection-list-request.json" + response_schema_ref: "collection/create-collection-list-response.json" + doc_ref: "/governance/collection/tasks/collection_lists" + comply_scenario: governance_collection_lists + stateful: true + expected: | + Return the created collection list: + - list_id: platform-assigned identifier + - collection_count reflecting the resolved programs + - auth_token issued for sellers to fetch the list + - Timestamps populated + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + brand: + domain: "novamotors.example" + name: "Nova Motors approved programs" + base_collections: + - selection_type: "distribution_ids" + identifiers: + - type: "imdb_id" + value: "tt9999901" + - type: "imdb_id" + value: "tt9999902" + - type: "imdb_id" + value: "tt9999903" + filters: + kinds: ["series"] + + idempotency_key: "$generate:uuid_v4#collection_lists_create_list_create_inclusion_list" + context: + correlation_id: "collection_lists--create_inclusion_list" + context_outputs: + - path: "list.list_id" + key: "collection_list_id" + + validations: + - check: response_schema + description: "Response matches create-collection-list-response.json schema" + - check: field_present + path: "list.list_id" + description: "Governance agent assigns list_id — must be echoed in get/update/delete" + - check: field_present + path: "auth_token" + description: "Agent issues an auth_token at creation time for seller fetches" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "collection_lists--create_inclusion_list" + description: "Context correlation_id returned unchanged" + + - id: list_and_get + title: "Query collection lists" + narrative: | + The buyer lists all collection lists for the brand and retrieves a specific list + with resolved collections. + + steps: + - id: list_collection_lists + title: "List all collection lists" + narrative: | + The buyer lists all collection lists for the brand. The response includes + list metadata without full resolved collection details. + task: list_collection_lists + schema_ref: "collection/list-collection-lists-request.json" + response_schema_ref: "collection/list-collection-lists-response.json" + doc_ref: "/governance/collection/tasks/collection_lists" + comply_scenario: governance_collection_lists + stateful: true + expected: | + Return collection list summaries: + - Array of lists with list_id, name, collection_count + - Includes the list created in the prior phase + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + name_contains: "Nova Motors" + + context: + correlation_id: "collection_lists--list_collection_lists" + validations: + - check: response_schema + description: "Response matches list-collection-lists-response.json schema" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "collection_lists--list_collection_lists" + description: "Context correlation_id returned unchanged" + + - id: get_collection_list + title: "Get a specific collection list with resolved collections" + narrative: | + The buyer retrieves a specific collection list with resolve:true to confirm + the agent can materialize the programs the list references. + task: get_collection_list + schema_ref: "collection/get-collection-list-request.json" + response_schema_ref: "collection/get-collection-list-response.json" + doc_ref: "/governance/collection/tasks/collection_lists" + comply_scenario: governance_collection_lists + stateful: true + expected: | + Return the full collection list: + - list_id, name, collection_count + - Resolved collections with distribution_ids, content_rating, genre + - cache_valid_until timestamp for seller caching + + sample_request: + list_id: "$context.collection_list_id" + resolve: true + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "collection_lists--get_collection_list" + validations: + - check: response_schema + description: "Response matches get-collection-list-response.json schema" + - check: field_present + path: "list.list_id" + description: "List id returned" + - check: field_present + path: "collections" + description: "Resolved collections returned when resolve:true" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "collection_lists--get_collection_list" + description: "Context correlation_id returned unchanged" + + - id: update_list + title: "Update collection lists" + narrative: | + The buyer modifies an existing collection list — replacing the base collections + as the campaign's content preferences evolve. + + steps: + - id: update_collection_list + title: "Update a collection list" + narrative: | + The buyer replaces the base collections on an existing list with a new set + of distribution identifiers. + task: update_collection_list + schema_ref: "collection/update-collection-list-request.json" + response_schema_ref: "collection/update-collection-list-response.json" + doc_ref: "/governance/collection/tasks/collection_lists" + comply_scenario: governance_collection_lists + stateful: true + expected: | + Return the updated collection list: + - Same list_id + - Updated collection_count reflecting the replaced base collections + - Updated updated_at timestamp + + sample_request: + list_id: "$context.collection_list_id" + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + base_collections: + - selection_type: "distribution_ids" + identifiers: + - type: "imdb_id" + value: "tt9999901" + - type: "imdb_id" + value: "tt9999904" + - type: "imdb_id" + value: "tt9999905" + - type: "imdb_id" + value: "tt9999906" + + idempotency_key: "$generate:uuid_v4#collection_lists_update_list_update_collection_list" + context: + correlation_id: "collection_lists--update_collection_list" + validations: + - check: response_schema + description: "Response matches update-collection-list-response.json schema" + - check: field_present + path: "list.list_id" + description: "Updated list retains its list_id" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "collection_lists--update_collection_list" + description: "Context correlation_id returned unchanged" + + - id: delete_list + title: "Delete a collection list" + narrative: | + The buyer removes a collection list that is no longer needed. Deleting a list + revokes the associated auth_token. + + steps: + - id: delete_collection_list + title: "Delete a collection list" + narrative: | + The buyer deletes a collection list. The governance agent removes the list + and returns confirmation. + task: delete_collection_list + schema_ref: "collection/delete-collection-list-request.json" + response_schema_ref: "collection/delete-collection-list-response.json" + doc_ref: "/governance/collection/tasks/collection_lists" + comply_scenario: governance_collection_lists + stateful: true + expected: | + Confirm deletion: + - deleted: true + - list_id: the deleted list + + sample_request: + list_id: "$context.collection_list_id" + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + + idempotency_key: "$generate:uuid_v4#collection_lists_delete_list_delete_collection_list" + context: + correlation_id: "collection_lists--delete_collection_list" + validations: + - check: response_schema + description: "Response matches delete-collection-list-response.json schema" + - check: field_value + path: "deleted" + value: true + description: "Delete returns deleted: true" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "collection_lists--delete_collection_list" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/content-standards/index.yaml b/dist/compliance/3.0.9/specialisms/content-standards/index.yaml new file mode 100644 index 0000000000..e063e3e01a --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/content-standards/index.yaml @@ -0,0 +1,572 @@ +id: content_standards +version: "1.0.0" +title: "Content standards" +protocol: governance +category: content_standards +summary: "Define creative quality rules, calibrate content against them, and validate that delivered ads met the standards." +track: governance +required_tools: + - list_content_standards + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph. Silent on content-standards-only runs (no tracked +# lifecycle resource), but wired so phases that touch creative / +# account status are automatically gated. +invariants: + - status.monotonic + +narrative: | + You run a governance agent that manages content standards — rules that define what + creative content is acceptable for a brand or campaign. Buyers create standards that + specify quality requirements, safety constraints, and brand compliance rules. Your + agent stores these standards, calibrates sample content against them, and validates + that delivered creatives met the requirements. + + Content standards complement property governance. Property lists control where ads appear; + content standards control what the ads look like. Together they form a complete brand + safety framework. + + This storyboard covers the full content standards lifecycle: defining standards, querying + them, calibrating content before launch, and validating delivery after the fact. + +agent: + interaction_model: governance_agent + capabilities: + - content_standards + - creative_quality + examples: + - "Creative quality platforms" + - "Brand safety services" + - "Ad verification platforms" + - "GARM-aligned quality tools" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and creative quality requirements. The test kit + provides a sample brand with visual assets for calibration. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports governance before registering plans or checking compliance. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring governance in supported_protocols, confirming the agent provides governance services. + sample_request: + context: + correlation_id: "content_standards--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: create_standards + title: "Define content standards" + narrative: | + The buyer creates content standards that define what creative content is acceptable. + Standards include quality requirements, safety constraints, and brand-specific rules. + + steps: + - id: create_content_standards + title: "Create content standards" + narrative: | + The buyer defines a set of content standards. These rules will be used to + evaluate creatives before and after delivery. + task: create_content_standards + schema_ref: "content-standards/create-content-standards-request.json" + response_schema_ref: "content-standards/create-content-standards-response.json" + doc_ref: "/governance/content-standards/tasks/create_content_standards" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return the created content standards: + - standards_id: platform-assigned identifier + - Rules registered with severity levels + - Status: active + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + scope: + languages_any: ["en"] + description: "Acme Outdoor creative quality standards" + policies: + - policy_id: no_violent_imagery + policy_categories: [brand_safety] + enforcement: must + policy: "No violent or controversial imagery" + - policy_id: min_display_dpi + policy_categories: [imagery_quality] + enforcement: should + channels: [display] + policy: "Minimum 72 DPI for display assets" + - policy_id: brand_color_tolerance + policy_categories: [brand_compliance] + enforcement: must + policy: "Brand colors must match palette within 5% tolerance" + + idempotency_key: "$generate:uuid_v4#content_standards_create_standards_create_content_standards" + context: + correlation_id: "content_standards--create_content_standards" + validations: + - check: response_schema + description: "Response matches create-content-standards-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--create_content_standards" + description: "Context correlation_id returned unchanged" + - id: list_and_get + title: "Query content standards" + narrative: | + The buyer lists all content standards for the account and retrieves a specific + standard to inspect its rules. + + steps: + - id: list_content_standards + title: "List all content standards" + narrative: | + The buyer lists all content standards for the brand. The response includes + standard metadata without full rule details. + task: list_content_standards + schema_ref: "content-standards/list-content-standards-request.json" + response_schema_ref: "content-standards/list-content-standards-response.json" + doc_ref: "/governance/content-standards/tasks/list_content_standards" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return content standard summaries: + - Array of standards with standards_id, name, rule_count + - Status of each standard set + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "content_standards--list_content_standards" + validations: + - check: response_schema + description: "Response matches list-content-standards-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--list_content_standards" + description: "Context correlation_id returned unchanged" + - id: get_content_standards + title: "Get a specific content standard" + narrative: | + The buyer retrieves the full details of a specific content standard, including + all rules and their severity levels. + task: get_content_standards + schema_ref: "content-standards/get-content-standards-request.json" + response_schema_ref: "content-standards/get-content-standards-response.json" + doc_ref: "/governance/content-standards/tasks/get_content_standards" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return the full content standard: + - standards_id, name + - All rules with categories, descriptions, and severity + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + + context: + correlation_id: "content_standards--get_content_standards" + validations: + - check: response_schema + description: "Response matches get-content-standards-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--get_content_standards" + description: "Context correlation_id returned unchanged" + - id: update_standards + title: "Update content standards" + narrative: | + The buyer modifies existing content standards — adding rules, adjusting severity, + or removing obsolete constraints. + + steps: + - id: update_content_standards + title: "Update content standards" + narrative: | + The buyer updates an existing content standard with new or modified rules. + task: update_content_standards + schema_ref: "content-standards/update-content-standards-request.json" + response_schema_ref: "content-standards/update-content-standards-response.json" + doc_ref: "/governance/content-standards/tasks/update_content_standards" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return the updated content standard: + - Updated rule set + - Confirmation of changes applied + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + policies: + - policy_id: no_violent_imagery + policy_categories: [brand_safety] + enforcement: must + policy: "No violent or controversial imagery" + - policy_id: min_display_dpi + policy_categories: [imagery_quality] + enforcement: should + channels: [display] + policy: "Minimum 72 DPI for display assets" + - policy_id: brand_color_tolerance + policy_categories: [brand_compliance] + enforcement: must + policy: "Brand colors must match palette within 5% tolerance" + - policy_id: image_alt_text + policy_categories: [accessibility] + enforcement: should + policy: "All images must have alt text" + + idempotency_key: "$generate:uuid_v4#content_standards_update_standards_update_content_standards" + context: + correlation_id: "content_standards--update_content_standards" + validations: + - check: response_schema + description: "Response matches update-content-standards-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--update_content_standards" + description: "Context correlation_id returned unchanged" + - id: calibration + title: "Calibrate content" + narrative: | + Before launching a campaign, the buyer calibrates sample creatives against the + content standards. This is a pre-flight check — does the creative meet the rules + before it goes live? + + steps: + - id: calibrate_content + title: "Calibrate content against standards" + narrative: | + The buyer submits sample creative content for evaluation against the content + standards. The governance agent checks each rule and returns a calibration + report indicating pass/fail per rule. + task: calibrate_content + schema_ref: "content-standards/calibrate-content-request.json" + response_schema_ref: "content-standards/calibrate-content-response.json" + doc_ref: "/governance/content-standards/tasks/calibrate_content" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return a calibration report: + - overall_pass: boolean + - Per-rule results with pass/fail and evidence + - Remediation guidance for failed rules + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + artifact: + property_rid: "test-publisher.example" + artifact_id: "display_trail_pro_300x250" + assets: + - type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + width: 300 + height: 250 + + idempotency_key: "$generate:uuid_v4#content_standards_calibration_calibrate_content" + context: + correlation_id: "content_standards--calibrate_content" + validations: + - check: response_schema + description: "Response matches calibrate-content-response.json schema" + - check: field_present + path: "verdict" + description: "Response includes calibration verdict" + + - id: must_rule_violation + title: "Must-rule violation" + narrative: | + The buyer submits content that violates a "must" severity rule in the content + standards. The governance agent must reject it — this tests that the agent + distinguishes between "must" (blocking) and "should" (advisory) severity levels. + + steps: + - id: calibrate_must_violation + title: "Calibrate content that violates a must rule" + narrative: | + The buyer submits creative content containing violent imagery, which violates + the "No violent or controversial imagery (must)" rule. The agent must return + a failing verdict with a reference to the violated rule. + task: calibrate_content + schema_ref: "content-standards/calibrate-content-request.json" + response_schema_ref: "content-standards/calibrate-content-response.json" + doc_ref: "/governance/content-standards/tasks/calibrate_content" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return a failing calibration: + - verdict: fail + - At least one rule failure referencing the violent imagery must-rule + - Remediation guidance explaining why the content was rejected + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + artifact: + property_rid: "test-publisher.example" + artifact_id: "display_violent_imagery_300x250" + description: "Display ad featuring graphic hunting scene with violent imagery" + assets: + - type: "image" + url: "https://cdn.pinnacle-agency.example/violent-hunting-scene-300x250.png" + width: 300 + height: 250 + + idempotency_key: "$generate:uuid_v4#content_standards_must_rule_violation_calibrate_must_violation" + validations: + - check: response_schema + description: "Response matches calibrate-content-response.json schema" + - check: field_value + path: "verdict" + value: "fail" + description: "Content that violates a must-rule receives fail verdict" + + - id: standards_version_change + title: "Policy version change" + narrative: | + The buyer updates the content standards to add a stricter rule, then re-calibrates + previously passing content. This tests that the agent applies the updated policy + rather than caching the old version. + + steps: + - id: update_stricter_standards + title: "Add stricter must-rule to content standards" + narrative: | + The buyer updates the content standards to require alt text on all images + as a must-severity rule (previously "should"). Content that previously passed + calibration may now fail. + task: update_content_standards + schema_ref: "content-standards/update-content-standards-request.json" + response_schema_ref: "content-standards/update-content-standards-response.json" + doc_ref: "/governance/content-standards/tasks/update_content_standards" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return the updated content standard: + - Updated policy with stricter alt text rule + - Confirmation of changes applied + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + policies: + - policy_id: no_violent_imagery + policy_categories: [brand_safety] + enforcement: must + policy: "No violent or controversial imagery" + - policy_id: min_display_dpi + policy_categories: [imagery_quality] + enforcement: should + channels: [display] + policy: "Minimum 72 DPI for display assets" + - policy_id: brand_color_tolerance + policy_categories: [brand_compliance] + enforcement: must + policy: "Brand colors must match palette within 5% tolerance" + - policy_id: image_alt_text + policy_categories: [accessibility] + enforcement: must + policy: "All images must have alt text" + - policy_id: no_stock_photography + policy_categories: [brand_compliance] + enforcement: must + policy: "No stock photography" + + idempotency_key: "$generate:uuid_v4#content_standards_standards_version_change_update_stricter_standards" + validations: + - check: response_schema + description: "Response matches update-content-standards-response.json schema" + + - id: calibrate_after_policy_change + title: "Re-calibrate content against updated standards" + narrative: | + The buyer re-calibrates the same sample content against the updated standards. + If the agent is properly applying the current policy version, content without + alt text or using stock photography should now fail. + task: calibrate_content + schema_ref: "content-standards/calibrate-content-request.json" + response_schema_ref: "content-standards/calibrate-content-response.json" + doc_ref: "/governance/content-standards/tasks/calibrate_content" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return calibration results reflecting the updated policy: + - verdict reflects evaluation against current (not cached) standards + - Rule results reference the updated policy rules + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + artifact: + property_rid: "test-publisher.example" + artifact_id: "display_stock_photo_300x250" + description: "Display ad using stock photography without alt text" + assets: + - type: "image" + url: "https://cdn.pinnacle-agency.example/stock-photo-300x250.png" + width: 300 + height: 250 + + idempotency_key: "$generate:uuid_v4#content_standards_standards_version_change_calibrate_after_policy_change" + context: + correlation_id: "content_standards--calibrate_after_policy_change" + validations: + - check: response_schema + description: "Response matches calibrate-content-response.json schema" + - check: field_value + path: "verdict" + value: "fail" + description: "Content violating updated must-rules receives fail verdict" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--calibrate_after_policy_change" + description: "Context correlation_id returned unchanged" + - id: delivery_validation + title: "Validate delivered content" + narrative: | + After the campaign runs, the buyer validates that the delivered creatives met the + content standards. The governance agent checks actual delivered content against + the rules and flags any violations. + + steps: + - id: validate_content_delivery + title: "Validate content delivery compliance" + narrative: | + The buyer submits delivery data with creative references. The governance agent + evaluates the delivered content against the content standards and returns + a compliance report. + task: validate_content_delivery + schema_ref: "content-standards/validate-content-delivery-request.json" + response_schema_ref: "content-standards/validate-content-delivery-response.json" + doc_ref: "/governance/content-standards/tasks/validate_content_delivery" + comply_scenario: governance_content_standards + stateful: true + expected: | + Return validation results: + - compliant: boolean overall status + - Per-creative compliance status + - Violations with rule references and evidence + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + standards_id: "$context.content_standards_id" + records: + - record_id: "delivery_display_001" + artifact: + property_rid: "test-publisher.example" + artifact_id: "display_trail_pro_300x250" + assets: + - type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + width: 300 + height: 250 + - record_id: "delivery_video_001" + artifact: + property_rid: "test-publisher.example" + artifact_id: "video_30s_trail_pro" + assets: + - type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + duration_ms: 30000 + + context: + correlation_id: "content_standards--validate_content_delivery" + validations: + - check: response_schema + description: "Response matches validate-content-delivery-response.json schema" + - check: field_present + path: "summary" + description: "Response includes validation summary with pass/fail counts" + - check: field_present + path: "results" + description: "Response includes per-record validation results" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "content_standards--validate_content_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/creative-ad-server/index.yaml b/dist/compliance/3.0.9/specialisms/creative-ad-server/index.yaml new file mode 100644 index 0000000000..2d0e2b3c9a --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/creative-ad-server/index.yaml @@ -0,0 +1,383 @@ +id: creative_ad_server +version: "1.1.0" +title: "Creative ad server" +protocol: creative +category: creative_ad_server +summary: "Stateful ad server with pre-loaded creatives. Generates serving tags per media buy. Optionally bills through AdCP." +track: creative +required_tools: + - build_creative + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. approved → processing on a creative asset. +invariants: + - status.monotonic + +narrative: | + You run a creative ad server — think Innovid, Flashtalking, or CM360. Your clients + have already uploaded their creatives through your UI. Buyers connect to browse your + creative library and request serving tags for their media buys. + + Your agent is stateful: creatives already exist in your system. The buyer never pushes + assets to you. Instead, they browse your library, pick creatives, and ask you to generate + tags for specific placements. For a campaign with 25 media buys, you'll generate 25 tags. + + Billing is optional. If you bill through AdCP, the buyer's account drives the + rate card: list_creatives surfaces pricing_options, build_creative returns the + applied pricing_option_id and vendor_cost, and report_usage closes the loop + after delivery. If you bill out of band (flat license, SaaS contract, bundled + enterprise agreement — CM360 is the canonical example), omit the pricing fields + and surface report_usage records as not accepted rather than fake-accepting. + response_schema validates shape in both cases; the specialism doesn't force a + billing business model on you. + + This storyboard walks through that flow from the buyer's perspective. + +agent: + interaction_model: stateful_preloaded + capabilities: + - has_creative_library + examples: + - "Innovid" + - "Flashtalking" + - "CM360" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + Creatives must already exist in the ad server's library, loaded through the + platform's own UI or API. The buyer does not push assets — they browse and + request tags for what's already there. + + Pricing is optional. Ad servers that bill through AdCP (rate-carded creative + serving) expose pricing_options on creatives and pricing_option_id on build + responses. Ad servers that bill out of band (flat license, SaaS contract, or + bundled enterprise agreement — e.g. CM360) return creatives and tags without + those fields. Both shapes are conformant; response_schema validates shape when + the fields are present. + controller_seeding: true + +fixtures: + creatives: + - creative_id: "campaign_hero_video" + status: "approved" + format_id: + id: "vast_30s" + pricing_options: + - pricing_option_id: "po_vast_30s_cpm" + pricing_model: "cpm" + rate: 0.50 + currency: "USD" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_ad_server--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_ad_server--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: browse_library + title: "Browse the creative library" + narrative: | + The buyer connects to your ad server and wants to see what creatives are + available for a campaign. They call list_creatives to browse concepts and + individual creatives in your library. + + If your agent bills through AdCP and the buyer provides an account, each + creative includes the applicable rate from the account's rate card. If you + bill out of band, pricing_options is omitted. + + steps: + - id: list_creatives + title: "Browse available creatives" + narrative: | + The buyer asks: "What creatives do you have for this advertiser?" This is + the primary entry point for ad server interactions. + + The buyer sends include_pricing=true and an account reference. If your + agent bills through AdCP, each creative returns pricing_options from the + account's rate card; vendors may offer multiple options (volume tiers, + context-specific rates, or different models per product line). If your + agent bills out of band (flat license, SaaS contract, or bundled + enterprise agreement), omit pricing_options — response_schema validates + shape either way. + task: list_creatives + schema_ref: "creative/list-creatives-request.json" + response_schema_ref: "creative/list-creatives-response.json" + doc_ref: "/creative/task-reference/list_creatives" + comply_scenario: creative_flow + stateful: true + expected: | + Return creatives from your library. Each creative should include: + - creative_id (your platform's identifier) + - format_id referencing the creative's format + - name and status (approved, pending_review, rejected) + - concept_id grouping related creatives across sizes + - pricing_options array with pricing_option_id, model, rate, currency — + only when your agent bills through AdCP and include_pricing=true with + an account provided. Agents that bill out of band omit this field. + + sample_request: + account: + account_id: "acct_acme_creative" + include_pricing: true + filters: + statuses: + - "approved" + + context: + correlation_id: "creative_ad_server--list_creatives" + validations: + - check: response_schema + description: "Response matches list-creatives-response.json schema. Validates pricing_options shape when present; absence is conformant for agents that bill out of band." + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_ad_server--list_creatives" + description: "Context correlation_id returned unchanged" + - id: list_output_formats + title: "Check available output formats" + narrative: | + The buyer checks what output formats your ad server supports. For an ad + server, this is typically the tag formats you can generate — HTML, JavaScript, + VAST, native. The input formats are less relevant because creatives are already + in your system. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_sync + stateful: false + expected: | + Return the output formats you support. For an ad server, these are typically + tag formats (HTML, JavaScript, VAST) rather than visual ad formats. + + - id: generate_tags + title: "Generate serving tags" + narrative: | + The buyer has selected creatives from your library and now needs serving tags + for their media buys. They call build_creative for each media buy/package + combination, passing the creative_id, account, and the context needed to + generate the right tag. + + If your agent bills through AdCP, the response carries pricing_option_id and + vendor_cost (zero at build time for CPM — cost accrues on impressions). If + your agent bills out of band, omit these fields; the manifest is the + required output. + + steps: + - id: build_tag + title: "Generate a tag for a media buy" + narrative: | + The buyer requests a serving tag for a specific creative, format, and media + buy. Your ad server generates a tag scoped to that placement. + + For Innovid, this produces a VAST tag for a CTV placement. For Flashtalking, + this might produce an HTML tag for a display placement. The media_buy_id and + package_id provide the trafficking context. + + Agents that bill through AdCP return pricing_option_id and vendor_cost so + the buyer knows which rate applies. Agents that bill out of band omit + those fields. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a creative manifest with the serving tag. The output should include: + - An HTML, JavaScript, or VAST asset containing the tag + - The format_id matching the target format + - Macro placeholders (CLICK_URL, CACHEBUSTER) if applicable + - pricing_option_id, vendor_cost, and currency — only when your agent + bills through AdCP. Agents billing out of band omit these fields. + + sample_request: + creative_id: "campaign_hero_video" + account: + account_id: "acct_acme_creative" + target_format_id: + agent_url: "https://your-ad-server.example.com" + id: "vast_30s" + media_buy_id: "mb_summer_campaign_001" + package_id: "pkg_ctv_premium" + + idempotency_key: "$generate:uuid_v4#creative_ad_server_generate_tags_build_tag" + context: + correlation_id: "creative_ad_server--build_tag" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.assets" + description: "Output includes a serving tag asset" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_ad_server--build_tag" + description: "Context correlation_id returned unchanged" + - id: track_delivery + title: "Track creative delivery" + narrative: | + After the campaign runs, the buyer checks how each creative performed. They call + get_creative_delivery to get variant-level delivery data — impressions, spend, and + breakdowns by creative variant. + + steps: + - id: get_delivery + title: "Get creative delivery metrics" + narrative: | + The buyer asks: "How did my creatives perform across the media buys?" Your ad + server returns delivery data per creative, including impressions, spend, and + variant-level breakdowns. + task: get_creative_delivery + schema_ref: "creative/get-creative-delivery-request.json" + response_schema_ref: "creative/get-creative-delivery-response.json" + doc_ref: "/creative/task-reference/get_creative_delivery" + comply_scenario: creative_flow + stateful: true + expected: | + Return per-creative delivery metrics including: + - Impressions and spend per creative + - Variant-level breakdowns (which version of each creative was served) + - Media buy context (which buys each creative was active on) + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "mb_summer_campaign_001" + + context: + correlation_id: "creative_ad_server--get_delivery" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches get-creative-delivery-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_ad_server--get_delivery" + description: "Context correlation_id returned unchanged" + - id: report_billing + title: "Report usage for billing" + narrative: | + After delivery, the buyer reports creative usage to the ad server. The billing + path is optional at the specialism level — agents that bill through AdCP + accept the record and reconcile against their rate card; agents that bill + out of band return accepted: 0 with an entry in the errors array pointing at + the offending usage record and a message explaining that billing is handled + out of band for this account. response_schema validates the response shape + either way. + + steps: + - id: report_usage + title: "Report impressions and cost" + narrative: | + The buyer sends a usage report covering a billing period. Each record + includes the creative_id, pricing_option_id (from the build_creative + response — omitted by agents that bill out of band), impressions served, + and the computed vendor_cost. The sample_request below is written for + the AdCP-billed case; out-of-band agents receive the same request shape + and decide the response. + + Agents that bill through AdCP verify the rate and return accepted: 1 + with empty errors. Agents that bill out of band return accepted: 0 and + populate errors with a field pointing at the offending record (e.g. + `usage[0].pricing_option_id`) and a human-readable message. Don't + fake-accept records you won't bill on — silent acceptance breaks + reconciliation for buyers who trust the response. A standard error + code for "billing is handled out of band" is not yet defined in the + spec; vendor codes are fine today. + task: report_usage + schema_ref: "account/report-usage-request.json" + response_schema_ref: "account/report-usage-response.json" + doc_ref: "/accounts/tasks/report_usage" + comply_scenario: creative_flow + stateful: true + expected: | + Return a response matching the report-usage-response.json schema. + Agents that bill through AdCP return accepted: 1 and empty errors. + Agents that bill out of band return accepted: 0 with an errors entry + pointing at the offending record and explaining that billing is + handled out of band. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" + reporting_period: + start: "2026-03-01T00:00:00Z" + end: "2026-03-31T23:59:59Z" + usage: + - account: + account_id: "acct_acme_creative" + creative_id: "campaign_hero_video" + pricing_option_id: "po_vast_30s_cpm" + impressions: 2400000 + vendor_cost: 1200.00 + currency: "USD" + + context: + correlation_id: "creative_ad_server--report_usage" + validations: + - check: response_schema + description: "Response matches report-usage-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_ad_server--report_usage" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/creative-generative/generative-seller.yaml b/dist/compliance/3.0.9/specialisms/creative-generative/generative-seller.yaml new file mode 100644 index 0000000000..d0a4cf69db --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/creative-generative/generative-seller.yaml @@ -0,0 +1,758 @@ +id: creative_generative/seller +version: "1.0.0" +title: "Generative seller agent" +category: creative_generative +summary: "Seller agent that generates creatives from briefs at buy time — no pre-built assets required." +track: media_buy +required_tools: + - get_products + - create_media_buy + - list_creative_formats + - sync_creatives + - get_media_buy_delivery +requires_scenarios: + - media_buy_seller/refine_products + - media_buy_seller/delivery_reporting + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives on a media_buy or +# approved → processing on a creative asset. +invariants: + - status.monotonic + +narrative: | + You run a generative sell-side platform — an AI ad network, generative DSP, or any system that + both sells inventory and generates creatives from a brief. The buyer doesn't upload finished + assets. Instead, they describe what they want via a creative brief, point you at a brand.json, + and your platform produces finished creatives ready for delivery. + + This is the media buy seller flow with generative creative capabilities. Your platform handles + the full lifecycle from brief to reporting, but the creative sync step accepts briefs instead + of static assets. Your formats declare what brief inputs they accept, and your platform + generates the creative — potentially asynchronously. + + A programmatic seller with generative capabilities should also support standard IAB formats + (display, video, VAST, etc.) for buyers who bring their own assets. A platform that sells + programmatic inventory but can't accept a pre-built VAST tag or display banner is incoherent. + The generative capability is additive to standard programmatic creative acceptance. + + This storyboard focuses on the generative creative delta. Governance agent registration and + proposal refinement work identically to the base media_buy_seller storyboard — see that + storyboard for those phases. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - supports_generation + - supports_guaranteed + - supports_non_guaranteed + examples: + - "OpenAds" + - "AI ad networks" + - "Generative DSPs" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity hosted at the brand's domain (brand.json) or resolvable + via AgenticAdvertising.org. The brand.json provides visual identity — logos, colors, fonts, + tone — that the generative seller uses to produce on-brand creatives. The test kit provides + a sample brand with campaign parameters. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "outdoor_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "outdoor_display_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "creative_generative--seller--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account setup" + narrative: | + Before buying anything, the buyer establishes an account relationship with your platform. + This is the same handshake as any media buy seller — the buyer identifies the brand and + operator, and you provision the account. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. Your platform should + resolve the brand via AgenticAdvertising.org to pull brand identity for creative + generation. If the brand domain doesn't resolve, return an error — don't generate + creatives for unknown brands. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier for this relationship + - action: created or updated + - status: active (instant approval) or pending_approval (requires human review) + - account_scope: operator, brand, operator_brand, or agent + - setup: URL and message if pending_approval + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#creative_generative_seller_account_setup_sync_accounts" + context: + correlation_id: "creative_generative--seller--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + - check: field_present + path: "accounts[0].status" + description: "Account has a status (active or pending_approval)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: format_discovery + title: "Format discovery" + narrative: | + The buyer discovers what your platform can accept. A generative seller's format catalog + has two parts: generative formats that accept briefs as inputs, and standard IAB formats + that accept pre-built assets. + + Generative formats declare brief asset slots. Standard formats declare image, video, or + HTML asset slots. The buyer uses this to decide whether to send a brief or upload finished + assets for each package. + + A platform selling programmatic inventory should support both. Generative-only is fine for + a pure creative agent, but a seller that takes media buys needs to accept pre-built assets + from buyers who already have creatives. + + steps: + - id: list_formats + title: "Discover creative formats" + narrative: | + The buyer asks what formats your platform supports. Your response should include both + generative formats (accepting brief inputs) and standard IAB formats (accepting image, + video, VAST, etc.). + + A programmatic seller that only returns generative formats means the buyer can never + bring their own assets. If you sell inventory, you should accept pre-built creatives + too — the generative capability is additive. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return your format catalog. It should include: + + Generative formats: + - format_id with your agent_url and a unique id (e.g., "display_300x250_generative") + - Asset slots accepting brief asset types + - Render dimensions for the output + - Description indicating this is a generative format + + Standard formats: + - Standard IAB display formats (300x250, 728x90, etc.) + - Video formats (VAST, pre-roll, etc.) if applicable + - Asset slots accepting image, video, html, etc. + + Both types should be present for a programmatic seller. Buyers who already + have creatives need to be able to upload them directly. + + sample_request: + context: + correlation_id: "creative_generative--seller--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains a formats array" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Each format has a format_id with agent_url" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--list_formats" + description: "Context correlation_id returned unchanged" + - id: product_discovery + title: "Product discovery" + narrative: | + The buyer sends a natural-language brief describing what they want to buy. Your platform + interprets the brief against your inventory and returns products with pricing, delivery + forecasts, and creative format requirements. + + Products from a generative seller should reference generative format IDs — telling the + buyer that this product accepts a brief rather than requiring pre-built assets. Products + may also reference standard formats for buyers who prefer to supply their own creatives. + + steps: + - id: get_products_brief + title: "Send a brief" + narrative: | + The buyer describes what they want. Your platform returns products. Each product's + creative_format_ids should reference formats from the catalog — some generative, + some standard. The buyer uses this to decide the creative approach per product. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. Each product should include: + - product_id: unique identifier + - name and description + - delivery_type: guaranteed or non_guaranteed + - pricing_models: available pricing options + - forecast: estimated impressions, reach + - creative_format_ids: formats this product accepts (including generative formats) + + sample_request: + buying_mode: "brief" + brief: "Premium display and video inventory on outdoor lifestyle content. Q2 flight, $50K budget. Adults 25-54, US. We want your platform to generate the creatives from our brand brief." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "creative_generative--seller--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_buy + title: "Create the media buy" + narrative: | + The buyer commits to specific products with budgets and flight dates. This is the same + create_media_buy flow as any seller — the generative aspect doesn't change the buy + creation. The buy may return pending_creatives status, indicating the buyer needs to + sync creatives (via brief) before the campaign can go live. + + steps: + - id: create_media_buy + title: "Create a media buy" + narrative: | + The buyer commits to products. The response may include pending_creatives status + for packages that require creative sync before activation. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Process the media buy request and return one of: + - completed: buy confirmed, packages may be pending_creatives + - working: platform is processing (poll for status) + - submitted: long-running async (approval workflow, IO signing) + - input-required: need more information + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "outdoor_display_q2" + budget: 30000 + pricing_option_id: "cpm_guaranteed" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + push_notification_config: + url: "https://buyer.example/webhooks/adcp" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "creative-generative-seller-webhook-secret-token" + + idempotency_key: "$generate:uuid_v4#creative_generative_seller_create_buy_create_media_buy" + context: + correlation_id: "creative_generative--seller--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: creative_brief_sync + title: "Creative brief sync" + narrative: | + This is where a generative seller diverges from the standard flow. Instead of the buyer + uploading finished assets, they send a creative brief through sync_creatives. The brief + describes campaign messaging, tone, audience, and compliance requirements. The format_id + points to a generative format that accepts brief asset types. + + Your platform resolves the brand via AgenticAdvertising.org (using the account's brand + domain), pulls brand.json for visual identity, and generates the creative. This may be + synchronous (accepted immediately) or asynchronous (pending_review while generation + completes). The buyer polls or waits for a webhook to know when the creative is ready. + + steps: + - id: sync_creatives_brief + title: "Send creative brief via sync_creatives" + narrative: | + The buyer sends a creative brief through sync_creatives, using a generative format_id + that accepts brief asset types. The account's brand domain tells your platform where + to resolve brand identity for generation. + + Your platform should actually read the operator and brand from the request — not + ignore them. If the brand domain is invalid or unresolvable, return a rejection + with a clear error rather than generating with defaults. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept the creative brief and begin generation: + - Per-creative action: created + - Per-creative status: pending_review (generation in progress) or accepted (instant) + - The brand domain from the account should be resolved via AgenticAdvertising.org + - If the brand domain is invalid, reject with a clear error + - If the brief references a non-generative format, reject with format mismatch error + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "gen_display_summer_sale" + name: "Summer Sale - Generated Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250_generative" + assets: + brief: + asset_type: "brief" + name: "Acme Outdoor Summer Sale Q2" + objective: "conversion" + tone: "Bold, adventurous, urgent" + audience: "Active adults 25-54, outdoor enthusiasts" + territory: "Summer gear clearance" + messaging: + headline: "Summer Sale — 40% Off All Gear" + tagline: "Gear up for less" + cta: "Shop Now" + key_messages: + - "40% off all trail running and hiking gear" + - "Free shipping on orders over $75" + compliance: + required_disclosures: + - text: "Sale ends June 30, 2026. Exclusions apply." + position: "footer" + jurisdictions: ["US"] + prohibited_claims: + - "Best price guaranteed" + - creative_id: "gen_video_summer_sale" + name: "Summer Sale - Generated Video 30s" + format_id: + agent_url: "https://your-platform.example.com" + id: "video_30s_generative" + assets: + brief: + asset_type: "brief" + name: "Acme Outdoor Summer Sale Q2 - Video" + objective: "awareness" + tone: "Cinematic, energetic" + audience: "Active adults 25-54" + territory: "Summer adventure" + messaging: + headline: "Your Next Adventure Starts Here" + tagline: "Acme Outdoor — Gear up for less" + cta: "Shop the Sale" + key_messages: + - "40% off all gear this summer" + compliance: + required_disclosures: + - text: "Sale ends June 30, 2026. Exclusions apply." + position: "audio" + jurisdictions: ["US"] + assignments: + - creative_id: "gen_display_summer_sale" + package_id: "outdoor_display_q2" + - creative_id: "gen_video_summer_sale" + package_id: "outdoor_video_q2" + + idempotency_key: "$generate:uuid_v4#creative_generative_seller_creative_brief_sync_sync_creatives_brief" + context: + correlation_id: "creative_generative--seller--sync_creatives_brief" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + - check: field_present + path: "creatives[0].action" + description: "Each creative has a status (pending_review or accepted)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--sync_creatives_brief" + description: "Context correlation_id returned unchanged" + - id: sync_creatives_standard + title: "Sync standard creatives alongside generative" + narrative: | + The buyer sends a pre-built creative using a standard format — verifying that this + programmatic seller also accepts traditional asset uploads alongside generative briefs. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept the standard creative: + - Per-creative action: created + - Per-creative status: accepted or pending_review + - Standard asset validation (dimensions, file size, mime type) + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "static_display_300x250" + name: "Trail Pro 3000 - Pre-built Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-300x250.png" + format: "png" + width: 300 + height: 250 + + idempotency_key: "$generate:uuid_v4#creative_generative_seller_creative_brief_sync_sync_creatives_standard" + context: + correlation_id: "creative_generative--seller--sync_creatives_standard" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Standard creative has an action" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--sync_creatives_standard" + description: "Context correlation_id returned unchanged" + - id: sync_creatives_invalid_brand + title: "Reject brief with invalid brand" + narrative: | + The buyer sends a brief referencing a brand domain that doesn't exist in + AgenticAdvertising.org. The seller should reject the creative rather than + generating with unknown brand identity. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Reject the creative with a clear error: + - Per-creative status: rejected + - Validation error indicating the brand domain could not be resolved + - The seller should not generate creatives for unknown brands + + sample_request: + account: + brand: + domain: "nonexistent-brand-xyz.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "gen_invalid_brand" + name: "Invalid Brand Test" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250_generative" + assets: + brief: + asset_type: "brief" + name: "Test brief" + objective: "awareness" + tone: "Neutral" + messaging: + headline: "Test" + cta: "Click" + + idempotency_key: "$generate:uuid_v4#creative_generative_seller_creative_brief_sync_sync_creatives_invalid_brand" + context: + correlation_id: "creative_generative--seller--sync_creatives_invalid_brand" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Creative has a status (expected: rejected)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--sync_creatives_invalid_brand" + description: "Context correlation_id returned unchanged" + - id: creative_generation_lifecycle + title: "Creative generation lifecycle" + narrative: | + If creative generation is asynchronous, the buyer monitors progress. The seller + transitions creatives through processing → pending_review → approved as generation + completes. The comply_test_controller can force these transitions for deterministic + testing via force_creative_status. + + The buyer polls by re-calling sync_creatives with the same creatives. Because + sync_creatives has upsert semantics, re-sending the same brief is idempotent — the + seller returns the current status without restarting generation. + + steps: + - id: check_creative_status + title: "Poll creative generation status" + narrative: | + The buyer re-sends the same creatives via sync_creatives. Because sync has upsert + semantics, this is idempotent — the seller returns updated statuses without + restarting generation. If the creative was pending_review after the initial sync, + it should transition to approved once generation completes. In testing, the + comply_test_controller forces this transition via force_creative_status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Return updated creative statuses: + - Generative creatives should transition from pending_review to approved + - Approved creatives should include generated assets (image URLs, VAST tags, etc.) + - The generated output should reflect the brand identity from the original brief + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "gen_display_summer_sale" + name: "Summer Sale - Generated Display 300x250" + format_id: + agent_url: "https://your-platform.example.com" + id: "display_300x250_generative" + assets: + brief: + asset_type: "brief" + name: "Acme Outdoor Summer Sale Q2" + objective: "conversion" + tone: "Bold, adventurous, urgent" + messaging: + headline: "Summer Sale — 40% Off All Gear" + cta: "Shop Now" + - creative_id: "gen_video_summer_sale" + name: "Summer Sale - Generated Video 30s" + format_id: + agent_url: "https://your-platform.example.com" + id: "video_30s_generative" + assets: + brief: + asset_type: "brief" + name: "Acme Outdoor Summer Sale Q2 - Video" + objective: "awareness" + tone: "Cinematic, energetic" + messaging: + headline: "Your Next Adventure Starts Here" + cta: "Shop the Sale" + + idempotency_key: "$generate:uuid_v4#creative_generative_seller_creative_generation_lifecycle_check_creative_status" + context: + correlation_id: "creative_generative--seller--check_creative_status" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has a current status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--check_creative_status" + description: "Context correlation_id returned unchanged" + - id: delivery_monitoring + title: "Delivery and reporting" + narrative: | + The campaign is live. Delivery monitoring is identical to any media buy seller — + the generative aspect doesn't change how delivery is reported. The buyer monitors + impressions, clicks, spend, and pacing. + + steps: + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data for the active media buy. Reporting is the same + regardless of whether creatives were generated or uploaded. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the media buy: + - Per-package delivery: impressions, clicks, spend, completion rates + - Daily breakdown if requested + - Pacing information + - Budget utilization + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "creative_generative--seller--get_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--seller--get_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/creative-generative/index.yaml b/dist/compliance/3.0.9/specialisms/creative-generative/index.yaml new file mode 100644 index 0000000000..299dc1d5cb --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/creative-generative/index.yaml @@ -0,0 +1,746 @@ +id: creative_generative +version: "1.0.0" +title: "Generative creative agent" +protocol: creative +category: creative_generative +summary: "Agent that takes a brief and generates finished creatives from scratch — no input assets required." +required_tools: + - build_creative +# Note: sync_catalogs is NOT in required_tools. The catalog_augmented_generation +# phase below is additive — brief-only generative agents (pure-prompt DSPs) pass +# the other phases without claiming catalog support. Agents that do not implement +# sync_catalogs grade that phase not_applicable rather than fail the specialism. + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. approved → processing on a creative asset. +invariants: + - status.monotonic + +narrative: | + You run a generative creative platform — an AI ad network, a generative DSP, or any system + that creates ad creatives from a natural-language brief and brand identity. The buyer doesn't + push assets to you. Instead, they describe what they want, point you at a brand.json, and + your agent produces finished creatives ready for trafficking. + + This is fundamentally different from template-based transformation (Celtra) or library-based + retrieval (Innovid). Your agent creates something new. The buyer sends a brief, optionally + with seed assets or constraints, and your platform generates creatives — potentially in + multiple formats at once. + + This storyboard walks through the generation flow: format discovery, brief-driven generation, + multi-format builds, iterative refinement, and quality progression from draft to production. + +agent: + interaction_model: stateless_generate + capabilities: + - supports_generation + examples: + - "OpenAds" + - "AI ad networks" + - "Generative DSPs" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity (brand.json at the brand's domain) for the agent + to resolve visual identity — logos, colors, fonts, tone. The test kit provides a + sample brand with campaign parameters. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_generative--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: format_discovery + title: "Discover generative formats" + narrative: | + The buyer needs to know what your agent can generate. Unlike a template platform + where formats are fixed dimensions, generative formats describe what the agent + produces — the output type, constraints, and what inputs it accepts. A generative + format might be "display_300x250_generative" that accepts a brief and produces + a finished banner, or "social_post_generative" that creates platform-native content. + + steps: + - id: discover_formats + title: "Discover available generative formats" + narrative: | + The buyer asks: "What can you generate?" Your platform returns the formats + you support. Each format describes the output type, dimensions, and what + inputs it needs (brief text, brand reference, seed images, etc.). + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_sync + stateful: false + expected: | + Return your generative formats. Each format should include: + - format_id with your agent_url and a unique id + - Human-readable name and description + - Asset slots describing what inputs are accepted (brief text, images, etc.) + - Render dimensions for the output + - Variables for any dynamic fields the buyer can control + + sample_request: + context: + correlation_id: "creative_generative--discover_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains a formats array" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Each format has a format_id with agent_url" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--discover_formats" + description: "Context correlation_id returned unchanged" + - id: generate_from_brief + title: "Generate from brief" + narrative: | + The buyer describes what they want in natural language and your agent generates + a creative from scratch. The brief includes the campaign message, target audience + context, and a brand reference so your agent can resolve visual identity. + + This is the core generative flow. The buyer doesn't provide finished assets — + they provide intent, and your agent creates. + + steps: + - id: build_draft + title: "Generate a draft creative from brief" + narrative: | + The buyer sends a brief describing the campaign and a target format. Your + agent generates a draft creative — fast, lower-fidelity output the buyer can + review before committing to a production build. + + The brief is passed as a message alongside the target format. The brand + reference lets your agent resolve brand.json for visual identity. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return a generated creative manifest: + - creative_manifest with the generated assets (images, copy, serving code) + - format_id matching the target format + - If include_preview is true, include a preview render + + The output should be a coherent creative that reflects the brief and brand + identity — not a template with placeholder text. + + sample_request: + message: "Create a display banner for a summer outdoor gear sale. Bold, adventurous tone. Headline should emphasize 40% off. Target audience: active adults 25-54." + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + quality: "draft" + include_preview: true + + idempotency_key: "$generate:uuid_v4#creative_generative_generate_from_brief_build_draft" + context: + correlation_id: "creative_generative--build_draft" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.assets" + description: "Output manifest includes generated assets" + - check: field_present + path: "creative_manifest.format_id" + description: "Output manifest includes format_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--build_draft" + description: "Context correlation_id returned unchanged" + - id: refine + title: "Refine the creative" + narrative: | + The buyer reviews the draft and wants changes. They send the generated manifest + back with refinement instructions. Your agent modifies the creative based on the + feedback — adjusting copy, swapping imagery, or changing the layout. + + This is iterative: the buyer can refine multiple times until they're satisfied, + then request a production-quality build. + + steps: + - id: refine_creative + title: "Refine with feedback" + narrative: | + The buyer passes the generated manifest back with a message describing what + to change. Your agent applies the refinements and returns an updated creative. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return a refined creative manifest reflecting the requested changes. + The output should preserve what worked in the original while applying + the refinements. + + sample_request: + message: "Make the headline larger. Replace the mountain imagery with a trail running scene. Add a CTA button that says 'Shop Now'." + creative_manifest: + format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + assets: + generated_image: + asset_type: "image" + url: "https://your-agent.example.com/generated/abc123.jpg" + width: 300 + height: 250 + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + quality: "draft" + include_preview: true + + idempotency_key: "$generate:uuid_v4#creative_generative_refine_refine_creative" + context: + correlation_id: "creative_generative--refine_creative" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.assets" + description: "Refined manifest includes assets" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--refine_creative" + description: "Context correlation_id returned unchanged" + - id: multi_format + title: "Multi-format generation" + narrative: | + The buyer needs the creative in multiple sizes. Instead of generating each + format separately, they pass target_format_ids (plural) and your agent produces + all formats in a single call. This is where generative agents shine — adapting + a concept across formats while maintaining visual coherence. + + steps: + - id: build_multi_format + title: "Generate for multiple formats" + narrative: | + The buyer passes the refined manifest with multiple target formats. Your + agent generates a creative for each format, adapting layout, copy, and + imagery to fit each size while maintaining brand consistency. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return creative manifests for each requested format in creative_manifests + (plural). Each manifest should: + - Have a format_id matching one of the target formats + - Contain complete, format-appropriate assets + - Maintain visual coherence across formats + + If a format cannot be produced, include it with an error — don't fail + the entire request. + + sample_request: + message: "Generate production-ready versions for all three sizes." + creative_manifest: + format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + assets: + generated_image: + asset_type: "image" + url: "https://your-agent.example.com/generated/abc123-refined.jpg" + width: 300 + height: 250 + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + cta: + asset_type: "text" + content: "Shop Now" + target_format_ids: + - agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + - agent_url: "https://your-agent.example.com" + id: "display_728x90_generative" + - agent_url: "https://your-agent.example.com" + id: "display_320x50_generative" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + quality: "production" + + idempotency_key: "$generate:uuid_v4#creative_generative_multi_format_build_multi_format" + context: + correlation_id: "creative_generative--build_multi_format" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifests" + description: "Response contains creative_manifests array (plural)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--build_multi_format" + description: "Context correlation_id returned unchanged" + - id: production_build + title: "Production build" + narrative: | + The buyer is satisfied with the concept and needs a final, production-quality + creative ready for trafficking. They switch quality from draft to production. + Your agent generates the finished output with full-fidelity assets. + + steps: + - id: build_production + title: "Build at production quality" + narrative: | + The buyer requests a production-quality build of the approved concept. Your + agent generates the final creative with full-fidelity rendering, polished + assets, and serving code ready for trafficking. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return a production-quality creative manifest: + - Full-fidelity generated assets + - Serving code (HTML, JavaScript, or VAST) ready for trafficking + - Provenance metadata if AI-generated (digital_source_type, ai_tool) + + sample_request: + message: "Final production build. No changes needed." + creative_manifest: + format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + assets: + generated_image: + asset_type: "image" + url: "https://your-agent.example.com/generated/abc123-refined.jpg" + width: 300 + height: 250 + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + cta: + asset_type: "text" + content: "Shop Now" + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + quality: "production" + include_preview: true + + idempotency_key: "$generate:uuid_v4#creative_generative_production_build_build_production" + context: + correlation_id: "creative_generative--build_production" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.assets" + description: "Production manifest includes assets" + - check: field_present + path: "creative_manifest.format_id" + description: "Production manifest includes format_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--build_production" + description: "Context correlation_id returned unchanged" + + - id: catalog_augmented_generation + title: "Catalog-augmented generation" + narrative: | + Generative platforms routinely hydrate catalog items into the generation context: + the buyer pushes a product catalog, and each generated creative references a specific + catalog item (via `{SKU}`, `{GTIN}`, or the catalog-item family) in its impression + trackers and click trackers. This is how generative DSPs produce per-SKU dynamic + creative at scale. + + This phase exercises the catalog-acceptance leg and emits a generative build that + includes catalog-item macros in its tracker URL assets. `preview_creative` is the + natural observation point for the runtime substitution-safety check tracked in + #2638 — when the substitution-observer contract lands, a runner can assert that + the previewed HTML contains percent-encoded catalog-item values per + `docs/creative/universal-macros#substitution-safety-catalog-item-macros`. + + steps: + - id: sync_generation_catalog + title: "Push a product catalog for generation" + narrative: | + The buyer pushes a small inline catalog. Your agent will use these items as the + hydration targets for subsequent generated creatives. + task: sync_catalogs + schema_ref: "media-buy/sync-catalogs-request.json" + response_schema_ref: "media-buy/sync-catalogs-response.json" + doc_ref: "/media-buy/task-reference/sync_catalogs" + stateful: true + expected: | + Return per-catalog results with catalog_id, action, item_count, and + items_approved counts. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + catalogs: + - catalog_id: "acme_generative_source" + type: "product" + content_id_type: "sku" + name: "Acme Outdoor — Generative source feed" + items: + - item_id: "peak_jacket_x" + title: "Peak Jacket X" + description: "Goretex 3L shell, seam-taped, 280g." + url: "https://acmeoutdoor.example/gear/peak-jacket-x" + image_url: "https://cdn.acmeoutdoor.example/gear/peak-jacket-x.jpg" + price: + amount: 449.00 + currency: "USD" + + idempotency_key: "$generate:uuid_v4#creative_generative_catalog_augmented_generation_sync_generation_catalog" + context: + correlation_id: "creative_generative--sync_generation_catalog" + validations: + - check: response_schema + description: "Response matches sync-catalogs-response.json schema" + - check: field_present + path: "catalogs[0].catalog_id" + description: "Catalog has an ID" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--sync_generation_catalog" + description: "Context correlation_id returned unchanged" + + - id: build_catalog_aware_creative + title: "Generate a creative bound to a catalog item" + narrative: | + The buyer asks your agent to build a creative for a specific catalog item. + The generated manifest MUST include tracker URLs with catalog-item macros + (`{SKU}`, `{GTIN}`) so impression-time substitution renders per-SKU output. + + Per #2620, the substitution step MUST percent-encode catalog-item values + against the RFC 3986 unreserved set — but the substitution itself happens at + serve time, not in the build_creative response. Runtime observability is + tracked in #2638. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Return a creative manifest whose tracker assets include catalog-item + macros ({SKU} or {GTIN}) and whose format references a catalog-capable + format declared by the agent. + + sample_request: + message: "Generate a 300x250 display ad for the Peak Jacket X, using our catalog feed for product fields and tracker URLs." + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + quality: "draft" + include_preview: true + + idempotency_key: "$generate:uuid_v4#creative_generative_catalog_augmented_generation_build_catalog_aware_creative" + context: + correlation_id: "creative_generative--build_catalog_aware_creative" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.format_id" + description: "Generated manifest includes format_id" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_generative--build_catalog_aware_creative" + description: "Context correlation_id returned unchanged" + + - id: catalog_substitution_safety + title: "Catalog-item macro substitution safety" + narrative: | + Per docs/creative/universal-macros#substitution-safety-catalog-item-macros, + sales and creative agents MUST percent-encode catalog-item macro values + such that only RFC 3986 `unreserved` characters remain unescaped before + substituting them into a URL context. Nested macro expansion is prohibited. + + This phase exercises the rule with five canonical attacker-shaped + catalog values drawn from the fixture at + `static/test-vectors/catalog-macro-substitution.json`: + reserved-char breakout, nested-expansion preservation, non-ASCII UTF-8, + CRLF injection, and bidi-override neutralization. Generative pipelines + have a higher risk profile on bidi-override than template pipelines + (LLM-generated copy with embedded user text can round-trip bidi + controls into attribute contexts), so this phase exercises bidi + alongside the baseline vectors. The two remaining fixture vectors — + `mixed-path-and-query-contexts` (requires the same macro in two URL + positions) and `url-scheme-injection-neutralized` (requires an + href-whole-value macro binding) — are unit-test-layer conformance only; + runtime observation in a storyboard requires specialism-specific + template shapes tracked as follow-ups. + + `build_creative`-with-`include_preview: true` is the natural observation + point on a generative specialism — the substitution-observer runner + (#2638) parses the returned `preview_html` and asserts each macro + position carries the fixture's `expected_encoded` form, not the raw + attacker bytes. + + Scope note: this phase validates substitution on the PREVIEW surface + only. Sellers with divergent preview vs impression-time substitution + paths MAY pass here while failing at serve time; serve-time attestation + or log-introspection observability is deferred (#2651, out-of-scope v1 + per the observer contract). + + The `expect_substitution_safe` step is gated on the + `substitution_observer_runner` test-kit contract (see + `test-kits/substitution-observer-runner.yaml`). Runners that do not + advertise the contract grade the step as `not_applicable` — the earlier + `sync_substitution_probe_catalog` and `build_substitution_probe_creative` + steps still run (exercising the catalog-acceptance and build paths), + but the substituted-URL assertion is skipped. + + steps: + - id: sync_substitution_probe_catalog + title: "Push a probe catalog with attacker-shaped values" + narrative: | + Push a small probe catalog whose `sku` fields contain three of the + canonical attacker-shaped values from the substitution-safety + fixture. The agent MUST accept the payload at sync_catalogs — the + encoding rule applies at substitution time, not at ingest — but + subsequent build_creative output must percent-encode each value. + task: sync_catalogs + schema_ref: "media-buy/sync-catalogs-request.json" + response_schema_ref: "media-buy/sync-catalogs-response.json" + doc_ref: "/media-buy/task-reference/sync_catalogs" + stateful: true + expected: | + Catalog accepted with per-item counts. Runner captures the + catalog_id + item_ids for the downstream assertion binding. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + catalogs: + - catalog_id: "creative_generative_substitution_probe_v1" + type: "product" + content_id_type: "sku" + name: "Substitution safety probe" + items: + # item_id names the vector; the `sku` field carries the + # attacker-shaped value that substitution must encode. + - item_id: "reserved_char_breakout" + sku: "00013&cmd=drop" + title: "Reserved-char breakout probe" + url: "https://acmeoutdoor.example/probe/reserved" + image_url: "https://cdn.acmeoutdoor.example/probe/reserved.jpg" + price: { amount: 1.00, currency: "USD" } + - item_id: "nested_expansion" + sku: "vacancy-{DEVICE_ID}-42" + title: "Nested-expansion probe" + url: "https://acmeoutdoor.example/probe/nested" + image_url: "https://cdn.acmeoutdoor.example/probe/nested.jpg" + price: { amount: 1.00, currency: "USD" } + - item_id: "non_ascii" + sku: "café-amsterdam" + title: "Non-ASCII UTF-8 probe" + url: "https://acmeoutdoor.example/probe/non-ascii" + image_url: "https://cdn.acmeoutdoor.example/probe/non-ascii.jpg" + price: { amount: 1.00, currency: "USD" } + - item_id: "crlf_injection" + sku: "abc\r\nHost: evil.example" + title: "CRLF injection probe" + url: "https://acmeoutdoor.example/probe/crlf" + image_url: "https://cdn.acmeoutdoor.example/probe/crlf.jpg" + price: { amount: 1.00, currency: "USD" } + - item_id: "bidi_override" + sku: "VIN-\u202E1234" + title: "Bidi-override probe" + url: "https://acmeoutdoor.example/probe/bidi" + image_url: "https://cdn.acmeoutdoor.example/probe/bidi.jpg" + price: { amount: 1.00, currency: "USD" } + + idempotency_key: "$generate:uuid_v4#creative_generative_catalog_substitution_safety_sync_substitution_probe_catalog" + context: + correlation_id: "creative_generative--sync_substitution_probe_catalog" + validations: + - check: response_schema + description: "Response matches sync-catalogs-response.json schema" + - check: field_present + path: "catalogs[0].catalog_id" + description: "Probe catalog accepted" + + - id: build_substitution_probe_creative + title: "Generate a creative with catalog-item macros bound to the probe" + narrative: | + Generate a draft creative that uses `{SKU}` in its impression tracker + URL and request `include_preview: true` so the substitution-observer + runner has a preview surface to inspect. + + The `message` is a natural-language brief; a generative agent that + ignores the `{SKU}` directive (generates its own macro, inlines an + encoded value, omits the tracker) fails the downstream + `expect_substitution_safe` step with `substitution_binding_missing`. + To keep that failure mode diagnostically clear, this step validates + the creative_manifest contains `{SKU}` unsubstituted in at least + one tracker asset before the observer runs. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Creative manifest returned with preview_html populated. Impression + tracker asset includes {SKU} unsubstituted in its template (to be + resolved at impression time against catalog items). + + sample_request: + message: "Generate a 300x250 display ad for the creative_generative_substitution_probe_v1 catalog. Include the `{SKU}` macro as a literal token in the impression tracker URL — the platform resolves it per-impression against catalog items." + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_generative" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + quality: "draft" + include_preview: true + + idempotency_key: "$generate:uuid_v4#creative_generative_catalog_substitution_safety_build_substitution_probe_creative" + context: + correlation_id: "creative_generative--build_substitution_probe_creative" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.format_id" + description: "Creative manifest returned" + + - id: expect_substitution_safe + title: "Assert substituted tracker URLs percent-encode attacker shapes" + narrative: | + The runner inspects the preview_html from the previous step, extracts + tracker URLs that bind `{SKU}` to a probe catalog item, and asserts + each value is percent-encoded per RFC 3986 (unreserved-whitelist). + Raw-byte leakage fails. Every declared binding MUST be observed — a + generative agent that silently strips `{SKU}` rather than + substituting it fails with `substitution_binding_missing`. + task: expect_substitution_safe + requires_contract: substitution_observer_runner + source: html_inline + source_path: "/creative_manifest/preview_html" + macro_template: "https://track.example/imp?sku={SKU}" + require_every_binding_observed: true + catalog_bindings: + # Each binding: `catalog_item_id` is the item_id in the probe + # catalog; `vector_name` is the fixture entry whose raw_value and + # expected_encoded the runner loads from the unit-test fixture. + - macro: "{SKU}" + catalog_item_id: "reserved_char_breakout" + vector_name: "reserved-character-breakout" + - macro: "{SKU}" + catalog_item_id: "nested_expansion" + vector_name: "nested-expansion-preserved-as-literal" + - macro: "{SKU}" + catalog_item_id: "non_ascii" + vector_name: "non-ascii-utf8-percent-encoding" + - macro: "{SKU}" + catalog_item_id: "crlf_injection" + vector_name: "crlf-injection-neutralized" + - macro: "{SKU}" + catalog_item_id: "bidi_override" + vector_name: "bidi-override-neutralized" diff --git a/dist/compliance/3.0.9/specialisms/creative-template/index.yaml b/dist/compliance/3.0.9/specialisms/creative-template/index.yaml new file mode 100644 index 0000000000..d2a71913da --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/creative-template/index.yaml @@ -0,0 +1,413 @@ +id: creative_template +version: "1.0.0" +title: "Creative template and transformation agent" +protocol: creative +category: creative_template +summary: "Stateless creative agent that takes assets in, applies templates, and produces tags or rendered output." +track: creative +required_tools: + - build_creative + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. approved → processing on a creative asset. +invariants: + - status.monotonic + +narrative: | + You build a creative management or rich media platform — think Celtra, a format + conversion service, or any tool that defines ad templates and transforms input assets + into finished creatives. Your agent is stateless: every call is self-contained. The + caller passes assets inline with each request, and you return the result. There is no + creative library to sync to and no persistent state between calls. + + A buyer agent connects to discover your templates, preview them with real brand assets, + and request fully built creatives for trafficking. This storyboard walks through that + flow step by step. + +agent: + interaction_model: stateless_transform + capabilities: + - supports_transformation + examples: + - "Celtra" + - "Format conversion services" + - "Rich media template platforms" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity (brand.json with colors, fonts, logos) and creative + assets that match the format's requirements (images at the right dimensions, text at + the right lengths). The test kit provides these ingredients so you can test without + assembling them yourself. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports creative operations before browsing or building creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring creative in supported_protocols, confirming the agent handles creative operations. + sample_request: + context: + correlation_id: "creative_template--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_template--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: format_exposure + title: "Format discovery" + narrative: | + The buyer's first question is: what can your platform do? They call + list_creative_formats to discover your templates — the available ad formats, + what assets each one requires, what dimensions they render at, and what + variables (dynamic fields) they support. + + For a Celtra-like platform, this is where your publisher templates (Yahoo + Lighthouse, custom slider units) and standard templates (300x250 banners, + responsive display) get exposed as AdCP-compliant format definitions. + + steps: + - id: discover_formats + title: "Discover available formats" + narrative: | + A buyer agent asks: "What creative formats do you support?" This is + the entry point for any interaction with your agent. The response tells + the buyer what templates are available, what assets each one needs, and + what the output dimensions will be. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_sync + stateful: false + expected: | + Return your supported formats. Each format must include: + - format_id with your agent_url and a unique id + - Human-readable name and description + - Asset slots with types, roles, and requirements (dimensions, file sizes, mime types) + - Render dimensions (width/height per render) + - Variables array if the format supports dynamic fields (text, colors, images) + + sample_request: + context: + correlation_id: "creative_template--discover_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains a formats array" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Each format has a format_id with agent_url" + - check: field_present + path: "formats[0].assets" + description: "Each format defines its asset requirements" + - check: field_present + path: "formats[0].renders" + description: "Each format defines its render dimensions" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_template--discover_formats" + description: "Context correlation_id returned unchanged" + - id: filter_by_type + title: "Filter formats by type" + narrative: | + The buyer narrows the search. Maybe they only want display formats, or + they need formats that accept image assets at specific dimensions. This + tests that your agent handles filter parameters correctly. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_sync + stateful: false + expected: | + Return only formats matching the filter criteria. If no formats match, + return an empty formats array — not an error. + + sample_request: + type: "display" + max_width: 728 + max_height: 90 + + context: + correlation_id: "creative_template--filter_by_type" + validations: + - check: response_schema + description: "Response matches schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_template--filter_by_type" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - id: preview + title: "Preview with real assets" + narrative: | + The buyer wants to see what the template looks like with actual content. + They pass a complete creative manifest inline — brand identity, assets, + and format reference — and your agent renders a preview. + + This is where the stateless nature matters: the buyer doesn't sync assets + to your library first. Everything needed is in the request. + + To test this, you need compatible "ingredients" — a brand.json and assets + that match your format's requirements. The test kit provides these. + + steps: + - id: preview_creative + title: "Preview a creative" + narrative: | + The buyer has chosen a format from Phase 1 and assembled a manifest with + brand assets. They call preview_creative with the full manifest inline to + see how the template renders with real content. + + For a Celtra-like platform, this is where the template gets populated with + the advertiser's images, copy, and brand colors, and a preview URL or HTML + snippet is returned. + task: preview_creative + schema_ref: "creative/preview-creative-request.json" + response_schema_ref: "creative/preview-creative-response.json" + doc_ref: "/creative/task-reference/preview_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return a preview render. The response should include: + - A preview URL (iframe-embeddable) and/or inline HTML + - Render dimensions matching the format spec + - An expiration timestamp (previews are ephemeral) + + The preview should show the template populated with the provided assets — + not a placeholder or empty template. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + request_type: "single" + creative_manifest: + format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-300x250.jpg" + width: 300 + height: 250 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + output_format: "url" + quality: "draft" + + context: + correlation_id: "creative_template--preview_creative" + validations: + - check: response_schema + description: "Response matches preview-creative-response.json schema" + - check: field_present + path: "previews[0].renders[0].preview_url" + description: "Preview includes a renderable URL" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_template--preview_creative" + description: "Context correlation_id returned unchanged" + - id: build + title: "Full build (transformation)" + narrative: | + Now the buyer needs a finished creative — a serving tag or rendered output they + can traffic in a media buy. They call build_creative with the same inline manifest + and a target format. Your agent applies the template and returns the output. + + This is where the three tag generation models come in: + - Universal tags: a single tag that adapts to any environment + - Single-placement tags: scoped to specific dimensions + - Multi-placement tags: covering multiple sizes in one tag + + The output is a creative manifest with the serving code in an HTML, JavaScript, + or VAST asset — ready for the buyer to traffic. + + steps: + - id: build_creative + title: "Build a creative from assets" + narrative: | + The buyer passes assets inline along with a target format and asks your agent + to produce a finished creative. This is the core transformation: raw assets in, + serving tag out. + + For a Celtra-like platform, this means applying the template, assembling the + rich media unit, and returning an ad tag the buyer can traffic. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return a creative manifest with the output creative. The response should include: + - A creative manifest with serving code (html, javascript, or vast asset) + - The format_id matching the target format + - Assets array with the generated output + + The output should be a valid, traffickable creative — not a preview or placeholder. + + sample_request: + creative_manifest: + format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-300x250.jpg" + width: 300 + height: 250 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + idempotency_key: "$generate:uuid_v4#creative_template_build_build_creative" + context: + correlation_id: "creative_template--build_creative" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.assets" + description: "Output manifest includes assets" + - check: field_present + path: "creative_manifest.format_id" + description: "Output manifest includes format_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_template--build_creative" + description: "Context correlation_id returned unchanged" + - id: build_multi_format + title: "Build for multiple formats" + narrative: | + The buyer needs the same creative adapted to several sizes — 300x250, 728x90, + and 320x50. Instead of making three separate calls, they pass target_format_ids + (plural) and your agent returns manifests for each. + + This tests the multi-format generation capability that makes template agents + valuable — build once, get all sizes. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: false + expected: | + Return creative manifests for each requested format. If a format cannot be + produced, include it in the response with an error — don't fail the entire + request. + + sample_request: + creative_manifest: + format_id: + agent_url: "https://your-agent.example.com" + id: "source_master" + assets: + image: + asset_type: "image" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-master.jpg" + width: 1200 + height: 628 + click_url: + asset_type: "url" + url: "https://acmeoutdoor.example/summer-sale" + headline: + asset_type: "text" + content: "Summer Sale — 40% Off All Gear" + target_format_ids: + - agent_url: "https://your-agent.example.com" + id: "display_300x250" + - agent_url: "https://your-agent.example.com" + id: "display_728x90" + - agent_url: "https://your-agent.example.com" + id: "display_320x50" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + idempotency_key: "$generate:uuid_v4#creative_template_build_build_multi_format" + context: + correlation_id: "creative_template--build_multi_format" + validations: + - check: response_schema + description: "Response matches schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "creative_template--build_multi_format" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/governance-aware-seller/index.yaml b/dist/compliance/3.0.9/specialisms/governance-aware-seller/index.yaml new file mode 100644 index 0000000000..ce4be2d241 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/governance-aware-seller/index.yaml @@ -0,0 +1,136 @@ +id: governance_aware_seller +version: "1.0.0" +title: "Governance-aware seller" +protocol: media-buy +category: governance_aware_seller +summary: "Seller agent that composes with a campaign-governance agent on the buyer side — accepts sync_governance, calls check_governance before confirming spend, and propagates governance approvals, conditions, and denials unchanged. Optional claim; pure sellers without governance composition do not claim this specialism and skip the governance scenarios as not_applicable." +track: media_buy +required_tools: + - sync_governance + - create_media_buy + +# Cross-step assertion (adcp#2639): governance-aware sellers are exactly +# the surface this assertion protects. If a seller propagates a denial +# response from its governance agent but then creates the media buy +# anyway, no per-step validation notices — this assertion does. +# Cross-step assertion (adcp#2664): status.monotonic rejects media_buy +# status transitions observed across steps that aren't on the spec +# lifecycle graph — catches an approved-with-conditions buy jumping +# from pending_creatives back to pending_start, or active → pending_creatives. +invariants: + - governance.denial_blocks_mutation + - status.monotonic +requires_scenarios: + - media_buy_seller/governance_approved + - media_buy_seller/governance_conditions + - media_buy_seller/governance_denied + - media_buy_seller/governance_denied_recovery + +narrative: | + Governance composition is an optional seller capability. A seller agent can be + fully spec-compliant without integrating with a buyer-side governance agent — + it accepts `create_media_buy`, validates the request against its own rules + (budget, inventory, creative), and returns success or a seller-scoped error + (`VALIDATION_ERROR`, `INVENTORY_UNAVAILABLE`, `TERMS_REJECTED`, etc.). The + governance handshake — `sync_governance` registration, `check_governance` + consultation before confirming spend, and `GOVERNANCE_DENIED` / governance + condition propagation — is only exercisable by a seller that has opted into + composing with the buyer's governance agent. + + Sellers that want to advertise "I honor the buyer's governance plan" claim + this specialism. The grading exercises the four scenarios that require the + governance handshake: + - `governance_approved` — seller accepts a buy that governance approved and + echoes the `governance_context` token back, + - `governance_conditions` — seller accepts a buy with conditions attached by + governance and propagates them through to the response, + - `governance_denied` — seller refuses a buy that governance denied and + returns `GOVERNANCE_DENIED` with the governance findings, + - `governance_denied_recovery` — seller accepts the corrected retry that + falls within the plan limits after the buyer reads the denial findings. + + Sellers that do not claim this specialism are graded `not_applicable` on + these scenarios rather than failed. This mirrors the pattern used by the + other governance-* specialisms (`governance-spend-authority`, + `governance-delivery-monitor`) and by `measurement-verification` — + cross-cutting capabilities that involve campaign behavior are gated behind + explicit specialism claims instead of being force-included under every + sales-* specialism. (`signed-requests` was previously listed here; it was + reclassified in 3.1 to a universal capability-gated storyboard.) + + Composition means the seller: + - accepts `sync_governance` to register the buyer's governance agent URL and + authority categories, + - calls `check_governance` on the registered agent before confirming a + spend-committing request, + - returns `GOVERNANCE_DENIED` with the governance findings propagated + unchanged when the check denies, + - echoes the `governance_context` token from approvals and surfaces + governance conditions to the buyer, + - does not silently downgrade the denial to a seller-scoped error + (`VALIDATION_ERROR` etc.) — the buyer needs to know the denial came from + its own governance agent to correct and retry. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Publisher seller that integrates sync_governance and check_governance" + - "SSP that honors buyer-side governance plans before confirming bids" + - "Retail media network that refuses buys when the buyer's governance denies" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer) with a registered governance agent" + +prerequisites: + description: | + A governance agent that supports `sync_plans` and `check_governance`, and + a seller agent that supports `sync_governance` and propagates governance + approvals, conditions, and denials unchanged. + + By default, the grading runner uses the training governance agent at + `test-agent.adcontextprotocol.org`. Override with `--governance-agent-url` + to use a custom governance agent that satisfies the campaign-governance + protocol. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "governance_aware_seller--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_aware_seller--get_capabilities" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/governance-delivery-monitor/index.yaml b/dist/compliance/3.0.9/specialisms/governance-delivery-monitor/index.yaml new file mode 100644 index 0000000000..45e1a2b993 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/governance-delivery-monitor/index.yaml @@ -0,0 +1,441 @@ +id: governance_delivery_monitor +version: "1.0.0" +title: "Campaign governance — delivery monitoring with drift detection" +protocol: governance +category: governance_delivery_monitor +summary: "Governance agent monitors delivery, detects budget drift past thresholds, and triggers re-evaluation." +track: campaign_governance +required_tools: + - check_governance + +# Cross-step assertion (adcp#2639): mid-flight drift can return a denial +# re-evaluation. The assertion gates any post-denial mutation on the +# affected plan, catching sellers that ignore a mid-campaign denial and +# keep delivering / committing new spend. +# Cross-step assertion (adcp#2664): status.monotonic rejects media_buy +# status transitions observed across steps that aren't on the spec +# lifecycle graph — paused → active → paused is fine, +# active → pending_creatives fails. +invariants: + - governance.denial_blocks_mutation + - status.monotonic + +narrative: | + After a media buy is confirmed with governance approval, the governance agent monitors + delivery. When spend drifts past a reallocation threshold — for example, one line item + is overspending while another is underspending — the governance agent triggers a delivery + phase re-evaluation. + + This storyboard covers the delivery monitoring governance flow: initial approval, + delivery metrics showing drift, governance re-check triggered by drift, and either + re-approval with adjusted conditions or a pause recommendation. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Publisher platform with governance and delivery reporting" + - "Retail media network with pacing data" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity, operator credentials, a governance agent URL, + and an active media buy with delivery data. The governance plan defines a + reallocation threshold that triggers re-evaluation. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "outdoor_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_q2" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_15s" + pricing_options: + - product_id: "outdoor_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + - product_id: "outdoor_video_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 12.0 + plans: + - plan_id: "gov_acme_delivery_monitor" + brand: + domain: "acmeoutdoor.example" + objectives: "Delivery-phase governance with drift detection and rebalancing" + budget: + total: 40000 + currency: "USD" + reallocation_threshold: 8000 + flight: + start: "2027-01-01T00:00:00Z" + end: "2027-12-31T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "drift_reevaluation" + enforcement: "must" + policy: "Re-evaluate governance if any line item drifts more than 20% from plan." + media_buys: + - media_buy_id: "mb_acme_q2_2026" + status: "active" + budget: + total: 40000 + currency: "USD" + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "governance_delivery_monitor--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_delivery_monitor--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: plan_registration + title: "Register governance plan with delivery monitoring" + narrative: | + The buyer registers a governance plan that includes delivery monitoring thresholds. + When spend drift exceeds the reallocation threshold, the governance agent triggers + a delivery-phase re-evaluation. + + steps: + - id: sync_plans + title: "Register a governance plan with reallocation threshold" + narrative: | + The buyer's governance agent registers a plan with full spending authority and + a 20% reallocation threshold. If any line item's spend drifts more than 20% + from the planned budget allocation, the governance agent re-evaluates. + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + comply_scenario: governance_delivery_monitor + stateful: true + expected: | + Acknowledge the governance plan: + - plan_id: identifier for this governance plan + - budget.reallocation_threshold: amount above which reallocation requires re-evaluation + + sample_request: + idempotency_key: "governance-delivery-monitor-sync-plans-v1" + plans: + - plan_id: "gov_acme_delivery_monitor" + brand: + domain: "acmeoutdoor.example" + objectives: "Delivery-phase governance with drift detection and rebalancing" + budget: + total: 40000 + currency: "USD" + reallocation_threshold: 8000 + flight: + start: "2027-01-01T00:00:00Z" + end: "2027-12-31T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "drift_reevaluation" + enforcement: "must" + policy: "Re-evaluate governance if any line item drifts more than 20% from plan." + + context: + correlation_id: "governance_delivery_monitor--sync_plans" + context_outputs: + - name: plan_id + path: 'plans[0].plan_id' + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_delivery_monitor--sync_plans" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "plans[0].plan_id" + description: "Governance agent assigns plan_id — must be echoed in check_governance" + - id: initial_approval + title: "Initial governance check — approved" + narrative: | + The buyer proposes a media buy within authority. The governance agent approves + the buy with the delivery monitoring conditions active. + + steps: + - id: check_governance_approved + title: "Pre-buy governance check (approved)" + narrative: | + The buyer calls check_governance with a media buy within authority. The governance + agent approves and notes that delivery monitoring is active with the 20% drift + threshold. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_delivery_monitor + stateful: true + expected: | + Return an approved governance decision: + - decision: approved + - governance_context: token for the media buy + - monitoring: delivery phase governance is active + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + tool: "create_media_buy" + payload: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + total_budget: 40000 + packages: + - product_id: "sports_ctv_q2" + budget: 20000 + - product_id: "outdoor_video_q2" + budget: 20000 + + context: + correlation_id: "governance_delivery_monitor--check_governance_approved" + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains a governance decision" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_delivery_monitor--check_governance_approved" + description: "Context correlation_id returned unchanged" + - id: create_buy + title: "Create media buy under governance approval" + narrative: | + With governance approved, the buyer creates the media buy. The seller confirms + and returns a media_buy_id the buyer will use to pull delivery metrics as the + campaign runs. + + steps: + - id: create_media_buy + title: "Create a media buy" + narrative: | + The buyer creates the media buy with the governance_context from the approved + check. The seller confirms and returns a media_buy_id that subsequent delivery + monitoring steps reference. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: governance_delivery_monitor + stateful: true + expected: | + Confirm the media buy: + - media_buy_id: platform-assigned identifier + - status: active + - packages: confirmed line items + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + governance_context: "gov_ctx_acme_delivery_approved" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 20000 + pricing_option_id: "cpm_guaranteed" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + + idempotency_key: "$generate:uuid_v4#governance_delivery_monitor_create_buy_create_media_buy" + context: + correlation_id: "governance_delivery_monitor--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + - check: field_present + path: "media_buy_id" + description: "Response contains a media_buy_id for delivery monitoring" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_delivery_monitor--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: delivery_monitoring + title: "Delivery metrics show budget drift" + narrative: | + The campaign is running. The buyer checks delivery and finds that one line item + is overspending while the other is underspending. The CTV line item has consumed + 70% of its budget at the halfway point while the video line item is at 30%. + + steps: + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data and finds budget drift. One line item is + significantly overpacing while the other is underpacing. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: governance_delivery_monitor + stateful: true + expected: | + Return delivery metrics showing drift: + - Per-package delivery with impressions, spend, and pacing + - One package overpacing (>60% spend at 50% flight) + - One package underpacing (<40% spend at 50% flight) + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "governance_delivery_monitor--get_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_delivery_monitor--get_delivery" + description: "Context correlation_id returned unchanged" + - id: drift_recheck + title: "Governance re-check — drift detected" + narrative: | + The buyer's governance agent detects that budget drift has exceeded the 20% + reallocation threshold. It triggers a delivery-phase governance check with the + current delivery data as evidence. The governance agent re-evaluates and returns + a decision — either re-approved with updated conditions or a recommendation to + pause and rebalance. + + steps: + - id: check_governance_drift + title: "Delivery-phase governance re-check (drift exceeded)" + narrative: | + The buyer calls check_governance with phase: delivery and attaches + the current delivery metrics as evidence. The governance agent evaluates the + drift against the reallocation threshold and returns a decision. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_delivery_monitor + stateful: true + expected: | + Return a governance decision about the delivery drift: + - decision: approved (with rebalancing conditions) or denied (pause recommended) + - findings: warning-severity findings noting the drift amounts + - severity: warning + - category_id: BUDGET_DRIFT_EXCEEDED + - explanation: explains which line items drifted and by how much + - conditions: if approved, conditions for rebalancing (e.g., "Reallocate $5K from CTV to video") + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + phase: "delivery" + governance_context: "gov_ctx_acme_delivery_approved" + delivery_metrics: + reporting_period: + start: "2026-05-01T00:00:00Z" + end: "2026-05-15T00:00:00Z" + spend: 20000 + cumulative_spend: 20000 + channel_distribution: + ctv: 70 + video: 30 + pacing: "ahead" + + context: + correlation_id: "governance_delivery_monitor--check_governance_drift" + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains a governance decision" + - check: field_present + path: "findings" + description: "Response contains findings about the drift" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_delivery_monitor--check_governance_drift" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/governance-spend-authority/denied.yaml b/dist/compliance/3.0.9/specialisms/governance-spend-authority/denied.yaml new file mode 100644 index 0000000000..65816831c7 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/governance-spend-authority/denied.yaml @@ -0,0 +1,221 @@ +id: governance_spend_authority/denied +version: "1.0.0" +title: "Campaign governance — denied" +category: governance_spend_authority +summary: "Governance agent denies a media buy that exceeds the agent's spending authority. No human escalation — the buy is blocked." +track: campaign_governance +required_tools: + - sync_plans + - check_governance + +# Cross-step assertion (adcp#2639): once a plan is denied, no subsequent +# step in the same run may acquire a resource for that plan. Catches the +# failure mode where a seller surfaces the denial response but still +# creates the media buy / activates the signal / syncs the property list +# anyway. Plan-scoped — unrelated plans proceed normally. +# Cross-step assertion (adcp#2664): status.monotonic rejects media_buy +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives. +invariants: + - governance.denial_blocks_mutation + - status.monotonic + +narrative: | + The buyer's governance agent registers a plan with strict spending authority. The buyer + then proposes a media buy that exceeds the agent's per-transaction threshold. The + governance agent denies the buy outright with a must-severity finding. + + Unlike the escalation storyboard, there is no human override here. The denial is final. + The buyer must reduce the buy amount or restructure into smaller transactions that fall + within the agent's authority. This tests the hard-stop governance path. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Publisher platform with governance support" + - "Retail media network" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity, operator credentials, and a governance agent URL. + The governance plan must define a per-transaction threshold below the intended buy amount. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "governance_spend_authority--denied--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--denied--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: plan_registration + title: "Register governance plan" + narrative: | + The buyer registers a governance plan with strict spending authority. The plan sets + an agent_limited authority level with a $10K per-transaction threshold. Any buy above + this amount is denied without escalation. + + steps: + - id: sync_plans + title: "Register a governance plan with strict authority" + narrative: | + The buyer's governance agent registers a plan with a $10K per-transaction limit + and no escalation path. Buys that exceed this threshold are denied outright. + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + comply_scenario: governance_spend_authority/denied + stateful: true + expected: | + Acknowledge the governance plan: + - plan_id: identifier for this governance plan + - budget.total: $10K cap that any single buy above will exceed + + sample_request: + idempotency_key: "governance-spend-authority-denied-sync-plans-v1" + plans: + - plan_id: "gov_acme_strict" + brand: + domain: "acmeoutdoor.example" + objectives: "Acme Outdoor low-budget validation flight" + budget: + total: 10000 + currency: "USD" + reallocation_threshold: 0 + flight: + start: "2027-01-01T00:00:00Z" + end: "2027-12-31T23:59:59Z" + countries: ["US"] + + context: + correlation_id: "governance_spend_authority--denied--sync_plans" + context_outputs: + - name: plan_id + path: 'plans[0].plan_id' + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--denied--sync_plans" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "plans[0].plan_id" + description: "Governance agent assigns plan_id — must be echoed in check_governance" + - id: governance_check + title: "Governance check — denied" + narrative: | + The buyer proposes a $50K media buy against the $10K threshold. The governance agent + evaluates the binding and returns a denied decision with a must-severity finding + explaining the spending authority violation. No escalation instructions are provided + because the plan has no escalation path. + + steps: + - id: check_governance_denied + title: "Pre-buy governance check (denied, no escalation)" + narrative: | + The buyer calls check_governance with a $50K media buy binding. The governance + agent denies the buy because the total exceeds the $10K per-transaction authority. + The response includes a critical-severity finding with the violation details. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_spend_authority/denied + stateful: true + expected: | + Return a denied governance decision: + - decision: denied + - findings: array with at least one critical-severity finding + - severity: critical + - category_id: SPENDING_AUTHORITY_EXCEEDED + - explanation: explains the threshold and how much the buy exceeds it + - No escalation instructions (plan has no escalation path) + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + tool: "create_media_buy" + payload: + idempotency_key: "$generate:uuid_v4#governance_spend_authority_denied_check_governance_denied_payload" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + start_time: "2027-01-01T00:00:00Z" + end_time: "2027-03-31T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 30000 + pricing_option_id: "cpm_standard" + - product_id: "outdoor_video_q2" + budget: 20000 + pricing_option_id: "cpm_standard" + + context: + correlation_id: "governance_spend_authority--denied--check_governance_denied" + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains a governance decision" + - check: field_value + path: "status" + value: "denied" + description: "Decision is denied" + - check: field_present + path: "findings" + description: "Response contains findings explaining the denial" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--denied--check_governance_denied" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/governance-spend-authority/index.yaml b/dist/compliance/3.0.9/specialisms/governance-spend-authority/index.yaml new file mode 100644 index 0000000000..437b4bcfba --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/governance-spend-authority/index.yaml @@ -0,0 +1,330 @@ +id: governance_spend_authority +version: "1.0.0" +title: "Campaign governance — conditional approval" +protocol: governance +category: governance_spend_authority +summary: "Governance agent approves a media buy with conditions. Buyer re-checks after meeting the conditions." +track: campaign_governance +required_tools: + - sync_plans + - check_governance + +# Cross-step assertion (adcp#2639): silent on approval runs; fires only +# if a seller surfaces a denial signal mid-flow and then still mutates +# the plan's resources. Kept wired here so any future "conditions not +# met → denied" phase added to this storyboard is automatically gated. +# Cross-step assertion (adcp#2664): status.monotonic rejects media_buy +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives. +invariants: + - governance.denial_blocks_mutation + - status.monotonic + +narrative: | + The buyer's governance agent evaluates a media buy that falls within spending authority but + triggers policy conditions — for example, the buy targets a channel that requires weekly + reporting, or the creative format requires brand safety review. + + The governance agent returns approved_with_conditions, attaching conditions the buyer must + honor during the campaign. The buyer can proceed with the media buy by passing the + governance context, but the conditions are binding. + + This storyboard tests the middle path between outright approval and denial: the buy is + allowed, but with strings attached. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - governance_aware + examples: + - "Publisher platform with governance support" + - "SSP that respects governance checks" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity, operator credentials, and a governance agent URL. + The governance plan defines policy conditions that trigger on specific buy parameters. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_ctv_q2" + delivery_type: "guaranteed" + channels: ["ctv"] + format_ids: + - id: "video_30s" + - product_id: "lifestyle_display_q2" + delivery_type: "guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + pricing_options: + - product_id: "sports_ctv_q2" + pricing_option_id: "cpm_guaranteed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 45.0 + - product_id: "lifestyle_display_q2" + pricing_option_id: "cpm_standard" + pricing_model: "cpm" + currency: "USD" + fixed_price: 8.0 + plans: + - plan_id: "gov_acme_spend_authority_q2_2027" + brand: + domain: "acmeoutdoor.example" + objectives: "Full spending authority with conditional policies on CTV reporting and UGC brand safety" + budget: + total: 100000 + currency: "USD" + reallocation_unlimited: true + flight: + start: "2027-01-01T00:00:00Z" + end: "2027-12-31T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "ctv_weekly_reporting" + enforcement: "must" + policy: "CTV buys require weekly delivery reporting." + - policy_id: "ugc_brand_safety" + enforcement: "must" + policy: "UGC placements require brand safety review before go-live." + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "governance_spend_authority--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: plan_registration + title: "Register governance plan with policy conditions" + narrative: | + The buyer registers a governance plan that allows the agent full spending authority + but attaches policy conditions for specific channels or formats. Buys that trigger + these policies are approved with conditions rather than denied. + + steps: + - id: sync_plans + title: "Register a governance plan with policy conditions" + narrative: | + The buyer's governance agent registers a plan with full spending authority but + custom policies that require weekly reporting for CTV buys and brand safety + review for user-generated content placements. + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + comply_scenario: governance_spend_authority + stateful: true + expected: | + Acknowledge the governance plan: + - plan_id: identifier for this governance plan + - custom_policies: policy conditions registered + + sample_request: + idempotency_key: "governance-spend-authority-sync-plans-v1" + plans: + - plan_id: "gov_acme_spend_authority_q2_2027" + brand: + domain: "acmeoutdoor.example" + objectives: "Full spending authority with conditional policies on CTV reporting and UGC brand safety" + budget: + total: 100000 + currency: "USD" + reallocation_unlimited: true + flight: + start: "2027-01-01T00:00:00Z" + end: "2027-12-31T23:59:59Z" + countries: ["US"] + custom_policies: + - policy_id: "ctv_weekly_reporting" + enforcement: "must" + policy: "CTV buys require weekly delivery reporting." + - policy_id: "ugc_brand_safety" + enforcement: "must" + policy: "UGC placements require brand safety review before go-live." + + context: + correlation_id: "governance_spend_authority--sync_plans" + context_outputs: + - name: plan_id + path: 'plans[0].plan_id' + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--sync_plans" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "plans[0].plan_id" + description: "Governance agent assigns plan_id — must be echoed in check_governance" + - check: field_present + path: "plans[0].version" + description: "Plan includes version for concurrency" + - id: governance_check_conditions + title: "Governance check — approved with conditions" + narrative: | + The buyer proposes a media buy that includes CTV inventory. The governance agent + approves the buy but attaches the weekly reporting condition from the plan. The + buyer receives a governance context that encodes these conditions. + + steps: + - id: check_governance_conditions + title: "Pre-buy governance check (approved with conditions)" + narrative: | + The buyer calls check_governance with a media buy that includes CTV products. + The governance agent approves the buy but attaches conditions: weekly delivery + reporting is required for the CTV line items. + task: check_governance + schema_ref: "governance/check-governance-request.json" + response_schema_ref: "governance/check-governance-response.json" + doc_ref: "/governance/campaign/tasks/check_governance" + comply_scenario: governance_spend_authority + stateful: true + expected: | + Return an approved governance decision with conditions: + - decision: approved + - conditions: array of requirements the buyer must honor + - e.g., "Weekly delivery reporting required for CTV line items" + - governance_context: token the buyer passes to create_media_buy + - findings: may include warning-severity findings noting the conditions + + sample_request: + plan_id: "$context.plan_id" + caller: "https://pinnacle-agency.example" + tool: "create_media_buy" + payload: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + total_budget: 40000 + packages: + - product_id: "sports_ctv_q2" + budget: 25000 + - product_id: "lifestyle_display_q2" + budget: 15000 + + context: + correlation_id: "governance_spend_authority--check_governance_conditions" + validations: + - check: response_schema + description: "Response matches check-governance-response.json schema" + - check: field_present + path: "status" + description: "Response contains a governance decision" + - check: field_present + path: "conditions" + description: "Approval includes conditions" + - check: field_present + path: "governance_context" + description: "Response includes governance context for the media buy" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--check_governance_conditions" + description: "Context correlation_id returned unchanged" + - id: create_buy_with_conditions + title: "Create media buy with governance conditions" + narrative: | + The buyer creates the media buy, passing the governance context from the conditional + approval. The seller validates the governance approval and confirms the buy. The + conditions from the governance check are now binding for the campaign duration. + + steps: + - id: create_media_buy + title: "Create a media buy with conditional governance approval" + narrative: | + The buyer creates the media buy with the governance_context token from the + conditional approval. The seller confirms the buy. The buyer is now bound by + the conditions (weekly reporting for CTV line items). + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: governance_spend_authority + stateful: true + expected: | + Confirm the media buy with governance approval: + - media_buy_id: your platform's identifier + - status: active + - governance_context: echoed back confirming governance was validated + - packages: line items + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + governance_context: "gov_ctx_acme_conditional_approved" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_ctv_q2" + budget: 25000 + pricing_option_id: "cpm_guaranteed" + - product_id: "lifestyle_display_q2" + budget: 15000 + pricing_option_id: "cpm_standard" + + idempotency_key: "$generate:uuid_v4#governance_spend_authority_create_buy_with_conditions_create_media_buy" + context: + correlation_id: "governance_spend_authority--create_media_buy" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "governance_spend_authority--create_media_buy" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/property-lists/index.yaml b/dist/compliance/3.0.9/specialisms/property-lists/index.yaml new file mode 100644 index 0000000000..24fa47926d --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/property-lists/index.yaml @@ -0,0 +1,482 @@ +id: property_lists +version: "1.0.0" +title: "Property lists" +protocol: governance +category: property_lists +summary: "Curated property lists for inventory grouping, targeting governance, and delivery compliance — create, query, update, delete, and validate." +track: governance +required_tools: + - create_property_list + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph. Silent on property-list-only runs (no tracked +# lifecycle resource), but wired so phases that touch media_buy / +# account status (e.g. via a validation run against delivery) are +# automatically gated. +invariants: + - status.monotonic + +narrative: | + You run a governance agent that manages property lists for brand safety. Buyers create + inclusion and exclusion lists that define where their ads can and cannot appear. Your + agent stores these lists, lets buyers query and update them, and validates that actual + ad delivery complied with the property constraints. + + Property governance is how brands control their environment. An inclusion list says + "only show my ads on these properties." An exclusion list says "never show my ads here." + The validation step checks after the fact: did the seller actually respect the lists? + + This storyboard covers the full property list lifecycle: creating lists, querying them, + updating and deleting, and validating that delivery matched the constraints. + +agent: + interaction_model: governance_agent + capabilities: + - property_lists + - brand_safety + examples: + - "IAS" + - "DoubleVerify" + - "GARM-aligned platforms" + - "Brand safety services" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and property domain knowledge. The test kit + provides a sample brand with campaign context. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports governance before registering plans or checking compliance. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring governance in supported_protocols, confirming the agent provides governance services. + sample_request: + context: + correlation_id: "property_lists--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: create_list + title: "Create property lists" + narrative: | + The buyer creates inclusion and exclusion property lists for the campaign. These + define the safe and unsafe environments for the brand's ads. + + steps: + - id: create_inclusion_list + title: "Create an inclusion list" + narrative: | + The buyer creates an inclusion list specifying which properties (domains, + apps, channels) are approved for ad placement. + task: create_property_list + schema_ref: "property/create-property-list-request.json" + response_schema_ref: "property/create-property-list-response.json" + doc_ref: "/governance/property/tasks/property_lists" + comply_scenario: governance_property_lists + stateful: true + expected: | + Return the created property list: + - list_id: platform-assigned identifier + - list_type: inclusion + - Properties registered + - Status: active + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + brand: + domain: "acmeoutdoor.example" + name: "Acme Outdoor approved properties" + base_properties: + - selection_type: "identifiers" + identifiers: + - type: "domain" + value: "outdoormagazine.example" + - type: "domain" + value: "hikingtrails.example" + - type: "domain" + value: "campinggear.example" + + idempotency_key: "$generate:uuid_v4#property_lists_create_list_create_inclusion_list" + context: + correlation_id: "property_lists--create_inclusion_list" + context_outputs: + - path: "list.list_id" + key: "property_list_id" + + validations: + - check: response_schema + description: "Response matches create-property-list-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--create_inclusion_list" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "list.list_id" + description: "Governance agent assigns list_id — must be echoed in get/update/delete" + - id: list_and_get + title: "Query property lists" + narrative: | + The buyer lists all property lists for the account and retrieves a specific list + to inspect its contents. + + steps: + - id: list_property_lists + title: "List all property lists" + narrative: | + The buyer lists all property lists for the brand. The response includes list + metadata (name, type, property count) without full property details. + task: list_property_lists + schema_ref: "property/list-property-lists-request.json" + response_schema_ref: "property/list-property-lists-response.json" + doc_ref: "/governance/property/tasks/property_lists" + comply_scenario: property_list_filters + stateful: true + expected: | + Return property list summaries: + - Array of lists with list_id, name, list_type, property_count + - Includes both inclusion and exclusion lists + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + name_contains: "Acme Outdoor" + + context: + correlation_id: "property_lists--list_property_lists" + validations: + - check: response_schema + description: "Response matches list-property-lists-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--list_property_lists" + description: "Context correlation_id returned unchanged" + - id: get_property_list + title: "Get a specific property list" + narrative: | + The buyer retrieves the full details of a specific property list, including + all properties in the list. + task: get_property_list + schema_ref: "property/get-property-list-request.json" + response_schema_ref: "property/get-property-list-response.json" + doc_ref: "/governance/property/tasks/property_lists" + comply_scenario: governance_property_lists + stateful: true + expected: | + Return the full property list: + - list_id, name, list_type + - All properties in the list with their details + + sample_request: + list_id: "$context.property_list_id" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "property_lists--get_property_list" + validations: + - check: response_schema + description: "Response matches get-property-list-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--get_property_list" + description: "Context correlation_id returned unchanged" + - id: update_list + title: "Update property lists" + narrative: | + The buyer modifies an existing property list — replacing the base properties + as brand safety requirements evolve. + + steps: + - id: update_property_list + title: "Update a property list" + narrative: | + The buyer replaces the base properties on an existing list with a new set. + task: update_property_list + schema_ref: "property/update-property-list-request.json" + response_schema_ref: "property/update-property-list-response.json" + doc_ref: "/governance/property/tasks/property_lists" + comply_scenario: governance_property_lists + stateful: true + expected: | + Return the updated property list: + - Updated property count + - Confirmation of the replaced base properties + + sample_request: + list_id: "$context.property_list_id" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + base_properties: + - selection_type: "identifiers" + identifiers: + - type: "domain" + value: "outdoormagazine.example" + - type: "domain" + value: "hikingtrails.example" + - type: "domain" + value: "mountaineering.example" + + idempotency_key: "$generate:uuid_v4#property_lists_update_list_update_property_list" + context: + correlation_id: "property_lists--update_property_list" + validations: + - check: response_schema + description: "Response matches update-property-list-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--update_property_list" + description: "Context correlation_id returned unchanged" + - id: delivery_validation + title: "Validate delivery compliance" + narrative: | + After ads have been delivered, the buyer validates that the seller respected the + property lists. The governance agent checks actual delivery data against the + inclusion/exclusion constraints. + + steps: + - id: validate_property_delivery + title: "Validate property compliance" + narrative: | + The buyer submits delivery data and the governance agent checks whether all + placements complied with the property lists. Non-compliant placements are + flagged with details. + task: validate_property_delivery + schema_ref: "property/validate-property-delivery-request.json" + response_schema_ref: "property/validate-property-delivery-response.json" + doc_ref: "/governance/property/tasks/validate_property_delivery" + comply_scenario: governance_property_lists + stateful: true + expected: | + Return validation results: + - compliant: boolean overall status + - summary aggregates compliant/non_compliant/not_covered/unidentified counts + - Per-placement compliance status + - features[] entries on each non_compliant record explaining the breach + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + list_id: "$context.property_list_id" + records: + - record_id: "delivery_outdoor_001" + identifier: + type: "domain" + value: "outdoormagazine.example" + impressions: 50000 + - record_id: "delivery_random_001" + identifier: + type: "domain" + value: "randomsite.example" + impressions: 200 + + context: + correlation_id: "property_lists--validate_property_delivery" + validations: + - check: response_schema + description: "Response matches validate-property-delivery-response.json schema" + - check: field_value + path: "compliant" + value: false + description: "Overall compliance is false when randomsite.example violates inclusion list" + + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--validate_property_delivery" + description: "Context correlation_id returned unchanged" + + - id: enforcement + title: "Property list enforcement" + narrative: | + These steps test behavioral compliance beyond schema validation. An agent that + returns well-shaped responses but does not actually enforce property constraints + would pass CRUD tests but fail here. + + steps: + - id: validate_all_compliant_delivery + title: "Validate fully compliant delivery" + narrative: | + The buyer submits delivery data where all properties are on the inclusion list. + The governance agent must return records with status: compliant and no failed features. + task: validate_property_delivery + schema_ref: "property/validate-property-delivery-request.json" + response_schema_ref: "property/validate-property-delivery-response.json" + doc_ref: "/governance/property/tasks/validate_property_delivery" + comply_scenario: governance_property_lists + stateful: true + expected: | + Return validation results: + - record status: compliant + - No failed or warning features + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + list_id: "$context.property_list_id" + records: + - record_id: "delivery_outdoor_001" + identifier: + type: "domain" + value: "outdoormagazine.example" + impressions: 50000 + - record_id: "delivery_hiking_001" + identifier: + type: "domain" + value: "hikingtrails.example" + impressions: 30000 + + validations: + - check: response_schema + description: "Response matches validate-property-delivery-response.json schema" + - check: field_value + path: "compliant" + value: true + description: "Delivery is compliant when all properties are on inclusion list" + + - id: validate_unauthorized_publisher + title: "Reject delivery on unlisted domain" + narrative: | + The buyer submits delivery data that includes randomsite.example — a domain + not on the inclusion list. The governance agent must flag this as a violation + with specific violation details, not just return a generic failure. + task: validate_property_delivery + schema_ref: "property/validate-property-delivery-request.json" + response_schema_ref: "property/validate-property-delivery-response.json" + doc_ref: "/governance/property/tasks/validate_property_delivery" + comply_scenario: governance_property_lists + stateful: true + expected: | + Return validation results: + - record status: non_compliant + - features array with at least one failed feature entry for randomsite.example + - feature entry references the inclusion list that was breached (via policy_id or code) + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + list_id: "$context.property_list_id" + records: + - record_id: "delivery_random_001" + identifier: + type: "domain" + value: "randomsite.example" + impressions: 500 + + validations: + - check: response_schema + description: "Response matches validate-property-delivery-response.json schema" + - check: field_value + path: "compliant" + value: false + description: "Delivery is non-compliant for unlisted domain" + + - id: delete_list + title: "Delete a property list" + narrative: | + The buyer removes a property list that is no longer needed. Delete runs after + validation phases so they can reference the list created earlier. + + steps: + - id: delete_property_list + title: "Delete a property list" + narrative: | + The buyer deletes a property list. The governance agent removes the list and + returns confirmation. + task: delete_property_list + schema_ref: "property/delete-property-list-request.json" + response_schema_ref: "property/delete-property-list-response.json" + doc_ref: "/governance/property/tasks/property_lists" + comply_scenario: governance_property_lists + stateful: true + expected: | + Confirm deletion: + - list_id: the deleted list + - status: deleted + + sample_request: + list_id: "$context.property_list_id" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + idempotency_key: "$generate:uuid_v4#property_lists_delete_list_delete_property_list" + context: + correlation_id: "property_lists--delete_property_list" + validations: + - check: response_schema + description: "Response matches delete-property-list-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "property_lists--delete_property_list" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/sales-broadcast-tv/index.yaml b/dist/compliance/3.0.9/specialisms/sales-broadcast-tv/index.yaml new file mode 100644 index 0000000000..deb907b99d --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/sales-broadcast-tv/index.yaml @@ -0,0 +1,689 @@ +id: sales_broadcast_tv +version: "1.0.0" +title: "Broadcast linear TV seller agent" +protocol: media-buy +category: sales_broadcast_tv +summary: "Seller agent for broadcast linear TV inventory — primetime and fringe spots with measurement windows, agency estimate numbers, Ad-ID-based creative sync, and delayed delivery reporting." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/refine_products + - media_buy_seller/delivery_reporting + - media_buy_seller/measurement_terms_rejected + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/inventory_list_targeting + - media_buy_seller/inventory_list_no_match + - media_buy_seller/invalid_transitions + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives on a media_buy. +invariants: + - status.monotonic + +narrative: | + You run a broadcast television platform — a local TV station group, network affiliate, + or television rep firm that sells linear advertising inventory. A buyer agent connects + to discover your dayparts and programs, negotiate guaranteed buys, sync broadcast spot + files, and monitor delivery against measurement windows. + + Broadcast buying differs from digital in several ways. Products are organized by daypart + and program rather than audience segment. Pricing is unit-based (cost per spot) rather + than impression-based. Measurement accumulates over time through Live, C3, and C7 + windows as DVR playback is counted. Creative assets are broadcast-grade video files + identified by Ad-ID — no VAST wrappers, no impression trackers, no click URLs. + + Delivery data arrives on a delay. Live ratings are available within a day, but C3 and + C7 data take 4 and 8 days respectively after broadcast. Final reconciliation happens + against C7 numbers, which means billing data is not complete until ~15 days after the + last air date. + + This storyboard walks through the broadcast buying cycle from product discovery through + reconciliation, exercising the protocol fields that distinguish linear TV from digital. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - supports_guaranteed + examples: + - "Local TV station groups" + - "Broadcast network affiliates" + - "Television rep firms" + +caller: + role: buyer_agent + example: "Pinnacle Agency" + +prerequisites: + description: | + The caller needs an established account relationship with the seller. The test + kit provides the Nova Motors Volta EV launch campaign — an automotive brand with + national broadcast reach goals and budget appropriate for primetime linear TV. + test_kit: "test-kits/nova-motors.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "primetime_30s_mf" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "broadcast_spot_30s" + - product_id: "late_fringe_15s_mf" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "broadcast_spot_15s" + pricing_options: + - product_id: "primetime_30s_mf" + pricing_option_id: "unit_primetime_30" + pricing_model: "flat_rate" + currency: "USD" + fixed_price: 4500.0 + - product_id: "late_fringe_15s_mf" + pricing_option_id: "unit_fringe_15" + pricing_model: "flat_rate" + currency: "USD" + fixed_price: 1200.0 + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "sales_broadcast_tv--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: product_discovery + title: "Product discovery" + narrative: | + The buyer sends a brief describing what they want to buy on linear TV. The seller + interprets the brief against their program schedule, ratings estimates, and + available inventory to return products organized by daypart. + + Broadcast products include measurement windows — Live, C3, and C7 — that describe + how ratings data accumulates over time. The buyer uses these windows to understand + when delivery data will be available and which window will serve as the guarantee + basis for reconciliation. + + steps: + - id: get_products_brief + title: "Send a broadcast brief" + narrative: | + The buyer describes their linear TV goals in natural language. The seller + returns products representing available dayparts and programs, each with + unit-based pricing, audience delivery estimates, creative format requirements, + and measurement windows. + + Measurement windows on each product tell the buyer: "Here is when data + becomes available and how it accumulates." Live ratings arrive within a day. + C3 (live + 3 days of DVR) arrives ~4 days after broadcast. C7 (live + 7 days + of DVR) arrives ~8 days after broadcast. The buyer decides which window to + use as the guarantee basis when creating the media buy. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products matching the brief. Each product should include: + - product_id: unique identifier for the daypart or program package + - name: descriptive name (e.g., "Primetime :30 — M-F 8-11pm") + - delivery_type: guaranteed (standard for broadcast upfront and scatter) + - pricing_models: unit-based pricing (cost per spot or cost per unit) + - forecast: estimated impressions by demo, GRPs, reach + - creative_format_ids: broadcast spot formats (:15, :30, :60) + - reporting_capabilities with measurement_windows: + - live: real-time linear viewing, available within 24 hours + - c3: live + 3 days DVR playback, available ~4 days after air + - c7: live + 7 days DVR playback, available ~8 days after air + + sample_request: + buying_mode: "brief" + brief: "Primetime and late fringe broadcast spots for an automotive EV launch. Q4 flight, $400K budget. Adults 25-54, national footprint. Need :30 and :15 spot lengths." + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_broadcast_tv--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + - check: field_present + path: "products[0].delivery_type" + description: "Each product declares guaranteed delivery" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_buy + title: "Create the media buy" + narrative: | + The buyer commits to specific daypart packages with unit counts and flight dates. + Broadcast buys carry an agency estimate number — the financial reference that links + the order to the agency's media plan and billing system. This number travels with + the order through traffic, delivery, and invoicing. + + The buyer also specifies measurement terms, declaring which measurement window + (typically C7) serves as the guarantee basis. This tells the seller: "Bill me + based on C7 ratings, not live." + + steps: + - id: create_media_buy + title: "Create a broadcast media buy" + narrative: | + The buyer places the order with an agency estimate number at the buy level. + Each package references a product from discovery. The measurement_terms + specify C7 as the guarantee window — the seller will reconcile delivery + and billing against C7 ratings. + + The response may be synchronous (buy confirmed) or — when traffic-manager + review is needed — the A2A task returns submitted with a task_id, and the + buyer waits on a webhook or tasks/get poll until the order is scheduled. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Process the broadcast media buy and return: + - media_buy_id: the seller's order identifier + - agency_estimate_number: echoed from the request + - status: pending_creatives (awaiting spot files) or active + - packages: line items with confirmed units, rates, and flight dates + - measurement_terms: confirmed guarantee window (c7) + - valid_actions: sync_creatives as the next step + + If traffic-manager review is needed, return an A2A task envelope instead: + - status: submitted (task-level — not a MediaBuy status) + - task_id / taskId: handle the buyer polls or receives webhooks on + - message (optional): explanation that the traffic manager is reviewing + + Do NOT use a "pending_approval" media buy status — that value is not in the + MediaBuy.status enum. IO / traffic-manager review is modelled at the task layer. + + sample_request: + brand: + domain: "novamotors.example" + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + agency_estimate_number: "PNNL-NM-2026-Q4-0847" + start_time: "2026-10-01T00:00:00Z" + end_time: "2026-12-31T23:59:59Z" + packages: + - product_id: "primetime_30s_mf" + budget: 280000 + pricing_option_id: "unit_primetime_30" + creative_assignments: + - creative_id: "volta_ev_launch_30s" + measurement_terms: + billing_measurement: + vendor: + domain: "videoamp.example" + measurement_window: "c7" + max_variance_percent: 10 + - product_id: "late_fringe_15s_mf" + budget: 120000 + pricing_option_id: "unit_fringe_15" + creative_assignments: + - creative_id: "volta_ev_launch_15s" + measurement_terms: + billing_measurement: + vendor: + domain: "videoamp.example" + measurement_window: "c7" + max_variance_percent: 10 + push_notification_config: + url: "{{runner.webhook_url:create_media_buy}}" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "pinnacle-broadcast-tv-webhook-secret-token" + + idempotency_key: "$generate:uuid_v4#sales_broadcast_tv_create_buy_create_media_buy" + context: + correlation_id: "sales_broadcast_tv--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: check_buy_status + title: "Check media buy status" + narrative: | + If create_media_buy returned working or submitted, the buyer polls for status + updates. For broadcast, the seller's traffic department may need to confirm + scheduling availability before the buy is active. + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the current state of the media buy: + - media_buy_id: matches what was returned from create_media_buy + - status: pending_creatives, pending_start, active, paused, completed + - packages: line items with scheduling confirmation + - valid_actions: what operations are available in this state + + If pending_creatives: + - Include message explaining that broadcast spot files are needed + - valid_actions should include sync_creatives + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "sales_broadcast_tv--check_buy_status" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_present + path: "media_buys[0].status" + description: "Each media buy has a status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--check_buy_status" + description: "Context correlation_id returned unchanged" + - id: creative_sync + title: "Creative sync" + narrative: | + Broadcast creative sync differs from digital in three ways. First, assets are + broadcast-grade video files — no VAST wrappers, no impression trackers, no click + URLs. The seller ingests the video file directly into their traffic and playout + system. Second, each creative carries an Ad-ID in industry_identifiers, which ties + the spot to rotation instructions and downstream traffic systems. Third, format + requirements are defined by spot length (:15, :30, :60) rather than pixel dimensions. + + steps: + - id: list_formats + title: "Check broadcast format requirements" + narrative: | + The buyer confirms what broadcast spot formats the seller accepts. The seller + returns format specs defined by spot length, codec requirements, and file + delivery specifications. No VAST or tracker-related formats appear. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return broadcast spot formats the platform accepts. Each format should define: + - format_id with your agent_url and unique id (e.g., "broadcast_30s", "broadcast_15s") + - Asset requirements: video codec, container format, bitrate, frame rate + - Duration constraints matching the spot length + - No VAST, VPAID, or tracker-related asset slots + + sample_request: + context: + correlation_id: "sales_broadcast_tv--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--list_formats" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - id: sync_creatives + title: "Push broadcast spot files" + narrative: | + The buyer uploads broadcast spot files with Ad-ID identifiers. Each creative + has a single video asset and one or more industry_identifiers with type ad_id. + There are no impression_tracker or click_url assets — broadcast spots are + self-contained video files that the seller loads into playout. + + The Ad-ID is the critical link. It connects the creative asset to rotation + instructions, traffic logs, and post-log reconciliation. Without a valid + Ad-ID, the spot cannot be scheduled. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept and validate broadcast spot files: + - Per-creative action: created or updated + - Per-creative status: accepted, pending_review, or rejected + - Validation of video technical specs (codec, bitrate, duration) + - Confirmation that Ad-ID is recognized and valid + - Rejection if Ad-ID is missing or malformed + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "volta_ev_launch_30s" + name: "Nova Volta EV Launch - :30" + format_id: + agent_url: "https://your-station.example.com" + id: "broadcast_30s" + industry_identifiers: + - type: "ad_id" + value: "NOVA0042000H" + assets: + video_file: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/nova-volta-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + container_format: "mp4" + video_codec: "h264" + - creative_id: "volta_ev_launch_15s" + name: "Nova Volta EV Launch - :15" + format_id: + agent_url: "https://your-station.example.com" + id: "broadcast_15s" + industry_identifiers: + - type: "ad_id" + value: "NOVA0042001H" + assets: + video_file: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/nova-volta-15s.mp4" + width: 1920 + height: 1080 + duration_ms: 15000 + container_format: "mp4" + video_codec: "h264" + + idempotency_key: "$generate:uuid_v4#sales_broadcast_tv_creative_sync_sync_creatives" + context: + correlation_id: "sales_broadcast_tv--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: delivery_monitoring + title: "Delivery and reporting" + narrative: | + Broadcast delivery reporting operates on a fundamentally different timeline than + digital. Live ratings are available within 24 hours, but the numbers that matter + for billing — C7 — are not final until 8 days after each air date. A buyer + checking delivery mid-flight will see data at different maturation stages: recent + airings show only live numbers, while airings from two weeks ago have full C7. + + The seller sends delivery data with measurement window context so the buyer + understands which numbers are preliminary and which are final. As windows mature, + the seller sends window_update notifications with supersedes_window indicating + which prior data is being replaced. The buyer should not make pacing decisions + based on live-only data for a C7-guaranteed buy. + + Window maturation is graded by observing the webhooks the seller emits to the + push_notification_config registered on create_media_buy. An agent that emits + no webhooks at all scores identically to one that emits the correct C3 and C7 + progression — closing that gap requires the webhook_receiver_runner test-kit + contract (so the runner has somewhere to observe deliveries); without it, the + grading step below grades as not_applicable rather than failing. + + steps: + - id: expect_window_update_webhook + title: "Seller emits at least one window_update delivery webhook" + narrative: | + After create_media_buy, the seller emits delivery-notification webhooks + as each measurement window matures (C3 superseding live with is_final: + false, then C7 superseding C3 with is_final: true). The envelope payload + MUST carry a valid idempotency_key and validate against the canonical + MCP webhook payload schema; the nested delivery result SHOULD carry + notification_type: window_update with the supersedes_window progression + appropriate to the window that has matured. + + The filter currently matches on operation_id only — runner extensions + for nested payload matching (e.g. result.notification_type) will allow + separating the C3 and C7 deliveries into distinct assertions in a + follow-up. Runs only when the webhook_receiver_runner contract is in + scope. + task: expect_webhook + triggered_by: create_media_buy + filter: + operation_id: "{{prior_step.create_media_buy.operation_id}}" + timeout_seconds: 30 + expect_idempotency_key: true + webhook_payload_schema_ref: "core/mcp-webhook-payload.json" + requires_contract: webhook_receiver_runner + stateful: true + expected: | + A webhook arrives within 30 seconds whose envelope validates against + mcp-webhook-payload.json and carries an idempotency_key matching + ^[A-Za-z0-9_.:-]{16,255}$. The nested delivery result (per + get-media-buy-delivery-response.json) SHOULD carry notification_type: + window_update with supersedes_window reflecting window progression + (e.g. "live" for C3, "c3" for C7). Sellers that emit no delivery + webhooks at all fail with no_webhook_received. + + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data for the active broadcast buy. The seller + returns ratings-based metrics broken down by package, with each data point + tagged by measurement window. + + Data arrives on a delay. For a spot that aired on October 15: + - Live data: available October 16 + - C3 data: available ~October 19 + - C7 data: available ~October 23 + + The buyer should expect that the most recent 8 days of the flight will not + have final C7 numbers. The response should make this clear through the + measurement window tagging on each data point. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the broadcast buy: + - Per-package: impressions, GRPs, spots aired, spend + - measurement_window on each package (live, c3, or c7) + - is_final: false for packages with data still maturing + - Pacing information relative to the guaranteed unit count + + For webhook delivery, use notification_type: window_update when + sending updated data for a period with a wider window. Include + supersedes_window to indicate which prior data is being replaced + (e.g., supersedes_window: "live" when sending C3 data). + + The buyer uses this to understand: + - How many spots have aired vs. the guaranteed count + - What the preliminary (live) and final (C7) audience delivery looks like + - Whether makegoods may be needed if C7 delivery is under-performing + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "sales_broadcast_tv--get_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--get_delivery" + description: "Context correlation_id returned unchanged" + - id: reconciliation + title: "Post-flight reconciliation" + narrative: | + Broadcast reconciliation cannot happen until C7 data has matured for every air + date in the flight. For a flight ending December 31, final C7 data for the last + air dates arrives around January 8. The seller then has approximately one week to + compile final delivery numbers and issue a post-log. + + The buyer pulls final delivery data ~15 days after the last air date. This is the + authoritative data set for billing reconciliation. If C7 delivery fell short of + the guaranteed audience levels, the buyer requests makegoods — additional spots + to close the delivery gap. + + steps: + - id: get_final_delivery + title: "Pull final reconciliation data" + narrative: | + The buyer requests delivery data after C7 has fully matured for all air dates. + This is the same get_media_buy_delivery task used during monitoring, but now + all data points reflect final C7 measurement. + + The buyer compares final C7 delivery against the guaranteed audience levels + from the media buy. If delivery is short, the buyer contacts the seller to + negotiate makegoods. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return final delivery metrics with all data points at C7 maturation: + - Per-package: final impressions, GRPs, spots aired, spend + - measurement_window: "c7" on all packages + - is_final: true on all packages (all windows fully matured) + - supersedes_window: "c3" (this C7 data replaced the prior C3 data) + - Final pacing: delivered vs. guaranteed audience levels + - Budget reconciliation: actual spend vs. committed + + If delivery fell short of guaranteed levels: + - Shortfall amount by package + - The buyer uses this data to negotiate makegoods with the seller + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "sales_broadcast_tv--get_final_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains final delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_broadcast_tv--get_final_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/sales-catalog-driven/index.yaml b/dist/compliance/3.0.9/specialisms/sales-catalog-driven/index.yaml new file mode 100644 index 0000000000..20715b1c0c --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/sales-catalog-driven/index.yaml @@ -0,0 +1,779 @@ +id: sales_catalog_driven +version: "1.0.0" +title: "Catalog-driven creative and conversion tracking" +protocol: media-buy +category: sales_catalog_driven +summary: "Seller that renders dynamic ads from product catalogs, tracks conversions, and optimizes delivery based on performance feedback." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/refine_products + - media_buy_seller/delivery_reporting + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/invalid_transitions + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives on a media_buy or +# approved → pending on a catalog_item. +invariants: + - status.monotonic + +narrative: | + You run a platform that supports catalog-driven advertising — think Snap Dynamic Ads, + Meta Product Catalogs, or retail media product listing ads. The buyer pushes a product + catalog (menu items, retail products, hotel listings), and your platform renders ads + dynamically from that feed. When a user converts, events are logged back and attributed + to catalog items, closing the optimization loop. + + This storyboard walks through the full catalog-to-conversion flow: account setup, + catalog sync, creative formats for catalog items, media buy with catalog packages, + event source configuration, conversion logging, and performance feedback. + + The key difference from the standard media buy flow is that creatives are catalog-driven — + the buyer doesn't build individual ads. They push a feed, and your platform renders + the right item to the right user at the right time. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_catalogs + - supports_conversion_tracking + - catalog_driven_creative + examples: + - "Snap (Dynamic Ads)" + - "Retail media networks" + - "Travel platforms" + - "Local commerce platforms" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a product catalog (feed URL or inline items) and an event source + for conversion tracking. The test kit provides a sample brand with catalog-compatible + assets. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "sales_catalog_driven--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account setup" + narrative: | + The buyer establishes an account relationship with your platform. For catalog-driven + campaigns, the account must be active before any catalog sync can happen. + + steps: + - id: sync_accounts + title: "Establish account" + narrative: | + The buyer registers their brand and operator. Sandbox accounts are provisioned + instantly for testing catalog flows. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with account_id, status (active for sandbox), and billing terms. + + sample_request: + accounts: + - brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + billing: "operator" + sandbox: true + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_account_setup_sync_accounts" + context: + correlation_id: "sales_catalog_driven--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: catalog_sync + title: "Product catalog sync" + narrative: | + The buyer pushes their product catalog to your platform. This is the foundation of + catalog-driven advertising — every ad your platform renders comes from this feed. + + For a restaurant, this is the menu. For retail, it is the product feed. For travel, + it is hotel or flight listings. Your platform validates each item against your + format requirements (image dimensions, required fields, pricing format) and returns + per-item approval status. + + Large feeds may go async — the buyer gets back a submitted status and waits for + your platform to finish processing. Small feeds (inline items) are processed + synchronously. + + steps: + - id: discover_catalog_formats + title: "Check catalog format requirements" + narrative: | + Before pushing catalog items, the buyer checks what creative formats your + platform supports for catalog-driven ads. This tells the buyer what image + dimensions, text lengths, and required fields each catalog item needs. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return creative formats that accept catalog assets. Look for formats with + catalog-specific asset slots (product_image, product_title, price, description). + + sample_request: + channels: ["display", "native"] + + context: + correlation_id: "sales_catalog_driven--discover_catalog_formats" + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--discover_catalog_formats" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - id: sync_catalogs + title: "Push product catalog" + narrative: | + The buyer pushes their product feed. This can be a URL to an existing feed + (your platform fetches and re-fetches on a schedule) or inline items for + small catalogs. + + Your platform validates each item: images meet minimum dimensions, required + fields are present, prices are formatted correctly. Items that fail validation + are rejected with specific reasons. Items that pass are approved and available + for dynamic creative rendering. + task: sync_catalogs + schema_ref: "media-buy/sync-catalogs-request.json" + response_schema_ref: "media-buy/sync-catalogs-response.json" + doc_ref: "/media-buy/task-reference/sync_catalogs" + stateful: true + expected: | + Return per-catalog results with: + - catalog_id and action (created/updated) + - item_count, items_approved, items_pending, items_rejected + - item_issues for rejected items with specific reasons + - next_fetch_at for URL-based feeds + + sample_request: + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + catalogs: + - catalog_id: "menu_spring_2026" + type: "product" + name: "Spring 2026 Menu" + items: + - item_id: "ribeye_36oz" + title: "36oz Tomahawk Ribeye" + description: "Dry-aged 45 days, served with truffle butter and roasted bone marrow" + url: "https://amsterdam-steakhouse.example/menu/ribeye-36oz" + image_url: "https://cdn.amsterdam-steakhouse.example/menu/ribeye-36oz-hero.jpg" + price: + amount: 89.00 + currency: "USD" + - item_id: "seafood_tower" + title: "Grand Seafood Tower" + description: "Oysters, king crab, lobster tail, shrimp cocktail, tuna tartare" + url: "https://amsterdam-steakhouse.example/menu/seafood-tower" + image_url: "https://cdn.amsterdam-steakhouse.example/menu/seafood-tower-hero.jpg" + price: + amount: 145.00 + currency: "USD" + - item_id: "wagyu_flight" + title: "A5 Wagyu Tasting Flight" + description: "Three cuts of Japanese A5 Wagyu — striploin, ribeye cap, tenderloin" + url: "https://amsterdam-steakhouse.example/menu/wagyu-flight" + image_url: "https://cdn.amsterdam-steakhouse.example/menu/wagyu-flight-hero.jpg" + price: + amount: 195.00 + currency: "USD" + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_catalog_sync_sync_catalogs" + context: + correlation_id: "sales_catalog_driven--sync_catalogs" + validations: + - check: response_schema + description: "Response matches sync-catalogs-response.json schema" + - check: field_present + path: "catalogs[0].catalog_id" + description: "Catalog has an ID" + - check: field_present + path: "catalogs[0].item_count" + description: "Catalog reports item count" + - check: field_present + path: "catalogs[0].items_approved" + description: "Catalog reports approved item count" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--sync_catalogs" + description: "Context correlation_id returned unchanged" + - id: substitution_safety + title: "Catalog-item macro substitution safety" + narrative: | + Per docs/creative/universal-macros#substitution-safety-catalog-item-macros, + sales agents MUST percent-encode catalog-item macro values such that only + RFC 3986 `unreserved` characters remain unescaped before substituting them + into a URL context. Nested macro expansion is prohibited. + + This phase exercises the rule with three attacker-shaped catalog values + drawn from the unit-test fixture at `static/test-vectors/catalog-macro-substitution.json`: + reserved-char breakout, nested-expansion preservation, and non-ASCII + UTF-8. The remaining canonical vectors (CRLF injection, bidi-override, + mixed path/query, url-scheme injection) are exercised at the unit-test + layer and, where a specialism's template shape supports them, in + specialism-specific substitution-safety phases (see + `creative-generative/index.yaml` for CRLF + bidi coverage). + + The `expect_substitution_safe` step is gated on the + `substitution_observer_runner` test-kit contract (see + `test-kits/substitution-observer-runner.yaml`). Runners that do not + advertise the contract grade the step as `not_applicable` — the earlier + `sync_attacker_shaped_catalog` and `build_catalog_aware_creative` steps + still run (they exercise the catalog-acceptance and build paths), but + the substituted-URL assertion is skipped. + + steps: + - id: sync_attacker_shaped_catalog + title: "Push a catalog with attacker-shaped values" + narrative: | + Push a small catalog whose fields contain the six canonical + attacker-shaped values from the substitution-safety fixture. The + seller MUST accept the payload at sync_catalogs (the rule applies at + substitution time, not at ingest — the seller is free to pass values + through, then encode them at serve time). + task: sync_catalogs + schema_ref: "media-buy/sync-catalogs-request.json" + response_schema_ref: "media-buy/sync-catalogs-response.json" + doc_ref: "/media-buy/task-reference/sync_catalogs" + stateful: true + expected: | + Catalog accepted with per-item counts. Runner captures the item_ids + for downstream assertion binding. + + sample_request: + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + catalogs: + - catalog_id: "substitution_safety_probe_v1" + type: "product" + content_id_type: "sku" + name: "Substitution safety probe" + items: + # item_id is the vector name; the `sku` field carries the + # attacker-shaped value that substitution must encode. + - item_id: "reserved_char_breakout" + sku: "00013&cmd=drop" + title: "Reserved-char breakout probe" + url: "https://amsterdam-steakhouse.example/probe/reserved" + image_url: "https://cdn.amsterdam-steakhouse.example/probe/reserved.jpg" + price: { amount: 1.00, currency: "USD" } + - item_id: "nested_expansion" + sku: "vacancy-{DEVICE_ID}-42" + title: "Nested-expansion probe" + url: "https://amsterdam-steakhouse.example/probe/nested" + image_url: "https://cdn.amsterdam-steakhouse.example/probe/nested.jpg" + price: { amount: 1.00, currency: "USD" } + - item_id: "non_ascii" + sku: "café-amsterdam" + title: "Non-ASCII UTF-8 probe" + url: "https://amsterdam-steakhouse.example/probe/non-ascii" + image_url: "https://cdn.amsterdam-steakhouse.example/probe/non-ascii.jpg" + price: { amount: 1.00, currency: "USD" } + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_substitution_safety_sync_attacker_shaped_catalog" + context: + correlation_id: "sales_catalog_driven--sync_attacker_shaped_catalog" + validations: + - check: response_schema + description: "Response matches sync-catalogs-response.json schema" + - check: field_present + path: "catalogs[0].catalog_id" + description: "Catalog accepted" + + - id: build_catalog_aware_creative + title: "Build a creative with catalog-item macros in tracker URLs" + narrative: | + Build a creative whose impression and click trackers include + catalog-item macros bound to the `sku` field of the attacker-shaped + catalog items above. Request `include_preview: true` so the + substitution-observer runner has a preview surface to inspect. + task: build_creative + schema_ref: "media-buy/build-creative-request.json" + response_schema_ref: "media-buy/build-creative-response.json" + doc_ref: "/creative/task-reference/build_creative" + comply_scenario: creative_flow + stateful: true + expected: | + Creative manifest returned with preview_html or preview_url populated. + + sample_request: + message: "Build a catalog-driven display ad for the substitution_safety_probe_v1 catalog. Use {SKU} in impression and click tracker URLs." + target_format_id: + agent_url: "https://your-agent.example.com" + id: "display_300x250_catalog" + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + quality: "draft" + include_preview: true + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_substitution_safety_build_catalog_aware_creative" + context: + correlation_id: "sales_catalog_driven--build_catalog_aware_creative" + validations: + - check: response_schema + description: "Response matches build-creative-response.json schema" + - check: field_present + path: "creative_manifest.format_id" + description: "Creative manifest returned" + + - id: expect_substitution_safe + title: "Assert substituted tracker URLs percent-encode attacker shapes" + narrative: | + The runner inspects the preview artifact from the previous step, + extracts tracker URLs that bind `{SKU}` to a catalog item from the + attacker-shaped catalog, and asserts each value is percent-encoded + per RFC 3986 (unreserved-whitelist). Raw-byte leakage fails. Every + declared binding MUST be observed — a seller that silently strips + `{SKU}` rather than substituting it fails with + `substitution_binding_missing`. + task: expect_substitution_safe + requires_contract: substitution_observer_runner + source: html_inline + source_path: "/creative_manifest/preview_html" + macro_template: "https://track.example/imp?sku={SKU}" + require_every_binding_observed: true + catalog_bindings: + # Each binding: `catalog_item_id` is the item_id in the synced + # catalog; `vector_name` is the fixture entry whose raw_value and + # expected_encoded the runner loads from + # static/test-vectors/catalog-macro-substitution.json. + - macro: "{SKU}" + catalog_item_id: "reserved_char_breakout" + vector_name: "reserved-character-breakout" + - macro: "{SKU}" + catalog_item_id: "nested_expansion" + vector_name: "nested-expansion-preserved-as-literal" + - macro: "{SKU}" + catalog_item_id: "non_ascii" + vector_name: "non-ascii-utf8-percent-encoding" + - id: create_buy + title: "Create catalog-driven media buy" + narrative: | + The buyer creates a media buy with catalog-driven packages. Instead of assigning + individual creatives, the buyer references the synced catalog. Your platform + renders the right catalog items dynamically based on user context, intent signals, + and inventory availability. + + The key schema difference: packages include a catalogs[] array instead of (or + alongside) creative_assignments[]. + + steps: + - id: get_products + title: "Discover catalog-compatible products" + narrative: | + The buyer finds products that support catalog-driven delivery. These products + accept catalog references and render dynamic ads from the feed. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products that support catalog-driven creative. Products should indicate + catalog compatibility in their format requirements. + + sample_request: + buying_mode: "brief" + brief: "Dynamic product ads for a high-end steakhouse. Geo-targeted to 10 miles around Amsterdam location. Drive reservations and foot traffic." + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_catalog_driven--get_products" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains products" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--get_products" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_media_buy + title: "Create media buy with catalog packages" + narrative: | + The buyer creates the media buy with packages that reference the synced catalog. + Each package has a catalogs[] array specifying which catalog feeds drive the + dynamic creative for that line item. + + Your platform optimizes across catalog items within each package's budget + envelope — showing the ribeye to steak lovers and the seafood tower to + seafood enthusiasts. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Create the media buy with catalog-driven packages. Return: + - media_buy_id + - status: active + - packages with catalog references preserved + + sample_request: + brand: + domain: "amsterdam-steakhouse.example" + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-07T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "local_display_dynamic" + pricing_option_id: "cpm_standard" + budget: 5000 + catalogs: + - catalog_id: "menu_spring_2026" + type: "product" + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_create_buy_create_media_buy" + context: + correlation_id: "sales_catalog_driven--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: event_setup + title: "Conversion tracking setup" + narrative: | + The buyer configures event sources so your platform can attribute conversions to + catalog items. For a restaurant, the events are reservations and walk-ins. For + retail, they are purchases and add-to-carts. + + Your platform returns setup snippets (pixel code, SDK instructions) that the + buyer installs on their conversion surfaces. + + steps: + - id: sync_event_sources + title: "Configure event sources" + narrative: | + The buyer tells your platform where conversion events will come from — + a website pixel, a mobile SDK, or a server-to-server integration. Your + platform returns the integration code. + task: sync_event_sources + schema_ref: "media-buy/sync-event-sources-request.json" + response_schema_ref: "media-buy/sync-event-sources-response.json" + doc_ref: "/media-buy/task-reference/sync_event_sources" + stateful: true + expected: | + Return event sources with: + - event_source_id and seller_id + - setup.snippet: integration code (JavaScript pixel, HTML tag, or pixel URL) + - setup.instructions: human-readable integration guide + - action: created or updated + + sample_request: + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + event_sources: + - event_source_id: "amsterdam_website" + name: "Amsterdam Steakhouse Website" + event_types: ["purchase", "add_to_cart", "page_view", "lead"] + allowed_domains: ["amsterdam-steakhouse.example", "book.amsterdam-steakhouse.example"] + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_event_setup_sync_event_sources" + context: + correlation_id: "sales_catalog_driven--sync_event_sources" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches sync-event-sources-response.json schema" + - check: field_present + path: "event_sources[0].setup.snippet" + description: "Event source includes setup snippet" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--sync_event_sources" + description: "Context correlation_id returned unchanged" + - id: conversion_tracking + title: "Log conversions" + narrative: | + The campaign is running and customers are converting. The buyer logs conversion + events back to your platform, attributing them to catalog items via content_ids. + + When someone books a reservation after seeing the ribeye ad, the event includes + content_ids: ["ribeye_36oz"] — linking the conversion to the catalog item that + drove it. Your platform uses this signal to optimize which items to show. + + steps: + - id: log_events + title: "Send conversion events" + narrative: | + The buyer sends a batch of conversion events. Each event includes the event + type, value, and content_ids linking to catalog items. Your platform processes + these for attribution and optimization. + task: log_event + schema_ref: "media-buy/log-event-request.json" + response_schema_ref: "media-buy/log-event-response.json" + doc_ref: "/media-buy/task-reference/log_event" + stateful: true + expected: | + Process the events and return: + - events_received and events_processed counts + - partial_failures for events that failed validation + - match_quality: how well events matched to ad exposures (0.0-1.0) + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + event_source_id: "amsterdam_website" + events: + - event_id: "evt_001" + event_type: "purchase" + event_time: "2026-04-15T19:30:00Z" + content_ids: ["ribeye_36oz"] + value: 89.00 + currency: "USD" + - event_id: "evt_002" + event_type: "lead" + event_time: "2026-04-15T20:15:00Z" + content_ids: ["wagyu_flight"] + value: 195.00 + currency: "USD" + - event_id: "evt_003" + event_type: "page_view" + event_time: "2026-04-15T20:45:00Z" + content_ids: ["seafood_tower"] + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_conversion_tracking_log_events" + context: + correlation_id: "sales_catalog_driven--log_events" + validations: + - check: response_schema + description: "Response matches log-event-response.json schema" + - check: field_present + path: "events_received" + description: "Response reports events received" + - check: field_present + path: "match_quality" + description: "Response includes match quality score" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--log_events" + description: "Context correlation_id returned unchanged" + - id: optimization_loop + title: "Performance feedback and optimization" + narrative: | + The buyer closes the optimization loop by telling your platform how the campaign + is performing against their goals. A performance_index above 1.0 means the + campaign is exceeding expectations — your platform should maintain or increase + delivery. Below 1.0 means underperforming — your platform should adjust targeting, + item selection, or pacing. + + steps: + - id: provide_feedback + title: "Submit performance feedback" + narrative: | + The buyer reports that the campaign is driving 1.4x the expected reservation + rate. Your platform uses this signal to optimize delivery — showing more of + the high-performing catalog items and adjusting bid strategies. + task: provide_performance_feedback + schema_ref: "media-buy/provide-performance-feedback-request.json" + response_schema_ref: "media-buy/provide-performance-feedback-response.json" + doc_ref: "/media-buy/task-reference/provide_performance_feedback" + stateful: true + expected: | + Acknowledge the feedback. The seller should adjust delivery optimization + based on the performance_index signal. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + measurement_period: + start: "2026-04-07T00:00:00Z" + end: "2026-04-14T23:59:59Z" + performance_index: 1.4 + metric_type: "conversion_rate" + feedback_source: "buyer_attribution" + + idempotency_key: "$generate:uuid_v4#sales_catalog_driven_optimization_loop_provide_feedback" + context: + correlation_id: "sales_catalog_driven--provide_feedback" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches provide-performance-feedback-response.json schema" + - check: field_value + path: "success" + value: true + description: "Feedback accepted" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--provide_feedback" + description: "Context correlation_id returned unchanged" + - id: check_delivery + title: "Monitor delivery with catalog attribution" + narrative: | + The buyer checks delivery metrics to see which catalog items are driving + performance and how spend is allocated across the product feed. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics including impressions, clicks, spend, and + conversion data attributed to catalog items. + + sample_request: + account: + brand: + domain: "amsterdam-steakhouse.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "sales_catalog_driven--check_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_catalog_driven--check_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/sales-guaranteed/index.yaml b/dist/compliance/3.0.9/specialisms/sales-guaranteed/index.yaml new file mode 100644 index 0000000000..9b394e13f7 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/sales-guaranteed/index.yaml @@ -0,0 +1,504 @@ +id: sales_guaranteed +version: "1.0.0" +title: "Guaranteed media buy with human IO approval" +protocol: media-buy +category: sales_guaranteed +summary: "Seller agent that requires human-in-the-loop IO signing before guaranteed media buys go live." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/refine_products + - media_buy_seller/delivery_reporting + - media_buy_seller/measurement_terms_rejected + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/inventory_list_targeting + - media_buy_seller/inventory_list_no_match + - media_buy_seller/invalid_transitions + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives on a media_buy. +invariants: + - status.monotonic + +narrative: | + You run a sell-side platform that requires human approval before guaranteed media buys go + live. When a buyer creates a guaranteed buy, your platform returns an A2A task in the + submitted state with a task_id — no media_buy_id is issued yet because IO signing may fail. + A human reviewer on your side reviews the deal terms and signs the IO through your own + internal workflow. + + The buyer either polls tasks/get with the task_id or configures a push_notification_config + webhook to receive a callback when IO signing completes. Only on task completion does your + platform issue a media_buy_id and the final CreateMediaBuy result; the buyer then calls + get_media_buys to confirm the buy is active and sync creatives. + + This storyboard isolates the guaranteed approval path — the async handshake between agent + automation and human decision-making that makes guaranteed buys work in practice. IO review + is modelled entirely at the A2A task layer; there is no interim "pending_approval" media buy + status (that value only exists on Account.status, not MediaBuy.status). + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_guaranteed + - requires_io_approval + examples: + - "Premium publisher with IO requirements" + - "Retail media network with sales approval" + - "CTV platform with guaranteed deals" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials for account setup. + The test kit provides a sample brand (Acme Outdoor) with campaign parameters + suitable for testing the guaranteed approval flow. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_preroll_q2_guaranteed" + delivery_type: "guaranteed" + channels: ["video"] + format_ids: + - id: "video_30s" + - product_id: "outdoor_ctv_q2_guaranteed" + delivery_type: "guaranteed" + channels: ["ctv"] + format_ids: + - id: "video_30s" + pricing_options: + - product_id: "sports_preroll_q2_guaranteed" + pricing_option_id: "cpm_guaranteed_fixed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 35.0 + - product_id: "outdoor_ctv_q2_guaranteed" + pricing_option_id: "cpm_guaranteed_fixed" + pricing_model: "cpm" + currency: "USD" + fixed_price: 45.0 + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "sales_guaranteed--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account setup" + narrative: | + Before buying anything, the buyer establishes an account relationship with + your platform. This is the handshake: the buyer tells you which brand and + agency (operator) they represent, and you return an account ID, status, and + any setup requirements. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. This is + the first call in any new relationship. Your platform validates the request, + provisions the account, and returns its status. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier for this relationship + - action: created or updated + - status: active or pending_approval + - account_scope: operator, brand, operator_brand, or agent + - setup: URL and message if pending_approval + - payment_terms: net_30, prepay, etc. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#sales_guaranteed_account_setup_sync_accounts" + context: + correlation_id: "sales_guaranteed--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + - check: field_present + path: "accounts[0].status" + description: "Account has a status (active or pending_approval)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: product_discovery + title: "Discover guaranteed products" + narrative: | + The buyer sends a brief to discover your guaranteed inventory. The emphasis + here is on products with delivery_type: guaranteed — fixed-price, reserved + inventory that requires an IO commitment. Your platform returns products with + pricing, delivery forecasts, and SLA commitments. + + steps: + - id: get_products_brief + title: "Send a brief targeting guaranteed inventory" + narrative: | + The buyer describes what they want, emphasizing guaranteed delivery. Your + platform returns products with delivery_type: guaranteed, including SLA + commitments, minimum spend requirements, and IO terms. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return guaranteed products matching the brief. Each product should include: + - product_id: unique identifier + - name and description + - delivery_type: guaranteed + - pricing_models: fixed CPM or flat-rate pricing + - forecast: committed impressions with SLA guarantees + - creative_format_ids: required creative formats + - minimum_spend or commitment terms if applicable + + sample_request: + buying_mode: "brief" + brief: "Guaranteed premium video on sports and outdoor lifestyle publishers. Q2 flight, $50K budget. Adults 25-54, US only. Need completion rate SLA." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_guaranteed--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + - check: field_present + path: "products[0].delivery_type" + description: "Each product declares delivery_type: guaranteed" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_buy_submitted + title: "Create guaranteed buy (task submitted for approval)" + narrative: | + The buyer creates a guaranteed media buy. Because your platform requires human + IO signing, the A2A task transitions to submitted rather than completed. The + buyer gets back a task_id and configures a webhook (or polls tasks/get) to be + notified when IO review finishes. + + steps: + - id: create_media_buy + title: "Create a guaranteed media buy" + narrative: | + The buyer commits to guaranteed products with budgets and flight dates. Your + platform accepts the request but does not create the media buy yet. Instead, + the A2A task enters the submitted state — no media_buy_id is issued because + IO signing may fail. The buyer receives a task_id to watch. + + The buyer includes push_notification_config so your platform can call back + when the IO is signed (completed) or rejected (failed). + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Return an A2A task envelope in submitted state: + - status: submitted (task-level — the CreateMediaBuy success artifact is not yet produced) + - task_id / taskId: the handle the buyer polls or receives webhooks on + - message (optional): human-readable explanation (e.g., "Awaiting IO signature from sales team; typical turnaround 2–4 hours") + + Do NOT return media_buy_id or packages yet — those land on the task's final artifact + when the task transitions to completed. Do NOT return completed status for guaranteed + buys that require IO signing. Do NOT use a "pending_approval" media buy status; that + value is not in MediaBuy.status — IO review is modelled at the task layer only. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_preroll_q2_guaranteed" + budget: 30000 + pricing_option_id: "cpm_guaranteed_fixed" + creative_assignments: + - creative_id: "video_30s_trail_pro" + - product_id: "outdoor_ctv_q2_guaranteed" + budget: 20000 + pricing_option_id: "cpm_guaranteed_fixed" + push_notification_config: + url: "https://buyer.example/webhooks/adcp" + authentication: + schemes: + - "HMAC-SHA256" + credentials: "sales-guaranteed-webhook-secret-token" + + idempotency_key: "$generate:uuid_v4#sales_guaranteed_create_buy_submitted_create_media_buy" + context: + correlation_id: "sales_guaranteed--create_media_buy" + context_outputs: + - name: media_buy_id + path: "task_completion.media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: confirm_active + title: "Confirm active after IO signing" + narrative: | + The human on your side reviews and signs the IO through your internal workflow. + Your platform then transitions the A2A task to completed and emits the final + CreateMediaBuy result — including the newly-issued media_buy_id — to the buyer's + push_notification webhook (or to the next tasks/get poll). The buyer now calls + get_media_buys with that media_buy_id and sees the buy active. There is no + intermediate "pending_approval" media buy status in this flow; the buy does not + exist as a queryable MediaBuy until the task completes. + + steps: + - id: get_media_buys_active + title: "Check media buy status (active)" + narrative: | + After the task completes and your platform issues a media_buy_id, the buyer + calls get_media_buys to confirm the buy is live. Your platform returns active + (or pending_creatives when creatives are still outstanding), indicating the + buy is approved and inventory is reserved. + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the media buy in active status: + - media_buy_id: matches the buy created earlier + - status: active (IO has been signed) + - confirmed_at: timestamp when the IO was signed + - packages: line items with reserved inventory + - valid_actions: updated for active state (creative sync, pause, etc.) + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "sales_guaranteed--get_media_buys_active" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_present + path: "media_buys[0].status" + description: "Media buy status is active" + - check: field_present + path: "media_buys[0].confirmed_at" + description: "Active buy includes a confirmed_at timestamp" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--get_media_buys_active" + description: "Context correlation_id returned unchanged" + - id: creative_sync + title: "Creative sync" + narrative: | + With the IO signed and the media buy active, the buyer syncs creative assets + to your platform. Each package has creative format requirements that the buyer + discovered during product discovery. + + steps: + - id: sync_creatives + title: "Push creative assets" + narrative: | + The buyer uploads creative assets for the confirmed packages. Your platform + validates each creative against the format specs and returns per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept and validate creatives: + - Per-creative action: created or updated + - Per-creative status: accepted, pending_review, or rejected + - Validation errors for rejected creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "video_30s_trail_pro" + name: "Trail Pro 3000 - 30s CTV Spot" + format_id: + agent_url: "https://your-platform.example.com" + id: "ssai_30s" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + mime_type: "video/mp4" + + idempotency_key: "$generate:uuid_v4#sales_guaranteed_creative_sync_sync_creatives" + context: + correlation_id: "sales_guaranteed--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: delivery_monitoring + title: "Delivery and reporting" + narrative: | + The campaign is live with guaranteed delivery commitments. The buyer monitors + delivery to ensure the seller is meeting the SLA guarantees from the IO. + + steps: + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data for the active guaranteed media buy. Your + platform returns performance metrics with pacing against the guaranteed + commitment. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the guaranteed media buy: + - Per-package delivery: impressions, clicks, spend, completion rates + - Pacing against guaranteed commitment: on track, ahead, behind + - Budget utilization: spent vs. committed + - SLA compliance: completion rate vs. guaranteed threshold + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "sales_guaranteed--get_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_guaranteed--get_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/sales-non-guaranteed/index.yaml b/dist/compliance/3.0.9/specialisms/sales-non-guaranteed/index.yaml new file mode 100644 index 0000000000..517fcd67fa --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/sales-non-guaranteed/index.yaml @@ -0,0 +1,428 @@ +id: sales_non_guaranteed +version: "1.0.0" +title: "Non-guaranteed auction-based media buy" +protocol: media-buy +category: sales_non_guaranteed +summary: "Seller agent for auction-based, non-guaranteed buying where the buyer sets bid prices and budgets." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/delivery_reporting + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/inventory_list_targeting + - media_buy_seller/inventory_list_no_match + - media_buy_seller/invalid_transitions + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. active → pending_creatives on a media_buy. +invariants: + - status.monotonic + +narrative: | + You run a sell-side platform with auction-based inventory. Non-guaranteed buys don't + require IOs or human approval — the buyer sets a bid price and budget, and your platform + runs the auction. Delivery is best-effort based on bid competitiveness. + + The buyer discovers products with floor prices and bid guidance, creates a buy with bid + prices per package, monitors win rates and pacing, and adjusts bids or budgets in-flight + to optimize performance. + + This storyboard covers the non-guaranteed buying path — fast setup, no human approval, + real-time optimization through bid and budget adjustments. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - supports_non_guaranteed + - auction_based + examples: + - "SSP with programmatic auction inventory" + - "Exchange-based publisher platform" + - "Open marketplace seller" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials. Non-guaranteed buys + typically have lower barriers to entry — no IO signing required. The test kit + provides a sample brand (Acme Outdoor) with bid parameters suitable for testing + the auction-based flow. + test_kit: "test-kits/acme-outdoor.yaml" + controller_seeding: true + +fixtures: + products: + - product_id: "sports_display_auction" + delivery_type: "non_guaranteed" + channels: ["display"] + format_ids: + - id: "display_300x250" + - product_id: "outdoor_video_auction" + delivery_type: "non_guaranteed" + channels: ["video"] + format_ids: + - id: "video_30s" + pricing_options: + - product_id: "sports_display_auction" + pricing_option_id: "cpm_auction" + pricing_model: "cpm" + currency: "USD" + floor_price: 5.0 + - product_id: "outdoor_video_auction" + pricing_option_id: "cpm_auction" + pricing_model: "cpm" + currency: "USD" + floor_price: 15.0 + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "sales_non_guaranteed--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_non_guaranteed--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: product_discovery + title: "Discover auction-based products" + narrative: | + The buyer sends a brief to discover your non-guaranteed inventory. Products + come back with delivery_type: non_guaranteed, floor prices, and bid guidance + that helps the buyer set competitive bids. + + steps: + - id: get_products_brief + title: "Send a brief for non-guaranteed inventory" + narrative: | + The buyer describes what they want. Your platform returns non-guaranteed + products with auction mechanics: floor prices, recommended bid ranges, + estimated win rates at different bid levels, and available audience segments. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return non-guaranteed products matching the brief. Each product should include: + - product_id: unique identifier + - name and description + - delivery_type: non_guaranteed + - pricing_models: auction-based pricing with floor_price and recommended bid range + - forecast: estimated impressions at different bid levels (best-effort) + - creative_format_ids: required creative formats + - targeting: available audiences and contexts + + sample_request: + buying_mode: "brief" + brief: "Display and video inventory across sports and outdoor lifestyle sites. Q2 flight, $25K budget. Adults 25-54, US. Auction-based, looking for competitive CPMs." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_non_guaranteed--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "products[0].product_id" + description: "Each product has a product_id" + - check: field_present + path: "products[0].delivery_type" + description: "Each product declares delivery_type: non_guaranteed" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_non_guaranteed--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: create_buy + title: "Create non-guaranteed buy" + narrative: | + The buyer creates a media buy with bid prices per package. Since this is + non-guaranteed, no IO is required and no human approval is needed. The response + comes back as completed — the buy is immediately active and your platform starts + bidding in auctions on the buyer's behalf. + + steps: + - id: create_media_buy + title: "Create a media buy with bid prices" + narrative: | + The buyer commits to non-guaranteed products with bid prices and budgets. + Your platform validates the bids against floor prices, confirms the buy + immediately (completed status), and begins auction participation. + + Each package includes a bid_price that the buyer is willing to pay per unit + (CPM, CPC, etc.). The platform uses this to compete in auctions. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Return the media buy in completed status: + - media_buy_id: your platform's identifier + - status: active (no async approval needed) + - confirmed_at: timestamp + - packages: confirmed line items with bid prices acknowledged + - valid_actions: pause, update_bid, get_delivery + + Bids below floor_price should be rejected with a clear error. + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + packages: + - product_id: "sports_display_auction" + budget: 10000 + bid_price: 8.50 + pricing_option_id: "cpm_auction" + creative_assignments: + - creative_id: "display_trail_pro_300x250" + - product_id: "outdoor_video_auction" + budget: 15000 + bid_price: 22.00 + pricing_option_id: "cpm_auction" + creative_assignments: + - creative_id: "video_30s_trail_pro" + + idempotency_key: "$generate:uuid_v4#sales_non_guaranteed_create_buy_create_media_buy" + context: + correlation_id: "sales_non_guaranteed--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + - name: first_package_id + path: "packages[0].package_id" + - name: second_package_id + path: "packages[1].package_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_non_guaranteed--create_media_buy" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "packages[0].package_id" + description: "Seller assigns package_id — must be echoed in update_media_buy" + - id: monitor_pacing + title: "Monitor win rates and pacing" + narrative: | + The non-guaranteed buy is live and bidding in auctions. The buyer checks + pacing to understand how competitive their bids are and whether the budget + is spending at the desired rate. + + steps: + - id: get_media_buys_pacing + title: "Check buy status and pacing" + narrative: | + The buyer polls for the media buy status. Your platform returns the active + buy with pacing data — how much budget has been spent, win rates, and whether + the buy is on pace to exhaust the budget by the end of the flight. + task: get_media_buys + schema_ref: "media-buy/get-media-buys-request.json" + response_schema_ref: "media-buy/get-media-buys-response.json" + doc_ref: "/media-buy/task-reference/get_media_buys" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Return the media buy with pacing data: + - media_buy_id: matches the buy created earlier + - status: active + - packages: line items with current spend, win rate, and pacing status + - valid_actions: update, pause, get_delivery + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + + context: + correlation_id: "sales_non_guaranteed--get_media_buys_pacing" + validations: + - check: response_schema + description: "Response matches get-media-buys-response.json schema" + - check: field_present + path: "media_buys[0].status" + description: "Media buy has a status" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_non_guaranteed--get_media_buys_pacing" + description: "Context correlation_id returned unchanged" + - id: adjust_bids + title: "Adjust bids and budget" + narrative: | + Based on pacing data, the buyer adjusts bids or budgets in-flight. If a package + is underspending (low win rate), the buyer increases the bid. If a package is + overspending, the buyer decreases the bid or caps the daily budget. + + steps: + - id: update_media_buy + title: "Update bid prices and budget" + narrative: | + The buyer modifies the active media buy — adjusting bid prices, reallocating + budget between packages, or changing daily spend caps. Your platform applies + the updates immediately to the live auction participation. + task: update_media_buy + schema_ref: "media-buy/update-media-buy-request.json" + response_schema_ref: "media-buy/update-media-buy-response.json" + doc_ref: "/media-buy/task-reference/update_media_buy" + comply_scenario: media_buy_lifecycle + stateful: true + expected: | + Apply the updates and return the modified media buy: + - media_buy_id: matches the existing buy + - status: active (still running) + - packages: updated line items reflecting new bids and budgets + - Changes take effect immediately for subsequent auctions + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_id: "$context.media_buy_id" + packages: + - package_id: "$context.first_package_id" + bid_price: 10.00 + budget: 12000 + - package_id: "$context.second_package_id" + bid_price: 20.00 + budget: 13000 + + idempotency_key: "$generate:uuid_v4#sales_non_guaranteed_adjust_bids_update_media_buy" + context: + correlation_id: "sales_non_guaranteed--update_media_buy" + validations: + - check: response_schema + description: "Response matches update-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_non_guaranteed--update_media_buy" + description: "Context correlation_id returned unchanged" + - id: delivery + title: "Delivery and auction metrics" + narrative: | + The buyer reviews delivery data including auction-specific metrics: win rates, + average clearing prices, and bid competitiveness across packages. + + steps: + - id: get_delivery + title: "Check delivery with auction metrics" + narrative: | + The buyer requests delivery data for the active non-guaranteed media buy. + Your platform returns standard delivery metrics plus auction-specific data: + win rates, average clearing prices, bid-to-win ratios, and budget pacing. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the non-guaranteed media buy: + - Per-package delivery: impressions, clicks, spend + - Win rate: percentage of auctions won per package + - Average clearing price: what the buyer actually paid vs. bid price + - Pacing: on track, ahead, behind relative to budget and flight dates + - Budget utilization: spent vs. committed + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "sales_non_guaranteed--get_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_non_guaranteed--get_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/sales-proposal-mode/index.yaml b/dist/compliance/3.0.9/specialisms/sales-proposal-mode/index.yaml new file mode 100644 index 0000000000..5b26504dcc --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/sales-proposal-mode/index.yaml @@ -0,0 +1,520 @@ +id: sales_proposal_mode +version: "1.0.0" +title: "Media buy via proposal acceptance" +protocol: media-buy +category: sales_proposal_mode +summary: "Seller agent that generates curated media plan proposals the buyer can review, refine, and accept." +track: media_buy +required_tools: + - get_products + - create_media_buy +requires_scenarios: + - media_buy_seller/proposal_finalize + - media_buy_seller/delivery_reporting + - media_buy_seller/measurement_terms_rejected + - media_buy_seller/pending_creatives_to_start + - media_buy_seller/inventory_list_targeting + - media_buy_seller/inventory_list_no_match + - media_buy_seller/invalid_transitions + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. committed → draft on a proposal or active → +# pending_creatives on a media_buy. +invariants: + - status.monotonic + +narrative: | + Your seller generates curated media plan proposals. The buyer sends a brief, your platform + returns products alongside proposals — curated bundles with budget allocations and rationale + for each recommendation. The buyer reviews, optionally refines, and then accepts a proposal + as-is instead of manually building packages. + + Proposal mode is the recommended flow for buyers who trust the seller's product + recommendations. Instead of the buyer cherry-picking individual products and assembling + packages, the seller's AI builds an optimized media plan that the buyer can accept with a + single call. + + This storyboard walks through the proposal lifecycle: brief, proposal generation, optional + refinement, acceptance, creative sync, and delivery monitoring. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - generates_proposals + examples: + - "Full-service publisher with AI planning" + - "Retail media network with curated packages" + - "Premium video platform with proposal engine" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The caller needs a brand identity and operator credentials for account setup. + The test kit provides a sample brand (Acme Outdoor) with campaign parameters + suitable for testing the proposal acceptance flow. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before sending briefs or creating buys. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "sales_proposal_mode--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account setup" + narrative: | + The buyer establishes an account relationship with your platform before + requesting proposals. + + steps: + - id: sync_accounts + title: "Establish account relationship" + narrative: | + The buyer registers their brand and operator with your platform. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the account with: + - account_id: your platform's identifier for this relationship + - action: created or updated + - status: active or pending_approval + - payment_terms: net_30, prepay, etc. + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + + idempotency_key: "$generate:uuid_v4#sales_proposal_mode_account_setup_sync_accounts" + context: + correlation_id: "sales_proposal_mode--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: brief_with_proposals + title: "Brief with proposals" + narrative: | + The buyer sends a brief. Your platform returns products and — because you support + proposal mode — also returns proposals: curated media plans with budget allocations, + product selections, and rationale for each recommendation. The buyer can review + multiple proposals side by side. + + steps: + - id: get_products_brief + title: "Send a brief and receive proposals" + narrative: | + The buyer describes what they want. Your platform returns products alongside + one or more proposals. Each proposal bundles products with budget allocations + and explains why those products were selected and how the budget was distributed. + + Proposals give the buyer a ready-to-accept media plan instead of requiring + manual package assembly. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: false + expected: | + Return products and proposals matching the brief: + - products: individual products with pricing and forecasts + - proposals: curated media plans, each containing: + - proposal_id: unique identifier for this proposal + - name: descriptive label (e.g., "Balanced Reach Plan") + - budget_allocations: how the total budget is split across products + - rationale: why these products were selected and how the budget was distributed + - total_budget: sum of allocations + - forecast: aggregate impressions, reach, frequency + + sample_request: + buying_mode: "brief" + brief: "Premium video and display across outdoor lifestyle and sports. Q2 flight, $50K total budget. Adults 25-54, US and Canada. Looking for a balanced plan across CTV, online video, and display." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_proposal_mode--get_products_brief" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "products" + description: "Response contains a products array" + - check: field_present + path: "proposals" + description: "Response contains a proposals array" + - check: field_present + path: "proposals[0].proposal_id" + description: "Each proposal has a proposal_id" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--get_products_brief" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: review_refine + title: "Refine a proposal" + narrative: | + The buyer reviews the proposals and wants to adjust one. They call get_products + in refine mode targeting a specific proposal_id. The refinement might adjust + budget splits, swap a product, or add constraints. Your platform returns the + updated proposal. + + steps: + - id: get_products_refine + title: "Refine a specific proposal" + narrative: | + The buyer targets a specific proposal for refinement. They reference the + proposal_id and describe what they want to change. Your platform applies + the refinements to that proposal and returns the updated version. + task: get_products + schema_ref: "media-buy/get-products-request.json" + response_schema_ref: "media-buy/get-products-response.json" + doc_ref: "/media-buy/task-reference/get_products" + comply_scenario: full_sales_flow + stateful: true + expected: | + Return the refined proposal: + - proposals: updated proposal reflecting the requested changes + - refinement_applied: how each refinement was handled + - Updated budget allocations, product selections, and forecasts + - products: updated product set if products were swapped + + sample_request: + buying_mode: "refine" + refine: + - scope: "proposal" + proposal_id: "balanced_reach_q2" + ask: "Shift 60% of budget to CTV. Drop the display product and redistribute that budget to video." + - scope: "request" + ask: "All products must support frequency capping at 3 per day." + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_proposal_mode--get_products_refine" + validations: + - check: response_schema + description: "Response matches get-products-response.json schema" + - check: field_present + path: "proposals" + description: "Response contains updated proposals" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--get_products_refine" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "products[0].format_ids" + description: "Products include format_ids for creative requirements" + - check: field_present + path: "products[0].format_ids[0].agent_url" + description: "Format IDs include agent_url — must match this agent's URL" + - check: field_present + path: "products[0].format_ids[0].id" + description: "Format IDs include id — must be accepted back in sync_creatives" + - id: accept_proposal + title: "Accept the proposal" + narrative: | + The buyer is satisfied with the refined proposal and accepts it by creating a + media buy with the proposal_id. Instead of specifying individual packages, the + buyer passes the proposal_id and total_budget. Your platform converts the proposal + into an active media buy with the exact product selections and budget allocations + from the proposal. + + steps: + - id: create_media_buy + title: "Create a media buy from proposal" + narrative: | + The buyer accepts a proposal by passing proposal_id to create_media_buy. The + buyer does NOT specify a packages array — the platform uses the proposal's + product selections and budget allocations to build the packages automatically. + + This is the key difference from manual package creation: the buyer trusts the + seller's recommendation and accepts the plan as-is. + task: create_media_buy + schema_ref: "media-buy/create-media-buy-request.json" + response_schema_ref: "media-buy/create-media-buy-response.json" + doc_ref: "/media-buy/task-reference/create_media_buy" + comply_scenario: create_media_buy + stateful: true + expected: | + Convert the proposal into an active media buy: + - media_buy_id: your platform's identifier + - status: active + - confirmed_at: timestamp + - packages: line items derived from the proposal's budget allocations + - proposal_id: echoed back to confirm which proposal was accepted + - valid_actions: creative sync, pause, get_delivery + + sample_request: + brand: + domain: "acmeoutdoor.example" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + proposal_id: "balanced_reach_q2" + total_budget: + amount: 50000 + currency: "USD" + start_time: "2026-04-01T00:00:00Z" + end_time: "2026-06-30T23:59:59Z" + + idempotency_key: "$generate:uuid_v4#sales_proposal_mode_accept_proposal_create_media_buy" + context: + correlation_id: "sales_proposal_mode--create_media_buy" + context_outputs: + - name: media_buy_id + path: "media_buy_id" + validations: + - check: response_schema + description: "Response matches create-media-buy-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--create_media_buy" + description: "Context correlation_id returned unchanged" + - id: creative_sync + title: "Creative sync" + narrative: | + With the proposal accepted and the media buy confirmed, the buyer syncs creative + assets. The buyer first checks what formats the accepted products require, then + pushes matching creative assets. + + steps: + - id: list_formats + title: "Check creative format requirements" + narrative: | + The buyer confirms what creative formats the accepted proposal's products + require. Your platform returns format specs with asset requirements. + task: list_creative_formats + schema_ref: "creative/list-creative-formats-request.json" + response_schema_ref: "creative/list-creative-formats-response.json" + doc_ref: "/creative/task-reference/list_creative_formats" + comply_scenario: creative_lifecycle + stateful: false + expected: | + Return creative formats your platform accepts. Each format should define: + - format_id with your agent_url and unique id + - Asset requirements (dimensions, file sizes, mime types) + - Render dimensions + + sample_request: + context: + correlation_id: "sales_proposal_mode--list_formats" + + validations: + - check: response_schema + description: "Response matches list-creative-formats-response.json schema" + - check: field_present + path: "formats" + description: "Response contains formats array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--list_formats" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "formats[0].format_id.agent_url" + description: "Format IDs include agent_url" + - check: field_present + path: "formats[0].format_id.id" + description: "Format IDs include id — must match those in get_products" + - id: sync_creatives + title: "Push creative assets" + narrative: | + The buyer uploads creative assets for the products in the accepted proposal. + Your platform validates each creative against the format specs and returns + per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept and validate creatives: + - Per-creative action: created or updated + - Per-creative status: accepted, pending_review, or rejected + - Validation errors for rejected creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "video_30s_trail_pro" + name: "Trail Pro 3000 - 30s CTV Spot" + format_id: + agent_url: "https://your-platform.example.com" + id: "ssai_30s" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-30s.mp4" + width: 1920 + height: 1080 + duration_ms: 30000 + mime_type: "video/mp4" + - creative_id: "video_15s_trail_pro" + name: "Trail Pro 3000 - 15s Online Video" + format_id: + agent_url: "https://your-platform.example.com" + id: "preroll_15s" + assets: + video: + asset_type: "video" + url: "https://cdn.pinnacle-agency.example/trail-pro-15s.mp4" + width: 1920 + height: 1080 + duration_ms: 15000 + mime_type: "video/mp4" + + idempotency_key: "$generate:uuid_v4#sales_proposal_mode_creative_sync_sync_creatives" + context: + correlation_id: "sales_proposal_mode--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "creatives[0].action" + description: "Each creative has an action (created/updated)" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: delivery + title: "Delivery and reporting" + narrative: | + The campaign from the accepted proposal is live. The buyer monitors delivery + to verify the proposal's forecasts are tracking. + + steps: + - id: get_delivery + title: "Check delivery metrics" + narrative: | + The buyer requests delivery data for the media buy created from the proposal. + Your platform returns performance metrics that the buyer can compare against + the proposal's original forecasts. + task: get_media_buy_delivery + schema_ref: "media-buy/get-media-buy-delivery-request.json" + response_schema_ref: "media-buy/get-media-buy-delivery-response.json" + doc_ref: "/media-buy/task-reference/get_media_buy_delivery" + comply_scenario: reporting_flow + stateful: true + expected: | + Return delivery metrics for the media buy: + - Per-package delivery: impressions, clicks, spend, completion rates + - Pacing against the proposal's original forecast + - Budget utilization: spent vs. committed per package + - Daily breakdown if requested + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + media_buy_ids: + - "$context.media_buy_id" + include_package_daily_breakdown: true + + context: + correlation_id: "sales_proposal_mode--get_delivery" + validations: + - check: response_schema + description: "Response matches get-media-buy-delivery-response.json schema" + - check: field_present + path: "media_buy_deliveries" + description: "Response contains media buy delivery data" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_proposal_mode--get_delivery" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/sales-social/index.yaml b/dist/compliance/3.0.9/specialisms/sales-social/index.yaml new file mode 100644 index 0000000000..c8e388633f --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/sales-social/index.yaml @@ -0,0 +1,584 @@ +id: sales_social +version: "1.0.0" +title: "Social platform" +protocol: media-buy +category: sales_social +summary: "Social media platform that accepts audience segments, native creatives, and conversion events from buyer agents." +track: audiences +required_tools: + - sync_audiences + - sync_catalogs + - sync_creatives + - sync_event_sources + +# Cross-step assertion (adcp#2664). status.monotonic rejects resource +# status transitions observed across steps that aren't on the spec +# lifecycle graph — e.g. approved → processing on a creative asset or +# active → pending_creatives on a media_buy. +invariants: + - status.monotonic + +narrative: | + You run a social media platform — Snap, Meta, TikTok, Pinterest, or any walled garden that + sells advertising through audience-based targeting and native creative formats. A buyer agent + connects to set up an account, push audience segments, sync native creatives, track conversion + events, and monitor spend. + + Unlike open-web media buys, social platforms require the buyer to push assets into the + platform's environment. Audiences are activated via sync_audiences, creatives are pushed via + sync_creatives in platform-native formats, and conversion events flow back via log_event. + + This storyboard covers the social platform integration from the buyer's perspective: + account setup, audience activation, native creative push, event tracking, and financial + monitoring. + +agent: + interaction_model: media_buy_seller + capabilities: + - sells_media + - accepts_briefs + - supports_non_guaranteed + examples: + - "Snap" + - "Meta" + - "TikTok" + - "Pinterest" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The caller needs a brand identity, operator credentials, audience segment definitions, + and native creative assets. The test kit provides a sample brand with creative assets + suitable for social formats. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports media buying before syncing audiences and native creatives. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring media_buy in supported_protocols, confirming the agent sells media. + sample_request: + context: + correlation_id: "sales_social--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: account_setup + title: "Account setup" + narrative: | + The buyer establishes an account with the social platform and verifies its status. + Social platforms often require advertiser verification before accepting ad spend. + + steps: + - id: sync_accounts + title: "Register advertiser account" + narrative: | + The buyer registers their brand and operator with the social platform. The platform + provisions an advertiser account and returns its status. Social platforms may require + identity verification before the account goes active. + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Return the advertiser account with: + - account_id: platform's identifier + - status: active or pending_approval (if verification required) + - account_scope: how the platform scopes this relationship + + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + + idempotency_key: "$generate:uuid_v4#sales_social_account_setup_sync_accounts" + context: + correlation_id: "sales_social--sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--sync_accounts" + description: "Context correlation_id returned unchanged" + - id: list_accounts + title: "Verify account status" + narrative: | + The buyer checks which accounts exist on the platform and their current status. + This confirms the account is active and shows any pending setup requirements. + task: list_accounts + schema_ref: "account/list-accounts-request.json" + response_schema_ref: "account/list-accounts-response.json" + doc_ref: "/accounts/tasks/list_accounts" + stateful: true + # Explicit-mode social platforms (Snap, Meta, TikTok) pre-provision + # advertiser accounts out-of-band — `sync_accounts` is intentionally + # missing_tool, with `list_accounts` as the canonical alternative. + # Declaring the substitution here lets the runner waive the + # downstream missing_tool cascade when list_accounts passes. + # See adcontextprotocol/adcp#3734. + provides_state_for: sync_accounts + expected: | + Return accounts matching the query: + - accounts array with status, account_id, brand, operator + - Active accounts ready for ad operations + - Pending accounts with accounts[].setup.url populated if verification is needed + + sample_request: + context: + correlation_id: "sales_social--list_accounts" + validations: + - check: response_schema + description: "Response matches list-accounts-response.json schema" + - check: field_present + path: "accounts" + description: "Response contains accounts array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--list_accounts" + description: "Context correlation_id returned unchanged" + - id: audience_sync + title: "Audience activation" + narrative: | + The buyer pushes audience segments to the platform. Social platforms use these segments + for targeting — the buyer defines who to reach, and the platform matches against its + user base. + + steps: + - id: sync_audiences + title: "Push audience segments" + narrative: | + The buyer syncs audience segment definitions to the platform. Each segment includes + targeting criteria that the platform evaluates against its user graph. The platform + returns match rates and segment status. + task: sync_audiences + schema_ref: "media-buy/sync-audiences-request.json" + response_schema_ref: "media-buy/sync-audiences-response.json" + doc_ref: "/media-buy/task-reference/sync_audiences" + comply_scenario: sync_audiences + stateful: true + expected: | + Accept and process audience segments: + - Per-segment status: active, processing, or rejected + - Match rate estimates where available + - Platform-assigned segment IDs + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + audiences: + - audience_id: "outdoor_enthusiasts_25_54" + name: "Outdoor enthusiasts 25-54" + description: "Adults 25-54 interested in hiking, camping, and outdoor gear" + + idempotency_key: "$generate:uuid_v4#sales_social_audience_sync_sync_audiences" + context: + correlation_id: "sales_social--sync_audiences" + validations: + - check: response_schema + description: "Response matches sync-audiences-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--sync_audiences" + description: "Context correlation_id returned unchanged" + - id: creative_push + title: "Native creative sync" + narrative: | + The buyer pushes native creative assets to the platform. Social platforms render ads + in their native format — the buyer provides assets (images, headlines, descriptions) + and the platform assembles them into the native ad unit. + + steps: + - id: sync_creatives + title: "Push native creative assets" + narrative: | + The buyer syncs creative assets for native ad formats. The platform validates + each creative against its format requirements and returns per-creative status. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept and validate native creatives: + - Per-creative action: created or updated + - Per-creative status: accepted, pending_review, or rejected + - Validation errors for rejected creatives + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "native_trail_pro" + name: "Trail Pro 3000 - Native" + format_id: + agent_url: "https://social-platform.example.com" + id: "native_feed" + assets: + image: + asset_type: "image" + url: "https://cdn.pinnacle-agency.example/trail-pro-native.png" + width: 1200 + height: 628 + mime_type: "image/png" + headline: + asset_type: "text" + content: "Trail Pro 3000 — Built for the Summit" + + idempotency_key: "$generate:uuid_v4#sales_social_creative_push_sync_creatives" + context: + correlation_id: "sales_social--sync_creatives" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--sync_creatives" + description: "Context correlation_id returned unchanged" + - id: catalog_driven_dynamic_ads + title: "Catalog-driven dynamic product ads" + narrative: | + Social platforms routinely ship dynamic product ads (Snap Dynamic Ads, Meta DPA, + TikTok Dynamic Showcase): the buyer pushes a product catalog and the platform + renders per-impression creative pulling product images, titles, and tracker URLs + from catalog items. The creative template references catalog-item macros + (`{SKU}`, `{GTIN}`) whose values resolve to the specific item shown at + impression time. + + This phase exercises the catalog-acceptance leg of that flow: push a small inline + product catalog (with `content_id_type: "sku"` declared so macro resolution binds + to the right field), then push a DPA creative template using the AdCP-native + `product_carousel_3_to_10` format on `creative.adcontextprotocol.org`. Real + social-platform formats (Meta `native_carousel`, Snap dynamic ad set, TikTok + dynamic showcase) are platform-specific refinements tracked as follow-ups on + #2640; the AdCP-native format is the interop baseline. + + Runtime substitution-safety checks (that the emitted tracker URLs percent-encode + the macro values per `docs/creative/universal-macros#substitution-safety-catalog-item-macros`) + require the substitution-observer contract tracked in #2638; phases gated on + that contract activate when runners advertise it. + + steps: + - id: sync_product_catalog + title: "Push a product catalog" + narrative: | + The buyer pushes a small inline product catalog — the shape social platforms + accept for dynamic product ads. Your platform validates each item and + returns per-item approval status. + task: sync_catalogs + schema_ref: "media-buy/sync-catalogs-request.json" + response_schema_ref: "media-buy/sync-catalogs-response.json" + doc_ref: "/media-buy/task-reference/sync_catalogs" + stateful: true + expected: | + Return per-catalog results with catalog_id, action, item_count, and + items_approved counts. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + catalogs: + - catalog_id: "acme_gear_spring_2026" + type: "product" + content_id_type: "sku" + name: "Acme Outdoor — Spring 2026 Gear" + items: + - item_id: "trail_pro_3000" + title: "Trail Pro 3000 Backpack" + description: "65L expedition pack with torso-length adjustment." + url: "https://acmeoutdoor.example/gear/trail-pro-3000" + image_url: "https://cdn.acmeoutdoor.example/gear/trail-pro-3000.jpg" + price: + amount: 289.00 + currency: "USD" + - item_id: "summit_tent_2p" + title: "Summit 2P Ultralight Tent" + description: "2-person four-season tent, 1.8kg packed." + url: "https://acmeoutdoor.example/gear/summit-tent-2p" + image_url: "https://cdn.acmeoutdoor.example/gear/summit-tent-2p.jpg" + price: + amount: 549.00 + currency: "USD" + + idempotency_key: "$generate:uuid_v4#sales_social_catalog_driven_dynamic_ads_sync_product_catalog" + context: + correlation_id: "sales_social--sync_product_catalog" + validations: + - check: response_schema + description: "Response matches sync-catalogs-response.json schema" + - check: field_present + path: "catalogs[0].catalog_id" + description: "Catalog has an ID" + - check: field_present + path: "catalogs[0].item_count" + description: "Catalog reports item count" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--sync_product_catalog" + description: "Context correlation_id returned unchanged" + + - id: sync_dpa_creative + title: "Push a dynamic product ad creative with catalog-item macros" + narrative: | + The buyer pushes a creative template whose tracker URLs reference + catalog-item macros (`{SKU}`, `{GTIN}`). At impression time, your platform + substitutes each catalog item's values into the template to render the + per-impression ad. + + The template uses the AdCP-native `product_carousel_3_to_10` format + (see `docs/creative/channels/carousels.mdx`). Per #2620, values substituted + into URL contexts MUST be percent-encoded such that only RFC 3986 unreserved + characters remain unescaped; nested macro expansion is prohibited. This step + validates that the template is accepted into the library; runtime + substitution validation is tracked under #2638. + task: sync_creatives + schema_ref: "creative/sync-creatives-request.json" + response_schema_ref: "creative/sync-creatives-response.json" + doc_ref: "/creative/task-reference/sync_creatives" + comply_scenario: creative_sync + stateful: true + expected: | + Accept the DPA creative template. Per-creative action: created or updated. + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + creatives: + - creative_id: "acme_dpa_spring_2026" + name: "Acme Outdoor — Spring DPA template" + format_id: + agent_url: "https://creative.adcontextprotocol.org" + id: "product_carousel_3_to_10" + assets: + impression_pixel: + asset_type: "url" + url: "https://track.acmeoutdoor.example/imp?sku={SKU}>in={GTIN}&mb={MEDIA_BUY_ID}" + click_url: + asset_type: "url" + url: "https://track.acmeoutdoor.example/click?sku={SKU}" + + idempotency_key: "$generate:uuid_v4#sales_social_catalog_driven_dynamic_ads_sync_dpa_creative" + context: + correlation_id: "sales_social--sync_dpa_creative" + validations: + - check: response_schema + description: "Response matches sync-creatives-response.json schema" + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--sync_dpa_creative" + description: "Context correlation_id returned unchanged" + - id: event_setup + title: "Event source setup" + narrative: | + Before sending conversion events, the buyer registers the event sources the platform + should expect events from — a website pixel, a mobile SDK, or a server-to-server feed. + The platform returns setup instructions and binds the event_source_id that later + log_event calls will reference. + + steps: + - id: sync_event_sources + title: "Register conversion event sources" + narrative: | + The buyer tells the platform where conversion events will come from. The + platform records each event_source_id, returns integration code, and will + accept log_event calls that reference it. + task: sync_event_sources + schema_ref: "media-buy/sync-event-sources-request.json" + response_schema_ref: "media-buy/sync-event-sources-response.json" + doc_ref: "/media-buy/task-reference/sync_event_sources" + stateful: true + expected: | + Return event sources with: + - event_source_id and seller_id + - setup.snippet: integration code (JavaScript pixel, HTML tag, or pixel URL) + - setup.instructions: human-readable integration guide + - action: created or updated + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + event_sources: + - event_source_id: "acmeoutdoor_website" + name: "Acme Outdoor Website" + event_types: ["purchase", "add_to_cart", "page_view", "lead"] + allowed_domains: ["acmeoutdoor.example"] + + idempotency_key: "$generate:uuid_v4#sales_social_event_setup_sync_event_sources" + context: + correlation_id: "sales_social--sync_event_sources" + validations: + - check: response_schema + description: "Response matches sync-event-sources-response.json schema" + - check: field_present + path: "event_sources[0].setup.snippet" + description: "Event source includes setup snippet" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--sync_event_sources" + description: "Context correlation_id returned unchanged" + - id: event_logging + title: "Conversion event tracking" + narrative: | + The buyer sends conversion events back to the platform for measurement and optimization. + Events include purchases, signups, and other post-click actions that the platform uses + to optimize delivery and report on campaign performance. + + steps: + - id: log_event + title: "Send conversion events" + narrative: | + The buyer logs conversion events that occurred after ad exposure. The platform + records these events for attribution, reporting, and delivery optimization. + task: log_event + schema_ref: "media-buy/log-event-request.json" + response_schema_ref: "media-buy/log-event-response.json" + doc_ref: "/media-buy/task-reference/log_event" + stateful: true + expected: | + Acknowledge the events: + - Per-event status: accepted or rejected + - Event IDs for deduplication + - Attribution window validation + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + event_source_id: "acmeoutdoor_website" + events: + - event_type: "purchase" + event_id: "evt_trail_pro_001" + event_time: "2026-04-05T14:30:00Z" + value: 149.99 + currency: "USD" + + idempotency_key: "$generate:uuid_v4#sales_social_event_logging_log_event" + context: + correlation_id: "sales_social--log_event" + validations: + - check: response_schema + description: "Response matches log-event-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--log_event" + description: "Context correlation_id returned unchanged" + - id: financials + title: "Account financials" + narrative: | + The buyer checks account financials — spending, balance, and payment status. This is + essential for budget monitoring across multiple social platforms. + + steps: + - id: get_account_financials + title: "Check account spending and balance" + narrative: | + The buyer retrieves financial information for the advertiser account. The platform + returns current spend, remaining balance, and payment status. + task: get_account_financials + schema_ref: "account/get-account-financials-request.json" + response_schema_ref: "account/get-account-financials-response.json" + doc_ref: "/accounts/tasks/get_account_financials" + stateful: true + expected: | + Return account financial data: + - Current spend to date + - Remaining balance or credit + - Payment status and terms + - Budget utilization metrics + + sample_request: + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + + context: + correlation_id: "sales_social--get_account_financials" + validations: + - check: response_schema + description: "Response matches get-account-financials-response.json schema" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "sales_social--get_account_financials" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/signal-marketplace/index.yaml b/dist/compliance/3.0.9/specialisms/signal-marketplace/index.yaml new file mode 100644 index 0000000000..a733f17abf --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/signal-marketplace/index.yaml @@ -0,0 +1,415 @@ +id: signal_marketplace +version: "1.0.0" +title: "Marketplace signal agent" +protocol: signals +category: signal_marketplace +summary: "Signal agent that resells third-party data provider signals with verifiable catalog provenance." +track: signals +required_tools: + - get_signals +requires_scenarios: + - signal_marketplace/governance_denied + +narrative: | + You operate a signal marketplace — an intermediary that aggregates audience data from + multiple third-party providers and makes it available to buyers through a single interface. + Think LiveRamp Data Marketplace, Oracle Data Cloud, or Lotame. + + Your agent searches across catalogs published by data providers in their adagents.json + files. Buyers discover signals through natural language queries, verify provenance by + checking the data provider's catalog directly, and activate signals on DSPs or sales + agents for campaign targeting. + + The key property of marketplace signals: provenance is independently verifiable. Each + signal traces back to a data_provider_domain whose adagents.json lists your agent as + authorized. Buyers can (and should) verify this before spending. + + This storyboard walks through discovery, verification, and both activation patterns — + activating directly on a DSP (buyer manages targeting) and activating on a sales agent + (SA handles downstream coordination). + + Pricing is hard-required for signal marketplaces. Signals are rate-carded goods by + definition — the value exchange is paying for access to audience data. Unlike creative + ad servers (where billing can be handled via out-of-band enterprise contracts), a signal + marketplace without pricing_options is either non-commercial or misconfigured; buyers + cannot activate without a pricing_option_id to anchor billing. + +agent: + interaction_model: marketplace_catalog + capabilities: + - catalog_signals + examples: + - "LiveRamp Data Marketplace" + - "Oracle Data Cloud" + - "Lotame" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + The buyer has a campaign brief with targeting objectives. The test kit provides + sample signal definitions, pricing options, and destination configurations that + match the training agent's signal providers. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports signals before discovering or activating audience data. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring signals in supported_protocols, confirming the agent serves audience signals. + sample_request: + context: + correlation_id: "signal_marketplace--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_marketplace--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: discovery + title: "Signal discovery" + narrative: | + The buyer describes what they need in natural language. Your agent searches + across all authorized data provider catalogs and returns matching signals with + pricing, coverage estimates, and value types. + + This is where marketplace agents earn their keep — the buyer doesn't need to + know which providers exist or what taxonomies they use. One query, many sources. + + steps: + - id: search_by_spec + title: "Discover signals from a campaign brief" + narrative: | + The buyer's platform translates a campaign brief into a get_signals call. + Your agent searches catalogs from every authorized data provider and returns + what matches — automotive intent from one provider, geo data from another, + retail purchase history from a third. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + comply_scenario: signals_flow + stateful: false + expected: | + Return matching signals from multiple data providers. Each signal must include: + - signal_agent_segment_id for activation + - signal_id with source, data_provider_domain, and id + - name, description, and value_type (binary, categorical, or numeric) + - coverage_percentage (audience reach estimate) + - pricing_options with at least one pricing model + - signal_type: "marketplace" + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_spec: "In-market EV buyers with high purchase propensity, near auto dealerships" + + context: + correlation_id: "signal_marketplace--search_by_spec" + + context_outputs: + - name: first_signal_id + path: "signals[0].signal_id" + - name: first_signal_agent_segment_id + path: "signals[0].signal_agent_segment_id" + - name: first_signal_pricing_option_id + path: "signals[0].pricing_options[0].pricing_option_id" + - name: second_signal_id + path: "signals[1].signal_id" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "Each signal has a signal_agent_segment_id" + - check: field_present + path: "signals[0].signal_id.data_provider_domain" + description: "Each signal traces to a data provider domain" + - check: field_present + path: "signals[0].pricing_options" + description: "Each signal has pricing options" + - check: field_present + path: "signals[0].coverage_percentage" + description: "Each signal has a coverage estimate" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_marketplace--search_by_spec" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "signals[0].signal_id.source" + description: "Signal ID includes source discriminator" + - check: field_present + path: "signals[0].signal_id.data_provider_domain" + description: "Signal ID includes data_provider_domain for provenance" + - id: search_by_ids + title: "Look up specific signals by ID" + narrative: | + The buyer already knows which signals they want — discovered in the prior + step. They pass signal_ids directly instead of a natural language query. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + comply_scenario: signals_flow + stateful: true + expected: | + Return the exact signals requested, with full metadata and pricing. + If a signal_id doesn't exist, omit it from results — don't error. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_ids: + - "$context.first_signal_id" + + context: + correlation_id: "signal_marketplace--search_by_ids" + validations: + - check: response_schema + description: "Response matches schema" + - check: field_present + path: "signals" + description: "Response contains a signals array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_marketplace--search_by_ids" + description: "Context correlation_id returned unchanged" + - id: verification + title: "Catalog verification" + narrative: | + Before activating third-party data, the buyer verifies provenance. They fetch + the data provider's adagents.json directly and confirm the signal exists and + your agent is authorized. This independent check is outside the AdCP protocol — + but your agent must return the metadata that makes it possible. + + This phase tests that your get_signals responses include verifiable provenance + data: signal_id.source is "catalog" and signal_id.data_provider_domain points + to a real domain whose adagents.json the buyer can fetch independently. + + steps: + - id: verify_provenance_metadata + title: "Confirm signals carry verifiable provenance" + narrative: | + The buyer looks up a specific signal by ID (discovered earlier) and checks + that the response includes the metadata needed for independent verification — + source is "catalog" and data_provider_domain points to a fetchable adagents.json. + The actual HTTP fetch of adagents.json is the buyer's responsibility, but + your agent must provide the domain to fetch from. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/data-providers" + comply_scenario: signals_flow + stateful: true + expected: | + Return the requested signal with verifiable provenance metadata: + - signal_id.source is "catalog" + - signal_id.data_provider_domain matches a real domain + The buyer will independently fetch that domain's adagents.json to confirm + your agent is listed in authorized_agents. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_ids: + - "$context.first_signal_id" + + context: + correlation_id: "signal_marketplace--verify_provenance_metadata" + validations: + - check: field_value + path: "signals[0].signal_id.source" + value: "catalog" + description: "Signal source is 'catalog' (verifiable via adagents.json)" + - check: field_present + path: "signals[0].signal_id.data_provider_domain" + description: "Data provider domain is present for independent verification" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_marketplace--verify_provenance_metadata" + description: "Context correlation_id returned unchanged" + - id: platform_activation + title: "Activate on a DSP" + narrative: | + The buyer activates a signal directly on a DSP platform. The signal agent pushes + segment data to the platform, and the buyer gets back a segment_id they can + reference when configuring campaign targeting. + + Use platform destinations when the buyer is managing DSP campaigns directly — + not through a sales agent. + + steps: + - id: activate_on_platform + title: "Activate signal on a DSP" + narrative: | + The buyer selects a signal and a DSP destination. The signal agent pushes + the segment to the platform. This is typically asynchronous — the initial + response shows is_live: false with an estimated duration, and the buyer + polls for completion. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployment with: + - type: "platform" matching the requested destination + - is_live: false initially (async activation) + - estimated_activation_duration_minutes + After polling, the deployment should show: + - is_live: true + - activation_key with type: "segment_id" and a platform-native segment ID + - deployed_at timestamp + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "$context.first_signal_agent_segment_id" + pricing_option_id: "$context.first_signal_pricing_option_id" + destinations: + - type: "platform" + platform: "the-trade-desk" + account: "agency-123-ttd" + idempotency_key: "$generate:uuid_v4#signal_marketplace_activate_platform" + context: + correlation_id: "signal_marketplace--activate_on_platform" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].type" + description: "Deployment includes type" + - check: field_value + path: "deployments[0].type" + value: "platform" + description: "Deployment type is 'platform'" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_marketplace--activate_on_platform" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "deployments[0].activation_key" + description: "Deployment includes activation_key for targeting" + - id: agent_activation + title: "Activate on a sales agent" + narrative: | + The buyer activates a signal on a sales agent instead of a DSP. This is the + right pattern when the buyer is purchasing media through the SA — the SA handles + its own DSP coordination. + + The buyer doesn't need to know which DSP the SA uses. The activation key confirms + the signal is live on the SA, and the SA applies targeting when fulfilling media + buys through create_media_buy. + + steps: + - id: activate_on_agent + title: "Activate signal on a sales agent" + narrative: | + The buyer activates a signal with the sales agent's URL as the destination. + Agent activations are typically synchronous — the SA records the activation + immediately and returns a key_value activation key. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployment with: + - type: "agent" matching the requested destination + - agent_url matching the SA's URL + - is_live: true (sync activation) + - activation_key with type: "key_value" + - deployed_at timestamp + + The SA records the activation internally. When the buyer later calls + create_media_buy through this SA, signal-based targeting is already + in place. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "$context.first_signal_agent_segment_id" + pricing_option_id: "$context.first_signal_pricing_option_id" + destinations: + - type: "agent" + agent_url: "https://wonderstruck.salesagents.example" + idempotency_key: "$generate:uuid_v4#signal_marketplace_activate_agent" + context: + correlation_id: "signal_marketplace--activate_on_agent" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].activation_key" + description: "Deployment includes activation key" + - check: field_value + path: "deployments[0].type" + value: "agent" + description: "Deployment type is 'agent'" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_marketplace--activate_on_agent" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/specialisms/signal-marketplace/scenarios/governance_denied.yaml b/dist/compliance/3.0.9/specialisms/signal-marketplace/scenarios/governance_denied.yaml new file mode 100644 index 0000000000..5a6e38e782 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/signal-marketplace/scenarios/governance_denied.yaml @@ -0,0 +1,207 @@ +id: signal_marketplace/governance_denied +version: "1.0.0" +title: "Signal agent rejects activation when governance denies" +category: signal_marketplace +summary: "Verifies that a signal agent propagates GOVERNANCE_DENIED when the buyer's governance plan denies activation." +track: signals +required_tools: + - sync_governance + - get_signals + - activate_signal + +narrative: | + Signal activation is a spending event: activating a third-party segment costs per-user + or per-activation fees that must be authorized against the buyer's governance plan. The + signal agent must consult the registered governance agent before activation and deny + the request when governance returns denied. + + This scenario sets up a strict $100 plan, registers that governance with the signal + agent via sync_governance, then attempts to activate a signal whose pricing exceeds + the plan. The signal agent must return GOVERNANCE_DENIED with findings propagated + from the governance agent. + + By default, the governance agent is the training agent at test-agent.adcontextprotocol.org. + Override with --governance-agent-url to use a custom governance agent. + +agent: + interaction_model: marketplace_catalog + capabilities: + - catalog_signals + - governance_aware + examples: + - "Any signal agent that honors governance before activation" + +caller: + role: buyer_agent + example: "Pinnacle Agency (buyer)" + +prerequisites: + description: | + A governance agent that supports sync_plans and check_governance, and a signal + agent that supports sync_governance + activate_signal. + test_kit: "test-kits/acme-outdoor.yaml" + +phases: + - id: governance_plan_setup + title: "Set up strict governance plan" + narrative: | + Create a governance plan with a $100 budget — enough to block even a single + activation at typical CPM-for-segment pricing. + + steps: + - id: sync_plans + title: "Create strict governance plan" + task: sync_plans + schema_ref: "governance/sync-plans-request.json" + response_schema_ref: "governance/sync-plans-response.json" + doc_ref: "/governance/campaign/tasks/sync_plans" + stateful: true + expected: | + The governance agent acknowledges the plan. + sample_request: + plans: + - plan_id: "comply-signal-gov-denied" + brand: + domain: "acmeoutdoor.example" + objectives: "Restricted plan — signals activation test" + budget: + total: 100 + currency: "USD" + reallocation_threshold: 50 + flight: + start: "2026-04-01T00:00:00Z" + end: "2026-06-30T23:59:59Z" + countries: ["US"] + idempotency_key: "$generate:uuid_v4#signal_marketplace_governance_denied_governance_plan_setup_sync_plans" + validations: + - check: response_schema + description: "Response matches sync-plans-response.json schema" + + - id: signal_agent_setup + title: "Register account and governance with the signal agent" + steps: + - id: sync_accounts + title: "Establish account with signal agent" + task: sync_accounts + schema_ref: "account/sync-accounts-request.json" + response_schema_ref: "account/sync-accounts-response.json" + doc_ref: "/accounts/tasks/sync_accounts" + stateful: true + expected: | + Signal agent returns the account with account_id active. + sample_request: + accounts: + - brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + billing: "operator" + payment_terms: "net_30" + idempotency_key: "$generate:uuid_v4#signal_marketplace_governance_denied_signal_agent_setup_sync_accounts" + validations: + - check: response_schema + description: "Response matches sync-accounts-response.json schema" + - check: field_present + path: "accounts[0].account_id" + description: "Account has a platform-assigned ID" + + - id: sync_governance + title: "Register governance agent with signal agent" + task: sync_governance + schema_ref: "account/sync-governance-request.json" + response_schema_ref: "account/sync-governance-response.json" + doc_ref: "/accounts/tasks/sync_governance" + stateful: true + expected: | + Signal agent acknowledges governance registration. + sample_request: + accounts: + - account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + governance_agents: + - url: "$context.governance_agent_url" + authentication: + schemes: ["Bearer"] + credentials: "gov-token-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + categories: ["budget_authority"] + idempotency_key: "$generate:uuid_v4#signal_marketplace_governance_denied_signal_agent_setup_sync_governance" + validations: + - check: response_schema + description: "Response matches sync-governance-response.json schema" + + - id: activation_denied + title: "Attempt activation — governance denies" + steps: + - id: get_signals_list + title: "Discover a signal to activate" + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/task-reference/get_signals" + comply_scenario: signal_discovery + stateful: false + expected: | + Return at least one signal with a signal_agent_segment_id and pricing. + sample_request: + signal_spec: "outdoor enthusiasts, age 25-54, US" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + context_outputs: + - path: "signals[0].signal_agent_segment_id" + key: "signal_agent_segment_id" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "Signal has an activation identifier" + + - id: activate_signal_denied + title: "activate_signal — governance denies" + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/task-reference/activate_signal" + comply_scenario: signal_activation + expect_error: true + negative_path: payload_well_formed + stateful: true + expected: | + Signal agent rejects with: + - code: GOVERNANCE_DENIED + - findings propagated from the governance agent + + sample_request: + signal_agent_segment_id: "$context.signal_agent_segment_id" + destinations: + - type: "platform" + platform: "the-trade-desk" + account: "acmeoutdoor-ttd-seat" + account: + brand: + domain: "acmeoutdoor.example" + operator: "pinnacle-agency.example" + idempotency_key: "signal-gov-denied-v1" + + context: + correlation_id: "signal_marketplace--governance_denied--activate" + sample_response: + errors: + - code: "GOVERNANCE_DENIED" + message: "Signal activation requires governance approval. Call check_governance first — a governance plan is registered for this account." + details: + findings: + - category_id: "budget_authority" + severity: "critical" + explanation: "Signal activation requires governance approval. Call check_governance first — a governance plan is registered for this account." + plan_id: "comply-signal-gov-denied" + validations: + - check: error_code + value: "GOVERNANCE_DENIED" + description: "Error code is GOVERNANCE_DENIED" + - check: field_present + path: "context" + description: "Response echoes back the context object even on errors" diff --git a/dist/compliance/3.0.9/specialisms/signal-owned/index.yaml b/dist/compliance/3.0.9/specialisms/signal-owned/index.yaml new file mode 100644 index 0000000000..6687ae2923 --- /dev/null +++ b/dist/compliance/3.0.9/specialisms/signal-owned/index.yaml @@ -0,0 +1,316 @@ +id: signal_owned +version: "1.0.0" +title: "Owned signal agent" +protocol: signals +category: signal_owned +summary: "Signal agent serving first-party or proprietary audience data without external catalog verification." +track: signals +required_tools: + - get_signals + +narrative: | + You operate a first-party data platform — a retailer CDP, publisher contextual data + provider, or proprietary audience platform. Your signals are agent-native: they come + from your own data, not from third-party catalogs. + + Buyers trust your agent directly. There's no external adagents.json to verify against — + provenance is the agent itself. This simplifies the flow: discovery and activation, + without the verification step that marketplace agents require. + + Owned signal agents often have richer signal types. A retailer might expose purchase + frequency as a numeric signal (0-30 visits/month) or loyalty tier as categorical + (platinum/gold/silver/bronze). A publisher might expose content category, subscriber + tenure, or engagement scores. + + This storyboard walks through discovery and both activation patterns. + + Pricing is hard-required here for the same reason as signal marketplaces: signals + are rate-carded goods. Buyers cannot activate without a pricing_option_id to anchor + billing. Agents distributing first-party signals without commercial terms belong on + list_authorized_properties or a different specialism, not this one. + +agent: + interaction_model: owned_signals + capabilities: [] + examples: + - "Retailer CDPs (e.g., loyalty and purchase data)" + - "Publisher contextual platforms (e.g., content category, subscriber data)" + - "First-party audience platforms (e.g., CRM-derived segments)" + +caller: + role: buyer_agent + example: "Scope3 (DSP)" + +prerequisites: + description: | + The buyer has a campaign brief with targeting objectives. The test kit provides + sample owned signal definitions with various value types (binary, categorical, + numeric) and pricing models. + test_kit: "test-kits/nova-motors.yaml" + +phases: + - id: capability_discovery + title: "Capability discovery" + narrative: | + The buyer calls get_adcp_capabilities to confirm the agent supports signals before discovering or activating audience data. + + steps: + - id: get_capabilities + title: "Check agent capabilities" + narrative: | + Verify that the agent declares the expected protocol support before + proceeding with domain-specific operations. + task: get_adcp_capabilities + schema_ref: "protocol/get-adcp-capabilities-request.json" + response_schema_ref: "protocol/get-adcp-capabilities-response.json" + doc_ref: "/protocol/get_adcp_capabilities" + comply_scenario: capability_discovery + stateful: false + expected: | + Return capabilities declaring signals in supported_protocols, confirming the agent serves audience signals. + sample_request: + context: + correlation_id: "signal_owned--get_capabilities" + validations: + - check: response_schema + description: "Response matches get-adcp-capabilities-response.json schema" + - check: field_present + path: "supported_protocols" + description: "Agent declares supported protocols" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_owned--get_capabilities" + description: "Context correlation_id returned unchanged" + - id: discovery + title: "Signal discovery" + narrative: | + The buyer describes targeting objectives and your agent returns matching signals + from your proprietary data. Unlike marketplace agents, these signals have + signal_type "owned" — provenance is the agent itself. + + Owned signals often include richer value types. A retailer can expose loyalty + tiers as categorical signals and purchase frequency as numeric signals, giving + buyers more precise targeting than binary include/exclude. + + steps: + - id: search_owned_signals + title: "Discover owned signals" + narrative: | + The buyer searches for audience segments using a natural language description. + Your agent returns signals from your proprietary data — loyalty tiers, purchase + history, engagement scores, contextual categories. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + comply_scenario: signals_flow + stateful: false + expected: | + Return matching signals from your proprietary data. Each signal must include: + - signal_agent_segment_id for activation + - signal_id with source: "agent_native" + - name, description, and value_type + - coverage_percentage + - pricing_options + - signal_type: "owned" + + For categorical signals, include the allowed_values. + For numeric signals, include the range (min/max). + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_spec: "High-value customers likely to purchase electronics, with loyalty program data" + + context: + correlation_id: "signal_owned--search_owned_signals" + validations: + - check: response_schema + description: "Response matches get-signals-response.json schema" + - check: field_present + path: "signals[0].signal_agent_segment_id" + description: "Each signal has a signal_agent_segment_id" + - check: field_present + path: "signals[0].pricing_options" + description: "Each signal has pricing options" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_owned--search_owned_signals" + description: "Context correlation_id returned unchanged" + - check: field_present + path: "signals[0].signal_id.source" + description: "Signal ID includes source discriminator" + - id: filter_by_criteria + title: "Filter signals by criteria" + narrative: | + The buyer narrows the search using filters — maximum CPM, specific signal + types, or coverage thresholds. This tests that your agent handles filter + parameters correctly and returns only matching signals. + task: get_signals + schema_ref: "signals/get-signals-request.json" + response_schema_ref: "signals/get-signals-response.json" + doc_ref: "/signals/tasks/get_signals" + comply_scenario: signals_flow + stateful: false + expected: | + Return only signals matching the filter criteria. If no signals match, + return an empty signals array — not an error. + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_spec: "Purchase behavior signals" + filters: + max_cpm: 5.00 + + context: + correlation_id: "signal_owned--filter_by_criteria" + validations: + - check: response_schema + description: "Response matches schema" + - check: field_present + path: "signals" + description: "Response contains a signals array" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_owned--filter_by_criteria" + description: "Context correlation_id returned unchanged" + - id: platform_activation + title: "Activate on a DSP" + narrative: | + The buyer activates a signal directly on a DSP. Your agent pushes the segment + to the platform and returns a segment_id the buyer uses for campaign targeting. + + Use platform destinations when the buyer manages DSP campaigns directly. + + steps: + - id: activate_on_platform + title: "Activate owned signal on a DSP" + narrative: | + The buyer selects a signal and activates it on a DSP. For owned signals, + this means your platform pushes first-party segment data to the DSP. The + buyer gets back a segment_id for targeting. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployment with: + - type: "platform" + - is_live status (may be async) + - activation_key with type: "segment_id" when live + - estimated_activation_duration_minutes if not immediately live + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "prism_high_ltv" + pricing_option_id: "po_prism_flat_monthly" + destinations: + - type: "platform" + platform: "the-trade-desk" + account: "agency-123-ttd" + idempotency_key: "$generate:uuid_v4#signal_owned_activate_platform" + context: + correlation_id: "signal_owned--activate_on_platform" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].type" + description: "Deployment includes type" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_owned--activate_on_platform" + description: "Context correlation_id returned unchanged" + - id: agent_activation + title: "Activate on a sales agent" + narrative: | + The buyer activates a signal on a sales agent. The SA records the activation + and handles downstream DSP coordination — the buyer doesn't need to know which + platform the SA uses. + + This is the right pattern when buying media through a sales agent. + + steps: + - id: activate_on_agent + title: "Activate owned signal on a sales agent" + narrative: | + The buyer activates a signal with the SA's URL as the destination. The + activation is typically synchronous. The SA records it internally and + applies the targeting when the buyer later calls create_media_buy. + task: activate_signal + schema_ref: "signals/activate-signal-request.json" + response_schema_ref: "signals/activate-signal-response.json" + doc_ref: "/signals/tasks/activate_signal" + comply_scenario: signals_flow + stateful: true + expected: | + Return a deployment with: + - type: "agent" + - agent_url matching the SA + - is_live: true (sync activation) + - activation_key with type: "key_value" + - deployed_at timestamp + + sample_request: + account: + brand: + domain: "novamotors.example" + operator: "pinnacle-agency.example" + signal_agent_segment_id: "prism_cart_abandoner" + pricing_option_id: "po_prism_abandoner_cpm" + destinations: + - type: "agent" + agent_url: "https://wonderstruck.salesagents.example" + idempotency_key: "$generate:uuid_v4#signal_owned_activate_agent" + context: + correlation_id: "signal_owned--activate_on_agent" + ext: + test_platform: + test_run: true + validations: + - check: response_schema + description: "Response matches activate-signal-response.json schema" + - check: field_present + path: "deployments[0].activation_key" + description: "Deployment includes activation key" + - check: field_value + path: "deployments[0].type" + value: "agent" + description: "Deployment type is 'agent'" + + - check: field_present + path: "context" + description: "Response echoes back the context object" + - check: field_value + path: "context.correlation_id" + value: "signal_owned--activate_on_agent" + description: "Context correlation_id returned unchanged" diff --git a/dist/compliance/3.0.9/test-kits/acme-outdoor.yaml b/dist/compliance/3.0.9/test-kits/acme-outdoor.yaml new file mode 100644 index 0000000000..131a4d64ff --- /dev/null +++ b/dist/compliance/3.0.9/test-kits/acme-outdoor.yaml @@ -0,0 +1,210 @@ +# Acme Outdoor — Sample Advertiser Test Kit +# +# Entity definition: fictional-entities.yaml → advertisers[acme_outdoor] +# +# Provides the "ingredients" needed to test creative agent storyboards: +# a brand identity, sample assets at common dimensions, and sample text. +# +# This brand is registered as a sandbox brand in AgenticAdvertising.org (AAO). +# Agents resolve acmeoutdoor.example via the standard AAO brand resolution path. +# AAO returns the brand.json below but excludes sandbox brands from production +# brand discovery results. + +id: acme_outdoor +name: "Acme Outdoor" +description: "Fictional outdoor gear brand for storyboard testing" +sandbox: true + +# Authentication fixture for the universal security_baseline storyboard +# (universal/security.yaml). Declares: +# - api_key: the demo Bearer the runner sends on the positive api-key +# probe. The conformance handle is the `demo--` prefix — agents +# SHOULD accept any Bearer matching that prefix (not this literal +# value) so the suffix can rotate across spec versions without +# breaking previously-conformant agents. +# - probe_task: the protected, auth-required read the runner calls under +# `auth: none` and with a random-invalid key. Default is list_creatives; +# declare explicitly so the api_key phase runs instead of being skipped +# via `skip_if: "!test_kit.auth.api_key"`. +auth: + api_key: "demo-acme-outdoor-v1" + probe_task: list_creatives + +brand: + house: + domain: "acmeoutdoor.example" + name: "Acme Outdoor" + brand_id: "acme_outdoor" + names: + - en: "Acme Outdoor" + description: "Premium outdoor gear for every adventure. From trail to summit, we make gear that performs." + industry: "retail" + keller_type: "master" + logos: + - url: "https://test-assets.adcontextprotocol.org/acme-outdoor/logo-primary.png" + orientation: "horizontal" + background: "light-bg" + variant: "primary" + width: 400 + height: 100 + - url: "https://test-assets.adcontextprotocol.org/acme-outdoor/logo-icon.png" + orientation: "square" + background: "transparent-bg" + variant: "icon" + width: 200 + height: 200 + colors: + primary: "#1B5E20" + secondary: "#FF6F00" + accent: "#FDD835" + background: "#FAFAFA" + text: "#212121" + fonts: + heading: + family: "Montserrat" + weight: 700 + url: "https://fonts.googleapis.com/css2?family=Montserrat:wght@700" + body: + family: "Open Sans" + weight: 400 + url: "https://fonts.googleapis.com/css2?family=Open+Sans" + tone: + voice: "Confident and adventurous, but never pretentious. We talk to people who do things, not people who buy things." + attributes: + - "active" + - "direct" + - "warm" + dos: + - "Use action verbs" + - "Reference real outdoor activities" + - "Keep it short" + donts: + - "Use superlatives without evidence" + - "Talk down to the reader" + - "Use corporate jargon" + +# Sample assets at common ad dimensions. +# These are placeholder URLs — in production, these would point to actual hosted test images. +assets: + images: + - id: "hero_300x250" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-300x250.jpg" + width: 300 + height: 250 + mime_type: "image/jpeg" + description: "Medium rectangle hero — hiker on mountain trail" + + - id: "hero_728x90" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-728x90.jpg" + width: 728 + height: 90 + mime_type: "image/jpeg" + description: "Leaderboard hero — panoramic mountain vista" + + - id: "hero_320x50" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-320x50.jpg" + width: 320 + height: 50 + mime_type: "image/jpeg" + description: "Mobile banner hero — trail gear close-up" + + - id: "hero_160x600" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-160x600.jpg" + width: 160 + height: 600 + mime_type: "image/jpeg" + description: "Wide skyscraper hero — vertical trail scene" + + - id: "hero_master" + url: "https://test-assets.adcontextprotocol.org/acme-outdoor/hero-master.jpg" + width: 1200 + height: 628 + mime_type: "image/jpeg" + description: "Master hero image — high-res source for any size" + + text: + headlines: + - "Summer Sale — 40% Off All Gear" + - "Built for the Trail" + - "Adventure Starts Here" + descriptions: + - "Premium outdoor gear tested on the world's toughest trails. Shop the summer collection." + - "From daypacks to expedition tents, gear that performs when it matters." + cta: + - "Shop Now" + - "Explore the Collection" + - "Find Your Gear" + + click_url: "https://acmeoutdoor.example/summer-sale" + +# Inventory list fixtures for property_list / collection_list targeting tests. +# +# These reference lists a fictional governance/inventory agent would publish. +# They let storyboards exercise the full PropertyListReference / CollectionListReference +# contract without needing a live list server: scenarios point at list_id values +# and the seller's test engine returns canned contents. +# +# Both matching and non-matching sets are provided so a single test fixture +# covers targeting (buy should include listed inventory) and no-match behaviour +# (buy references a list that resolves to zero matches in this seller's catalog). +inventory_targets: + # --- Matching sets (Acme Outdoor's intended audience) --- + matching_properties: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_allowlist_v1" + description: "Outdoor lifestyle properties relevant to Acme Outdoor campaigns." + # Expected contents when the list is fetched via get_property_list. + expected_identifiers: + - type: domain + value: "outdoormagazine.example" + - type: domain + value: "hikingtrails.example" + - type: domain + value: "campinggear.example" + - type: domain + value: "mountaineering.example" + + matching_collections: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_collections_v1" + description: "Outdoor programming the buyer wants to run alongside." + # Expected collection entries when fetched via get_collection_list. + expected_collections: + - collection_rid: "rid:outdoor:trail_life" + name: "Trail Life" + kind: "series" + genre: "outdoor" + distribution_ids: + - type: imdb_id + value: "tt9100001" + - collection_rid: "rid:outdoor:summit_stories" + name: "Summit Stories" + kind: "series" + genre: "outdoor" + distribution_ids: + - type: imdb_id + value: "tt9100002" + + # --- No-match sets (nothing in this seller's catalog matches) --- + no_match_properties: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_v1" + description: "Properties outside this seller's inventory — should yield zero matches." + expected_identifiers: + - type: domain + value: "never-sold-here.example" + - type: domain + value: "also-not-indexed.example" + + no_match_collections: + agent_url: "https://governance.pinnacle-agency.example" + list_id: "acme_outdoor_no_match_collections_v1" + description: "Collections not carried by this seller — should yield zero matches." + expected_collections: + - collection_rid: "rid:food:bistro_kitchen" + name: "Bistro Kitchen" + kind: "series" + genre: "food" + distribution_ids: + - type: imdb_id + value: "tt9200001" diff --git a/dist/compliance/3.0.9/test-kits/bistro-oranje.yaml b/dist/compliance/3.0.9/test-kits/bistro-oranje.yaml new file mode 100644 index 0000000000..93f308d09f --- /dev/null +++ b/dist/compliance/3.0.9/test-kits/bistro-oranje.yaml @@ -0,0 +1,126 @@ +# Bistro Oranje — Hospitality Advertiser Test Kit +# +# Entity definition: fictional-entities.yaml → advertisers[bistro_oranje] +# +# Dutch restaurant chain used in rights licensing scenarios. +# +# This brand is registered as a sandbox brand in AgenticAdvertising.org (AAO). +# Agents resolve bistro-oranje.example via the standard AAO brand resolution path. +# AAO returns the brand.json below but excludes sandbox brands from production +# brand discovery results. + +id: bistro_oranje +name: "Bistro Oranje" +description: "Dutch restaurant chain for rights licensing storyboard testing" +sandbox: true + +# security_baseline auth fixture — see acme-outdoor.yaml for semantics. +auth: + api_key: "demo-bistro-oranje-v1" + probe_task: list_creatives + +brand: + house: + domain: "bistro-oranje.example" + name: "Bistro Oranje" + brand_id: "bistro_oranje" + names: + - en: "Bistro Oranje" + - nl: "Bistro Oranje" + description: "Modern Dutch dining — seasonal menus inspired by the Netherlands, served with warmth." + industry: "hospitality" + keller_type: "master" + logos: + - url: "https://test-assets.adcontextprotocol.org/bistro-oranje/logo-primary.png" + orientation: "horizontal" + background: "light-bg" + variant: "primary" + width: 400 + height: 100 + - url: "https://test-assets.adcontextprotocol.org/bistro-oranje/logo-icon.png" + orientation: "square" + background: "transparent-bg" + variant: "icon" + width: 200 + height: 200 + colors: + primary: "#E65100" + secondary: "#1B5E20" + accent: "#FFC107" + background: "#FFF8E1" + text: "#212121" + fonts: + heading: + family: "Playfair Display" + weight: 700 + url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700" + body: + family: "Source Sans Pro" + weight: 400 + url: "https://fonts.googleapis.com/css2?family=Source+Sans+Pro" + tone: + voice: "Warm and inviting, with understated confidence. We let the food speak — no excess, no pretension." + attributes: + - "welcoming" + - "seasonal" + - "unpretentious" + dos: + - "Mention seasonal ingredients by name" + - "Reference Dutch culinary traditions" + - "Keep descriptions sensory and specific" + donts: + - "Use fine-dining jargon" + - "Overuse superlatives" + - "Promise exclusivity — Bistro Oranje is for everyone" + +assets: + images: + - id: "hero_300x250" + url: "https://test-assets.adcontextprotocol.org/bistro-oranje/hero-300x250.jpg" + width: 300 + height: 250 + mime_type: "image/jpeg" + description: "Medium rectangle hero — seasonal dish on rustic table" + + - id: "hero_728x90" + url: "https://test-assets.adcontextprotocol.org/bistro-oranje/hero-728x90.jpg" + width: 728 + height: 90 + mime_type: "image/jpeg" + description: "Leaderboard hero — bistro exterior with canal backdrop" + + - id: "hero_320x50" + url: "https://test-assets.adcontextprotocol.org/bistro-oranje/hero-320x50.jpg" + width: 320 + height: 50 + mime_type: "image/jpeg" + description: "Mobile banner hero — close-up of signature stamppot" + + - id: "hero_160x600" + url: "https://test-assets.adcontextprotocol.org/bistro-oranje/hero-160x600.jpg" + width: 160 + height: 600 + mime_type: "image/jpeg" + description: "Wide skyscraper hero — vertical dining room scene" + + - id: "hero_master" + url: "https://test-assets.adcontextprotocol.org/bistro-oranje/hero-master.jpg" + width: 1200 + height: 628 + mime_type: "image/jpeg" + description: "Master hero image — high-res chef plating seasonal dish" + + text: + headlines: + - "Taste the Season at Bistro Oranje" + - "Dutch Comfort, Modern Kitchen" + - "Reserve Your Table Tonight" + descriptions: + - "Seasonal menus inspired by Dutch tradition, made with ingredients sourced within 50km. Bistro Oranje — Amsterdam, Rotterdam, Utrecht." + - "From stamppot to stroopwafel, every dish tells a story. Book your table at Bistro Oranje." + cta: + - "Reserve a Table" + - "View the Menu" + - "Find a Location" + + click_url: "https://bistro-oranje.example/reservations" diff --git a/dist/compliance/3.0.9/test-kits/nova-motors.yaml b/dist/compliance/3.0.9/test-kits/nova-motors.yaml new file mode 100644 index 0000000000..d89c863a90 --- /dev/null +++ b/dist/compliance/3.0.9/test-kits/nova-motors.yaml @@ -0,0 +1,262 @@ +# Nova Motors — Signal-Focused Advertiser Test Kit +# +# Entity definition: fictional-entities.yaml → advertisers[nova_motors] +# Data providers defined in this kit: see fictional-entities.yaml → data_providers +# +# This brand is registered as a sandbox brand in AgenticAdvertising.org (AAO). +# Agents resolve novamotors.example via the standard AAO brand resolution path. +# AAO returns the brand.json below but excludes sandbox brands from production +# brand discovery results. + +id: nova_motors +name: "Nova Motors" +description: | + Signal-focused test kit for the Nova Motors Volta EV launch campaign. + Provides sample signal definitions, pricing options, and destination + configurations for testing signal agent storyboards. +sandbox: true + +# security_baseline auth fixture — see acme-outdoor.yaml for semantics. +# +# probe_task is `get_signals` because this kit's primary consumers are +# signal agents (specialisms/signal-owned, specialisms/signal-marketplace) +# where list_creatives is not implemented. Non-signal storyboards that +# reuse this kit (collection-lists, sales-broadcast-tv, sponsored- +# intelligence) should select acme-outdoor for security_baseline, or +# override probe_task at the storyboard layer. +auth: + api_key: "demo-nova-motors-v1" + probe_task: get_signals + +brand: + house: + domain: "novamotors.example" + name: "Nova Motors" + brand_id: "nova_motors" + names: + - en: "Nova Motors" + description: "Electric vehicles for the next generation. The Volta EV — performance meets sustainability." + industry: "automotive" + keller_type: "master" + logos: + - url: "https://test-assets.adcontextprotocol.org/nova-motors/logo-primary.png" + orientation: "horizontal" + background: "light-bg" + variant: "primary" + width: 400 + height: 100 + - url: "https://test-assets.adcontextprotocol.org/nova-motors/logo-icon.png" + orientation: "square" + background: "transparent-bg" + variant: "icon" + width: 200 + height: 200 + colors: + primary: "#0D47A1" + secondary: "#00BFA5" + accent: "#FF6D00" + background: "#F5F5F5" + text: "#1A1A1A" + fonts: + heading: + family: "Inter" + weight: 700 + url: "https://fonts.googleapis.com/css2?family=Inter:wght@700" + body: + family: "Inter" + weight: 400 + url: "https://fonts.googleapis.com/css2?family=Inter" + tone: + voice: "Forward-thinking and precise. We speak to drivers who care about engineering and the planet — no greenwashing, no hype." + attributes: + - "innovative" + - "precise" + - "optimistic" + dos: + - "Lead with performance specs" + - "Reference sustainability with evidence" + - "Use clean, modern language" + donts: + - "Greenwash or overstate environmental claims" + - "Use legacy automotive clichés" + - "Condescend about range anxiety" + +campaign: + brand: "Nova Motors" + product: "Volta EV" + brief: "Reach in-market EV buyers with high purchase propensity, near dealerships, who haven't bought a Nova vehicle before." + budget: 250000 + currency: "USD" + markets: ["US"] + +signals: + marketplace: + - signal_agent_segment_id: "trident_likely_ev_buyers" + name: "Likely EV Buyers" + data_provider_domain: "tridentauto.example" + signal_id: "likely_ev_buyers" + value_type: "binary" + signal_type: "marketplace" + coverage_percentage: 8 + pricing: + - pricing_option_id: "po_trident_ev_cpm" + model: "cpm" + cpm: 3.50 + currency: "USD" + + - signal_agent_segment_id: "trident_purchase_propensity" + name: "Purchase Propensity" + data_provider_domain: "tridentauto.example" + signal_id: "purchase_propensity" + value_type: "numeric" + signal_type: "marketplace" + range: + min: 0 + max: 1 + coverage_percentage: 55 + pricing: + - pricing_option_id: "po_trident_propensity_cpm" + model: "cpm" + cpm: 4.00 + currency: "USD" + + - signal_agent_segment_id: "meridian_competitor_visitors" + name: "Competitor Visitors" + data_provider_domain: "meridiangeo.example" + signal_id: "competitor_visitors" + value_type: "binary" + signal_type: "marketplace" + coverage_percentage: 6 + pricing: + - pricing_option_id: "po_meridian_visitors_cpm" + model: "cpm" + cpm: 5.00 + currency: "USD" + + - signal_agent_segment_id: "shopgrid_new_to_brand" + name: "New to Brand" + data_provider_domain: "shopgrid.example" + signal_id: "new_to_brand" + value_type: "binary" + signal_type: "marketplace" + coverage_percentage: 25 + pricing: + - pricing_option_id: "po_shopgrid_retail_cpm" + model: "cpm" + cpm: 3.50 + currency: "USD" + + owned: + - signal_agent_segment_id: "prism_high_ltv" + name: "High LTV Customers" + value_type: "binary" + signal_type: "owned" + coverage_percentage: 12 + pricing: + - pricing_option_id: "po_prism_flat_monthly" + model: "flat_fee" + amount: 5000 + period: "monthly" + currency: "USD" + + - signal_agent_segment_id: "prism_cart_abandoner" + name: "Cart Abandoners" + value_type: "binary" + signal_type: "owned" + coverage_percentage: 18 + pricing: + - pricing_option_id: "po_prism_abandoner_cpm" + model: "cpm" + cpm: 8.00 + currency: "USD" + + - signal_agent_segment_id: "prism_engagement_score" + name: "Engagement Score" + value_type: "numeric" + signal_type: "owned" + range: + min: 0 + max: 100 + coverage_percentage: 65 + pricing: + - pricing_option_id: "po_prism_engagement_cpm" + model: "cpm" + cpm: 2.50 + currency: "USD" + + - signal_agent_segment_id: "prism_churn_risk" + name: "Churn Risk" + value_type: "categorical" + signal_type: "owned" + categories: ["low_risk", "medium_risk", "high_risk", "churned"] + coverage_percentage: 70 + pricing: + - pricing_option_id: "po_prism_churn_pom" + model: "percent_of_media" + percent: 12 + max_cpm: 3.00 + currency: "USD" + +assets: + images: + - id: "hero_300x250" + url: "https://test-assets.adcontextprotocol.org/nova-motors/hero-300x250.jpg" + width: 300 + height: 250 + mime_type: "image/jpeg" + description: "Medium rectangle hero — Volta EV on coastal highway" + + - id: "hero_728x90" + url: "https://test-assets.adcontextprotocol.org/nova-motors/hero-728x90.jpg" + width: 728 + height: 90 + mime_type: "image/jpeg" + description: "Leaderboard hero — Volta EV front profile with charging station" + + - id: "hero_320x50" + url: "https://test-assets.adcontextprotocol.org/nova-motors/hero-320x50.jpg" + width: 320 + height: 50 + mime_type: "image/jpeg" + description: "Mobile banner hero — Volta EV dashboard detail" + + - id: "hero_160x600" + url: "https://test-assets.adcontextprotocol.org/nova-motors/hero-160x600.jpg" + width: 160 + height: 600 + mime_type: "image/jpeg" + description: "Wide skyscraper hero — vertical Volta EV silhouette" + + - id: "hero_master" + url: "https://test-assets.adcontextprotocol.org/nova-motors/hero-master.jpg" + width: 1200 + height: 628 + mime_type: "image/jpeg" + description: "Master hero image — high-res Volta EV in motion" + + text: + headlines: + - "The Volta EV — 0 to 60 in 3.2s" + - "Performance Meets Sustainability" + - "Drive Electric. Drive Nova." + descriptions: + - "340 miles of range. 15-minute fast charge. The Volta EV is engineered for drivers who refuse to compromise." + - "Zero emissions, zero compromises. Test drive the Volta EV at your nearest Nova Motors dealer." + cta: + - "Reserve Yours" + - "Schedule a Test Drive" + - "Explore the Volta" + + click_url: "https://novamotors.example/volta-ev" + +destinations: + platforms: + - type: "platform" + platform: "the-trade-desk" + account: "agency-123-ttd" + - type: "platform" + platform: "streamhaus" + account: "agency-ctv-seat-456" + agents: + - type: "agent" + agent_url: "https://wonderstruck.salesagents.example" diff --git a/dist/compliance/3.0.9/test-kits/osei-natural.yaml b/dist/compliance/3.0.9/test-kits/osei-natural.yaml new file mode 100644 index 0000000000..840c428004 --- /dev/null +++ b/dist/compliance/3.0.9/test-kits/osei-natural.yaml @@ -0,0 +1,126 @@ +# Osei Natural — Beauty Advertiser Test Kit +# +# Entity definition: fictional-entities.yaml → advertisers[osei_natural] +# +# 8-person natural skincare company based in Nairobi. Represents the small +# business that advertising should serve but currently doesn't. +# +# This brand is registered as a sandbox brand in AgenticAdvertising.org (AAO). +# Agents resolve oseinatural.example via the standard AAO brand resolution path. +# AAO returns the brand.json below but excludes sandbox brands from production +# brand discovery results. + +id: osei_natural +name: "Osei Natural" +description: "Natural skincare brand for small-business advertiser storyboard testing" +sandbox: true + +# security_baseline auth fixture — see acme-outdoor.yaml for semantics. +auth: + api_key: "demo-osei-natural-v1" + probe_task: list_creatives + +brand: + house: + domain: "oseinatural.example" + name: "Osei Natural" + brand_id: "osei_natural" + names: + - en: "Osei Natural" + description: "Natural skincare rooted in East African botanicals. Small-batch, founder-led, and unapologetically simple." + industry: "beauty" + keller_type: "master" + logos: + - url: "https://test-assets.adcontextprotocol.org/osei-natural/logo-primary.png" + orientation: "horizontal" + background: "light-bg" + variant: "primary" + width: 400 + height: 100 + - url: "https://test-assets.adcontextprotocol.org/osei-natural/logo-icon.png" + orientation: "square" + background: "transparent-bg" + variant: "icon" + width: 200 + height: 200 + colors: + primary: "#5D4037" + secondary: "#C8E6C9" + accent: "#FF8F00" + background: "#FBF7F4" + text: "#2E2E2E" + fonts: + heading: + family: "Cormorant Garamond" + weight: 600 + url: "https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@600" + body: + family: "Lato" + weight: 400 + url: "https://fonts.googleapis.com/css2?family=Lato" + tone: + voice: "Calm, grounded, and personal. Amara speaks directly to customers like a trusted friend sharing what works." + attributes: + - "authentic" + - "gentle" + - "personal" + dos: + - "Name specific botanicals and their origins" + - "Use first-person when appropriate — the founder is the brand" + - "Keep it warm and conversational" + donts: + - "Use clinical or pharmaceutical language" + - "Make anti-aging claims — Osei Natural is about care, not correction" + - "Erase the brand's Kenyan identity — it's a feature, not a detail" + +assets: + images: + - id: "hero_300x250" + url: "https://test-assets.adcontextprotocol.org/osei-natural/hero-300x250.jpg" + width: 300 + height: 250 + mime_type: "image/jpeg" + description: "Medium rectangle hero — product jars with botanical ingredients" + + - id: "hero_728x90" + url: "https://test-assets.adcontextprotocol.org/osei-natural/hero-728x90.jpg" + width: 728 + height: 90 + mime_type: "image/jpeg" + description: "Leaderboard hero — Amara in workshop with shea butter" + + - id: "hero_320x50" + url: "https://test-assets.adcontextprotocol.org/osei-natural/hero-320x50.jpg" + width: 320 + height: 50 + mime_type: "image/jpeg" + description: "Mobile banner hero — close-up of baobab oil serum" + + - id: "hero_160x600" + url: "https://test-assets.adcontextprotocol.org/osei-natural/hero-160x600.jpg" + width: 160 + height: 600 + mime_type: "image/jpeg" + description: "Wide skyscraper hero — vertical product lineup on linen" + + - id: "hero_master" + url: "https://test-assets.adcontextprotocol.org/osei-natural/hero-master.jpg" + width: 1200 + height: 628 + mime_type: "image/jpeg" + description: "Master hero image — high-res flat lay of full product range" + + text: + headlines: + - "Your Skin Deserves Better Ingredients" + - "Small-Batch Skincare from Nairobi" + - "Rooted in East African Botanicals" + descriptions: + - "Shea butter, baobab oil, and aloe — sourced from East African growers, blended by hand in Nairobi. Osei Natural." + - "Eight people, twelve products, zero compromises. Natural skincare that actually works." + cta: + - "Shop the Collection" + - "Meet the Founder" + - "Try a Sample Kit" + + click_url: "https://oseinatural.example/shop" diff --git a/dist/compliance/3.0.9/test-kits/signed-requests-runner.yaml b/dist/compliance/3.0.9/test-kits/signed-requests-runner.yaml new file mode 100644 index 0000000000..e139113e1b --- /dev/null +++ b/dist/compliance/3.0.9/test-kits/signed-requests-runner.yaml @@ -0,0 +1,155 @@ +# Signed-Requests Runner — Harness Contract Test Kit +# +# Applies to: universal/signed-requests.yaml (gated on +# `request_signing.supported: true` in get_adcp_capabilities). +# +# The signed-requests storyboard grades an agent's RFC 9421 verifier against 28 +# conformance vectors at /compliance/{version}/test-vectors/request-signing/. +# Most vectors are pure black-box: the runner constructs a signed HTTP request, +# sends it, and checks the response. Three negative vectors assert behavior that +# depends on verifier state the runner cannot set from the outside: +# +# 016-replayed-nonce — replay cache must already contain (keyid, nonce) +# 017-key-revoked — keyid must already be in the revocation list +# 020-rate-abuse — per-keyid replay cache must be at its configured cap +# +# This test-kit defines the coordination contract between a black-box runner and +# a request-signing agent under test. Agents advertising +# `request_signing.supported: true` MUST pre-configure their verifier per this +# contract before the negative phase runs. The runner reads this file to know +# (a) the keyids it will sign with, (b) how to provoke each stateful negative +# vector, and (c) the grading-time cap values it will target (which are NOT +# production recommendations — see each field). See `scope` below for what the +# contract does NOT specify. + +id: signed_requests_runner +applies_to: + universal_storyboard: signed-requests + +description: | + Coordination contract between a black-box runner and a request-signing agent + under test. The runner signs vectors dynamically using the keypairs published + in keys.json. The agent pre-configures its verifier — accepted JWKS, pre-revoked + keyid, replay TTL, per-keyid cap — so that the three stateful negative vectors + produce their expected error codes without the runner injecting verifier state. + + Vectors requiring coordination declare `requires_contract` in their fixture; + the runner gates those vectors on this contract being in scope. + +endpoint_scope: sandbox +# The replay-window contract sends a live, validly-signed mutating request as +# its first step (the second copy is what the grader expects to be rejected). +# Running this against a production endpoint would create a real media buy. +# Graders MUST target a sandbox/staging endpoint or an idempotency-keyed +# sacrificial path the agent discards post-grading. Agents advertising +# `request_signing.supported: true` SHOULD expose a dedicated grading endpoint +# rather than grading in prod. + +harness_mode: black_box +# Agents participating in a white-box test harness (e.g., SDK internal tests +# against the reference verifier) MAY satisfy the stateful vectors via direct +# state injection using the vector's `test_harness_state` block, and declare +# `harness_mode: white_box` in their own runner configuration. AdCP Verified +# grading runs in black_box mode only. + +runner_signing_keys: + # Runner signs every non-negative-key vector with one of these keypairs. The + # agent's verifier MUST treat these keyids as a registered test counterparty + # whose JWKS contains the corresponding public keys with + # adcp_use: "request-signing". Private keys for signing live in keys.json + # under `_private_d_for_test_only`. + - keyid: test-ed25519-2026 + alg: ed25519 + jwks_source: /compliance/{version}/test-vectors/request-signing/keys.json + - keyid: test-es256-2026 + alg: ecdsa-p256-sha256 + jwks_source: /compliance/{version}/test-vectors/request-signing/keys.json + +stateful_vector_contract: + replay_window: + # Vector 016-replayed-nonce. + # + # The runner provokes the replay rejection by sending the same signed + # request twice in sequence. The agent MUST accept the first submission + # (standard positive path) and reject the second with + # request_signature_replayed at checklist step 12. + # + # The agent's replay-cache TTL for this test counterparty MUST be at least + # `min_replay_ttl_seconds`, which is strictly greater than + # `max_interval_seconds` to absorb clock skew between runner and agent and + # scheduler jitter on either side. Otherwise the cache entry for the first + # request may evict before the second arrives and the vector will pass + # spuriously (i.e., both requests accepted = no replay rejection). The + # runner's own interval between the two submissions MUST NOT exceed + # `max_interval_seconds`. + # + # This supersedes vector 016's `test_harness_state.replay_cache_entries` + # for black-box mode. In white-box mode, the harness MAY inject the cache + # entry directly and skip the first request. + vector_id: 016-replayed-nonce + black_box_behavior: repeat_request + max_interval_seconds: 5 + min_replay_ttl_seconds: 10 + + revocation: + # Vector 017-key-revoked. + # + # The runner cannot revoke a key on the agent's side. The agent MUST + # pre-configure its revocation list with `test-revoked-2026` before the + # negative phase runs. The runner signs vector 017 with this keyid and + # expects request_signature_key_revoked at checklist step 9. + # + # A dedicated revoked keypair (rather than reusing a runner signing key) + # keeps positive vectors and vector 017 independent: the positive phase + # remains runnable after vector 017 is graded. + vector_id: 017-key-revoked + pre_revoked_keyid: test-revoked-2026 + + rate_abuse: + # Vector 020-rate-abuse (checklist step 9a). + # + # The runner sends N+1 distinct-nonce requests signed by the same keyid + # within the window. The agent MUST reject the (N+1)th with + # request_signature_rate_abuse once the per-keyid cap is hit. + # + # `grading_target_per_keyid_cap_requests` is the cap the runner will + # target during grading — NOT a production recommendation. Agents MAY + # configure a lower cap for the test-kit counterparty only so grading + # finishes in a reasonable time. Production caps MUST follow the spec + # recommendation at docs/building/implementation/security.mdx + # §per-keyid cap (at least 1,000,000 entries per keyid). Implementers + # copying a value from this file into production code SHOULD use + # `production_min_per_keyid_cap_requests` below as the floor, not + # `grading_target_per_keyid_cap_requests`. + vector_id: 020-rate-abuse + grading_target_per_keyid_cap_requests: 100 + production_min_per_keyid_cap_requests: 1000000 + window_seconds: 60 + +scope: + in_scope: | + - Keyids the runner will sign with and their JWKS source. + - Agent-side preconditions for each stateful negative vector. + - Grading-time cap the runner will target for rate-abuse grading (NOT a + production recommendation; see rate_abuse block). + - Minimum replay-cache TTL so the replay-window contract is reliable. + - Black-box vs. white-box harness mode selection. + - Endpoint scope (sandbox only — see endpoint_scope). + out_of_scope: | + - Specific error-code strings — those live in each vector's + expected_outcome.error_code and are graded byte-for-byte. + - Checklist step numbers — informational only; grading is on the error + code, not the step. + - Pre-signed Signature bytes in the vectors — unchanged by this contract. + Black-box runners re-sign dynamically; pre-signed bytes remain valid + for white-box cross-SDK byte-equivalence checks. + - The agent's internal replay TTL or cap storage mechanism — the contract + specifies observable behavior, not implementation. + - Production verifier configuration — this contract configures a test + counterparty only. + +references: + universal_storyboard: static/compliance/source/universal/signed-requests.yaml + test_vectors: /compliance/{version}/test-vectors/request-signing/ + verifier_checklist: /docs/building/implementation/security.mdx#verifier-checklist-requests + runner_implementation: https://github.com/adcontextprotocol/adcp-client/issues/585 diff --git a/dist/compliance/3.0.9/test-kits/substitution-observer-runner.yaml b/dist/compliance/3.0.9/test-kits/substitution-observer-runner.yaml new file mode 100644 index 0000000000..2db5da6c49 --- /dev/null +++ b/dist/compliance/3.0.9/test-kits/substitution-observer-runner.yaml @@ -0,0 +1,690 @@ +# Substitution Observer Runner — Harness Contract Test Kit +# +# Applies to: +# - Any storyboard that exercises catalog-item macro substitution and needs +# to assert the RFC 3986 percent-encoding rule from +# docs/creative/universal-macros.mdx#substitution-safety-catalog-item-macros. +# - Current consumers (when phases are gated on this contract): +# - specialisms/sales-catalog-driven/index.yaml (substitution_safety phase) +# - specialisms/creative-generative/index.yaml (catalog_substitution_safety phase) +# - Deferred pending observation-hook design (#2651): +# - specialisms/sales-social/index.yaml — social platforms substitute at +# serve time in proprietary renderers; no AdCP-level preview hook. +# - Future consumers: sales-retail-media (post-retail-media-epic), +# sales-broadcast-tv dynamic-creative phases, and anywhere else catalog +# values expand into URL contexts through a previewable surface. +# +# The #2620 rule: sales agents MUST percent-encode catalog-item macro values +# such that only RFC 3986 `unreserved` characters remain unescaped before +# substitution into URL contexts. Nested macro expansion is prohibited. +# +# The ATTACK SURFACE is impression-time URL emission. AdCP's API surface does +# not normally expose substituted output — it happens at serve time outside +# the protocol. Two observable AdCP-layer hooks exist: +# +# preview_creative responses (preview_html / preview_url) +# build_creative responses with include_preview: true +# +# This contract defines how a runner consumes those preview artifacts, +# extracts tracker URLs with substituted catalog-item values, and asserts +# the values are encoded per the #2620 rule. +# +# Clean seam: the runner does NOT reimplement URL parsing, HTML extraction, +# or the encoding check. It delegates to @adcp/client primitives (proposed: +# `SubstitutionObserver` with `extract_tracker_urls` and `assert_rfc3986_safe` +# helpers) so the same library production receivers would use is what the +# conformance runner exercises. Library fixes cover both. + +id: substitution_observer_runner +# Shape extension vs webhook-receiver-runner: this contract applies to +# specialisms rather than universals, because catalog-macro substitution is a +# specialism-scoped behavior (only catalog-accepting sellers emit it). The +# `applies_to.specialisms` key is a deliberate structural addition for this +# class of contract. +applies_to: + specialisms: + - sales_catalog_driven + - creative_generative + # sales_social deferred — no AdCP-level preview hook (see #2651). + universals: [] + +description: | + Coordination contract between a runner that observes substituted tracker + URLs in preview artifacts and an agent under test that emits catalog-driven + creatives. The runner ingests preview_html or follows preview_url, extracts + tracker URLs bound to catalog-item macros, and asserts RFC 3986 percent- + encoding of catalog-item values per docs/creative/universal-macros#substitution-safety-catalog-item-macros. + +endpoint_scope: sandbox +# Storyboards consuming this contract synthesize attacker-shaped catalog +# values (e.g., title containing `abc&cmd=drop` or `\r\nHost: evil`) and +# push them via sync_catalogs. Running those against production would +# pollute live catalogs. Graders MUST target a sandbox/staging endpoint. + +harness_mode: black_box + +# --- Observation mechanism --- +# +# The runner captures preview output from two response shapes (either is +# sufficient — the storyboard author names which to observe): +# +# preview_html (inline HTML string in the response) +# The runner parses the HTML and extracts tracker URLs from the +# attribute set enumerated below. Zero network dependency. +# +# preview_url (HTTPS URL the runner fetches subject to the SSRF policy +# enumerated below) +# The runner performs a single GET against the URL and extracts +# identically. +# +# Both paths land at the same @adcp/client.SubstitutionObserver.extract_tracker_urls +# primitive, which returns a deterministic list of `{ url, source_attr, line_hint }` +# records that storyboards match against. + +observation_modes: + - mode: html_inline + default_for: [lint, fast, full_conformance] + description: | + Runner reads preview_html from the response and parses it. No network + fetch. Appropriate for CI lint gates and SDK self-tests. + runner_config: + source_path: preview_html + html_parser: "@adcp/client.SubstitutionObserver.parse_html" + + - mode: url_fetch + default_for: [adcp_verified] + description: | + Runner fetches preview_url over HTTPS, expects 200 + text/html, and + parses the body. Required for AdCP Verified grading where the preview + is a live asset. + runner_config: + source_path: preview_url + fetch: + method: GET + follow_redirects: false + max_body_bytes: 262144 # 256 KiB — matches typical creative preview sizes + max_connect_seconds: 3 + timeout_seconds: 10 + required_content_types: ["text/html", "application/xhtml+xml"] + # SSRF policy is NORMATIVE IN THIS CONTRACT (not deferred to a library). + # Verified graders MUST enforce every rule below. The runner MAY delegate + # the implementation to @adcp/client.SubstitutionObserver.enforce_ssrf_policy + # (or equivalent) provided the delegate implements this exact deny list. + ssrf_policy: + schemes_allowed: ["https"] + schemes_denied: ["http", "file", "gopher", "ftp", "ftps", "data", "javascript", "about", "ws", "wss"] + hosts_denied_ipv4_cidrs: + - "0.0.0.0/8" # "this network" + - "10.0.0.0/8" # RFC 1918 private + - "100.64.0.0/10" # CGNAT + - "127.0.0.0/8" # loopback + - "169.254.0.0/16" # link-local (incl. 169.254.169.254 IMDS v1/v2) + - "172.16.0.0/12" # RFC 1918 private + - "192.0.0.0/24" # IETF protocol assignments + - "192.168.0.0/16" # RFC 1918 private + - "224.0.0.0/4" # multicast + - "240.0.0.0/4" # reserved + hosts_denied_ipv6_cidrs: + - "::1/128" # loopback + - "::/128" # unspecified + - "::ffff:0:0/96" # IPv4-mapped (re-check as IPv4) + - "64:ff9b::/96" # IPv4/IPv6 translation + - "fc00::/7" # unique local + - "fe80::/10" # link-local + - "ff00::/8" # multicast + hosts_denied_metadata: + # Cloud metadata hosts by name — the IP CIDRs above catch the standard + # 169.254.169.254 cases, but some providers expose hostname aliases. + - "metadata.google.internal" + - "metadata" + - "metadata.packet.net" + - "fd00:ec2::254" # IMDS IPv6 + host_literal_policy_verified: reject + # In AdCP Verified grading, reject ANY bare IP literal in preview_url + # (both IPv4 and IPv6) regardless of range — forces resolution through + # a public DNS name the grader can audit. Local-dev flags MAY relax this. + dns_revalidation: required + # After DNS resolution, EVERY resolved address MUST be re-checked against + # hosts_denied_*. Resolve once, bind to the resolved address for the + # request — do NOT pass the hostname to the HTTP client (closes DNS + # rebinding between resolve and connect). + redirects: follow_false_strict + # follow_redirects: false is already set above; this field documents + # the companion: if the origin returns 3xx, treat it as a failure + # (`preview_url_unusable` + sub-reason `redirect_returned`), do NOT + # chase — redirect chasing would require re-running the full SSRF + # policy at each hop and is out of scope for v1. + +# --- HTML attribute extraction set (normative) --- +# +# The runner extracts tracker URLs from the following attributes only. This +# set is normative — runner MUST NOT under-extract (missing one of these +# attributes lets a seller hide an unencoded value); runner MUST NOT over- +# extract (e.g., arbitrary `data-*` attributes whose values happen to parse +# as URLs but are not trackers). Extension to additional attributes MAY +# happen in a future contract revision; today's set is closed. + +html_attribute_extraction_set: + tag_attribute_pairs: + - { tag: "a", attr: "href" } + - { tag: "img", attr: "src" } + - { tag: "img", attr: "srcset" } # may contain multiple URLs + - { tag: "iframe", attr: "src" } + - { tag: "source", attr: "src" } + - { tag: "source", attr: "srcset" } + - { tag: "link", attr: "href" } + - { tag: "meta", attr: "content" } # refresh redirects, og:image, etc. + - { tag: "*", attr: "data-impression-url" } + - { tag: "*", attr: "data-click-url" } + - { tag: "*", attr: "data-tracker-url" } + - { tag: "*", attr: "data-vast-url" } + srcset_handling: parse_per_descriptor + # srcset values are space-separated URL+descriptor pairs (e.g., + # "a.jpg 1x, b.jpg 2x"). The runner MUST extract every URL component. + comment_nodes: ignored + script_text_content: ignored + # Tracker URLs in `