@adcp/sdk 7.11.0 → 7.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compliance/cache/3.1.0-rc.2/domains/brand/index.yaml +160 -0
- package/compliance/cache/3.1.0-rc.2/domains/brand/scenarios/distributed_brand_resolution.yaml +415 -0
- package/compliance/cache/3.1.0-rc.2/domains/brand/scenarios/single_side_trust_extension.yaml +454 -0
- package/compliance/cache/3.1.0-rc.2/domains/creative/index.yaml +339 -0
- package/compliance/cache/3.1.0-rc.2/domains/creative/scenarios/billing_out_of_band.yaml +153 -0
- package/compliance/cache/3.1.0-rc.2/domains/creative/scenarios/canonical_supported_formats.yaml +212 -0
- package/compliance/cache/3.1.0-rc.2/domains/creative/scenarios/creative_lifecycle_webhooks.yaml +389 -0
- package/compliance/cache/3.1.0-rc.2/domains/creative/scenarios/native_in_feed.yaml +543 -0
- package/compliance/cache/3.1.0-rc.2/domains/governance/index.yaml +682 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/index.yaml +789 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/audience_buy_flow.yaml +380 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/available_actions.yaml +565 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/billing_finality_delivery.yaml +354 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/canonical_formats.yaml +861 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/clicks_buy_flow.yaml +264 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/completed_views_buy_flow.yaml +344 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/create_media_buy_async.yaml +234 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml +419 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/creative_reception.yaml +247 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/delivery_reporting.yaml +357 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/dependency_impairment.yaml +633 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/dependency_impairment_cardinality.yaml +800 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/event_dedup_flow.yaml +399 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/frequency_cap_enforcement.yaml +309 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/governance_approved.yaml +214 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/governance_conditions.yaml +199 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/governance_denied.yaml +204 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/governance_denied_recovery.yaml +252 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/invalid_transitions.yaml +289 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/inventory_list_no_match.yaml +148 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/inventory_list_targeting.yaml +276 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/measurement_accountability.yaml +244 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/measurement_terms_rejected.yaml +203 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/package_correlation_legacy_fallback.yaml +113 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/pending_creatives_to_start.yaml +292 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/per_creative_conversion_attribution.yaml +500 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/performance_buy_flow.yaml +428 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/performance_buy_flow_roas.yaml +470 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/product_signal_targeting.yaml +373 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/proposal_finalize.yaml +399 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/proposal_finalize_asap_timing.yaml +264 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/proposal_not_found_errors.yaml +257 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/provenance_audit_observation.yaml +333 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/provenance_enforcement.yaml +517 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/provenance_truth_of_claim.yaml +294 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/reach_buy_flow.yaml +823 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/refine_finalize_exclusivity.yaml +360 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/refine_products.yaml +148 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/vendor_metric_accountability.yaml +293 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +307 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/scenarios/vendor_metric_optimization_flow.yaml +576 -0
- package/compliance/cache/3.1.0-rc.2/domains/media-buy/state-machine.yaml +442 -0
- package/compliance/cache/3.1.0-rc.2/domains/signals/index.yaml +266 -0
- package/compliance/cache/3.1.0-rc.2/domains/sponsored-intelligence/index.yaml +256 -0
- package/compliance/cache/3.1.0-rc.2/index.json +356 -0
- package/compliance/cache/3.1.0-rc.2/protocols/brand/index.yaml +160 -0
- package/compliance/cache/3.1.0-rc.2/protocols/brand/scenarios/distributed_brand_resolution.yaml +415 -0
- package/compliance/cache/3.1.0-rc.2/protocols/brand/scenarios/single_side_trust_extension.yaml +454 -0
- package/compliance/cache/3.1.0-rc.2/protocols/creative/index.yaml +339 -0
- package/compliance/cache/3.1.0-rc.2/protocols/creative/scenarios/billing_out_of_band.yaml +153 -0
- package/compliance/cache/3.1.0-rc.2/protocols/creative/scenarios/canonical_supported_formats.yaml +212 -0
- package/compliance/cache/3.1.0-rc.2/protocols/creative/scenarios/creative_lifecycle_webhooks.yaml +389 -0
- package/compliance/cache/3.1.0-rc.2/protocols/creative/scenarios/native_in_feed.yaml +543 -0
- package/compliance/cache/3.1.0-rc.2/protocols/governance/index.yaml +682 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/index.yaml +789 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/audience_buy_flow.yaml +380 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/available_actions.yaml +565 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/billing_finality_delivery.yaml +354 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/canonical_formats.yaml +861 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/clicks_buy_flow.yaml +264 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/completed_views_buy_flow.yaml +344 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/create_media_buy_async.yaml +234 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml +419 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/creative_reception.yaml +247 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/delivery_reporting.yaml +357 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/dependency_impairment.yaml +633 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/dependency_impairment_cardinality.yaml +800 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/event_dedup_flow.yaml +399 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/frequency_cap_enforcement.yaml +309 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/governance_approved.yaml +214 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/governance_conditions.yaml +199 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/governance_denied.yaml +204 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/governance_denied_recovery.yaml +252 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/invalid_transitions.yaml +289 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/inventory_list_no_match.yaml +148 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/inventory_list_targeting.yaml +276 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/measurement_accountability.yaml +244 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/measurement_terms_rejected.yaml +203 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/package_correlation_legacy_fallback.yaml +113 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/pending_creatives_to_start.yaml +292 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/per_creative_conversion_attribution.yaml +500 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/performance_buy_flow.yaml +428 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/performance_buy_flow_roas.yaml +470 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/product_signal_targeting.yaml +373 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/proposal_finalize.yaml +399 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/proposal_finalize_asap_timing.yaml +264 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/proposal_not_found_errors.yaml +257 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/provenance_audit_observation.yaml +333 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/provenance_enforcement.yaml +517 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/provenance_truth_of_claim.yaml +294 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/reach_buy_flow.yaml +823 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/refine_finalize_exclusivity.yaml +360 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/refine_products.yaml +148 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/vendor_metric_accountability.yaml +293 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +307 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml +576 -0
- package/compliance/cache/3.1.0-rc.2/protocols/media-buy/state-machine.yaml +442 -0
- package/compliance/cache/3.1.0-rc.2/protocols/signals/index.yaml +266 -0
- package/compliance/cache/3.1.0-rc.2/protocols/sponsored-intelligence/index.yaml +256 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/audience-sync/index.yaml +313 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/brand-rights/index.yaml +350 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/brand-rights/scenarios/governance_denied.yaml +226 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/collection-lists/index.yaml +359 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/content-standards/index.yaml +572 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/creative-ad-server/index.yaml +409 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/creative-generative/generative-seller.yaml +807 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/creative-generative/index.yaml +758 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/creative-template/index.yaml +510 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/governance-aware-seller/index.yaml +143 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/governance-aware-seller/scenarios/governance_multi_agent_rejected.yaml +117 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/governance-delivery-monitor/index.yaml +441 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/governance-spend-authority/denied.yaml +221 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/governance-spend-authority/index.yaml +330 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/property-lists/index.yaml +482 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sales-broadcast-tv/index.yaml +738 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sales-catalog-driven/index.yaml +840 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sales-guaranteed/index.yaml +601 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sales-non-guaranteed/index.yaml +546 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sales-proposal-mode/index.yaml +586 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sales-social/index.yaml +919 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/signal-marketplace/index.yaml +424 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/signal-marketplace/scenarios/governance_denied.yaml +210 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/signal-owned/index.yaml +317 -0
- package/compliance/cache/3.1.0-rc.2/specialisms/sponsored-intelligence/index.yaml +59 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/acme-outdoor-live.yaml +78 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/acme-outdoor.yaml +223 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/billing-gate-runner.yaml +115 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/bistro-oranje.yaml +126 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/distributed-brand-runner.yaml +281 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/nova-motors.yaml +262 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/osei-natural.yaml +126 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/parallel-dispatch-runner.yaml +196 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/rate-limit-trip-runner.yaml +172 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/signed-requests-runner.yaml +155 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/single-side-trust-runner.yaml +294 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/substitution-observer-runner.yaml +688 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/summit-foods.yaml +125 -0
- package/compliance/cache/3.1.0-rc.2/test-kits/webhook-receiver-runner.yaml +265 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/001-minimal-plan.json +43 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/002-full-plan.json +217 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/003-bookkeeping-stripped.json +60 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/004a-human-review-omitted.json +43 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/004b-human-review-explicit-null.json +49 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/005a-policy-categories-order-1.json +53 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/005b-policy-categories-order-2.json +57 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/006a-ext-trace-v1.json +49 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/006b-ext-trace-v2.json +53 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/007-unicode-objectives.json +43 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/plan-hash/008-numeric-canonicalization.json +65 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/README.md +220 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/canonicalization.json +241 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/keys.json +60 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/001-no-signature-header.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/002-wrong-tag.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/003-expired-signature.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/004-window-too-long.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/005-alg-not-allowed.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/006-missing-covered-component.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/007-missing-content-digest.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/008-unknown-keyid.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/009-key-ops-missing-verify.json +27 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/010-content-digest-mismatch.json +33 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/011-malformed-header.json +27 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/012-missing-expires-param.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/013-expires-le-created.json +27 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/014-missing-nonce-param.json +27 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/015-signature-invalid.json +28 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/016-replayed-nonce.json +35 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/017-key-revoked.json +38 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/018-digest-covered-when-forbidden.json +28 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/019-signature-without-signature-input.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/020-rate-abuse.json +34 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/021-duplicate-signature-input-label.json +31 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/022-multi-valued-content-type.json +31 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/023-multi-valued-content-digest.json +32 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/024-unquoted-string-param.json +31 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/025-jwk-alg-crv-mismatch.json +43 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/026-non-ascii-host.json +31 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/027-webhook-registration-authentication-unsigned.json +25 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/negative/028-unsigned-protocol-method-required.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/001-basic-post.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/002-post-with-content-digest.json +31 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/003-es256-post.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/004-multiple-signature-labels.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/005-default-port-stripped.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/006-dot-segment-path.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/007-query-byte-preserved.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/008-percent-encoded-path.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/009-percent-encoded-unreserved-decoded.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/010-percent-encoded-slash-preserved.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/011-ipv6-authority.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/request-signing/positive/012-ipv6-authority-default-port-stripped.json +30 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/README.md +211 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/keys.json +61 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/001-wrong-tag.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/002-expired-signature.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/003-window-too-long.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/004-alg-not-allowed.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/005-missing-authority-component.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/006-missing-content-digest.json +25 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/007-unknown-keyid.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/008-wrong-adcp-use.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/009-content-digest-mismatch.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/010-malformed-signature-input.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/011-signature-without-input.json +25 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/012-missing-expires-param.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/013-expires-le-created.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/014-missing-nonce-param.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/015-signature-invalid.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/016-replayed-nonce.json +37 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/017-key-revoked.json +32 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/018-rate-abuse.json +33 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/019-revocation-stale.json +32 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/020-key-ops-missing-verify.json +41 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/negative/021-base64-alphabet-mixing.json +26 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/001-basic-post.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/002-es256-post.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/003-multiple-signature-labels.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/004-default-port-stripped.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/005-percent-encoded-path.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/006-query-byte-preserved.json +24 -0
- package/compliance/cache/3.1.0-rc.2/test-vectors/webhook-signing/positive/007-body-without-idempotency-key.json +25 -0
- package/compliance/cache/3.1.0-rc.2/universal/billing-gate-dispatch.yaml +450 -0
- package/compliance/cache/3.1.0-rc.2/universal/canonical-format-validate-input.yaml +640 -0
- package/compliance/cache/3.1.0-rc.2/universal/capability-discovery.yaml +125 -0
- package/compliance/cache/3.1.0-rc.2/universal/collection-lists-pagination-integrity.yaml +306 -0
- package/compliance/cache/3.1.0-rc.2/universal/comply-controller-mode-gate.yaml +141 -0
- package/compliance/cache/3.1.0-rc.2/universal/content-standards-pagination-integrity.yaml +326 -0
- package/compliance/cache/3.1.0-rc.2/universal/deterministic-testing.yaml +1430 -0
- package/compliance/cache/3.1.0-rc.2/universal/error-compliance-signals.yaml +377 -0
- package/compliance/cache/3.1.0-rc.2/universal/error-compliance.yaml +528 -0
- package/compliance/cache/3.1.0-rc.2/universal/fictional-entities.yaml +307 -0
- package/compliance/cache/3.1.0-rc.2/universal/get-media-buys-pagination-integrity.yaml +160 -0
- package/compliance/cache/3.1.0-rc.2/universal/get-signals-pagination-integrity.yaml +210 -0
- package/compliance/cache/3.1.0-rc.2/universal/idempotency.yaml +861 -0
- package/compliance/cache/3.1.0-rc.2/universal/notification-config-event-scope.yaml +119 -0
- package/compliance/cache/3.1.0-rc.2/universal/notification-config-lifecycle.yaml +337 -0
- package/compliance/cache/3.1.0-rc.2/universal/notification-config-rejections.yaml +107 -0
- package/compliance/cache/3.1.0-rc.2/universal/pagination-integrity-creative-formats.yaml +265 -0
- package/compliance/cache/3.1.0-rc.2/universal/pagination-integrity-list-accounts.yaml +245 -0
- package/compliance/cache/3.1.0-rc.2/universal/pagination-integrity.yaml +263 -0
- package/compliance/cache/3.1.0-rc.2/universal/property-lists-pagination-integrity.yaml +307 -0
- package/compliance/cache/3.1.0-rc.2/universal/read-tool-idempotency.yaml +405 -0
- package/compliance/cache/3.1.0-rc.2/universal/runner-output-contract.yaml +1285 -0
- package/compliance/cache/3.1.0-rc.2/universal/schema-validation-signals.yaml +181 -0
- package/compliance/cache/3.1.0-rc.2/universal/schema-validation.yaml +548 -0
- package/compliance/cache/3.1.0-rc.2/universal/security.yaml +539 -0
- package/compliance/cache/3.1.0-rc.2/universal/signed-requests.yaml +217 -0
- package/compliance/cache/3.1.0-rc.2/universal/stale-response-advisory.yaml +295 -0
- package/compliance/cache/3.1.0-rc.2/universal/storyboard-schema.yaml +2194 -0
- package/compliance/cache/3.1.0-rc.2/universal/v3-envelope-integrity.yaml +117 -0
- package/compliance/cache/3.1.0-rc.2/universal/version-negotiation.yaml +130 -0
- package/compliance/cache/3.1.0-rc.2/universal/webhook-emission.yaml +411 -0
- package/compliance/cache/3.1.0-rc.2/universal/wholesale-feed-bulk-webhooks.yaml +82 -0
- package/compliance/cache/3.1.0-rc.2/universal/wholesale-feed-product-webhooks.yaml +83 -0
- package/compliance/cache/3.1.0-rc.2/universal/wholesale-feed-products.yaml +151 -0
- package/compliance/cache/3.1.0-rc.2/universal/wholesale-feed-signal-webhooks.yaml +83 -0
- package/compliance/cache/3.1.0-rc.2/universal/wholesale-feed-signals.yaml +149 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/storyboard/default-invariants.js +23 -0
- package/dist/lib/testing/storyboard/default-invariants.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +84 -21
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/types.d.ts +21 -0
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/types.d.ts +9 -0
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
id: media_buy_seller/reach_buy_flow
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
title: "Seller fulfills a media buy with a reach optimization goal"
|
|
4
|
+
category: media_buy_seller
|
|
5
|
+
summary: "Verifies that a seller advertising reach in supported_optimization_metrics can accept a media buy whose metric-kind optimization_goal targets unique reach with a buyer-supplied reach_unit, reject reach_unit values not in the product's metric_optimization.supported_reach_units, and report reach + frequency on delivery. Sibling to the performance buy scenarios on the brand-reach side: sellers that don't advertise reach as an optimization metric (most pure performance DSPs, retail-media networks) grade not_applicable."
|
|
6
|
+
track: media_buy
|
|
7
|
+
|
|
8
|
+
# Gate: scenario runs only when the seller declares reach in
|
|
9
|
+
# media_buy.supported_optimization_metrics (proposed in #4669). Sellers
|
|
10
|
+
# whose products optimize for clicks / conversions / views but not unique
|
|
11
|
+
# reach grade `not_applicable`, not failing. Reach as an optimization
|
|
12
|
+
# metric is upper-funnel-shaped — broadcast TV, CTV, social brand
|
|
13
|
+
# campaigns, audio — and gating on the explicit metric in
|
|
14
|
+
# supported_optimization_metrics avoids over-claiming against the
|
|
15
|
+
# performance-tracking community. Requires runner support for the
|
|
16
|
+
# `contains:` matcher (adcp-client >= 7.70).
|
|
17
|
+
requires_capability:
|
|
18
|
+
path: media_buy.supported_optimization_metrics
|
|
19
|
+
contains: "reach"
|
|
20
|
+
|
|
21
|
+
required_tools:
|
|
22
|
+
- get_products
|
|
23
|
+
- create_media_buy
|
|
24
|
+
- get_media_buy_delivery
|
|
25
|
+
- comply_test_controller
|
|
26
|
+
|
|
27
|
+
invariants:
|
|
28
|
+
- status.monotonic
|
|
29
|
+
|
|
30
|
+
narrative: |
|
|
31
|
+
A "reach buy" is a media buy whose optimization_goal is metric-kind with
|
|
32
|
+
metric: reach and a buyer-supplied reach_unit naming the units of the
|
|
33
|
+
count (individuals / households / devices / accounts / cookies / custom).
|
|
34
|
+
Optionally the buyer specifies a target_frequency band — the seller treats
|
|
35
|
+
impressions toward entities already within [min, max] as lower-value and
|
|
36
|
+
prioritizes fresh reach. This is the upper-funnel optimization shape
|
|
37
|
+
(broadcast TV, CTV, social brand campaigns, audio) that's distinct from
|
|
38
|
+
the performance / conversion-tracking shapes.
|
|
39
|
+
|
|
40
|
+
Discriminating behaviors this scenario verifies:
|
|
41
|
+
1. create_media_buy with metric: reach, a reach_unit value declared in
|
|
42
|
+
the product's metric_optimization.supported_reach_units, and an optional
|
|
43
|
+
target_frequency band is accepted.
|
|
44
|
+
2. create_media_buy with a reach_unit value NOT in the product's
|
|
45
|
+
supported_reach_units is rejected with INVALID_REQUEST. error.field
|
|
46
|
+
points at the offending reach_unit path. Silent acceptance with
|
|
47
|
+
unit-coercion would create cross-platform comparison errors because
|
|
48
|
+
reach in cookies is not reach in households.
|
|
49
|
+
3. get_media_buy_delivery surfaces totals.reach (the unique-count value)
|
|
50
|
+
and totals.frequency (average exposures per reach unit over the
|
|
51
|
+
declared reach_window). reach_unit on delivery aligns with the
|
|
52
|
+
reach_unit declared on the optimization goal — the unit declaration is
|
|
53
|
+
load-bearing for cross-platform comparison.
|
|
54
|
+
|
|
55
|
+
Per-window reach-window breakdown (cumulative vs period vs rolling
|
|
56
|
+
semantics declared in delivery-metrics.json's reach_window block) is
|
|
57
|
+
asserted in dedicated follow-up phases after the base reach delivery
|
|
58
|
+
check. The omission case remains an advisory because reach_window is
|
|
59
|
+
schema-valid to omit, but buyers must treat omitted-window reach rows
|
|
60
|
+
as unsafe to sum or average across reporting periods.
|
|
61
|
+
|
|
62
|
+
agent:
|
|
63
|
+
interaction_model: media_buy_seller
|
|
64
|
+
capabilities:
|
|
65
|
+
- sells_media
|
|
66
|
+
- supports_non_guaranteed
|
|
67
|
+
examples:
|
|
68
|
+
- "Broadcast TV / CTV sellers optimizing for unique households"
|
|
69
|
+
- "Social platforms with reach optimization (Meta Reach & Frequency, TikTok Reach)"
|
|
70
|
+
- "Audio sellers optimizing for unique listeners"
|
|
71
|
+
- "OOH / DOOH sellers optimizing for unique passers-by"
|
|
72
|
+
|
|
73
|
+
caller:
|
|
74
|
+
role: buyer_agent
|
|
75
|
+
example: "Pinnacle Agency (buyer)"
|
|
76
|
+
|
|
77
|
+
prerequisites:
|
|
78
|
+
description: |
|
|
79
|
+
The seller must implement comply_test_controller with simulate_delivery
|
|
80
|
+
(already required for delivery_reporting). simulate_delivery injects
|
|
81
|
+
impressions, reach, and frequency values so get_media_buy_delivery has
|
|
82
|
+
something to surface for the reach-optimized buy.
|
|
83
|
+
|
|
84
|
+
The buyer fixture supplies a brand, an operator, and reach targeting
|
|
85
|
+
parameters sufficient to exercise the reach_unit binding contract and
|
|
86
|
+
the unsupported-unit rejection.
|
|
87
|
+
test_kit: "test-kits/acme-outdoor.yaml"
|
|
88
|
+
controller_seeding: true
|
|
89
|
+
|
|
90
|
+
fixtures:
|
|
91
|
+
products:
|
|
92
|
+
- product_id: "reach_ctv_q2"
|
|
93
|
+
delivery_type: "non_guaranteed"
|
|
94
|
+
channels: ["video"]
|
|
95
|
+
format_ids:
|
|
96
|
+
- id: "video_30s"
|
|
97
|
+
metric_optimization:
|
|
98
|
+
supported_metrics: ["reach"]
|
|
99
|
+
supported_reach_units: ["households", "individuals"]
|
|
100
|
+
pricing_options:
|
|
101
|
+
- product_id: "reach_ctv_q2"
|
|
102
|
+
pricing_option_id: "auction_cpm"
|
|
103
|
+
pricing_model: "cpm"
|
|
104
|
+
currency: "USD"
|
|
105
|
+
floor_price: 18.00
|
|
106
|
+
|
|
107
|
+
phases:
|
|
108
|
+
|
|
109
|
+
- id: setup
|
|
110
|
+
title: "Establish account and discover products"
|
|
111
|
+
steps:
|
|
112
|
+
- id: sync_accounts
|
|
113
|
+
title: "Establish account"
|
|
114
|
+
task: sync_accounts
|
|
115
|
+
schema_ref: "account/sync-accounts-request.json"
|
|
116
|
+
response_schema_ref: "account/sync-accounts-response.json"
|
|
117
|
+
doc_ref: "/accounts/tasks/sync_accounts"
|
|
118
|
+
stateful: true
|
|
119
|
+
expected: |
|
|
120
|
+
Return the account with account_id and status active.
|
|
121
|
+
sample_request:
|
|
122
|
+
accounts:
|
|
123
|
+
- brand:
|
|
124
|
+
domain: "acmeoutdoor.example"
|
|
125
|
+
operator: "pinnacle-agency.example"
|
|
126
|
+
billing: "operator"
|
|
127
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_setup_sync_accounts"
|
|
128
|
+
validations:
|
|
129
|
+
- check: response_schema
|
|
130
|
+
description: "Response matches sync-accounts-response.json schema"
|
|
131
|
+
- check: field_present
|
|
132
|
+
path: "accounts[0].account_id"
|
|
133
|
+
description: "Account has a platform-assigned ID"
|
|
134
|
+
|
|
135
|
+
- id: get_products_for_reach
|
|
136
|
+
title: "Discover products that support reach optimization"
|
|
137
|
+
task: get_products
|
|
138
|
+
schema_ref: "media-buy/get-products-request.json"
|
|
139
|
+
response_schema_ref: "media-buy/get-products-response.json"
|
|
140
|
+
doc_ref: "/media-buy/task-reference/get_products"
|
|
141
|
+
stateful: false
|
|
142
|
+
expected: |
|
|
143
|
+
Return at least one product whose metric_optimization capabilities
|
|
144
|
+
include reach as an optimization metric and declare
|
|
145
|
+
supported_reach_units. The buyer selects a unit from that list for
|
|
146
|
+
the create_media_buy call below.
|
|
147
|
+
sample_request:
|
|
148
|
+
buying_mode: "brief"
|
|
149
|
+
brief: "CTV video, US adults 25-54. Q2 brand flight ~$50K, optimizing for unique household reach with a target frequency cap."
|
|
150
|
+
account:
|
|
151
|
+
brand:
|
|
152
|
+
domain: "acmeoutdoor.example"
|
|
153
|
+
operator: "pinnacle-agency.example"
|
|
154
|
+
validations:
|
|
155
|
+
- check: response_schema
|
|
156
|
+
description: "Response matches get-products-response.json schema"
|
|
157
|
+
- check: field_present
|
|
158
|
+
path: "products"
|
|
159
|
+
description: "Response contains products"
|
|
160
|
+
|
|
161
|
+
- id: create_reach_buy
|
|
162
|
+
title: "Create a reach-optimized buy with a supported reach_unit"
|
|
163
|
+
narrative: |
|
|
164
|
+
The buyer creates a media buy whose package carries a metric-kind
|
|
165
|
+
optimization_goal with metric: reach, reach_unit: households (explicitly
|
|
166
|
+
declared in the product fixture's supported_reach_units), and
|
|
167
|
+
a target_frequency band of 1–3 over a 7-day rolling window. The seller
|
|
168
|
+
should accept and return a media_buy_id used for subsequent delivery
|
|
169
|
+
checks.
|
|
170
|
+
|
|
171
|
+
steps:
|
|
172
|
+
- id: create_media_buy_reach
|
|
173
|
+
title: "Create media buy with metric-kind reach goal"
|
|
174
|
+
task: create_media_buy
|
|
175
|
+
schema_ref: "media-buy/create-media-buy-request.json"
|
|
176
|
+
response_schema_ref: "media-buy/create-media-buy-response.json"
|
|
177
|
+
doc_ref: "/media-buy/task-reference/create_media_buy"
|
|
178
|
+
stateful: true
|
|
179
|
+
expected: |
|
|
180
|
+
Create the media buy. Response carries a media_buy_id used for
|
|
181
|
+
subsequent get_media_buy_delivery calls.
|
|
182
|
+
sample_request:
|
|
183
|
+
brand:
|
|
184
|
+
domain: "acmeoutdoor.example"
|
|
185
|
+
account:
|
|
186
|
+
brand:
|
|
187
|
+
domain: "acmeoutdoor.example"
|
|
188
|
+
operator: "pinnacle-agency.example"
|
|
189
|
+
start_time: "2026-06-01T00:00:00Z"
|
|
190
|
+
end_time: "2026-06-30T23:59:59Z"
|
|
191
|
+
packages:
|
|
192
|
+
- product_id: "reach_ctv_q2"
|
|
193
|
+
budget: 50000
|
|
194
|
+
pricing_option_id: "auction_cpm"
|
|
195
|
+
optimization_goals:
|
|
196
|
+
- kind: "metric"
|
|
197
|
+
metric: "reach"
|
|
198
|
+
reach_unit: "households"
|
|
199
|
+
target_frequency:
|
|
200
|
+
window:
|
|
201
|
+
interval: 7
|
|
202
|
+
unit: "days"
|
|
203
|
+
min: 1
|
|
204
|
+
max: 3
|
|
205
|
+
priority: 1
|
|
206
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_create_reach_buy_create_media_buy"
|
|
207
|
+
context_outputs:
|
|
208
|
+
- name: media_buy_id
|
|
209
|
+
path: "media_buy_id"
|
|
210
|
+
validations:
|
|
211
|
+
- check: response_schema
|
|
212
|
+
description: "Response matches create-media-buy-response.json schema"
|
|
213
|
+
- check: field_present
|
|
214
|
+
path: "media_buy_id"
|
|
215
|
+
description: "Media buy has a platform-assigned ID"
|
|
216
|
+
|
|
217
|
+
- id: rejection_unsupported_reach_unit
|
|
218
|
+
title: "Reject a reach goal whose reach_unit is not in supported_reach_units"
|
|
219
|
+
narrative: |
|
|
220
|
+
Silent acceptance of a reach_unit value not declared in the product's
|
|
221
|
+
metric_optimization.supported_reach_units is a façade — the seller
|
|
222
|
+
either coerces the unit (creating cross-platform comparison errors)
|
|
223
|
+
or runs without a unit (delivery reach values become uncomparable).
|
|
224
|
+
The seller MUST reject with INVALID_REQUEST and set error.field to the
|
|
225
|
+
offending reach_unit path. Same shape as the unbound event_source_id
|
|
226
|
+
rejection in performance_buy_flow and the unbound audience_id
|
|
227
|
+
rejection in audience_buy_flow.
|
|
228
|
+
|
|
229
|
+
"devices" is chosen because it is a valid spec-defined reach_unit enum
|
|
230
|
+
value that the reach_ctv_q2 product fixture deliberately omits from
|
|
231
|
+
supported_reach_units (which declares only "households" and
|
|
232
|
+
"individuals"). Using a valid enum value satisfies
|
|
233
|
+
negative_path: payload_well_formed (the request is schema-valid),
|
|
234
|
+
while the fixture exclusion ensures the rejection must come from the
|
|
235
|
+
seller's capability-checking logic rather than schema validation.
|
|
236
|
+
Sellers that silently accept "devices" are demonstrating they don't
|
|
237
|
+
validate reach_unit against their product capability declarations.
|
|
238
|
+
|
|
239
|
+
steps:
|
|
240
|
+
- id: create_media_buy_with_unsupported_reach_unit
|
|
241
|
+
title: "Submit a reach goal whose reach_unit is not in supported_reach_units"
|
|
242
|
+
task: create_media_buy
|
|
243
|
+
schema_ref: "media-buy/create-media-buy-request.json"
|
|
244
|
+
response_schema_ref: "media-buy/create-media-buy-response.json"
|
|
245
|
+
doc_ref: "/media-buy/task-reference/create_media_buy"
|
|
246
|
+
expect_error: true
|
|
247
|
+
negative_path: payload_well_formed
|
|
248
|
+
stateful: false
|
|
249
|
+
expected: |
|
|
250
|
+
Reject with INVALID_REQUEST. error.field points at the offending
|
|
251
|
+
reach_unit path. media_buy_id is not allocated.
|
|
252
|
+
sample_request:
|
|
253
|
+
brand:
|
|
254
|
+
domain: "acmeoutdoor.example"
|
|
255
|
+
account:
|
|
256
|
+
brand:
|
|
257
|
+
domain: "acmeoutdoor.example"
|
|
258
|
+
operator: "pinnacle-agency.example"
|
|
259
|
+
start_time: "2026-06-01T00:00:00Z"
|
|
260
|
+
end_time: "2026-06-30T23:59:59Z"
|
|
261
|
+
packages:
|
|
262
|
+
- product_id: "reach_ctv_q2"
|
|
263
|
+
budget: 10000
|
|
264
|
+
pricing_option_id: "auction_cpm"
|
|
265
|
+
optimization_goals:
|
|
266
|
+
- kind: "metric"
|
|
267
|
+
metric: "reach"
|
|
268
|
+
reach_unit: "devices"
|
|
269
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_rejection_unsupported_reach_unit"
|
|
270
|
+
validations:
|
|
271
|
+
- check: error_code
|
|
272
|
+
allowed_values: ["INVALID_REQUEST"]
|
|
273
|
+
description: "Seller rejects the unsupported reach_unit with INVALID_REQUEST"
|
|
274
|
+
- check: field_value
|
|
275
|
+
path: "errors[0].field"
|
|
276
|
+
value: "packages[0].optimization_goals[0].reach_unit"
|
|
277
|
+
description: "Error.field points at the offending reach_unit path (core/error.json defines field as deterministic JSONPath-lite — literal equality)"
|
|
278
|
+
|
|
279
|
+
- id: reach_delivery
|
|
280
|
+
title: "Delivery reporting carries reach and frequency"
|
|
281
|
+
narrative: |
|
|
282
|
+
The discriminating assertion of the reach buy mode: delivery reporting
|
|
283
|
+
MUST surface reach and frequency at the buy level — not just
|
|
284
|
+
impressions and spend. The runner injects simulated impressions,
|
|
285
|
+
reach, and frequency via the test controller, then verifies
|
|
286
|
+
get_media_buy_delivery returns reach + frequency on totals.
|
|
287
|
+
|
|
288
|
+
Per-row reach_window semantics (cumulative vs period vs rolling — see
|
|
289
|
+
delivery-metrics.json) are deliberately not asserted at this layer.
|
|
290
|
+
The scenario asserts the discriminating fields are present at the
|
|
291
|
+
buy level; reach_window correctness is asserted by the schema
|
|
292
|
+
validation alongside the totals payload.
|
|
293
|
+
|
|
294
|
+
steps:
|
|
295
|
+
- id: simulate_reach_delivery
|
|
296
|
+
title: "Inject impressions + reach + frequency"
|
|
297
|
+
task: comply_test_controller
|
|
298
|
+
requires_tool: comply_test_controller
|
|
299
|
+
stateful: true
|
|
300
|
+
expected: |
|
|
301
|
+
The test controller acknowledges the simulated delivery.
|
|
302
|
+
sample_request:
|
|
303
|
+
account:
|
|
304
|
+
sandbox: true
|
|
305
|
+
scenario: "simulate_delivery"
|
|
306
|
+
params:
|
|
307
|
+
media_buy_id: "$context.media_buy_id"
|
|
308
|
+
impressions: 750000
|
|
309
|
+
reach: 250000
|
|
310
|
+
frequency: 3.0
|
|
311
|
+
reported_spend:
|
|
312
|
+
amount: 14000.00
|
|
313
|
+
currency: "USD"
|
|
314
|
+
validations:
|
|
315
|
+
- check: field_value
|
|
316
|
+
path: "success"
|
|
317
|
+
allowed_values: [true]
|
|
318
|
+
description: "Delivery simulation succeeds"
|
|
319
|
+
|
|
320
|
+
- id: get_reach_delivery
|
|
321
|
+
title: "Get delivery and verify reach + frequency metrics"
|
|
322
|
+
task: get_media_buy_delivery
|
|
323
|
+
schema_ref: "media-buy/get-media-buy-delivery-request.json"
|
|
324
|
+
response_schema_ref: "media-buy/get-media-buy-delivery-response.json"
|
|
325
|
+
doc_ref: "/media-buy/task-reference/get_media_buy_delivery"
|
|
326
|
+
stateful: true
|
|
327
|
+
expected: |
|
|
328
|
+
Delivery response includes totals.reach (the unique-count value
|
|
329
|
+
in the declared reach_unit) and totals.frequency (average
|
|
330
|
+
exposures per reach unit over the declared reach_window). These
|
|
331
|
+
two fields are the discriminating output of reach optimization
|
|
332
|
+
and are required on reach-optimized buys.
|
|
333
|
+
sample_request:
|
|
334
|
+
account:
|
|
335
|
+
brand:
|
|
336
|
+
domain: "acmeoutdoor.example"
|
|
337
|
+
operator: "pinnacle-agency.example"
|
|
338
|
+
media_buy_ids:
|
|
339
|
+
- "$context.media_buy_id"
|
|
340
|
+
include_package_daily_breakdown: true
|
|
341
|
+
validations:
|
|
342
|
+
- check: response_schema
|
|
343
|
+
description: "Response matches get-media-buy-delivery-response.json schema"
|
|
344
|
+
- check: field_present
|
|
345
|
+
path: "media_buy_deliveries[0].totals.reach"
|
|
346
|
+
description: "Reach count surfaced at the buy level (in the declared reach_unit)"
|
|
347
|
+
- check: field_present
|
|
348
|
+
path: "media_buy_deliveries[0].totals.frequency"
|
|
349
|
+
description: "Frequency surfaced at the buy level — average exposures per reach unit"
|
|
350
|
+
|
|
351
|
+
- id: reach_window_cumulative
|
|
352
|
+
title: "Delivery row carries reach_window.kind cumulative"
|
|
353
|
+
narrative: |
|
|
354
|
+
A cumulative reach_window declares that the reach value represents
|
|
355
|
+
unique audiences since campaign start. Each successive delivery row
|
|
356
|
+
supersedes the prior one — buyers MUST NOT sum cumulative rows to
|
|
357
|
+
compute campaign reach, since each later row already includes all
|
|
358
|
+
prior audiences. No period field is required for cumulative.
|
|
359
|
+
|
|
360
|
+
This phase injects a delivery row with reach_window.kind cumulative
|
|
361
|
+
and verifies that the seller surfaces the window semantics on the
|
|
362
|
+
delivery response. Sellers that omit reach_window on a reach-optimized
|
|
363
|
+
buy force buyers to guess the window — creating silent double-counting
|
|
364
|
+
when buyers aggregate across reporting periods.
|
|
365
|
+
|
|
366
|
+
steps:
|
|
367
|
+
- id: create_media_buy_cumulative_reach
|
|
368
|
+
title: "Create media buy for cumulative reach-window delivery"
|
|
369
|
+
task: create_media_buy
|
|
370
|
+
schema_ref: "media-buy/create-media-buy-request.json"
|
|
371
|
+
response_schema_ref: "media-buy/create-media-buy-response.json"
|
|
372
|
+
doc_ref: "/media-buy/task-reference/create_media_buy"
|
|
373
|
+
stateful: true
|
|
374
|
+
expected: |
|
|
375
|
+
Create a fresh reach-optimized media buy so the cumulative
|
|
376
|
+
reach_window assertion is not contaminated by prior delivery
|
|
377
|
+
simulation calls.
|
|
378
|
+
sample_request:
|
|
379
|
+
brand:
|
|
380
|
+
domain: "acmeoutdoor.example"
|
|
381
|
+
account:
|
|
382
|
+
brand:
|
|
383
|
+
domain: "acmeoutdoor.example"
|
|
384
|
+
operator: "pinnacle-agency.example"
|
|
385
|
+
start_time: "2026-06-01T00:00:00Z"
|
|
386
|
+
end_time: "2026-06-30T23:59:59Z"
|
|
387
|
+
packages:
|
|
388
|
+
- product_id: "reach_ctv_q2"
|
|
389
|
+
budget: 10000
|
|
390
|
+
pricing_option_id: "auction_cpm"
|
|
391
|
+
optimization_goals:
|
|
392
|
+
- kind: "metric"
|
|
393
|
+
metric: "reach"
|
|
394
|
+
reach_unit: "households"
|
|
395
|
+
priority: 1
|
|
396
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_reach_window_cumulative_create_media_buy"
|
|
397
|
+
context_outputs:
|
|
398
|
+
- name: cumulative_media_buy_id
|
|
399
|
+
path: "media_buy_id"
|
|
400
|
+
validations:
|
|
401
|
+
- check: response_schema
|
|
402
|
+
description: "Response matches create-media-buy-response.json schema"
|
|
403
|
+
- check: field_present
|
|
404
|
+
path: "media_buy_id"
|
|
405
|
+
description: "Media buy has a platform-assigned ID"
|
|
406
|
+
|
|
407
|
+
- id: simulate_cumulative_reach
|
|
408
|
+
title: "Inject delivery with cumulative reach_window"
|
|
409
|
+
task: comply_test_controller
|
|
410
|
+
requires_tool: comply_test_controller
|
|
411
|
+
stateful: true
|
|
412
|
+
expected: |
|
|
413
|
+
The test controller acknowledges the simulated delivery with a
|
|
414
|
+
cumulative reach_window.
|
|
415
|
+
sample_request:
|
|
416
|
+
account:
|
|
417
|
+
sandbox: true
|
|
418
|
+
scenario: "simulate_delivery"
|
|
419
|
+
params:
|
|
420
|
+
media_buy_id: "$context.cumulative_media_buy_id"
|
|
421
|
+
impressions: 1200000
|
|
422
|
+
reach: 400000
|
|
423
|
+
frequency: 3.0
|
|
424
|
+
reach_window:
|
|
425
|
+
kind: "cumulative"
|
|
426
|
+
reported_spend:
|
|
427
|
+
amount: 22000.00
|
|
428
|
+
currency: "USD"
|
|
429
|
+
validations:
|
|
430
|
+
- check: field_value
|
|
431
|
+
path: "success"
|
|
432
|
+
allowed_values: [true]
|
|
433
|
+
description: "Delivery simulation with cumulative reach_window succeeds"
|
|
434
|
+
|
|
435
|
+
- id: get_cumulative_reach_delivery
|
|
436
|
+
title: "Verify reach_window.kind cumulative on delivery row"
|
|
437
|
+
task: get_media_buy_delivery
|
|
438
|
+
schema_ref: "media-buy/get-media-buy-delivery-request.json"
|
|
439
|
+
response_schema_ref: "media-buy/get-media-buy-delivery-response.json"
|
|
440
|
+
doc_ref: "/media-buy/task-reference/get_media_buy_delivery"
|
|
441
|
+
stateful: true
|
|
442
|
+
expected: |
|
|
443
|
+
Delivery row carries reach_window with kind: cumulative. No period
|
|
444
|
+
field is expected — cumulative reach is campaign-to-date and the
|
|
445
|
+
window is implicit. The row's reach value is the total unique count
|
|
446
|
+
since campaign start; each subsequent cumulative row supersedes
|
|
447
|
+
this one.
|
|
448
|
+
sample_request:
|
|
449
|
+
account:
|
|
450
|
+
brand:
|
|
451
|
+
domain: "acmeoutdoor.example"
|
|
452
|
+
operator: "pinnacle-agency.example"
|
|
453
|
+
media_buy_ids:
|
|
454
|
+
- "$context.cumulative_media_buy_id"
|
|
455
|
+
include_package_daily_breakdown: true
|
|
456
|
+
validations:
|
|
457
|
+
- check: response_schema
|
|
458
|
+
description: "Response matches get-media-buy-delivery-response.json schema"
|
|
459
|
+
- check: field_present
|
|
460
|
+
path: "media_buy_deliveries[0].totals.reach_window"
|
|
461
|
+
description: "Seller populates reach_window on the delivery row"
|
|
462
|
+
- check: field_value
|
|
463
|
+
path: "media_buy_deliveries[0].totals.reach_window.kind"
|
|
464
|
+
allowed_values: ["cumulative"]
|
|
465
|
+
description: "reach_window.kind is cumulative as injected"
|
|
466
|
+
|
|
467
|
+
- id: reach_window_period
|
|
468
|
+
title: "Delivery row carries reach_window.kind period with period field"
|
|
469
|
+
narrative: |
|
|
470
|
+
A period reach_window declares that the reach value covers a single
|
|
471
|
+
non-overlapping reporting period (e.g., one calendar day). Adjacent
|
|
472
|
+
period rows do not share audiences by construction — the same person
|
|
473
|
+
MAY appear across multiple periods — so buyers MUST NOT sum period
|
|
474
|
+
rows to compute campaign reach. The period field is REQUIRED for
|
|
475
|
+
kind: period and declares the snapshot length (e.g., 1 day).
|
|
476
|
+
|
|
477
|
+
This phase injects a delivery row with reach_window.kind period and
|
|
478
|
+
period: {interval:1, unit:"days"} and verifies the seller surfaces
|
|
479
|
+
both the kind and the period duration on the delivery response.
|
|
480
|
+
|
|
481
|
+
steps:
|
|
482
|
+
- id: create_media_buy_period_reach
|
|
483
|
+
title: "Create media buy for period reach-window delivery"
|
|
484
|
+
task: create_media_buy
|
|
485
|
+
schema_ref: "media-buy/create-media-buy-request.json"
|
|
486
|
+
response_schema_ref: "media-buy/create-media-buy-response.json"
|
|
487
|
+
doc_ref: "/media-buy/task-reference/create_media_buy"
|
|
488
|
+
stateful: true
|
|
489
|
+
expected: |
|
|
490
|
+
Create a fresh reach-optimized media buy so the period
|
|
491
|
+
reach_window assertion is isolated from cumulative or rolling
|
|
492
|
+
delivery simulations.
|
|
493
|
+
sample_request:
|
|
494
|
+
brand:
|
|
495
|
+
domain: "acmeoutdoor.example"
|
|
496
|
+
account:
|
|
497
|
+
brand:
|
|
498
|
+
domain: "acmeoutdoor.example"
|
|
499
|
+
operator: "pinnacle-agency.example"
|
|
500
|
+
start_time: "2026-06-01T00:00:00Z"
|
|
501
|
+
end_time: "2026-06-30T23:59:59Z"
|
|
502
|
+
packages:
|
|
503
|
+
- product_id: "reach_ctv_q2"
|
|
504
|
+
budget: 10000
|
|
505
|
+
pricing_option_id: "auction_cpm"
|
|
506
|
+
optimization_goals:
|
|
507
|
+
- kind: "metric"
|
|
508
|
+
metric: "reach"
|
|
509
|
+
reach_unit: "households"
|
|
510
|
+
priority: 1
|
|
511
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_reach_window_period_create_media_buy"
|
|
512
|
+
context_outputs:
|
|
513
|
+
- name: period_media_buy_id
|
|
514
|
+
path: "media_buy_id"
|
|
515
|
+
validations:
|
|
516
|
+
- check: response_schema
|
|
517
|
+
description: "Response matches create-media-buy-response.json schema"
|
|
518
|
+
- check: field_present
|
|
519
|
+
path: "media_buy_id"
|
|
520
|
+
description: "Media buy has a platform-assigned ID"
|
|
521
|
+
|
|
522
|
+
- id: simulate_period_reach
|
|
523
|
+
title: "Inject delivery with period reach_window"
|
|
524
|
+
task: comply_test_controller
|
|
525
|
+
requires_tool: comply_test_controller
|
|
526
|
+
stateful: true
|
|
527
|
+
expected: |
|
|
528
|
+
The test controller acknowledges the simulated delivery with a
|
|
529
|
+
period reach_window carrying a 1-day period.
|
|
530
|
+
sample_request:
|
|
531
|
+
account:
|
|
532
|
+
sandbox: true
|
|
533
|
+
scenario: "simulate_delivery"
|
|
534
|
+
params:
|
|
535
|
+
media_buy_id: "$context.period_media_buy_id"
|
|
536
|
+
impressions: 175000
|
|
537
|
+
reach: 140000
|
|
538
|
+
frequency: 1.25
|
|
539
|
+
reach_window:
|
|
540
|
+
kind: "period"
|
|
541
|
+
period:
|
|
542
|
+
interval: 1
|
|
543
|
+
unit: "days"
|
|
544
|
+
reported_spend:
|
|
545
|
+
amount: 3200.00
|
|
546
|
+
currency: "USD"
|
|
547
|
+
validations:
|
|
548
|
+
- check: field_value
|
|
549
|
+
path: "success"
|
|
550
|
+
allowed_values: [true]
|
|
551
|
+
description: "Delivery simulation with period reach_window succeeds"
|
|
552
|
+
|
|
553
|
+
- id: get_period_reach_delivery
|
|
554
|
+
title: "Verify reach_window.kind period and period duration on delivery row"
|
|
555
|
+
task: get_media_buy_delivery
|
|
556
|
+
schema_ref: "media-buy/get-media-buy-delivery-request.json"
|
|
557
|
+
response_schema_ref: "media-buy/get-media-buy-delivery-response.json"
|
|
558
|
+
doc_ref: "/media-buy/task-reference/get_media_buy_delivery"
|
|
559
|
+
stateful: true
|
|
560
|
+
expected: |
|
|
561
|
+
Delivery row carries reach_window with kind: period and a period
|
|
562
|
+
duration of 1 day. The period field is required for kind: period —
|
|
563
|
+
its absence would leave buyers unable to determine the snapshot
|
|
564
|
+
length, making cross-period reach comparisons unreliable.
|
|
565
|
+
sample_request:
|
|
566
|
+
account:
|
|
567
|
+
brand:
|
|
568
|
+
domain: "acmeoutdoor.example"
|
|
569
|
+
operator: "pinnacle-agency.example"
|
|
570
|
+
media_buy_ids:
|
|
571
|
+
- "$context.period_media_buy_id"
|
|
572
|
+
include_package_daily_breakdown: true
|
|
573
|
+
validations:
|
|
574
|
+
- check: response_schema
|
|
575
|
+
description: "Response matches get-media-buy-delivery-response.json schema"
|
|
576
|
+
- check: field_present
|
|
577
|
+
path: "media_buy_deliveries[0].totals.reach_window"
|
|
578
|
+
description: "Seller populates reach_window on the delivery row"
|
|
579
|
+
- check: field_value
|
|
580
|
+
path: "media_buy_deliveries[0].totals.reach_window.kind"
|
|
581
|
+
allowed_values: ["period"]
|
|
582
|
+
description: "reach_window.kind is period as injected"
|
|
583
|
+
- check: field_present
|
|
584
|
+
path: "media_buy_deliveries[0].totals.reach_window.period"
|
|
585
|
+
description: "period field is present — required for kind: period"
|
|
586
|
+
|
|
587
|
+
- id: reach_window_rolling
|
|
588
|
+
title: "Delivery row carries reach_window.kind rolling with period field"
|
|
589
|
+
narrative: |
|
|
590
|
+
A rolling reach_window declares that the reach value covers a trailing
|
|
591
|
+
window ending at the row's reporting timestamp (e.g., trailing-7-day
|
|
592
|
+
unique reach). Adjacent rolling rows overlap — each row stands alone
|
|
593
|
+
and MUST NOT be summed; summing rolling rows double-counts audiences
|
|
594
|
+
seen in multiple windows. The period field is REQUIRED for kind: rolling
|
|
595
|
+
and declares the trailing-window length (e.g., 7 days).
|
|
596
|
+
|
|
597
|
+
This phase injects a delivery row with reach_window.kind rolling and
|
|
598
|
+
period: {interval:7, unit:"days"} and verifies the seller surfaces
|
|
599
|
+
both the kind and the window duration. A trailing-7-day reach window
|
|
600
|
+
is the canonical frequency-cap companion — buyers reading this value
|
|
601
|
+
know exactly which audiences are within the 7-day dedup window.
|
|
602
|
+
|
|
603
|
+
steps:
|
|
604
|
+
- id: create_media_buy_rolling_reach
|
|
605
|
+
title: "Create media buy for rolling reach-window delivery"
|
|
606
|
+
task: create_media_buy
|
|
607
|
+
schema_ref: "media-buy/create-media-buy-request.json"
|
|
608
|
+
response_schema_ref: "media-buy/create-media-buy-response.json"
|
|
609
|
+
doc_ref: "/media-buy/task-reference/create_media_buy"
|
|
610
|
+
stateful: true
|
|
611
|
+
expected: |
|
|
612
|
+
Create a fresh reach-optimized media buy so the rolling
|
|
613
|
+
reach_window assertion is isolated from period or cumulative
|
|
614
|
+
delivery simulations.
|
|
615
|
+
sample_request:
|
|
616
|
+
brand:
|
|
617
|
+
domain: "acmeoutdoor.example"
|
|
618
|
+
account:
|
|
619
|
+
brand:
|
|
620
|
+
domain: "acmeoutdoor.example"
|
|
621
|
+
operator: "pinnacle-agency.example"
|
|
622
|
+
start_time: "2026-06-01T00:00:00Z"
|
|
623
|
+
end_time: "2026-06-30T23:59:59Z"
|
|
624
|
+
packages:
|
|
625
|
+
- product_id: "reach_ctv_q2"
|
|
626
|
+
budget: 10000
|
|
627
|
+
pricing_option_id: "auction_cpm"
|
|
628
|
+
optimization_goals:
|
|
629
|
+
- kind: "metric"
|
|
630
|
+
metric: "reach"
|
|
631
|
+
reach_unit: "households"
|
|
632
|
+
priority: 1
|
|
633
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_reach_window_rolling_create_media_buy"
|
|
634
|
+
context_outputs:
|
|
635
|
+
- name: rolling_media_buy_id
|
|
636
|
+
path: "media_buy_id"
|
|
637
|
+
validations:
|
|
638
|
+
- check: response_schema
|
|
639
|
+
description: "Response matches create-media-buy-response.json schema"
|
|
640
|
+
- check: field_present
|
|
641
|
+
path: "media_buy_id"
|
|
642
|
+
description: "Media buy has a platform-assigned ID"
|
|
643
|
+
|
|
644
|
+
- id: simulate_rolling_reach
|
|
645
|
+
title: "Inject delivery with rolling reach_window"
|
|
646
|
+
task: comply_test_controller
|
|
647
|
+
requires_tool: comply_test_controller
|
|
648
|
+
stateful: true
|
|
649
|
+
expected: |
|
|
650
|
+
The test controller acknowledges the simulated delivery with a
|
|
651
|
+
rolling reach_window carrying a 7-day trailing window.
|
|
652
|
+
sample_request:
|
|
653
|
+
account:
|
|
654
|
+
sandbox: true
|
|
655
|
+
scenario: "simulate_delivery"
|
|
656
|
+
params:
|
|
657
|
+
media_buy_id: "$context.rolling_media_buy_id"
|
|
658
|
+
impressions: 450000
|
|
659
|
+
reach: 210000
|
|
660
|
+
frequency: 2.1
|
|
661
|
+
reach_window:
|
|
662
|
+
kind: "rolling"
|
|
663
|
+
period:
|
|
664
|
+
interval: 7
|
|
665
|
+
unit: "days"
|
|
666
|
+
reported_spend:
|
|
667
|
+
amount: 8500.00
|
|
668
|
+
currency: "USD"
|
|
669
|
+
validations:
|
|
670
|
+
- check: field_value
|
|
671
|
+
path: "success"
|
|
672
|
+
allowed_values: [true]
|
|
673
|
+
description: "Delivery simulation with rolling reach_window succeeds"
|
|
674
|
+
|
|
675
|
+
- id: get_rolling_reach_delivery
|
|
676
|
+
title: "Verify reach_window.kind rolling and period duration on delivery row"
|
|
677
|
+
task: get_media_buy_delivery
|
|
678
|
+
schema_ref: "media-buy/get-media-buy-delivery-request.json"
|
|
679
|
+
response_schema_ref: "media-buy/get-media-buy-delivery-response.json"
|
|
680
|
+
doc_ref: "/media-buy/task-reference/get_media_buy_delivery"
|
|
681
|
+
stateful: true
|
|
682
|
+
expected: |
|
|
683
|
+
Delivery row carries reach_window with kind: rolling and a period
|
|
684
|
+
of 7 days. The period field is required for kind: rolling — without
|
|
685
|
+
it, buyers cannot determine the trailing window length and cannot
|
|
686
|
+
safely use the reach value for frequency optimization.
|
|
687
|
+
sample_request:
|
|
688
|
+
account:
|
|
689
|
+
brand:
|
|
690
|
+
domain: "acmeoutdoor.example"
|
|
691
|
+
operator: "pinnacle-agency.example"
|
|
692
|
+
media_buy_ids:
|
|
693
|
+
- "$context.rolling_media_buy_id"
|
|
694
|
+
include_package_daily_breakdown: true
|
|
695
|
+
validations:
|
|
696
|
+
- check: response_schema
|
|
697
|
+
description: "Response matches get-media-buy-delivery-response.json schema"
|
|
698
|
+
- check: field_present
|
|
699
|
+
path: "media_buy_deliveries[0].totals.reach_window"
|
|
700
|
+
description: "Seller populates reach_window on the delivery row"
|
|
701
|
+
- check: field_value
|
|
702
|
+
path: "media_buy_deliveries[0].totals.reach_window.kind"
|
|
703
|
+
allowed_values: ["rolling"]
|
|
704
|
+
description: "reach_window.kind is rolling as injected"
|
|
705
|
+
- check: field_present
|
|
706
|
+
path: "media_buy_deliveries[0].totals.reach_window.period"
|
|
707
|
+
description: "period field is present — required for kind: rolling"
|
|
708
|
+
|
|
709
|
+
- id: reach_window_absent_advisory
|
|
710
|
+
title: "Advisory: delivery row with reach but no reach_window is schema-valid but sum-unsafe"
|
|
711
|
+
narrative: |
|
|
712
|
+
Reach/frequency rows with `reach_window` omitted are schema-valid —
|
|
713
|
+
the schema marks the field SHOULD (not MUST) populate it. However,
|
|
714
|
+
buyers MUST NOT treat omitted-window rows as sum-safe: the value may
|
|
715
|
+
be a daily snapshot, a cumulative total, or something else. Summing
|
|
716
|
+
reach values across rows without a declared window silently
|
|
717
|
+
double-counts audiences.
|
|
718
|
+
|
|
719
|
+
This phase simulates a delivery row without reach_window and issues
|
|
720
|
+
an advisory (not a conformance fail) when the seller returns reach
|
|
721
|
+
without the window declaration. Advisory grading: sellers that do
|
|
722
|
+
not populate reach_window on reach-optimized buys are warned but
|
|
723
|
+
not failed — the omission is spec-valid. Sellers should treat this
|
|
724
|
+
advisory as a signal to add reach_window to their delivery responses
|
|
725
|
+
for 3.1+ buyers.
|
|
726
|
+
|
|
727
|
+
steps:
|
|
728
|
+
- id: create_media_buy_reach_no_window
|
|
729
|
+
title: "Create media buy for omitted reach-window advisory"
|
|
730
|
+
task: create_media_buy
|
|
731
|
+
schema_ref: "media-buy/create-media-buy-request.json"
|
|
732
|
+
response_schema_ref: "media-buy/create-media-buy-response.json"
|
|
733
|
+
doc_ref: "/media-buy/task-reference/create_media_buy"
|
|
734
|
+
stateful: true
|
|
735
|
+
expected: |
|
|
736
|
+
Create a fresh reach-optimized media buy so the omitted-window
|
|
737
|
+
advisory observes a delivery row whose reach_window was never
|
|
738
|
+
populated by earlier simulation calls.
|
|
739
|
+
sample_request:
|
|
740
|
+
brand:
|
|
741
|
+
domain: "acmeoutdoor.example"
|
|
742
|
+
account:
|
|
743
|
+
brand:
|
|
744
|
+
domain: "acmeoutdoor.example"
|
|
745
|
+
operator: "pinnacle-agency.example"
|
|
746
|
+
start_time: "2026-06-01T00:00:00Z"
|
|
747
|
+
end_time: "2026-06-30T23:59:59Z"
|
|
748
|
+
packages:
|
|
749
|
+
- product_id: "reach_ctv_q2"
|
|
750
|
+
budget: 10000
|
|
751
|
+
pricing_option_id: "auction_cpm"
|
|
752
|
+
optimization_goals:
|
|
753
|
+
- kind: "metric"
|
|
754
|
+
metric: "reach"
|
|
755
|
+
reach_unit: "households"
|
|
756
|
+
priority: 1
|
|
757
|
+
idempotency_key: "$generate:uuid_v4#reach_buy_flow_reach_window_absent_create_media_buy"
|
|
758
|
+
context_outputs:
|
|
759
|
+
- name: no_window_media_buy_id
|
|
760
|
+
path: "media_buy_id"
|
|
761
|
+
validations:
|
|
762
|
+
- check: response_schema
|
|
763
|
+
description: "Response matches create-media-buy-response.json schema"
|
|
764
|
+
- check: field_present
|
|
765
|
+
path: "media_buy_id"
|
|
766
|
+
description: "Media buy has a platform-assigned ID"
|
|
767
|
+
|
|
768
|
+
- id: simulate_reach_no_window
|
|
769
|
+
title: "Inject delivery with reach but no reach_window"
|
|
770
|
+
task: comply_test_controller
|
|
771
|
+
requires_tool: comply_test_controller
|
|
772
|
+
stateful: true
|
|
773
|
+
expected: |
|
|
774
|
+
The test controller injects a delivery row with reach and frequency
|
|
775
|
+
but no reach_window. This is a valid operation — the scenario
|
|
776
|
+
tests the advisory behavior, not a rejection.
|
|
777
|
+
sample_request:
|
|
778
|
+
account:
|
|
779
|
+
sandbox: true
|
|
780
|
+
scenario: "simulate_delivery"
|
|
781
|
+
params:
|
|
782
|
+
media_buy_id: "$context.no_window_media_buy_id"
|
|
783
|
+
impressions: 300000
|
|
784
|
+
reach: 180000
|
|
785
|
+
frequency: 1.67
|
|
786
|
+
reported_spend:
|
|
787
|
+
amount: 5800.00
|
|
788
|
+
currency: "USD"
|
|
789
|
+
validations:
|
|
790
|
+
- check: field_value
|
|
791
|
+
path: "success"
|
|
792
|
+
allowed_values: [true]
|
|
793
|
+
description: "Delivery simulation without reach_window succeeds"
|
|
794
|
+
|
|
795
|
+
- id: get_delivery_reach_no_window
|
|
796
|
+
title: "Verify advisory when reach is present but reach_window is absent"
|
|
797
|
+
task: get_media_buy_delivery
|
|
798
|
+
schema_ref: "media-buy/get-media-buy-delivery-request.json"
|
|
799
|
+
response_schema_ref: "media-buy/get-media-buy-delivery-response.json"
|
|
800
|
+
doc_ref: "/media-buy/task-reference/get_media_buy_delivery"
|
|
801
|
+
stateful: true
|
|
802
|
+
expected: |
|
|
803
|
+
Delivery row contains reach without reach_window. Schema-valid —
|
|
804
|
+
this is a SHOULD, not a MUST. The advisory validation below fires
|
|
805
|
+
when reach_window is absent, surfacing the omission for the seller
|
|
806
|
+
to address in future delivery responses.
|
|
807
|
+
sample_request:
|
|
808
|
+
account:
|
|
809
|
+
brand:
|
|
810
|
+
domain: "acmeoutdoor.example"
|
|
811
|
+
operator: "pinnacle-agency.example"
|
|
812
|
+
media_buy_ids:
|
|
813
|
+
- "$context.no_window_media_buy_id"
|
|
814
|
+
include_package_daily_breakdown: true
|
|
815
|
+
validations:
|
|
816
|
+
- check: response_schema
|
|
817
|
+
description: "Response matches get-media-buy-delivery-response.json schema"
|
|
818
|
+
- check: field_present
|
|
819
|
+
path: "media_buy_deliveries[0].totals.reach_window"
|
|
820
|
+
severity: advisory
|
|
821
|
+
permanent_advisory:
|
|
822
|
+
reason: "delivery-metrics.json declares reach_window as SHOULD (not MUST) for 3.1. Omitting it is schema-valid but prevents buyers from determining the measurement window, making the reach value sum-unsafe across reporting periods. Sellers on the 3.1 track should populate reach_window on all reach-bearing delivery rows. This advisory is permanent: promotion to required is a 4.0 breaking change and will be tracked separately."
|
|
823
|
+
description: "reach_window populated on reach-bearing delivery row (advisory — SHOULD per 3.1 spec)"
|