@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,2194 @@
|
|
|
1
|
+
# Storyboard Definition Schema
|
|
2
|
+
#
|
|
3
|
+
# Storyboards are narrative test workflows that walk agent builders through
|
|
4
|
+
# the sequence of calls their agent will receive, with context at each step.
|
|
5
|
+
#
|
|
6
|
+
# Each storyboard targets a specific agent interaction model and
|
|
7
|
+
# describes the flow from a caller's perspective.
|
|
8
|
+
|
|
9
|
+
# --- Schema definition ---
|
|
10
|
+
|
|
11
|
+
# A storyboard file must conform to this structure:
|
|
12
|
+
#
|
|
13
|
+
# id: string (unique identifier, e.g., "creative_template")
|
|
14
|
+
# version: string (semver, e.g., "1.0.0")
|
|
15
|
+
# title: string (human-readable title)
|
|
16
|
+
# category: string — matches the storyboard's `id` top-level segment.
|
|
17
|
+
#
|
|
18
|
+
# For *specialism* storyboards, this is the snake_case form of an entry in
|
|
19
|
+
# /schemas/enums/specialism.json (the wire-protocol enum declared by agents
|
|
20
|
+
# on `get_adcp_capabilities.specialisms[]`). The protocol enum is
|
|
21
|
+
# authoritative — when adding a new specialism storyboard, add the kebab-case
|
|
22
|
+
# form to specialism.json first. The current specialism categories
|
|
23
|
+
# (snake_case form) are:
|
|
24
|
+
#
|
|
25
|
+
# Sales: sales_guaranteed | sales_non_guaranteed | sales_proposal_mode
|
|
26
|
+
# | sales_catalog_driven | sales_broadcast_tv | sales_social
|
|
27
|
+
# Creative: creative_ad_server | creative_generative | creative_template
|
|
28
|
+
# Governance: content_standards | property_lists | collection_lists
|
|
29
|
+
# | governance_aware_seller | governance_delivery_monitor
|
|
30
|
+
# | governance_spend_authority
|
|
31
|
+
# Signals: signal_marketplace | signal_owned
|
|
32
|
+
# Brand: brand_rights
|
|
33
|
+
# Audiences: audience_sync
|
|
34
|
+
#
|
|
35
|
+
# For *universal / domain-level* storyboards, the category is descriptive
|
|
36
|
+
# only (not on the wire). Examples in current use: capability_discovery,
|
|
37
|
+
# schema_validation, error_compliance, security_baseline, si_baseline,
|
|
38
|
+
# media_buy_seller, media_buy_governance_escalation, signed_requests,
|
|
39
|
+
# idempotency, webhook_emission, deterministic_testing,
|
|
40
|
+
# runner_output_contract, v3_envelope_integrity. New universal storyboards
|
|
41
|
+
# pick a descriptive snake_case identifier; see existing files under
|
|
42
|
+
# static/compliance/source/universal/ and protocols/ for the conventions.
|
|
43
|
+
#
|
|
44
|
+
# Scenario variants use <category>/<variant> form, e.g.
|
|
45
|
+
# governance_spend_authority/denied, creative_generative/seller,
|
|
46
|
+
# brand_rights/governance_denied, media_buy_seller/provenance_enforcement.
|
|
47
|
+
# summary: string (one-line description for listings)
|
|
48
|
+
# track: enum (optional — compliance track this storyboard contributes to:
|
|
49
|
+
# core | products | media_buy | creative | reporting | governance |
|
|
50
|
+
# campaign_governance | signals | si | audiences | error_handling | brand | security)
|
|
51
|
+
# required_tools: string[] (optional — tool names the storyboard uses; empty for protocol-level tests.
|
|
52
|
+
# Lenient any-of: missing all listed tools triggers a coverage-gap skip
|
|
53
|
+
# (skip_result.reason: missing_tool at step scope). See `required_any_of_tools`
|
|
54
|
+
# below for the strict per-family load-time gate that grades requirement_unmet.)
|
|
55
|
+
# required_any_of_tools: array of OR-family objects (optional — declarative one-of-N
|
|
56
|
+
# tool-advertisement gate. Each entry is { tools: string[], rationale?: string };
|
|
57
|
+
# at least one tool in each family MUST be advertised by the agent under test.
|
|
58
|
+
# Multiple entries are AND-combined: every family must be satisfied independently.
|
|
59
|
+
# Distinct from `required_tools` (which is the lenient any-of for storyboard
|
|
60
|
+
# applicability — missing all listed tools triggers a coverage-gap skip).
|
|
61
|
+
# See the dedicated `required_any_of_tools` block below for full semantics.
|
|
62
|
+
# narrative: string (paragraph explaining the overall flow)
|
|
63
|
+
#
|
|
64
|
+
# requires_scenarios: string[] (optional — scenario IDs from storyboards/scenarios/ that must pass alongside this storyboard.
|
|
65
|
+
# Scenarios are small, focused behavior tests (e.g., "media_buy_seller/accepts_governance").
|
|
66
|
+
# The compliance engine resolves and runs them alongside the main storyboard.
|
|
67
|
+
#
|
|
68
|
+
# Flag flow across `requires_scenarios`:
|
|
69
|
+
# A `branch_set` flag contributed in a step of storyboard X is
|
|
70
|
+
# considered asserted if EITHER (a) a step in X's own phases asserts
|
|
71
|
+
# it via `assert_contribution` with `check: any_of, allowed_values:
|
|
72
|
+
# [<flag>]`, OR (b) a scenario Y listed in `X.requires_scenarios`
|
|
73
|
+
# asserts it via the same check. The `orphan_contribution` lint
|
|
74
|
+
# honors this flow.
|
|
75
|
+
#
|
|
76
|
+
# Reverse flow (a contribution in scenario Y asserted in parent X)
|
|
77
|
+
# is NOT supported — scenarios MUST be internally self-grading.
|
|
78
|
+
# The restriction preserves standalone lintability of scenarios
|
|
79
|
+
# and prevents cycles in the composition graph. Scenarios that
|
|
80
|
+
# contribute a flag without asserting it in their own phases will
|
|
81
|
+
# surface as `orphan_contribution` when linted standalone.
|
|
82
|
+
#
|
|
83
|
+
# Scenario IDs in `requires_scenarios` MUST match the referenced
|
|
84
|
+
# file's top-level `id:` exactly, and the referenced scenario file
|
|
85
|
+
# MUST exist in the source tree at build time. Duplicate IDs
|
|
86
|
+
# across files are a build-time error.)
|
|
87
|
+
#
|
|
88
|
+
# default_agent: string (optional — logical agent key used by multi-agent
|
|
89
|
+
# storyboard runners to route steps that do not have a unique specialism
|
|
90
|
+
# claimant in the runtime agents map. Resolved by the runner against the
|
|
91
|
+
# `agents` option passed to `runStoryboard({ agents: { sales: …, governance: …, … } })`.
|
|
92
|
+
#
|
|
93
|
+
# Key shape: free-form non-empty string, matched verbatim against the
|
|
94
|
+
# runtime `agents` map's keys. The spec does NOT constrain the key to the
|
|
95
|
+
# specialism enum (`sales`, `signals`, `governance`, `creative`, `brand`)
|
|
96
|
+
# because production multi-agent topologies legitimately fan out per-property
|
|
97
|
+
# (`nyt_sales`, `wsj_sales`), per-region (`sales_eu`, `sales_us`), or
|
|
98
|
+
# per-brand-rights-holder. Authors choose the convention that matches their
|
|
99
|
+
# operator's CI invocation; portability across operators is the author's
|
|
100
|
+
# concern, not the spec's.
|
|
101
|
+
#
|
|
102
|
+
# When to set it. Storyboards that exercise cross-domain tools — e.g.,
|
|
103
|
+
# `sync_creatives`, `list_creative_formats` — do not name a single specialism
|
|
104
|
+
# the runner can match. The author has the most context for which logical
|
|
105
|
+
# tenant should receive these calls (usually "wherever the seller is":
|
|
106
|
+
# `sales`). Encoding `default_agent: sales` once in the YAML beats
|
|
107
|
+
# re-asserting it on every CI invocation. (Note: `comply_test_controller`
|
|
108
|
+
# is routed via `prerequisites.controller_seeding`, not this field — the
|
|
109
|
+
# controller is a back-channel, not a specialism claimant.)
|
|
110
|
+
#
|
|
111
|
+
# Resolution order (runner contract — see adcp-client#1066, adcp-client#1355):
|
|
112
|
+
# 1. Step-level `agent:` override (if declared on the step).
|
|
113
|
+
# 2. Specialism-claimant match against the runtime agents map (matched
|
|
114
|
+
# via each agent's `get_adcp_capabilities.supported_protocols`):
|
|
115
|
+
# - Exactly one agent claims the step's task's specialism → route there.
|
|
116
|
+
# - Zero claimants → fall through to slot 3.
|
|
117
|
+
# - Two or more claimants → runner MUST grade the step `unrouted_step`
|
|
118
|
+
# rather than picking arbitrarily. Multi-claim is an operator-config
|
|
119
|
+
# error; the storyboard cannot disambiguate it. Slot 3/4 do NOT
|
|
120
|
+
# rescue this case — silently picking one would mask the misconfig.
|
|
121
|
+
# 3. Storyboard-level `default_agent` (THIS field) resolved against the
|
|
122
|
+
# runtime agents map. When the field is set but the key is absent from
|
|
123
|
+
# the map, the runner MUST grade the step `default_agent_unresolved`
|
|
124
|
+
# and MUST NOT fall through to slot 4 — silent fallback would invisibly
|
|
125
|
+
# override the storyboard author's encoded intent. Slot 4 fires only
|
|
126
|
+
# when the storyboard does NOT declare this field.
|
|
127
|
+
# 4. Run-options `default_agent` passed to `runStoryboard({ default_agent })`.
|
|
128
|
+
# Same key-resolution rule as slot 3: set-but-unmatched grades
|
|
129
|
+
# `default_agent_unresolved`; unset falls through to slot 5.
|
|
130
|
+
# 5. Fail-fast — runner raises `unrouted_step` and grades the step failed.
|
|
131
|
+
#
|
|
132
|
+
# Single-agent runs ignore this field entirely — there is no map to resolve
|
|
133
|
+
# against, every step routes to the only configured agent. Authors SHOULD
|
|
134
|
+
# still declare it for clarity; multi-agent runners treat it as advisory and
|
|
135
|
+
# single-agent runners as a no-op.
|
|
136
|
+
#
|
|
137
|
+
# Validation. The `<key>` MUST be a non-empty string. Validation that the
|
|
138
|
+
# key resolves to a configured tenant is a runtime concern (per the slot 3
|
|
139
|
+
# rule above), not a schema concern — the same storyboard runs against
|
|
140
|
+
# different topologies.)
|
|
141
|
+
#
|
|
142
|
+
# requires: string[] (optional — storyboard-level runtime requirement gate.
|
|
143
|
+
# Known values: controller | seeded_state | real_wire (forward-compat:
|
|
144
|
+
# unrecognized values are NOT schema violations — runners emit
|
|
145
|
+
# requirement_unmet at runtime; see Forward compatibility below).
|
|
146
|
+
# Default when absent: [real_wire] — untagged storyboards run unchanged.
|
|
147
|
+
# minItems: 1 — runners MUST fail-load on requires: [] (empty array) because
|
|
148
|
+
# no distinct semantic exists between the empty set and omitting the field;
|
|
149
|
+
# the error message SHOULD read "use requires: [real_wire] for the default
|
|
150
|
+
# behavior, or omit the field entirely."
|
|
151
|
+
#
|
|
152
|
+
# Values and availability:
|
|
153
|
+
#
|
|
154
|
+
# controller — gate on agent advertising comply_test_controller.
|
|
155
|
+
# Available when the agent's get_adcp_capabilities response
|
|
156
|
+
# includes comply_test_controller in its tool list. When
|
|
157
|
+
# unmet, the runner MUST emit skip_result.reason:
|
|
158
|
+
# requirement_unmet with detail naming "controller".
|
|
159
|
+
# Orthogonal to prerequisites.controller_seeding: the two
|
|
160
|
+
# fields are independent. controller_seeding is an execution-
|
|
161
|
+
# phase instruction ("inject a fixtures phase before the main
|
|
162
|
+
# phases run"); requires: [controller] is a load-phase gate
|
|
163
|
+
# ("skip the whole storyboard if the capability is absent").
|
|
164
|
+
# Storyboards may declare both: the gate fires first, and
|
|
165
|
+
# controller_seeding takes effect only if the gate passes.
|
|
166
|
+
# Declaring controller_seeding: true without requires:
|
|
167
|
+
# [controller] is also valid — mid-run, individual steps
|
|
168
|
+
# grade missing_test_controller if the agent lacks the
|
|
169
|
+
# tool; requires: [controller] coalesces that into a single
|
|
170
|
+
# whole-storyboard skip at load time instead.
|
|
171
|
+
# Mid-run transient loss: if the load-phase gate passes
|
|
172
|
+
# (the agent advertised comply_test_controller at load
|
|
173
|
+
# time) but the tool becomes unavailable mid-run, the
|
|
174
|
+
# runner MUST grade the affected step missing_test_controller
|
|
175
|
+
# per the existing per-step path, NOT a retroactive
|
|
176
|
+
# requirement_unmet at storyboard scope.
|
|
177
|
+
#
|
|
178
|
+
# seeded_state — gate on operator asserting out-of-band state provisioning.
|
|
179
|
+
# Available when the operator declares (e.g., via a CLI flag
|
|
180
|
+
# or runner option) that prerequisite state has been seeded
|
|
181
|
+
# externally before the run. When unmet, the runner MUST emit
|
|
182
|
+
# skip_result.reason: requirement_unmet with detail naming
|
|
183
|
+
# "seeded_state". Scenarios fail naturally on the first
|
|
184
|
+
# stateful step if state is not actually present.
|
|
185
|
+
# ANTI-GAMING: trust signals (verified-live badges, Tier-2
|
|
186
|
+
# production scores) MUST NOT consume requires: [seeded_state]
|
|
187
|
+
# pass results without independent verification. The field
|
|
188
|
+
# widens the graded set; it does not attest the state.
|
|
189
|
+
# This is an operator-governance norm; the runner cannot
|
|
190
|
+
# enforce it on behalf of downstream certification dashboards.
|
|
191
|
+
#
|
|
192
|
+
# real_wire — reserved for a future --mock-only execution mode. Current
|
|
193
|
+
# behavior: no runtime effect (gate is always met). Authors
|
|
194
|
+
# SHOULD omit this value rather than listing it explicitly,
|
|
195
|
+
# since the default when the field is absent is semantically
|
|
196
|
+
# equivalent to [real_wire] today (this equivalence holds
|
|
197
|
+
# only while --mock-only is not active; the --mock-only
|
|
198
|
+
# inversion may break it — see below).
|
|
199
|
+
# IMPORTANT: the future --mock-only semantics INVERT the
|
|
200
|
+
# skip direction compared to other values — this value will
|
|
201
|
+
# cause a storyboard to skip when --mock-only is active (i.e.,
|
|
202
|
+
# skip-when-mocked, pass-when-live), opposite to the
|
|
203
|
+
# skip-when-absent logic of controller and seeded_state.
|
|
204
|
+
# Whether the default-when-absent (omitting `requires`)
|
|
205
|
+
# also triggers the skip under --mock-only is to be defined
|
|
206
|
+
# when --mock-only ships; until then, authors MUST NOT rely
|
|
207
|
+
# on any particular behavior for omitted vs. explicit
|
|
208
|
+
# real_wire under a mock-only runner.
|
|
209
|
+
# Composing real_wire with other values (e.g., requires:
|
|
210
|
+
# [controller, real_wire]) is syntactically valid but the
|
|
211
|
+
# combined behavior under --mock-only is unspecified; authors
|
|
212
|
+
# SHOULD NOT mix real_wire with other values until --mock-only
|
|
213
|
+
# semantics are defined.
|
|
214
|
+
# Harnesses that consume this value MUST account for the
|
|
215
|
+
# skip-direction inversion when --mock-only support lands.
|
|
216
|
+
#
|
|
217
|
+
# Forward compatibility: runners encountering a requires value they do not
|
|
218
|
+
# recognize MUST emit skip_result.reason: requirement_unmet (not fail-load)
|
|
219
|
+
# and MUST name the unrecognized value in skip_result.detail so dashboards
|
|
220
|
+
# can surface it. This matches runner-output-contract.yaml > requirement_unmet
|
|
221
|
+
# and is consistent with the forward-compat posture throughout the harness —
|
|
222
|
+
# an unrecognized gate is not a coverage gap (not_applicable would signal
|
|
223
|
+
# "agent did not claim the protocol"), it is an unmet requirement. Only
|
|
224
|
+
# requires: [] (empty array) is a hard fail-load — that is a schema
|
|
225
|
+
# constraint, not a semantic gate, and schema violations are always loader
|
|
226
|
+
# errors.
|
|
227
|
+
#
|
|
228
|
+
# Interaction with per-step requires_tool: the two fields are independent.
|
|
229
|
+
# requires gates the whole storyboard at load time; requires_tool gates
|
|
230
|
+
# individual steps at execution time. Both may be declared on the same
|
|
231
|
+
# storyboard.)
|
|
232
|
+
#
|
|
233
|
+
# required_any_of_tools: array (optional — declarative one-of-N tool-advertisement
|
|
234
|
+
# gate at storyboard load time. Each array entry is an OR-family object:
|
|
235
|
+
#
|
|
236
|
+
# required_any_of_tools:
|
|
237
|
+
# - tools: [list_accounts, sync_accounts]
|
|
238
|
+
# rationale: "AdCP 3.0.9 §accounts/overview"
|
|
239
|
+
#
|
|
240
|
+
# Per-entry object shape:
|
|
241
|
+
# tools: string[] — tool names; at least one MUST be advertised by the agent
|
|
242
|
+
# under test. minItems: 2 (a single-tool family collapses
|
|
243
|
+
# to `required_tools`; encode it there instead).
|
|
244
|
+
# rationale: string (optional) — human-readable spec citation surfaced in
|
|
245
|
+
# skip messages so dashboards can attribute the gate to
|
|
246
|
+
# the underlying normative requirement. Storyboard-author-
|
|
247
|
+
# controlled string; runners that render this value into
|
|
248
|
+
# LLM-facing surfaces MUST fence per
|
|
249
|
+
# runner-output-contract.yaml > rendered_output_fencing
|
|
250
|
+
# (the value flows into skip_result.detail, which is
|
|
251
|
+
# already on the fenced list — this is defense-in-depth
|
|
252
|
+
# for renderers that consume rationale directly).
|
|
253
|
+
#
|
|
254
|
+
# Multiple entries are AND-combined: every family in the array MUST be
|
|
255
|
+
# satisfied independently. Empty array is fail-load — omit the field entirely
|
|
256
|
+
# for "no requirement" (same posture as `requires`).
|
|
257
|
+
#
|
|
258
|
+
# Distinct from `required_tools`. `required_tools` is the lenient any-of for
|
|
259
|
+
# storyboard applicability — missing all listed tools triggers a coverage-gap
|
|
260
|
+
# skip. `required_any_of_tools` is the strict variant: missing the family is
|
|
261
|
+
# still a skip (not a fail-load — see "Runner behavior" below), but with
|
|
262
|
+
# distinct `detail` attribution so the skip aggregates against the spec rule
|
|
263
|
+
# rather than the storyboard scope. The two compose cleanly on the same
|
|
264
|
+
# storyboard.
|
|
265
|
+
#
|
|
266
|
+
# Distinct from `provides_state_for`. `provides_state_for` is a per-step
|
|
267
|
+
# substitution mechanism that waives a downstream cascade when a peer step
|
|
268
|
+
# establishes equivalent state at runtime. `required_any_of_tools` is a
|
|
269
|
+
# storyboard-load-time gate on tool *advertisement* — it fires before any
|
|
270
|
+
# step executes and does not interact with step-level substitution. The two
|
|
271
|
+
# operate on different axes (storyboard scope vs step scope; advertisement-
|
|
272
|
+
# time vs run-time) and may coexist on the same storyboard.
|
|
273
|
+
#
|
|
274
|
+
# Runner behavior. When an agent advertises NONE of the tools in any family,
|
|
275
|
+
# the runner MUST emit at storyboard scope:
|
|
276
|
+
#
|
|
277
|
+
# skip_result.reason = requirement_unmet
|
|
278
|
+
# skip_result.detail = "missing_required_tool_family: needs <tool_a> or <tool_b>[ or <tool_c>...] (<rationale>)"
|
|
279
|
+
#
|
|
280
|
+
# The `missing_required_tool_family:` sub-reason prefix in `detail` is the
|
|
281
|
+
# canonical attribution string (see runner-output-contract.yaml >
|
|
282
|
+
# `requirement_unmet` for the full enumeration of recognized detail
|
|
283
|
+
# sub-reasons and the canonical wire shape for separators when more than one
|
|
284
|
+
# gate fails). This matches the same `requirement_unmet`-with-detail pattern
|
|
285
|
+
# already used for `requires:` gates — no new top-level reason enum is added
|
|
286
|
+
# to `runner-output-contract.yaml`, so this field ships without bumping the
|
|
287
|
+
# contract version.
|
|
288
|
+
#
|
|
289
|
+
# Per-family OR semantics. A family is satisfied as soon as the agent
|
|
290
|
+
# advertises ANY tool in `tools[]`. The runner MUST NOT additionally require
|
|
291
|
+
# that the named tools are exercised by storyboard steps — this field gates
|
|
292
|
+
# on tool *advertisement*, not on step execution. Per-step gating remains
|
|
293
|
+
# the job of `requires_tool` on individual steps.
|
|
294
|
+
#
|
|
295
|
+
# Forward compatibility. Runners encountering this field on a storyboard
|
|
296
|
+
# they otherwise support MUST honor it. Runners that pre-date this field
|
|
297
|
+
# will load and execute the storyboard as if the field were absent; this
|
|
298
|
+
# is acceptable because the universal account-discovery rule (the first
|
|
299
|
+
# use case) is also enforced by the SDK's runner-level conformance gate,
|
|
300
|
+
# which fires independently of per-storyboard tagging. Future runners that
|
|
301
|
+
# migrate to per-storyboard attribution (see adcp-client#1642) consume this
|
|
302
|
+
# field for richer skip-cause aggregation.
|
|
303
|
+
#
|
|
304
|
+
# Composition with other gates. `required_any_of_tools` is evaluated at
|
|
305
|
+
# load time alongside `requires`. Either gate failing produces
|
|
306
|
+
# skip_result.reason: requirement_unmet at storyboard scope. Runners SHOULD
|
|
307
|
+
# emit a single sub-reason in `detail` when only one gate is unmet. When
|
|
308
|
+
# multiple gates fail, runners MAY aggregate sub-reasons per the wire shape
|
|
309
|
+
# pinned in runner-output-contract.yaml > `requirement_unmet`; aggregated
|
|
310
|
+
# `detail` strings are intended for human display and SHOULD NOT be
|
|
311
|
+
# parsed beyond the first sub-reason by automated consumers. Dashboards
|
|
312
|
+
# that need exhaustive multi-gate attribution SHOULD surface a multi-gate
|
|
313
|
+
# indicator separately rather than splitting `detail` themselves.)
|
|
314
|
+
#
|
|
315
|
+
# agent:
|
|
316
|
+
# interaction_model: enum (stateless_transform | stateful_preloaded | stateful_push | stateless_generate | media_buy_seller | marketplace_catalog | owned_signals | si_platform | brand_rights_holder | governance_agent)
|
|
317
|
+
# capabilities: string[] (legacy descriptive capability labels; bundle selection is driven by get_adcp_capabilities.supported_protocols and specialisms)
|
|
318
|
+
# examples: string[] (real-world examples: "Celtra", "Innovid")
|
|
319
|
+
#
|
|
320
|
+
# caller:
|
|
321
|
+
# role: string (who initiates the calls: "buyer_agent", "orchestrator", "dsp")
|
|
322
|
+
# example: string (e.g., "Scope3", "Pinnacle Agency")
|
|
323
|
+
#
|
|
324
|
+
# prerequisites:
|
|
325
|
+
# description: string (what must be true before running this storyboard)
|
|
326
|
+
# test_kit: string (reference to a test kit file, e.g., "test-kits/acme-outdoor.yaml").
|
|
327
|
+
# Always a single path — test kits are not composed through this field. See
|
|
328
|
+
# "Test kit flavors" below for the two shapes a test kit may take and how
|
|
329
|
+
# storyboards compose brand identity with harness coordination when they need
|
|
330
|
+
# both.
|
|
331
|
+
# controller_seeding: boolean (optional — when true, the runner auto-injects a
|
|
332
|
+
# fixtures phase that seeds every entry in the storyboard's top-level
|
|
333
|
+
# `fixtures:` block via `comply_test_controller` before the main phases run.
|
|
334
|
+
# Storyboards that hardcode fixture IDs in sample_request payloads SHOULD
|
|
335
|
+
# declare this and populate `fixtures:`. Missing `seed_*` support on the
|
|
336
|
+
# agent grades the storyboard `not_applicable`, not failed.)
|
|
337
|
+
#
|
|
338
|
+
# fixtures: object (optional — declarative prerequisite state the runner seeds via
|
|
339
|
+
# comply_test_controller before executing phases. Structure:
|
|
340
|
+
# fixtures:
|
|
341
|
+
# products:
|
|
342
|
+
# - product_id: "test-product"
|
|
343
|
+
# delivery_type: "non_guaranteed"
|
|
344
|
+
# pricing_options:
|
|
345
|
+
# - pricing_option_id: "test-pricing"
|
|
346
|
+
# pricing_model: "cpm"
|
|
347
|
+
# creatives:
|
|
348
|
+
# - creative_id: "campaign_hero_video"
|
|
349
|
+
# status: "approved"
|
|
350
|
+
# format_id: { id: "video_30s" }
|
|
351
|
+
# plans:
|
|
352
|
+
# - plan_id: "gov_acme_q2_2027"
|
|
353
|
+
# budget: { total: 30000, currency: "USD" }
|
|
354
|
+
# media_buys:
|
|
355
|
+
# - media_buy_id: "mb_acme_q2_2026_auction"
|
|
356
|
+
# status: "active"
|
|
357
|
+
# Each top-level key maps to a seed_* scenario (products → seed_product,
|
|
358
|
+
# pricing_options → seed_pricing_option, creatives → seed_creative,
|
|
359
|
+
# plans → seed_plan, media_buys → seed_media_buy). Storyboards that need an
|
|
360
|
+
# external measurement vendor catalog SHOULD add an explicit
|
|
361
|
+
# comply_test_controller step with scenario: seed_measurement_catalog. Product
|
|
362
|
+
# fixtures may carry `measurement_catalogs[]` entries only as a compatibility
|
|
363
|
+
# fallback when a storyboard needs to keep that snapshot beside the product
|
|
364
|
+
# contract. Foreign-key dependency DAG the runner MUST honor when auto-seeding:
|
|
365
|
+
#
|
|
366
|
+
# product ──┬─→ pricing_option
|
|
367
|
+
# ├─→ plan
|
|
368
|
+
# └─→ media_buy
|
|
369
|
+
# creative ────→ media_buy
|
|
370
|
+
# plan ────────→ media_buy
|
|
371
|
+
#
|
|
372
|
+
# Products seed before pricing_options and plans that reference them;
|
|
373
|
+
# products, creatives, and plans all seed before media_buys that reference
|
|
374
|
+
# them. See docs/building/implementation/comply-test-controller.mdx for the
|
|
375
|
+
# full seeding semantics and per-scenario param shapes.
|
|
376
|
+
#
|
|
377
|
+
# Storyboards SHOULD prefer the `fixtures:` block over hardcoded sample_request
|
|
378
|
+
# IDs whose existence the runner cannot verify.
|
|
379
|
+
#
|
|
380
|
+
# Hardcoded-literal fixtures in sample_request (e.g., product_id: "test-product"
|
|
381
|
+
# without a matching `fixtures:` entry) are permitted through 3.x for existing
|
|
382
|
+
# storyboards that pre-seed matching IDs out of band, and SHOULD migrate to the
|
|
383
|
+
# `fixtures:` block before 4.0. New storyboards authored after this spec edit
|
|
384
|
+
# MUST NOT hardcode fixture IDs without a corresponding `fixtures:` block or a
|
|
385
|
+
# test-kit substitution.)
|
|
386
|
+
#
|
|
387
|
+
# --- Test kit flavors ---
|
|
388
|
+
#
|
|
389
|
+
# Files under `static/compliance/source/test-kits/` come in two shapes. The
|
|
390
|
+
# distinction is not enforced by a separate field today — it's identified by
|
|
391
|
+
# which fixture data the kit carries.
|
|
392
|
+
#
|
|
393
|
+
# Brand kit — carries a brand identity and authentication fixture. Declares:
|
|
394
|
+
# id string (short identifier)
|
|
395
|
+
# auth.api_key string (`demo-<kit>-v1` pattern; the demo Bearer the
|
|
396
|
+
# runner sends on positive api-key probes)
|
|
397
|
+
# OR
|
|
398
|
+
# auth.basic object (the demo HTTP Basic credential the runner sends
|
|
399
|
+
# on positive Basic probes). Shape is either:
|
|
400
|
+
# username: string
|
|
401
|
+
# password: string
|
|
402
|
+
# or:
|
|
403
|
+
# credentials: string
|
|
404
|
+
# where `credentials` is the unencoded `username:password`
|
|
405
|
+
# pair used to build the Authorization header.
|
|
406
|
+
# auth.probe_task string (the protected read the runner calls under
|
|
407
|
+
# `auth: none` and with random-invalid credentials)
|
|
408
|
+
# brand object (house + brand_id + names + logos + colors +
|
|
409
|
+
# fonts + tone — full brand.json payload)
|
|
410
|
+
# + optional products, creatives, pricing_options, destinations, etc.
|
|
411
|
+
#
|
|
412
|
+
# Today's brand kits: acme-outdoor, bistro-oranje, nova-motors, osei-natural,
|
|
413
|
+
# summit-foods. Used by storyboards that exercise brand-specific AdCP protocol
|
|
414
|
+
# flows (creative, media-buy, signals, governance, etc.).
|
|
415
|
+
#
|
|
416
|
+
# Runner contract — carries a harness coordination contract. Declares:
|
|
417
|
+
# id string (short identifier)
|
|
418
|
+
# applies_to object or list (which storyboards/specialisms consume
|
|
419
|
+
# this contract, e.g., `{ universal_storyboard: signed-requests }`
|
|
420
|
+
# or `{ specialism: sales-catalog-driven }`)
|
|
421
|
+
# + contract-specific fields describing how the runner should pre-configure
|
|
422
|
+
# the agent under test (e.g., `receiver_urls:`, `retry_replay_contract:`
|
|
423
|
+
# for webhook-receiver-runner; `endpoint_modes:` for signed-requests)
|
|
424
|
+
#
|
|
425
|
+
# Today's runner contracts: signed-requests-runner, substitution-observer-
|
|
426
|
+
# runner, webhook-receiver-runner. They carry no credentials today — the
|
|
427
|
+
# runner injects no auth for these storyboards because the storyboards test
|
|
428
|
+
# harness infrastructure (signature verification, webhook receiver
|
|
429
|
+
# coordination, URL substitution observability), not brand-specific AdCP
|
|
430
|
+
# flows. A future runner contract could legitimately carry its own test-
|
|
431
|
+
# coordination principal (e.g., a callback-authenticating receiver), so the
|
|
432
|
+
# "no credentials" property is temporal, not structural.
|
|
433
|
+
#
|
|
434
|
+
# Enforcement. `scripts/lint-storyboard-test-kits.cjs` fails the build if a
|
|
435
|
+
# kit under `test-kits/` declares none of `auth.api_key`, `auth.basic`, or
|
|
436
|
+
# `applies_to` — that's the bimodal partition this section describes. The lint
|
|
437
|
+
# tolerates kits that declare both a credential marker and `applies_to` (the
|
|
438
|
+
# future-branded-runner case above).
|
|
439
|
+
#
|
|
440
|
+
# Composition. Storyboards that need BOTH a brand identity AND a harness
|
|
441
|
+
# contract declare the brand kit in `prerequisites.test_kit` and opt into the
|
|
442
|
+
# runner contract through the `requires_contract: <runner_id>` field on
|
|
443
|
+
# specific assertion tasks that need it — today that's `expect_substitution_safe`
|
|
444
|
+
# and `expect_webhook*` (see their task definitions below for the per-task
|
|
445
|
+
# field shape). When the named contract is not in scope for the runner, the
|
|
446
|
+
# task grades as `not_applicable` rather than failing. Example:
|
|
447
|
+
# `specialisms/sales-catalog-driven/index.yaml` declares
|
|
448
|
+
# `prerequisites.test_kit: "test-kits/acme-outdoor.yaml"` and its
|
|
449
|
+
# `expect_substitution_safe` step carries `requires_contract:
|
|
450
|
+
# substitution_observer_runner`.
|
|
451
|
+
#
|
|
452
|
+
# Storyboards that test pure harness infrastructure without a specific brand
|
|
453
|
+
# (e.g., `universal/signed-requests.yaml`,
|
|
454
|
+
# `universal/webhook-emission.yaml`) point `prerequisites.test_kit` directly
|
|
455
|
+
# at the runner contract and fall back on `task_default:` when any
|
|
456
|
+
# `$test_kit.operations.<name>` reference resolves to null (see Step field
|
|
457
|
+
# docs).
|
|
458
|
+
#
|
|
459
|
+
# `test_kit=<path>` in the contradiction-lint fingerprint disambiguates the
|
|
460
|
+
# two flavors — `auth=kit_default` resolves to the kit's static credential for
|
|
461
|
+
# brand kits and to no-credential for runner contracts, but the two never
|
|
462
|
+
# collide because the `test_kit=` component separates them.
|
|
463
|
+
#
|
|
464
|
+
# Future: if a storyboard ever needs to compose TWO runner contracts
|
|
465
|
+
# simultaneously (e.g., signed-requests + substitution-observer), the step-
|
|
466
|
+
# level `requires_contract:` field will need to accept a list. Not reachable
|
|
467
|
+
# today.
|
|
468
|
+
#
|
|
469
|
+
# See `docs/contributing/storyboard-authoring.md` for the author-workflow
|
|
470
|
+
# side of kit selection.
|
|
471
|
+
#
|
|
472
|
+
# context: object (optional — initial values seeded into the runner's context
|
|
473
|
+
# accumulator before any step executes. Keys become $context.<key> references
|
|
474
|
+
# available throughout the storyboard run. Use this field to provide
|
|
475
|
+
# per-storyboard defaults for values that vary by operator topology (e.g.,
|
|
476
|
+
# a governance agent URL the runner can override at invocation time once the
|
|
477
|
+
# CLI supports --context on `storyboard run`).
|
|
478
|
+
#
|
|
479
|
+
# Example:
|
|
480
|
+
# context:
|
|
481
|
+
# governance_agent_url: "https://test-agent.adcontextprotocol.org"
|
|
482
|
+
#
|
|
483
|
+
# Precedence in the context accumulator (highest to lowest):
|
|
484
|
+
# 1. Step `context_outputs:` captures (populated after a step passes)
|
|
485
|
+
# 2. THIS field (storyboard-root `context:` block, fixed at run start)
|
|
486
|
+
# 3. Test-kit substitutions where explicitly scoped into context
|
|
487
|
+
#
|
|
488
|
+
# Values MUST be scalars (strings, numbers, booleans — JSON scalar types).
|
|
489
|
+
# Object and array values are not supported here — capture structured values
|
|
490
|
+
# from step responses via `context_outputs:` instead.
|
|
491
|
+
#
|
|
492
|
+
# See "Context accumulator and substitution" below for the full precedence
|
|
493
|
+
# contract, runner substitution rules, and unresolved-substitution grading.)
|
|
494
|
+
#
|
|
495
|
+
# phases: array of Phase objects
|
|
496
|
+
#
|
|
497
|
+
# --- Phase ---
|
|
498
|
+
#
|
|
499
|
+
# id: string (unique within storyboard)
|
|
500
|
+
# title: string (human-readable phase title)
|
|
501
|
+
# narrative: string (paragraph explaining this phase from the caller's perspective)
|
|
502
|
+
#
|
|
503
|
+
# depends_on: string[] (optional — overrides the cross-phase cascade default)
|
|
504
|
+
# Default semantics (field absent or undefined): "all prior phases." Every
|
|
505
|
+
# phase implicitly depends on every prior phase, so when ANY prior phase
|
|
506
|
+
# trips its stateful cascade (a stateful step fails or skips for a
|
|
507
|
+
# missing-state reason — see `runner-output-contract.yaml > cascade_rules.
|
|
508
|
+
# default_cascade`), every stateful step in this phase cascade-skips with
|
|
509
|
+
# `prerequisite_failed`. This default preserves the F6 round-2 pattern
|
|
510
|
+
# where setup phases establish state consumed by a later phase.
|
|
511
|
+
#
|
|
512
|
+
# Two override forms:
|
|
513
|
+
#
|
|
514
|
+
# - Independent phase: `depends_on: []` — explicitly declares the phase
|
|
515
|
+
# has no upstream dependencies and runs even if every prior phase
|
|
516
|
+
# tripped. Use for phases whose state derives entirely from in-phase
|
|
517
|
+
# steps (e.g., the phase creates its own media buy / session / account
|
|
518
|
+
# via a `comply_test_controller`-gated step) and that consume no
|
|
519
|
+
# `$context.*` value produced by an earlier phase. The
|
|
520
|
+
# `sole_stateful_step_exemption` in `runner-output-contract.yaml`
|
|
521
|
+
# addresses a related-but-narrower case at the step level; `depends_on:
|
|
522
|
+
# []` is the phase-level escape hatch and applies regardless of how
|
|
523
|
+
# many stateful steps the phase contains.
|
|
524
|
+
#
|
|
525
|
+
# - Targeted dependency: `depends_on: ['phase_id', ...]` — only the
|
|
526
|
+
# named phases gate this phase's cascade. Other phases tripping is
|
|
527
|
+
# irrelevant. Listed phase IDs MUST exist in the same storyboard and
|
|
528
|
+
# MUST be declared earlier in the `phases[]` array.
|
|
529
|
+
#
|
|
530
|
+
# Validation rules (enforced at storyboard load; fail at parse time):
|
|
531
|
+
# - Value MUST be an array of non-empty strings (or absent).
|
|
532
|
+
# - Self-references are rejected.
|
|
533
|
+
# - Forward references and unknown phase IDs are rejected.
|
|
534
|
+
# - Empty list is legal and means "no upstream dependencies."
|
|
535
|
+
#
|
|
536
|
+
# Authoring guidance: prefer the default. Use `[]` only when the phase
|
|
537
|
+
# verifiably builds its own state in-phase — read the phase's
|
|
538
|
+
# `context_inputs` and `$context.*` references to confirm no cross-phase
|
|
539
|
+
# coupling exists. Use the targeted form when a phase depends on a
|
|
540
|
+
# specific subset of prior phases (e.g., consumption phase depends only
|
|
541
|
+
# on setup phases, not on sibling consumption phases that may legitimately
|
|
542
|
+
# skip on different adopters).
|
|
543
|
+
#
|
|
544
|
+
# References: adcp-client#1161 (field introduction); adcp-client#1711
|
|
545
|
+
# follow-up + adcp#4750 (deterministic_testing scope tightening that
|
|
546
|
+
# surfaced the documentation gap).
|
|
547
|
+
#
|
|
548
|
+
# steps: array of Step objects
|
|
549
|
+
#
|
|
550
|
+
# --- Step ---
|
|
551
|
+
#
|
|
552
|
+
# id: string (unique within phase)
|
|
553
|
+
# title: string (human-readable step title)
|
|
554
|
+
# narrative: string (what's happening and why)
|
|
555
|
+
# task: string (AdCP task name: list_creative_formats, preview_creative, build_creative, get_products,
|
|
556
|
+
# create_media_buy, sync_accounts, etc. May reference a test-kit field, e.g.
|
|
557
|
+
# "$test_kit.auth.probe_task", in which case `task_default` provides the fallback.)
|
|
558
|
+
# task_default: string (optional — default task name when `task` is a test-kit reference that resolves to null)
|
|
559
|
+
# schema_ref: string (path to request schema, e.g., "creative/list-creative-formats-request.json" or "media-buy/get-products-request.json")
|
|
560
|
+
# response_schema_ref: string (path to response schema)
|
|
561
|
+
# doc_ref: string (path to documentation page)
|
|
562
|
+
# comply_scenario: string (maps to @adcp/client testing scenario, e.g., "creative_sync")
|
|
563
|
+
# expected: string (human-readable description of expected behavior)
|
|
564
|
+
# stateful: boolean (does this step depend on state from a previous step?)
|
|
565
|
+
#
|
|
566
|
+
# sample_request: object (optional — example request payload for this step)
|
|
567
|
+
# Placeholders: `$context.<name>` and `{{prior_step.<id>.<field>}}`. See
|
|
568
|
+
# the "Substitution" section below for the full semantics. For the narrow
|
|
569
|
+
# case where the SDK's per-task request builder discards a body field
|
|
570
|
+
# from `sample_request`, use `context_inputs:` (above) as the
|
|
571
|
+
# post-builder injection path.
|
|
572
|
+
# sample_response: object (optional — example expected response; also surfaces in published docs)
|
|
573
|
+
#
|
|
574
|
+
# auth: "none" | object (optional — overrides the transport's default credentials for this step)
|
|
575
|
+
# auth: none → strip any configured credentials before sending
|
|
576
|
+
# auth: { type: api_key, from_test_kit: true } → use the API key at `auth.api_key` in the test kit
|
|
577
|
+
# auth: { type: api_key, from_test_kit: "auth.principals.<name>.api_key" }
|
|
578
|
+
# → select a named principal within a multi-principal kit
|
|
579
|
+
# (forward-compatible shape; no kit exposes multiple
|
|
580
|
+
# principals today)
|
|
581
|
+
# auth: { type: api_key, value_strategy: random_invalid }
|
|
582
|
+
# → use a per-run random bogus API key (for invalid-key probes).
|
|
583
|
+
# Runner generates `invalid-<32 random bytes>` per run.
|
|
584
|
+
# auth: { type: basic, from_test_kit: "auth.basic" } → use the Basic credential at `auth.basic` in the test kit
|
|
585
|
+
# auth: { type: basic, value_strategy: random_invalid }
|
|
586
|
+
# → use a per-run random bogus Basic credential (for invalid-Basic probes).
|
|
587
|
+
# Runner generates random username/password bytes per run.
|
|
588
|
+
# auth: { type: oauth_bearer, value_strategy: random_invalid_jwt }
|
|
589
|
+
# → use a per-run random JWT-shaped bogus Bearer token.
|
|
590
|
+
# Runner generates `<base64url random>.<base64url random>.<base64url random>` per run.
|
|
591
|
+
#
|
|
592
|
+
# DISALLOWED: `auth: { type: ..., value: "<literal>" }` — literal credentials in
|
|
593
|
+
# storyboard YAML are a code smell (they bind the storyboard to a specific value,
|
|
594
|
+
# can't rotate without rewriting, and leak plaintext identity into source).
|
|
595
|
+
# `lint-storyboard-auth-shape.cjs` (rule `literal_value`) fails the build on this
|
|
596
|
+
# shape. Use `from_test_kit` or `value_strategy` instead. See #2720.
|
|
597
|
+
#
|
|
598
|
+
# omit_idempotency_key: boolean (optional — when true on a task step, the runner
|
|
599
|
+
# skips `applyIdempotencyInvariant` and signals the SDK client via
|
|
600
|
+
# `skipIdempotencyAutoInject` so the request reaches the agent WITHOUT an
|
|
601
|
+
# `idempotency_key`. Use for missing-key rejection vectors — without this flag
|
|
602
|
+
# the SDK may auto-inject a key and the agent never sees the missing-key path.
|
|
603
|
+
# 3.0 used this only for mutating tasks. 3.1 extends `idempotency_key` to every
|
|
604
|
+
# request, so read-tool omission probes SHOULD also set this flag to prevent
|
|
605
|
+
# current or future runner/client auto-injection from masking the vector.)
|
|
606
|
+
#
|
|
607
|
+
# contributes_to: string (optional — marks this step as contributing a named flag
|
|
608
|
+
# that a later assert_contribution step can require)
|
|
609
|
+
# contributes: boolean (optional — shorthand for `contributes_to: <enclosing phase's branch_set.id>`
|
|
610
|
+
# introduced alongside first-class `branch_set:` declarations.
|
|
611
|
+
# Legal only inside a phase that declares `branch_set:`; the runner's
|
|
612
|
+
# loader resolves `contributes: true` to the phase's branch_set.id
|
|
613
|
+
# and raises a storyboard-load error on `contributes: true` outside
|
|
614
|
+
# a branch_set phase or on a step that also declares `contributes_to`.
|
|
615
|
+
# See adcp-client#693 and `lint:storyboard-branch-sets` for the
|
|
616
|
+
# full rule set.)
|
|
617
|
+
# contributes_if: string (optional — runner-evaluated expression gating the contribution)
|
|
618
|
+
# Supported grammar (single form; not a general-purpose DSL):
|
|
619
|
+
# "prior_step.<step_id>.passed" → true if the named prior step in the same storyboard passed
|
|
620
|
+
# Storyboard authors should prefer expressing gating through step ordering when possible.
|
|
621
|
+
#
|
|
622
|
+
# provides_state_for: string | string[] (optional — declares that this step's pass
|
|
623
|
+
# establishes equivalent state for the named peer step(s) in the same phase. Used
|
|
624
|
+
# to rescue the cascade-skip default when one of two interchangeable stateful
|
|
625
|
+
# steps is missing on the agent: e.g., explicit-mode sellers that pre-provision
|
|
626
|
+
# accounts out-of-band declare `sync_accounts` as missing_tool but expose
|
|
627
|
+
# `list_accounts` as the canonical alternative — declaring
|
|
628
|
+
# `provides_state_for: sync_accounts` on `list_accounts` tells the runner that
|
|
629
|
+
# the substitute's pass satisfies the same downstream state contract.
|
|
630
|
+
#
|
|
631
|
+
# Grammar parallels `contributes_to`: declared on the substitute step, names
|
|
632
|
+
# the target peer(s) whose state it provides. The two fields are independent —
|
|
633
|
+
# `contributes_to` flows branch-set flags into a later `assert_contribution`;
|
|
634
|
+
# `provides_state_for` waives a peer's missing-tool / missing-test-controller
|
|
635
|
+
# cascade so downstream stateful steps still run.
|
|
636
|
+
#
|
|
637
|
+
# Array semantics: `provides_state_for: [A, B]` is ALL-OF — one substitute pass
|
|
638
|
+
# establishes state for both A and B simultaneously. ANY-OF semantics are not
|
|
639
|
+
# supported; if a substitute only conditionally satisfies a peer, model it as
|
|
640
|
+
# two separate substitute steps.
|
|
641
|
+
#
|
|
642
|
+
# Validation rules (enforced by lint:storyboard-provides-state-for, fail at
|
|
643
|
+
# parse time):
|
|
644
|
+
# - Same-phase only. The target step MUST live in the same phase as the
|
|
645
|
+
# substitute step. Cross-phase substitution is rejected; a cross-phase
|
|
646
|
+
# state contract belongs in `context_outputs` / `context_inputs`, not
|
|
647
|
+
# this field.
|
|
648
|
+
# - Target step must exist in the same phase by `id`.
|
|
649
|
+
# - Target step MUST declare `stateful: true`. Stateless peers don't
|
|
650
|
+
# carry a state contract to substitute for.
|
|
651
|
+
# - Substitute step MUST declare `stateful: true`. A stateless step
|
|
652
|
+
# cannot establish equivalent state on the agent side.
|
|
653
|
+
# - Self-references (`provides_state_for: <own_id>`) are rejected.
|
|
654
|
+
# - Cycles (A→B and B→A in the same phase) are rejected. The peer-graph
|
|
655
|
+
# per phase MUST be acyclic.
|
|
656
|
+
#
|
|
657
|
+
# Runner behavior (see runner-output-contract.yaml > skip_result.reasons.peer_substituted):
|
|
658
|
+
# - When the substitute step passes AND the target peer would otherwise
|
|
659
|
+
# grade `missing_tool` / `missing_test_controller`, the runner MUST
|
|
660
|
+
# grade the target peer with skip reason `peer_substituted` (not
|
|
661
|
+
# `missing_tool`) and MUST NOT cascade `prerequisite_failed` to
|
|
662
|
+
# downstream stateful steps in subsequent phases.
|
|
663
|
+
# - When the substitute step itself fails or is skipped, the cascade
|
|
664
|
+
# proceeds as if `provides_state_for` were absent — the substitute
|
|
665
|
+
# did not establish the state it claimed.
|
|
666
|
+
#
|
|
667
|
+
# See adcontextprotocol/adcp#3734 (mechanism rationale) and
|
|
668
|
+
# adcp-client#1130 (the cascade-skip default this field rescues).
|
|
669
|
+
#
|
|
670
|
+
# context_outputs: array of ContextOutput objects (optional — capture values from
|
|
671
|
+
# this step's response into the runner's context accumulator so later steps can
|
|
672
|
+
# reference them via $context.<name>). Each entry:
|
|
673
|
+
# - name: string (the context variable to populate; MUST be unique within the
|
|
674
|
+
# storyboard run)
|
|
675
|
+
# path: string (JSON path against this step's response body, e.g.
|
|
676
|
+
# "media_buy_id", "accounts[0].account_id", "plans[0].plan_id")
|
|
677
|
+
# generate: string (alternative to `path`: mint a value at run start
|
|
678
|
+
# rather than capture from a response. Supported generator names:
|
|
679
|
+
# - uuid_v4 — RFC 4122 v4 UUID
|
|
680
|
+
# - opaque_id — RFC 4122 v4 UUID (alias; the two names exist for
|
|
681
|
+
# spec-vs-implementation framing — use uuid_v4 when
|
|
682
|
+
# the threaded value will appear as a UUID on the
|
|
683
|
+
# wire, opaque_id when callers treat it as an
|
|
684
|
+
# opaque correlation token).
|
|
685
|
+
# Exactly one of `path` or `generate` MUST be set on each entry.
|
|
686
|
+
# Setting both is a storyboard-load failure. Generated values are
|
|
687
|
+
# materialized once per storyboard run and stored under
|
|
688
|
+
# `context.<name>` so later steps' `$context.<name>` substitutions
|
|
689
|
+
# resolve to the same value. Use for deterministic correlation
|
|
690
|
+
# IDs (e.g. threading task_id through force_create_media_buy_arm
|
|
691
|
+
# register → create_media_buy → force_task_completion → tasks/get
|
|
692
|
+
# poll) without authors copy-pasting magic strings. Requires
|
|
693
|
+
# runner >= adcp-client v6.x with `generate` support (see
|
|
694
|
+
# adcp-client#1006); older runners reject the field at load.
|
|
695
|
+
#
|
|
696
|
+
# Example — deterministic task_id threading:
|
|
697
|
+
# context_outputs:
|
|
698
|
+
# - name: forced_task_id
|
|
699
|
+
# generate: uuid_v4
|
|
700
|
+
# # Subsequent steps reference $context.forced_task_id
|
|
701
|
+
# # in sample_request / context_inputs.
|
|
702
|
+
#
|
|
703
|
+
# Special path prefix `task_completion.<inner>`: when the immediate response
|
|
704
|
+
# is a non-terminal task envelope (status `submitted` / `working` /
|
|
705
|
+
# `input-required`, carrying a `task_id`), the runner polls `tasks/get`
|
|
706
|
+
# until the task reaches a terminal state and resolves `<inner>` against
|
|
707
|
+
# the completion artifact's `data` instead of the immediate response. Use
|
|
708
|
+
# for captures whose value only exists on the completion artifact — e.g.
|
|
709
|
+
# the seller-assigned `media_buy_id` on an IO-signing / async-signed HITL
|
|
710
|
+
# flow where `create_media_buy` returns `submitted` and the ID lands on
|
|
711
|
+
# the completion artifact. Without the prefix, the literal key
|
|
712
|
+
# `task_completion` is looked up on the immediate response, which fails
|
|
713
|
+
# as `capture_path_not_resolvable`. Requires runner >= adcp-client v6.7;
|
|
714
|
+
# older runners treat the prefix as a literal key. See adcp-client#1417
|
|
715
|
+
# (rationale) and adcp-client#1426 (implementation).
|
|
716
|
+
#
|
|
717
|
+
# Runner behavior:
|
|
718
|
+
# - Captures occur AFTER the step's validations pass. A failed step MUST NOT
|
|
719
|
+
# populate the context accumulator — downstream $context.<name> references
|
|
720
|
+
# resolve to an unresolved_substitution error, which grades the dependent
|
|
721
|
+
# step as failed rather than the storyboard passing on fabricated state.
|
|
722
|
+
# - Paths that do not resolve to a usable value in the response body (absent,
|
|
723
|
+
# null, or "") are a runner-side grading failure on THIS step
|
|
724
|
+
# (capture_path_not_resolvable), not on the downstream reader — the capture
|
|
725
|
+
# declared a contract that the response did not meet. The capturing step
|
|
726
|
+
# grades as failed; downstream steps that depended on the captured value
|
|
727
|
+
# grade as skipped with reason: prerequisite_failed citing this step.
|
|
728
|
+
# See runner-output-contract.yaml > validation_result for the output shape.
|
|
729
|
+
# - The accumulator is storyboard-run-scoped; values do not leak across runs.
|
|
730
|
+
# - Special path prefix `task_completion.<inner>`: when the immediate
|
|
731
|
+
# response is a non-terminal task envelope (status submitted/working,
|
|
732
|
+
# with a task_id), the runner polls `tasks/get` until the task exits
|
|
733
|
+
# {submitted, working}. On `completed`: resolves <inner> against the
|
|
734
|
+
# `result` payload; a missing path grades this step as
|
|
735
|
+
# capture_path_not_resolvable. On any other status (failed, canceled,
|
|
736
|
+
# rejected, auth-required, input-required, unknown): `result` is absent;
|
|
737
|
+
# the capture grades as capture_path_not_resolvable on this step;
|
|
738
|
+
# downstream $context.<name> references grade as prerequisite_failed
|
|
739
|
+
# citing this step. See adcp-client#1426 for the implementation.
|
|
740
|
+
#
|
|
741
|
+
# context_inputs: array of ContextInput objects (optional — inject captured
|
|
742
|
+
# context values into the request at a specific path AFTER the per-task
|
|
743
|
+
# request builder runs).
|
|
744
|
+
#
|
|
745
|
+
# Shape:
|
|
746
|
+
# context_inputs:
|
|
747
|
+
# - key: product_format_id # name populated by a prior context_outputs capture
|
|
748
|
+
# inject_at: "format_ids[0]" # JSON path inside the request body
|
|
749
|
+
#
|
|
750
|
+
# Do NOT use `context_inputs` unless `$context.<name>` inside
|
|
751
|
+
# `sample_request:` has been tried and verified not to reach the wire.
|
|
752
|
+
# The default substitution path is `$context.<name>` placeholders inside
|
|
753
|
+
# `sample_request:` — keep the request body honest about what the agent
|
|
754
|
+
# will receive. Reasons the default is preferred:
|
|
755
|
+
# - The published sample_response/sample_request pair is what seller
|
|
756
|
+
# engineers read to implement against the storyboard; anything the
|
|
757
|
+
# runner injects out-of-band is invisible there.
|
|
758
|
+
# - `$context.<name>` produces a pointed `unresolved_substitution` failure
|
|
759
|
+
# when the referenced capture is missing. `context_inputs` is
|
|
760
|
+
# silently a no-op on a missing `key` (runner:
|
|
761
|
+
# `if (input.key in context) setPath(...)`) — a typo in `key` or a
|
|
762
|
+
# failed capture produces an empty field rather than a loud error.
|
|
763
|
+
#
|
|
764
|
+
# Legitimate uses:
|
|
765
|
+
# - The @adcp/client per-task request builder for a given tool discards
|
|
766
|
+
# body fields from `sample_request` (the runner's post-builder merge
|
|
767
|
+
# only forwards envelope fields: `context`, `ext`, `idempotency_key`,
|
|
768
|
+
# `push_notification_config`). The observable symptom is that the
|
|
769
|
+
# agent answers with an unfiltered/default result and a round-trip
|
|
770
|
+
# or substitution-observer validation grades on fabricated state.
|
|
771
|
+
# `context_inputs` runs AFTER the builder, so it round-trips the
|
|
772
|
+
# captured value to the wire regardless.
|
|
773
|
+
# - Dependency-aware multi-instance dispatch, where the runner needs to
|
|
774
|
+
# route a step to a specific replica URL derived from a prior step's
|
|
775
|
+
# response (not applicable to single-instance storyboards).
|
|
776
|
+
#
|
|
777
|
+
# Value types are preserved. The captured value is written to `inject_at`
|
|
778
|
+
# as-is — object and numeric captures are NOT stringified, matching the
|
|
779
|
+
# `$context.<name>` behavior described below.
|
|
780
|
+
#
|
|
781
|
+
# Document every SDK-gap-workaround entry inline. The comment MUST name
|
|
782
|
+
# the SDK version that carries the gap and the upstream PR that closes
|
|
783
|
+
# it, so the entry can be removed when we bump past that release. See
|
|
784
|
+
# `static/compliance/source/protocols/media-buy/index.yaml`
|
|
785
|
+
# (`list_formats_integrity` step) for a worked example tied to
|
|
786
|
+
# adcontextprotocol/adcp-client#789.
|
|
787
|
+
#
|
|
788
|
+
# validations: array of Validation objects (optional)
|
|
789
|
+
#
|
|
790
|
+
# expect_error: boolean (optional, default false — when true, the runner expects
|
|
791
|
+
# the agent to return an error response for this step. Paired with `negative_path`
|
|
792
|
+
# to control whether the step's `sample_request` is schema-validated.)
|
|
793
|
+
#
|
|
794
|
+
# negative_path: "schema_invalid" | "payload_well_formed" (optional, default "schema_invalid")
|
|
795
|
+
# Disambiguates two categories of negative-path steps when `expect_error: true`:
|
|
796
|
+
# - schema_invalid (default): the `sample_request` payload is intentionally malformed
|
|
797
|
+
# (missing required field, wrong type, bad enum value, etc.) to verify the agent's
|
|
798
|
+
# validation response. Schema validation is SKIPPED on this step.
|
|
799
|
+
# - payload_well_formed: the `sample_request` payload is schema-valid but the agent
|
|
800
|
+
# rejects it at runtime — wrong state-machine transition, resource not found,
|
|
801
|
+
# governance denial, auth failure, temporal constraint, etc. Schema validation
|
|
802
|
+
# RUNS even though the agent is expected to return an error. Shape drift on these
|
|
803
|
+
# steps is caught statically.
|
|
804
|
+
# Omitting `negative_path` when `expect_error: true` behaves as `schema_invalid`
|
|
805
|
+
# (backwards-compatible default).
|
|
806
|
+
#
|
|
807
|
+
# sample_request_skip_schema: boolean (optional — explicit opt-out of schema
|
|
808
|
+
# validation for a step regardless of `expect_error` / `negative_path`. Use
|
|
809
|
+
# sparingly; prefer `negative_path: schema_invalid` for intentionally malformed
|
|
810
|
+
# negative-path fixtures.)
|
|
811
|
+
#
|
|
812
|
+
# expected_arm: string (optional — names the discriminator value of the `oneOf`
|
|
813
|
+
# arm this step expects the agent to return. When present, both the
|
|
814
|
+
# context-output-paths and validations-paths lints restrict path resolution
|
|
815
|
+
# to the matching arm only. Default behavior (no annotation) accepts a path
|
|
816
|
+
# that resolves through ANY oneOf arm — the right call for "no prior info,"
|
|
817
|
+
# but it lets a step capture or assert on a path that's defined on, say, the
|
|
818
|
+
# Acquired arm even when the storyboard semantically expects the Rejected
|
|
819
|
+
# arm. The annotation tightens that.
|
|
820
|
+
#
|
|
821
|
+
# Match shape: the lint walks each `oneOf` / `anyOf` branch and looks for
|
|
822
|
+
# ANY property with `const: "<expected_arm>"`. The first matching branch
|
|
823
|
+
# is the expected arm; subsequent path resolution restricts to it.
|
|
824
|
+
#
|
|
825
|
+
# Examples:
|
|
826
|
+
# expected_arm: "acquired" # matches AcquireRightsAcquired (status.const = "acquired")
|
|
827
|
+
# expected_arm: "rejected" # matches AcquireRightsRejected
|
|
828
|
+
# expected_arm: "pending_approval" # matches AcquireRightsPendingApproval
|
|
829
|
+
#
|
|
830
|
+
# When `expect_error: true` is set without `expected_arm`, the lint also
|
|
831
|
+
# tries to find an Error arm (one whose `required` list includes `errors`
|
|
832
|
+
# and which has no `status` const) and restricts to it. This covers
|
|
833
|
+
# `acquire_rights` / `creative_approval` / similar response shapes whose
|
|
834
|
+
# Error arm isn't const-discriminated.
|
|
835
|
+
#
|
|
836
|
+
# When the named arm doesn't match any branch, the lint emits an
|
|
837
|
+
# `unknown_expected_arm` violation — an authoring bug to fix at storyboard
|
|
838
|
+
# author time, not at runtime.
|
|
839
|
+
#
|
|
840
|
+
# --- Phase (optional fields for conditional execution) ---
|
|
841
|
+
#
|
|
842
|
+
# optional: boolean (default false — see semantics below)
|
|
843
|
+
# skip_if: string (expression — phase is skipped when the expression evaluates to true,
|
|
844
|
+
# e.g., "!test_kit.auth.api_key")
|
|
845
|
+
# branch_set: object (optional — first-class declaration of branch-set membership;
|
|
846
|
+
# see "Branch sets" section below)
|
|
847
|
+
#
|
|
848
|
+
# Optional phase semantics:
|
|
849
|
+
# - An `optional: true` phase always runs unless `skip_if` evaluates to true.
|
|
850
|
+
# `skip_if` (e.g., "!test_kit.auth.api_key") means "skip this phase when the
|
|
851
|
+
# expression is true"; without `skip_if`, the phase runs every time.
|
|
852
|
+
# - Failures inside an optional phase do NOT fail the overall storyboard on
|
|
853
|
+
# their own — the phase is for accumulating contributions toward a later
|
|
854
|
+
# `assert_contribution` check. The final assert is what fails the storyboard
|
|
855
|
+
# when no optional path succeeded.
|
|
856
|
+
# - A non-optional phase that fails fails the storyboard unconditionally.
|
|
857
|
+
#
|
|
858
|
+
# Branch sets:
|
|
859
|
+
# A branch set is a group of peer optional phases exercising mutually
|
|
860
|
+
# exclusive agent behaviors for the same trigger — e.g., an agent that
|
|
861
|
+
# rejects a past start_time vs. an agent that accepts-and-adjusts. A
|
|
862
|
+
# later `assert_contribution` step over the set's `branch_set.id` fails
|
|
863
|
+
# the storyboard only when NO branch contributed.
|
|
864
|
+
#
|
|
865
|
+
# Minimal example — two peer phases and the assertion that grades them:
|
|
866
|
+
#
|
|
867
|
+
# phases:
|
|
868
|
+
# - id: past_start_reject_path
|
|
869
|
+
# optional: true
|
|
870
|
+
# branch_set:
|
|
871
|
+
# id: past_start_handled
|
|
872
|
+
# semantics: any_of
|
|
873
|
+
# steps:
|
|
874
|
+
# - id: create_buy_past_start_reject
|
|
875
|
+
# task: create_media_buy
|
|
876
|
+
# contributes_to: past_start_handled
|
|
877
|
+
# # ... expect INVALID_REQUEST ...
|
|
878
|
+
#
|
|
879
|
+
# - id: past_start_adjust_path
|
|
880
|
+
# optional: true
|
|
881
|
+
# branch_set:
|
|
882
|
+
# id: past_start_handled
|
|
883
|
+
# semantics: any_of
|
|
884
|
+
# steps:
|
|
885
|
+
# - id: create_buy_past_start_adjust
|
|
886
|
+
# task: create_media_buy
|
|
887
|
+
# contributes_to: past_start_handled
|
|
888
|
+
# # ... expect media_buy_id ...
|
|
889
|
+
#
|
|
890
|
+
# - id: past_start_enforcement
|
|
891
|
+
# steps:
|
|
892
|
+
# - task: assert_contribution
|
|
893
|
+
# validations:
|
|
894
|
+
# - check: any_of
|
|
895
|
+
# allowed_values: [past_start_handled]
|
|
896
|
+
#
|
|
897
|
+
# `@adcp/client` 5.8.0+ also accepts the boolean shorthand
|
|
898
|
+
# `contributes: true` on a step inside a branch_set phase — the runner's
|
|
899
|
+
# loader resolves it to the enclosing phase's `branch_set.id`. The two
|
|
900
|
+
# forms are semantically identical; `contributes: true` is preferred
|
|
901
|
+
# inside a branch_set phase because it eliminates the typo surface (the
|
|
902
|
+
# string form must equal `branch_set.id` exactly). Authors may use
|
|
903
|
+
# either; the lint enforces both.
|
|
904
|
+
#
|
|
905
|
+
# New storyboards MUST declare branch-set membership with a `branch_set:`
|
|
906
|
+
# object. The implicit-detection fallback below exists only so pre-#2633
|
|
907
|
+
# storyboards keep running unchanged.
|
|
908
|
+
#
|
|
909
|
+
# `branch_set:` field shape:
|
|
910
|
+
# - id: non-empty string. MUST equal the `contributes_to` value
|
|
911
|
+
# on every contributing step inside the phase and MUST
|
|
912
|
+
# appear in the `assert_contribution` step's any_of
|
|
913
|
+
# allowed_values.
|
|
914
|
+
# - semantics: `any_of`. No other value is supported today.
|
|
915
|
+
#
|
|
916
|
+
# Per-step contribution signalling is REQUIRED on each contributing step —
|
|
917
|
+
# the phase-level `branch_set:` declares membership but does not replace
|
|
918
|
+
# the step-level flag. Runners accumulate contributions at the step level.
|
|
919
|
+
# Two equivalent forms:
|
|
920
|
+
# - `contributes: true` (preferred inside a branch_set phase; resolves
|
|
921
|
+
# to the enclosing phase's `branch_set.id`)
|
|
922
|
+
# - `contributes_to: <branch_set.id>` (string form; required outside a
|
|
923
|
+
# branch_set phase and must equal `branch_set.id` inside one)
|
|
924
|
+
#
|
|
925
|
+
# Authoring rules (enforced by `lint:storyboard-branch-sets`):
|
|
926
|
+
# - A phase with `branch_set:` MUST set `optional: true`. A
|
|
927
|
+
# non-optional phase's failure would fail the storyboard
|
|
928
|
+
# unconditionally and defeat the any_of semantics.
|
|
929
|
+
# - All phases in a storyboard sharing the same `branch_set.id` MUST
|
|
930
|
+
# share the same `branch_set.semantics`.
|
|
931
|
+
# - A storyboard with a `branch_set:` declaration MUST contain an
|
|
932
|
+
# `assert_contribution` step whose `validations[].check: any_of`
|
|
933
|
+
# includes the `branch_set.id` in `allowed_values`. Any
|
|
934
|
+
# `contributes_to:` / `contributes: true` whose resolved flag has no
|
|
935
|
+
# matching assert_contribution is flagged as `orphan_contribution` —
|
|
936
|
+
# nothing grades it.
|
|
937
|
+
# - Any step inside a branch-set phase that declares `contributes_to`
|
|
938
|
+
# MUST use the value `<branch_set.id>`.
|
|
939
|
+
# - A step MUST NOT declare both `contributes` and `contributes_to`
|
|
940
|
+
# (ambiguous). `contributes: true` is legal only inside a phase
|
|
941
|
+
# that declares `branch_set:`.
|
|
942
|
+
# - If any phase declares `branch_set: { id: X, ... }`, every optional
|
|
943
|
+
# phase in the same storyboard whose steps contribute to X MUST also
|
|
944
|
+
# declare `branch_set: { id: X, ... }`. A mixed-mode storyboard (one
|
|
945
|
+
# peer declared, one peer relying on implicit detection) would be
|
|
946
|
+
# graded as a single-member set by a runner preferring the explicit
|
|
947
|
+
# declaration.
|
|
948
|
+
#
|
|
949
|
+
# Runner grading:
|
|
950
|
+
# Runners MUST prefer the explicit `branch_set:` declaration when
|
|
951
|
+
# present and fall back to implicit detection otherwise (see below).
|
|
952
|
+
# - If at least one peer branch contributed the `branch_set.id`, the
|
|
953
|
+
# non-contributing branch's failing steps MUST be graded with skip
|
|
954
|
+
# reason `peer_branch_taken` (see
|
|
955
|
+
# runner-output-contract.yaml > skip_result.reasons.peer_branch_taken).
|
|
956
|
+
# They MUST NOT surface as `failed` in the storyboard summary, and
|
|
957
|
+
# MUST NOT be conflated with `not_applicable` (reserved for coverage
|
|
958
|
+
# gaps — agent didn't declare the protocol).
|
|
959
|
+
# - If no branch contributed, the final `assert_contribution` step
|
|
960
|
+
# fails the storyboard. Individual branch step failures still grade
|
|
961
|
+
# `failed` so the implementor sees what each branch observed.
|
|
962
|
+
#
|
|
963
|
+
# When to use a branch set:
|
|
964
|
+
# Only when the AdCP spec permits multiple conformant behaviors for
|
|
965
|
+
# the same trigger. If the spec mandates one behavior, write a
|
|
966
|
+
# required phase — a branch set where only one branch is legal
|
|
967
|
+
# weakens the test to any_of and hides regressions. Branch sets are
|
|
968
|
+
# not for optional features (use `skip_if` and coverage flags) or
|
|
969
|
+
# for progressive disclosure (use sequential phases).
|
|
970
|
+
#
|
|
971
|
+
# Authoring guidance: keep branch sets flat — two or three peer phases
|
|
972
|
+
# is typical. Nested branch sets (a branch set whose member is itself a
|
|
973
|
+
# branch set) are out of scope for runners. If a scenario needs deeper
|
|
974
|
+
# branching, restructure into separate storyboards or a linear sequence.
|
|
975
|
+
#
|
|
976
|
+
# Implicit branch-set detection (DEPRECATED — pre-#2633 storyboards only):
|
|
977
|
+
# When no phase in a storyboard declares `branch_set:`, runners fall
|
|
978
|
+
# back to the legacy rule: a branch set is the set of `optional: true`
|
|
979
|
+
# phases whose steps declare the same `contributes_to: <flag>` value
|
|
980
|
+
# as a later `assert_contribution` step's
|
|
981
|
+
# `validations[].check: any_of, allowed_values: [<flag>]` target.
|
|
982
|
+
# Fragile against typos in `contributes_to:` — new storyboards MUST
|
|
983
|
+
# use the explicit form above. This fallback exists only so
|
|
984
|
+
# pre-#2633 storyboards keep running during migration.
|
|
985
|
+
#
|
|
986
|
+
# Cross-storyboard contradiction lint:
|
|
987
|
+
# The `lint:storyboard-contradictions` script groups every step-with-
|
|
988
|
+
# assertions across every storyboard by (task, canonicalized request
|
|
989
|
+
# fingerprint, prior-state fingerprint, env fingerprint) and flags
|
|
990
|
+
# groups whose asserted outcomes disagree in a way no conformant agent
|
|
991
|
+
# can satisfy (success ↔ error, or disjoint error-code sets).
|
|
992
|
+
#
|
|
993
|
+
# Env fingerprint includes the storyboard's top-level `id:`, so two
|
|
994
|
+
# separate storyboards exercising different controller-seeded states
|
|
995
|
+
# are legitimately independent test suites and do not collide. To make
|
|
996
|
+
# two different storyboards' assertions collide — e.g., to catch
|
|
997
|
+
# "storyboard A assumes media_buy_id X is active, storyboard B
|
|
998
|
+
# assumes it is canceled" — use matching `comply_scenario:` values or
|
|
999
|
+
# shared `prerequisites.controller_seeding`.
|
|
1000
|
+
#
|
|
1001
|
+
# Branch-set peers (two optional phases in the same storyboard sharing
|
|
1002
|
+
# a `branch_set.id`) are exempt from contradiction flagging by design:
|
|
1003
|
+
# any_of semantics intentionally asserts mutually exclusive outcomes.
|
|
1004
|
+
#
|
|
1005
|
+
# Mutating-task detection: the lint's prior-state fingerprint partitions
|
|
1006
|
+
# steps by the ordered list of prior MUTATING tasks. A task is mutating
|
|
1007
|
+
# iff its request schema declares `"x-mutates-state": true` at the top
|
|
1008
|
+
# level. The annotation is consumed by
|
|
1009
|
+
# `scripts/lint-storyboard-contradictions.cjs` (which builds the
|
|
1010
|
+
# mutating-task set at lint time) and is the single source of truth for
|
|
1011
|
+
# this concern.
|
|
1012
|
+
#
|
|
1013
|
+
# Decidability rule (apply to any new task):
|
|
1014
|
+
# A task declares `"x-mutates-state": true` iff a conformant compliance
|
|
1015
|
+
# suite could write an assertion — on any later `get_*`, `list_*`, or
|
|
1016
|
+
# follow-up call — whose outcome depends on this request having run.
|
|
1017
|
+
# Equivalently: "this task changes observable server state a later
|
|
1018
|
+
# conformant call may assert against."
|
|
1019
|
+
#
|
|
1020
|
+
# Mutating task classes (illustrative; the decidability rule above is
|
|
1021
|
+
# the actual test):
|
|
1022
|
+
# - Writes (create/update/delete/sync/build) — resource lifecycle.
|
|
1023
|
+
# - State transitions (cancel, approve, reject) — status moves.
|
|
1024
|
+
# - Session-scoped primitives (si_initiate_session, si_send_message,
|
|
1025
|
+
# si_terminate_session) — session lifecycle.
|
|
1026
|
+
# - Reporting/audit writes (log_event, report_usage,
|
|
1027
|
+
# report_plan_outcome, provide_performance_feedback) — mutating
|
|
1028
|
+
# because `get_plan_audit_logs` and billing-summary tasks can assert
|
|
1029
|
+
# against their recorded outputs.
|
|
1030
|
+
#
|
|
1031
|
+
# Non-mutating task classes:
|
|
1032
|
+
# - Read-only tasks (get_*, list_*, check_*, validate_*, preview_*) do
|
|
1033
|
+
# not declare `x-mutates-state` UNLESS the read itself produces
|
|
1034
|
+
# observable state a later task can assert against (e.g., a view
|
|
1035
|
+
# counter that `get_analytics` can read) — those are mutating
|
|
1036
|
+
# regardless of prefix.
|
|
1037
|
+
# - Pure-compute tasks (no state read or write — estimates,
|
|
1038
|
+
# calculations) MUST NOT declare `x-mutates-state`.
|
|
1039
|
+
#
|
|
1040
|
+
# Relationship to idempotency_key (decoupled, do not unify):
|
|
1041
|
+
# `x-mutates-state` declares mutation semantics. `required:
|
|
1042
|
+
# [idempotency_key]` declares the idempotency mechanism. The sets
|
|
1043
|
+
# overlap ~95% but legitimately diverge for naturally-idempotent
|
|
1044
|
+
# mutations. Example: `comply_test_controller` declares
|
|
1045
|
+
# `x-mutates-state: true` but omits `idempotency_key` from `required`
|
|
1046
|
+
# because the `scenario` enum is the dedup boundary — replaying
|
|
1047
|
+
# `force_media_buy_status=active` converges to the same observable
|
|
1048
|
+
# state without a key. `si_terminate_session` is the same shape
|
|
1049
|
+
# (session_id is the dedup boundary). `scripts/build-compliance.cjs`
|
|
1050
|
+
# reads `idempotency_key` for its own enforcement; do not try to share
|
|
1051
|
+
# the two reads.
|
|
1052
|
+
#
|
|
1053
|
+
# --- Validation ---
|
|
1054
|
+
#
|
|
1055
|
+
# CANONICAL CHECK ENUM
|
|
1056
|
+
# --------------------
|
|
1057
|
+
# The authored check kinds storyboards MAY declare in `step.validations[].check`
|
|
1058
|
+
# live in `runner-output-contract.yaml > authored_check_kinds` (structured
|
|
1059
|
+
# YAML; this file is comment-only documentation). The build-time lint
|
|
1060
|
+
# `scripts/lint-storyboard-check-enum.cjs` reads from there; runtime
|
|
1061
|
+
# forward-compat (unknown kinds → not_applicable) is documented in
|
|
1062
|
+
# runner-output-contract.yaml > validation_result. Add new kinds to that
|
|
1063
|
+
# enum AND document semantics here in the same PR.
|
|
1064
|
+
#
|
|
1065
|
+
# check: string (what to validate: "response_schema", "field_present",
|
|
1066
|
+
# "envelope_field_present" / "envelope_field_absent" (walks
|
|
1067
|
+
# protocol-envelope.json instead of response_schema_ref — use for
|
|
1068
|
+
# top-level envelope fields like `status`, `task_status`, and
|
|
1069
|
+
# `response_status`),
|
|
1070
|
+
# "field_value",
|
|
1071
|
+
# "field_value_or_absent", "status_code", "http_status", "http_status_in",
|
|
1072
|
+
# "error_code", "on_401_require_header", "resource_equals_agent_url",
|
|
1073
|
+
# "any_of", "refs_resolve", "a2a_submitted_artifact",
|
|
1074
|
+
# "field_less_than" / "field_equals_context" (compare a field on this
|
|
1075
|
+
# step's response against a value captured by a prior step — see
|
|
1076
|
+
# "Cross-step comparison" section below),
|
|
1077
|
+
# "upstream_traffic" (asserts side-effects via comply_test_controller's
|
|
1078
|
+
# query_upstream_traffic scenario — see "upstream_traffic" section below),
|
|
1079
|
+
# "canonical_format_satisfaction" (asserts create_media_buy package
|
|
1080
|
+
# selectors against prior get_products format_options[] context — see
|
|
1081
|
+
# "Canonical format satisfaction" section below))
|
|
1082
|
+
# severity: string (optional, default "required"): "required" | "advisory".
|
|
1083
|
+
# A "required" validation failure fails the step and contributes
|
|
1084
|
+
# to steps_failed; this is the default. An "advisory" failure
|
|
1085
|
+
# surfaces in the validation_result with passed: false but does
|
|
1086
|
+
# NOT fail the step — the step grades on its remaining required
|
|
1087
|
+
# validations. Advisory failures contribute to a distinct
|
|
1088
|
+
# `validations_advisory_failed` counter on run_summary so they
|
|
1089
|
+
# stay visible without polluting conformance verdicts.
|
|
1090
|
+
#
|
|
1091
|
+
# Use case: a storyboard declares a check whose runner support
|
|
1092
|
+
# is rolling out (e.g., upstream_traffic during the months
|
|
1093
|
+
# between the contract spec landing and the @adcp/sdk runner
|
|
1094
|
+
# shipping it). Authors flip severity: advisory while
|
|
1095
|
+
# instrumentation matures, then drop the field (or set
|
|
1096
|
+
# "required") once adoption is stable. Without this escape
|
|
1097
|
+
# valve, the choice was binary: declare the check and break
|
|
1098
|
+
# conformance for adopters who haven't caught up, or omit it
|
|
1099
|
+
# entirely and lose the validation surface during rollout.
|
|
1100
|
+
# Distinct from the runtime forward-compat default (unknown
|
|
1101
|
+
# check kinds → not_applicable): forward-compat handles
|
|
1102
|
+
# runner-spec version skew; severity handles author-managed
|
|
1103
|
+
# rollout gating.
|
|
1104
|
+
# expires_after_version: string (optional, only meaningful when severity:
|
|
1105
|
+
# advisory): semver gating runtime promotion of this advisory
|
|
1106
|
+
# to "required". Closes the advisory-drift problem:
|
|
1107
|
+
# storyboards declared with severity: advisory during an
|
|
1108
|
+
# adoption window get force-promoted once runners catch up,
|
|
1109
|
+
# so authors don't need to track every advisory entry across
|
|
1110
|
+
# every PR.
|
|
1111
|
+
#
|
|
1112
|
+
# Versioning anchor: the comparison is against the runner's
|
|
1113
|
+
# self-declared `runner_capability_version` (see
|
|
1114
|
+
# runner-output-contract.yaml > run_summary), NOT against
|
|
1115
|
+
# any specific implementation's package version. The runner
|
|
1116
|
+
# chooses what its capability version corresponds to —
|
|
1117
|
+
# @adcp/sdk semver, the AdCP spec version it implements, or
|
|
1118
|
+
# a runner-binary version — and declares it in run_summary.
|
|
1119
|
+
# Storyboard authors target the spec capability they need,
|
|
1120
|
+
# not a specific SDK package; runners self-report the
|
|
1121
|
+
# capability they offer.
|
|
1122
|
+
#
|
|
1123
|
+
# Runner behavior: at storyboard-load time the runner
|
|
1124
|
+
# compares its own runner_capability_version to
|
|
1125
|
+
# expires_after_version using semver.gte. If
|
|
1126
|
+
# runner_capability_version >= expires_after_version, the
|
|
1127
|
+
# runner MUST promote severity to "required" — advisory
|
|
1128
|
+
# failures then fail the step. The validation_result MUST
|
|
1129
|
+
# carry severity_promoted_from_advisory: true to surface the
|
|
1130
|
+
# promotion on rendered reports.
|
|
1131
|
+
#
|
|
1132
|
+
# Forward-compat ordering: when a storyboard declares both
|
|
1133
|
+
# an unknown check kind (graded `not_applicable` per #3816's
|
|
1134
|
+
# forward-compat clause) AND severity: advisory +
|
|
1135
|
+
# expires_after_version, forward-compat resolves FIRST. The
|
|
1136
|
+
# check grades not_applicable and severity_promoted_from_advisory
|
|
1137
|
+
# MUST be absent — no promotion was evaluated because no
|
|
1138
|
+
# grading occurred. Promotion only applies to checks the
|
|
1139
|
+
# runner actually grades.
|
|
1140
|
+
#
|
|
1141
|
+
# Pre-release tags: comparison uses standard semver including
|
|
1142
|
+
# pre-releases (RFC §11). `6.5.0-rc.3 < 6.5.0`. Authors
|
|
1143
|
+
# wanting "after 6.5.0 stable" write `6.5.0`; authors
|
|
1144
|
+
# wanting "after any 6.5.0 pre-release" write `6.5.0-0`.
|
|
1145
|
+
# The runner MUST use semver.gte semantics, not a custom
|
|
1146
|
+
# comparator. Authors targeting a specific minor without
|
|
1147
|
+
# regard to pre-release status SHOULD write the lowest
|
|
1148
|
+
# pre-release form (`X.Y.0-0`) when they want to include
|
|
1149
|
+
# early adopters running RC builds.
|
|
1150
|
+
#
|
|
1151
|
+
# Permanent advisories — see `permanent_advisory` field
|
|
1152
|
+
# below for declaring an advisory that's deliberately not
|
|
1153
|
+
# gated on runner version.
|
|
1154
|
+
#
|
|
1155
|
+
# Format: a semver-valid string. The build-time lint at
|
|
1156
|
+
# scripts/lint-storyboard-advisory-expiry.cjs MUST validate
|
|
1157
|
+
# the value via Node's `semver.valid()` and reject malformed
|
|
1158
|
+
# values; this prevents typos AND bounds the trust surface
|
|
1159
|
+
# on the value when it's interpolated into rendered reports.
|
|
1160
|
+
#
|
|
1161
|
+
# permanent_advisory: object (optional, only meaningful when severity:
|
|
1162
|
+
# advisory and expires_after_version is absent): structured
|
|
1163
|
+
# marker declaring this advisory is deliberately not gated
|
|
1164
|
+
# on runner version. Use case: experimental signals where
|
|
1165
|
+
# the spec authors explicitly want the advisory grade to
|
|
1166
|
+
# persist regardless of runner adoption.
|
|
1167
|
+
#
|
|
1168
|
+
# Shape:
|
|
1169
|
+
# permanent_advisory:
|
|
1170
|
+
# reason: "<text>"
|
|
1171
|
+
#
|
|
1172
|
+
# Replaces the earlier `# advisory-permanent: <reason>` YAML
|
|
1173
|
+
# comment marker (which broke under YAML round-tripping in
|
|
1174
|
+
# editor formatters and downstream tooling). Structured
|
|
1175
|
+
# field is greppable, schema-validatable, and the reason is
|
|
1176
|
+
# machine-readable for dashboards.
|
|
1177
|
+
#
|
|
1178
|
+
# Schema requirement: when severity: advisory is declared,
|
|
1179
|
+
# exactly one of `expires_after_version` or
|
|
1180
|
+
# `permanent_advisory` MUST be present. The build-time lint
|
|
1181
|
+
# surfaces violations (warning, not error — drift is a
|
|
1182
|
+
# judgment call).
|
|
1183
|
+
# path: string (JSON path to the field, e.g., "formats[0].format_id")
|
|
1184
|
+
# value: any (expected value — string, number, or boolean; required when check is "field_value";
|
|
1185
|
+
# accepted (optional) when check is "field_value_or_absent";
|
|
1186
|
+
# accepted (optional) on "field_less_than" as a literal comparand when
|
|
1187
|
+
# `context_key` is not set)
|
|
1188
|
+
# allowed_values: array (acceptable values for "field_value", "field_value_or_absent",
|
|
1189
|
+
# "http_status_in", "error_code", "any_of")
|
|
1190
|
+
# context_key: string (only meaningful for "field_less_than" / "field_equals_context"
|
|
1191
|
+
# cross-step comparison checks — names a key populated by a prior
|
|
1192
|
+
# step's `context_outputs`. Required on "field_equals_context";
|
|
1193
|
+
# optional on "field_less_than" (falls back to the literal `value`
|
|
1194
|
+
# when omitted). Ignored on every other check kind. When set and
|
|
1195
|
+
# the key is absent from the storyboard accumulator, the check
|
|
1196
|
+
# passes with a `context_key_absent` observation — see
|
|
1197
|
+
# "Cross-step comparison" section below.)
|
|
1198
|
+
# description: string (human-readable description of validation)
|
|
1199
|
+
#
|
|
1200
|
+
# field_value_or_absent check:
|
|
1201
|
+
# Passes when the field is absent OR present and equal to `value` / contained in
|
|
1202
|
+
# `allowed_values`; fails only when the field is present with a disallowed value.
|
|
1203
|
+
# Use for fields that the spec permits to be omitted but MUST NOT carry a wrong value
|
|
1204
|
+
# when present (e.g., a `replayed` flag that MAY be omitted on a fresh path but MUST
|
|
1205
|
+
# NOT be true). Accepts both `value` (single expected value) and `allowed_values` (set
|
|
1206
|
+
# membership); at least one of the two MUST be provided. A `field_value_or_absent` check
|
|
1207
|
+
# that declares neither `value` nor `allowed_values` is a storyboard-load error; runners
|
|
1208
|
+
# MUST reject the storyboard before execution begins.
|
|
1209
|
+
# For fields that are required by the response schema, pair this check with a separate
|
|
1210
|
+
# `check: field_present` to avoid silently accepting absent fields that are required.
|
|
1211
|
+
#
|
|
1212
|
+
# Error-shape validation — use `check: error_code`:
|
|
1213
|
+
# The `error_code` check is shape-agnostic. The runner resolves the code from
|
|
1214
|
+
# any of the transport-binding locations defined by the client detection
|
|
1215
|
+
# order (see docs/building/implementation/transport-errors#client-detection-order):
|
|
1216
|
+
# - `adcp_error.code` (MCP structuredContent, A2A artifact DataPart, JSON-RPC
|
|
1217
|
+
# error.data, MCP text-fallback content JSON)
|
|
1218
|
+
# - `errors[0].code` (task-payload errors array, top-level or under payload)
|
|
1219
|
+
# A storyboard SHOULD assert error shape via `check: error_code` rather than
|
|
1220
|
+
# `check: field_present, path: "errors"` or `path: "adcp_error"` — pinning to
|
|
1221
|
+
# a specific shape makes the validator flakey against conformant agents that
|
|
1222
|
+
# surface errors on the other layer.
|
|
1223
|
+
#
|
|
1224
|
+
# Error-code vocabulary:
|
|
1225
|
+
# Every code referenced in a `value:` or `allowed_values:` under `check: error_code`
|
|
1226
|
+
# MUST exist in the canonical enum at `static/schemas/source/enums/error-code.json`
|
|
1227
|
+
# (or be registered as a deprecation alias). The `lint:error-codes` script
|
|
1228
|
+
# enforces this at build time — references to undefined codes fail the build.
|
|
1229
|
+
# Sellers MAY emit vendor-specific codes outside the enum, but storyboards
|
|
1230
|
+
# MUST NOT assert on them (that couples the conformance suite to a specific
|
|
1231
|
+
# vendor's taxonomy). When a test needs to accept multiple possible codes,
|
|
1232
|
+
# use `allowed_values: [...]` rather than a single-code `value:` assertion.
|
|
1233
|
+
#
|
|
1234
|
+
# Canonical format satisfaction — use `check: canonical_format_satisfaction`:
|
|
1235
|
+
# Applies only to create_media_buy steps. The runner compares each package's
|
|
1236
|
+
# format selector against the target product captured from prior get_products
|
|
1237
|
+
# context. When multiple get_products responses exist, the runner MUST use
|
|
1238
|
+
# the most recent prior get_products response containing the package's
|
|
1239
|
+
# product_id; if no such response exists, the check fails as an authoring or
|
|
1240
|
+
# setup error. `value: true` means the request should be accepted; `value:
|
|
1241
|
+
# false` means the request should be rejected for a format-selector cause.
|
|
1242
|
+
#
|
|
1243
|
+
# The check normalizes legacy `format_ids[]` through `v1_format_ref[]` and the
|
|
1244
|
+
# v1-to-canonical registry, resolves `format_option_refs[]`, and applies
|
|
1245
|
+
# directional product gating for fixed dimensions, duration declarations,
|
|
1246
|
+
# canonical params, and range containment. Negative cases only pass when the
|
|
1247
|
+
# observed rejection is attributable to formats, so unrelated auth or tenant
|
|
1248
|
+
# failures do not mask selector bugs.
|
|
1249
|
+
#
|
|
1250
|
+
# Fields:
|
|
1251
|
+
# check: canonical_format_satisfaction
|
|
1252
|
+
# value: boolean # true = expected accepted; false = expected rejected.
|
|
1253
|
+
# path: string # optional; selects one package object or package array.
|
|
1254
|
+
# # default is the request payload's packages[] array.
|
|
1255
|
+
#
|
|
1256
|
+
# Example:
|
|
1257
|
+
# - check: canonical_format_satisfaction
|
|
1258
|
+
# value: false
|
|
1259
|
+
# description: "Bare image selector lacks the dimensions required by the fixed product"
|
|
1260
|
+
#
|
|
1261
|
+
# Cross-step integrity — use `check: refs_resolve`:
|
|
1262
|
+
# `refs_resolve` asserts every ref in a source set resolves to a member of a
|
|
1263
|
+
# target set, matched on declared keys. Use it when one step's response
|
|
1264
|
+
# contains references (e.g., `format_ids` on `products`) that MUST exist in
|
|
1265
|
+
# another step's response (e.g., `formats` from `list_creative_formats`).
|
|
1266
|
+
# Without this primitive, broken references are silent until a later call
|
|
1267
|
+
# (e.g., `sync_creatives`) fails at runtime, after commitments are already
|
|
1268
|
+
# made.
|
|
1269
|
+
#
|
|
1270
|
+
# Fields:
|
|
1271
|
+
# source:
|
|
1272
|
+
# from: current_step | context
|
|
1273
|
+
# # `current_step` reads the step's task-result data;
|
|
1274
|
+
# # `context` reads values captured by prior steps.
|
|
1275
|
+
# path: <string> # supports `[*]` wildcards — see below.
|
|
1276
|
+
# target:
|
|
1277
|
+
# from: current_step | context
|
|
1278
|
+
# path: <string>
|
|
1279
|
+
# match_keys: [<key>, ...] # keys compared on each ref — e.g. [agent_url, id].
|
|
1280
|
+
# # A ref missing any declared key is NEVER a match
|
|
1281
|
+
# # (agents that drop a key don't fuzzy-match others
|
|
1282
|
+
# # that also dropped it).
|
|
1283
|
+
# scope: # optional — restrict integrity to in-scope refs.
|
|
1284
|
+
# key: <string> # e.g. `agent_url`.
|
|
1285
|
+
# equals: <string> # literal value, or `$agent_url` for the
|
|
1286
|
+
# # runner target URL. Keys ending in `url`
|
|
1287
|
+
# # get trailing-slash / case normalization
|
|
1288
|
+
# # on both sides before compare. Transport
|
|
1289
|
+
# # path canonicalization (`/mcp`, well-known
|
|
1290
|
+
# # A2A card path) for `$agent_url` is tracked
|
|
1291
|
+
# # in adcp-client#710 — until it lands, MCP
|
|
1292
|
+
# # agents should expect refs to fall
|
|
1293
|
+
# # out-of-scope when format_ids name the
|
|
1294
|
+
# # bare agent URL.
|
|
1295
|
+
# on_out_of_scope: warn | ignore | fail
|
|
1296
|
+
# # how refs outside `scope` are graded.
|
|
1297
|
+
# # default `warn`: pass the check, attach
|
|
1298
|
+
# # observations naming the skipped refs.
|
|
1299
|
+
# # `ignore`: silent. `fail`: promote to
|
|
1300
|
+
# # missing so reports name them.
|
|
1301
|
+
#
|
|
1302
|
+
# Path wildcards:
|
|
1303
|
+
# `[*]` in a `source.path` or `target.path` flattens over arrays. So
|
|
1304
|
+
# `products[*].format_ids[*]` walks every product and flattens every
|
|
1305
|
+
# `format_ids` array into a single list of refs. Runners cap terminal
|
|
1306
|
+
# fan-out at 10,000 values to bound malicious agent responses; paths
|
|
1307
|
+
# realistic for any catalog size are well under the cap.
|
|
1308
|
+
#
|
|
1309
|
+
# Grading output:
|
|
1310
|
+
# A failing `refs_resolve` check names the specific unresolved ref tuples
|
|
1311
|
+
# in `actual.missing` (projected to `match_keys`). Duplicates are collapsed
|
|
1312
|
+
# on the projected tuple so one broken ref across 50 products shows up
|
|
1313
|
+
# once — compliance reports stay readable.
|
|
1314
|
+
#
|
|
1315
|
+
# Example — every `format_id` on products resolves to a format on this agent:
|
|
1316
|
+
# - check: refs_resolve
|
|
1317
|
+
# description: "Every format_id on products resolves to a format returned by list_creative_formats"
|
|
1318
|
+
# source:
|
|
1319
|
+
# from: context
|
|
1320
|
+
# path: "products[*].format_ids[*]"
|
|
1321
|
+
# target:
|
|
1322
|
+
# from: current_step
|
|
1323
|
+
# path: "formats[*].format_id"
|
|
1324
|
+
# match_keys: [agent_url, id]
|
|
1325
|
+
# scope:
|
|
1326
|
+
# key: agent_url
|
|
1327
|
+
# equals: $agent_url
|
|
1328
|
+
# on_out_of_scope: warn
|
|
1329
|
+
#
|
|
1330
|
+
# Cross-step comparison — use `check: field_less_than` / `field_equals_context`:
|
|
1331
|
+
# These two checks compare a field in THIS step's response against a value
|
|
1332
|
+
# captured by a PRIOR step via `context_outputs`. Without them, every existing
|
|
1333
|
+
# `check:` is single-step (`field_value` resolves the comparand at YAML
|
|
1334
|
+
# authoring time via `$context.<name>` substitution; the runtime value the
|
|
1335
|
+
# prior step actually returned is opaque to the validation).
|
|
1336
|
+
#
|
|
1337
|
+
# Use cases:
|
|
1338
|
+
# - Round-trip identity: assert that `media_buys[0].media_buy_id` on
|
|
1339
|
+
# `get_media_buys` equals the id returned by the earlier `create_media_buy`
|
|
1340
|
+
# step. Today's `field_value` + `$context.media_buy_id` substitution
|
|
1341
|
+
# resolves the placeholder against the request, not the response — a
|
|
1342
|
+
# seller could return a different id on the read and still pass.
|
|
1343
|
+
# - Filtered-vs-baseline: assert that a filtered `get_rights` returns a
|
|
1344
|
+
# strictly smaller `rights.length` than the unfiltered baseline call.
|
|
1345
|
+
# - Idempotency replay: assert that the replayed response's body hash
|
|
1346
|
+
# equals the originally captured hash (byte-for-byte cache hit).
|
|
1347
|
+
#
|
|
1348
|
+
# Fields:
|
|
1349
|
+
# check: field_less_than | field_equals_context
|
|
1350
|
+
# path: string # JSON path on this step's response.
|
|
1351
|
+
# context_key: string # name set by a prior step's
|
|
1352
|
+
# # `context_outputs[].name`. Required for
|
|
1353
|
+
# # `field_equals_context`. Optional for
|
|
1354
|
+
# # `field_less_than` — when omitted, the
|
|
1355
|
+
# # check falls back to the literal `value`
|
|
1356
|
+
# # (so authors can compare against a
|
|
1357
|
+
# # static threshold without context).
|
|
1358
|
+
# value: number # Literal comparand for `field_less_than`
|
|
1359
|
+
# # when `context_key` is not set. Ignored on
|
|
1360
|
+
# # `field_equals_context` (which always reads
|
|
1361
|
+
# # from context).
|
|
1362
|
+
# description: string
|
|
1363
|
+
#
|
|
1364
|
+
# Type semantics:
|
|
1365
|
+
# `field_less_than` requires both operands to be finite numbers; non-numeric
|
|
1366
|
+
# or absent values fail the check with a type error in the validation_result.
|
|
1367
|
+
# `field_equals_context` deep-equals the response value against the captured
|
|
1368
|
+
# comparand using the same equality semantics as `field_value`.
|
|
1369
|
+
#
|
|
1370
|
+
# Absent context_key — context_key_absent observation:
|
|
1371
|
+
# When `context_key` is set but the named entry is missing from the
|
|
1372
|
+
# storyboard accumulator (typically because the prior capturing step was
|
|
1373
|
+
# legitimately skipped on a branch-set path), the check PASSES with a
|
|
1374
|
+
# `context_key_absent` observation rather than failing. This preserves the
|
|
1375
|
+
# "skipped prerequisite cascades to skip, not fail" semantics that
|
|
1376
|
+
# `context_outputs` already establishes for `$context.<name>` substitution.
|
|
1377
|
+
# A genuinely missing capture (the prior step ran but its capture path did
|
|
1378
|
+
# not resolve) is graded `capture_path_not_resolvable` ON THE PRIOR STEP —
|
|
1379
|
+
# downstream cross-step checks on a never-populated key are not the place
|
|
1380
|
+
# to surface that failure.
|
|
1381
|
+
#
|
|
1382
|
+
# Examples:
|
|
1383
|
+
# # Round-trip: get_media_buys returns the id we just created.
|
|
1384
|
+
# # Pair with `context_outputs` on the earlier create_media_buy step:
|
|
1385
|
+
# # context_outputs:
|
|
1386
|
+
# # - name: created_media_buy_id
|
|
1387
|
+
# # path: "media_buy_id"
|
|
1388
|
+
# - check: field_equals_context
|
|
1389
|
+
# path: "media_buys[0].media_buy_id"
|
|
1390
|
+
# context_key: "created_media_buy_id"
|
|
1391
|
+
# description: "get_media_buys echoes the id returned by create_media_buy"
|
|
1392
|
+
#
|
|
1393
|
+
# # Filtered call returns fewer rights than baseline.
|
|
1394
|
+
# # Capture the baseline length on the unfiltered step:
|
|
1395
|
+
# # context_outputs:
|
|
1396
|
+
# # - name: baseline_rights_count
|
|
1397
|
+
# # path: "rights.length"
|
|
1398
|
+
# - check: field_less_than
|
|
1399
|
+
# path: "rights.length"
|
|
1400
|
+
# context_key: "baseline_rights_count"
|
|
1401
|
+
# description: "Filtered rights array is smaller than unfiltered baseline"
|
|
1402
|
+
#
|
|
1403
|
+
# # Idempotency replay: replayed body deep-equals the originally-captured one.
|
|
1404
|
+
# # Pair with `context_outputs` on the original call:
|
|
1405
|
+
# # context_outputs:
|
|
1406
|
+
# # - name: original_create_response
|
|
1407
|
+
# # path: "" # capture the entire response body
|
|
1408
|
+
# - check: field_equals_context
|
|
1409
|
+
# path: ""
|
|
1410
|
+
# context_key: "original_create_response"
|
|
1411
|
+
# description: "Replay returned the cached response body, deep-equal to the original"
|
|
1412
|
+
#
|
|
1413
|
+
# # Static threshold (single-step form — no context_key).
|
|
1414
|
+
# # NOTE: `field_less_than` is primarily a cross-step check; the literal-
|
|
1415
|
+
# # `value` form is a convenience for numeric thresholds. Authors who
|
|
1416
|
+
# # need same-step equality should reach for `field_value` instead.
|
|
1417
|
+
# - check: field_less_than
|
|
1418
|
+
# path: "delivery.spend"
|
|
1419
|
+
# value: 10000
|
|
1420
|
+
# description: "Spend stays under the buy's budget cap"
|
|
1421
|
+
#
|
|
1422
|
+
# A2A wire-shape — use `check: a2a_submitted_artifact`:
|
|
1423
|
+
# Asserts the A2A envelope invariants for AdCP `submitted` arms: `Task.id` /
|
|
1424
|
+
# `Task.contextId` non-empty, `Task.state` normalised to `completed` (A2A 0.3
|
|
1425
|
+
# wire value `"completed"`; A2A 1.0 enum `"TASK_STATE_COMPLETED"`), and the
|
|
1426
|
+
# last non-null, object-typed DataPart's `data.status === 'submitted'`
|
|
1427
|
+
# preserving the AdCP discriminator. Grades `not_applicable` on non-A2A
|
|
1428
|
+
# transports so storyboards can include it alongside MCP-shape assertions
|
|
1429
|
+
# without forking by transport.
|
|
1430
|
+
#
|
|
1431
|
+
# Upstream side-effects — use `check: upstream_traffic`:
|
|
1432
|
+
# Asserts that the agent caused observable upstream traffic with a payload
|
|
1433
|
+
# carrying the storyboard-supplied identifiers. The mechanism raises the bar
|
|
1434
|
+
# against UNINTENTIONAL façades — adapters (often LLM-generated) that satisfy
|
|
1435
|
+
# AdCP schema requirements with synthetic placeholders without forwarding the
|
|
1436
|
+
# payload upstream. It is NOT an adversarial integrity check: adopters
|
|
1437
|
+
# self-report their own traffic, and a determined façade could fabricate
|
|
1438
|
+
# recorded_calls. Spec consumers MUST NOT treat upstream_traffic passing as
|
|
1439
|
+
# cryptographic proof of adapter behavior; the value is "raises the bar
|
|
1440
|
+
# against accidental façades," not "stops malicious façades."
|
|
1441
|
+
#
|
|
1442
|
+
# The runner queries the adopter's comply_test_controller with scenario
|
|
1443
|
+
# `query_upstream_traffic`, scoped to traffic recorded since the storyboard
|
|
1444
|
+
# step's request timestamp, and applies the assertion declared on the
|
|
1445
|
+
# storyboard step. Adopters who do not advertise `query_upstream_traffic` in
|
|
1446
|
+
# `list_scenarios` grade the check `not_applicable` — the contract is opt-in
|
|
1447
|
+
# by adopter capability. Adopters who DO advertise it but return zero
|
|
1448
|
+
# recorded calls during the assertion window grade as failed (the
|
|
1449
|
+
# "controller present and observed nothing" path is the façade signal).
|
|
1450
|
+
#
|
|
1451
|
+
# Runner attestation-mode selection (normative):
|
|
1452
|
+
# Conforming runners MUST choose params.attestation_mode for
|
|
1453
|
+
# comply_test_controller query_upstream_traffic calls in a way that
|
|
1454
|
+
# preserves assertion coverage:
|
|
1455
|
+
# 1. If any upstream_traffic validation in the current step sets
|
|
1456
|
+
# attestation_mode_required: "raw", request "raw".
|
|
1457
|
+
# 2. Else if any upstream_traffic validation in the current step declares
|
|
1458
|
+
# payload_must_contain, request "raw"; arbitrary payload-path
|
|
1459
|
+
# assertions cannot be evaluated from digest attestations.
|
|
1460
|
+
# 3. Else if every upstream_traffic validation in the current step can
|
|
1461
|
+
# be evaluated from count/endpoint/purpose plus identifier_paths, and
|
|
1462
|
+
# the runner can supply params.identifier_value_digests for all
|
|
1463
|
+
# resolved identifier values, the runner SHOULD request "digest" when
|
|
1464
|
+
# it knows the matching upstream calls are JSON-shaped
|
|
1465
|
+
# (`application/json` or `*/*+json`). If the runner does not know the
|
|
1466
|
+
# matching calls are JSON-shaped, request "raw"; digest-mode
|
|
1467
|
+
# identifier_match_proofs are intentionally unavailable for non-JSON
|
|
1468
|
+
# payloads, so an unconditional digest request can turn an otherwise
|
|
1469
|
+
# evaluable identifier_paths assertion into not_applicable.
|
|
1470
|
+
# 4. Otherwise, request "raw".
|
|
1471
|
+
# Controllers MAY still return digest-mode attestations for a raw request
|
|
1472
|
+
# when local policy forbids raw payload disclosure; raw-required and
|
|
1473
|
+
# payload_must_contain assertions then grade not_applicable as described
|
|
1474
|
+
# below.
|
|
1475
|
+
# Storyboard authors SHOULD NOT invent non-schema attestation-mode hints.
|
|
1476
|
+
# Use attestation_mode_required: "raw" only for hard raw requirements;
|
|
1477
|
+
# otherwise rely on this runner selection rule.
|
|
1478
|
+
#
|
|
1479
|
+
# Fields:
|
|
1480
|
+
# min_count: integer (default: 1)
|
|
1481
|
+
# # Minimum number of recorded calls that match
|
|
1482
|
+
# # the optional `endpoint_pattern` filter. Use 0
|
|
1483
|
+
# # only for negative assertions (e.g., a step
|
|
1484
|
+
# # MUST NOT cause upstream traffic).
|
|
1485
|
+
# endpoint_pattern: string (optional)
|
|
1486
|
+
# # Glob pattern matched against
|
|
1487
|
+
# # recorded_calls[].endpoint. Examples:
|
|
1488
|
+
# # "POST */audience/upload"
|
|
1489
|
+
# # "POST *" (any POST, useful when
|
|
1490
|
+
# # the storyboard stays
|
|
1491
|
+
# # platform-agnostic)
|
|
1492
|
+
# # When omitted, all recorded calls in the window
|
|
1493
|
+
# # match. Storyboards in the shared corpus SHOULD
|
|
1494
|
+
# # stay platform-agnostic — host-specific patterns
|
|
1495
|
+
# # like "POST api.tiktok.com/*" couple the spec
|
|
1496
|
+
# # to specific upstream platforms and break for
|
|
1497
|
+
# # adopters whose upstream is something else.
|
|
1498
|
+
# # Reserve host-specific patterns for
|
|
1499
|
+
# # adapter-specific extensions, not shared
|
|
1500
|
+
# # storyboards.
|
|
1501
|
+
# payload_must_contain: array of MatchSpec (optional)
|
|
1502
|
+
# # Each entry asserts a path/value pair that MUST
|
|
1503
|
+
# # appear in at least one matching call's payload.
|
|
1504
|
+
# # Path syntax: JSONPath-lite positional path
|
|
1505
|
+
# # with `[*]` array wildcards. Shared storyboards
|
|
1506
|
+
# # SHOULD use the bare dotted form. Examples:
|
|
1507
|
+
# # "users[*].hashed_email"
|
|
1508
|
+
# # "data.audience.members[*].id"
|
|
1509
|
+
# # The literal `[*]` wildcard flattens over arrays
|
|
1510
|
+
# # and passes if any element matches. The runner
|
|
1511
|
+
# # also tolerates optional `$` / `$.` prefixes and
|
|
1512
|
+
# # standalone numeric index tokens for SDK
|
|
1513
|
+
# # compatibility (for example `items.[0].id`);
|
|
1514
|
+
# # shared storyboards should avoid indexes unless
|
|
1515
|
+
# # a position-specific assertion is intentional.
|
|
1516
|
+
# # RFC 9535 JSONPath descendant syntax (`$..foo`) is NOT
|
|
1517
|
+
# # supported — the runner uses
|
|
1518
|
+
# # JSONPath-lite matching from @adcp/sdk. Path matching only
|
|
1519
|
+
# # applies when recorded_calls[].content_type is
|
|
1520
|
+
# # JSON-shaped — defined as `application/json`
|
|
1521
|
+
# # or any structured-syntax-suffix `*/*+json`
|
|
1522
|
+
# # per RFC 6839 §3.1 (e.g., `application/vnd.api+json`,
|
|
1523
|
+
# # `application/scim+json`). All other content
|
|
1524
|
+
# # types (form-urlencoded, multipart, plain text,
|
|
1525
|
+
# # newline-delimited JSON like `application/json-seq`,
|
|
1526
|
+
# # JSON Lines `application/jsonl`) take the non-
|
|
1527
|
+
# # JSON path. Against non-JSON ALL match modes
|
|
1528
|
+
# # (`present` / `equals` / `contains_any`) grade
|
|
1529
|
+
# # `not_applicable`. Earlier sketches downgraded
|
|
1530
|
+
# # `match: present` to a terminal-key substring
|
|
1531
|
+
# # search of the raw payload string; that
|
|
1532
|
+
# # heuristic creates false positives (a payload
|
|
1533
|
+
# # mentioning the key name in any context — URL
|
|
1534
|
+
# # fragment, comment, unrelated metadata field —
|
|
1535
|
+
# # would pass), which is exactly the anti-façade
|
|
1536
|
+
# # contract this surface exists to protect.
|
|
1537
|
+
# # Storyboards that need a "the upstream call
|
|
1538
|
+
# # carried this value" signal against non-JSON
|
|
1539
|
+
# # payloads use `identifier_paths` instead — that
|
|
1540
|
+
# # surface substring-searches storyboard-supplied
|
|
1541
|
+
# # VALUES (not path-derived strings), which is
|
|
1542
|
+
# # encoding-agnostic and doesn't suffer the
|
|
1543
|
+
# # false-positive surface.
|
|
1544
|
+
# # Shape:
|
|
1545
|
+
# # - path: <dotted-with-[*] path>
|
|
1546
|
+
# # match: present | equals | contains_any
|
|
1547
|
+
# # value: <value> # for equals
|
|
1548
|
+
# # allowed_values: [...] # for contains_any
|
|
1549
|
+
# # Example:
|
|
1550
|
+
# # - path: "users[*].hashed_email"
|
|
1551
|
+
# # match: present
|
|
1552
|
+
# identifier_paths: array of string (optional)
|
|
1553
|
+
# # Paths into the storyboard's sample_request that
|
|
1554
|
+
# # name the load-bearing identifiers the adapter
|
|
1555
|
+
# # MUST forward upstream. The runner extracts the
|
|
1556
|
+
# # values at these paths from the storyboard's
|
|
1557
|
+
# # request and asserts each value appears in at
|
|
1558
|
+
# # least one matching recorded_call's payload at
|
|
1559
|
+
# # any depth. This is the strongest single
|
|
1560
|
+
# # anti-façade signal — adapters cannot satisfy
|
|
1561
|
+
# # it by fabricating a constant placeholder hash,
|
|
1562
|
+
# # because the placeholder will not match the
|
|
1563
|
+
# # storyboard's supplied value.
|
|
1564
|
+
# # Portable path grammar (normative):
|
|
1565
|
+
# # request-payload-relative dotted paths where
|
|
1566
|
+
# # each segment is an identifier key optionally
|
|
1567
|
+
# # followed by one `[*]` array wildcard. Example:
|
|
1568
|
+
# # "audiences[*].add[*].hashed_email"
|
|
1569
|
+
# # Supported segment form:
|
|
1570
|
+
# # [A-Za-z_][A-Za-z0-9_-]* or
|
|
1571
|
+
# # [A-Za-z_][A-Za-z0-9_-]*[*]
|
|
1572
|
+
# # Unsupported forms MUST be rejected at
|
|
1573
|
+
# # storyboard load/lint time rather than treated
|
|
1574
|
+
# # as paths that resolve to zero values:
|
|
1575
|
+
# # bracket-quoted keys (`foo["bar"]`), numeric
|
|
1576
|
+
# # indexes (`items[0]`), recursive descent
|
|
1577
|
+
# # (`$..foo`), absolute roots (`$.foo`), empty
|
|
1578
|
+
# # segments, and reserved roots (`request.*`,
|
|
1579
|
+
# # `response.*`, `context.*`). The path is
|
|
1580
|
+
# # already relative to the storyboard step's
|
|
1581
|
+
# # sample_request; adding an explicit root makes
|
|
1582
|
+
# # the same storyboard ambiguous across runners.
|
|
1583
|
+
# # Each path MAY resolve to a single value or an
|
|
1584
|
+
# # array; all resolved values MUST be present in
|
|
1585
|
+
# # the recorded payload. Replaces the earlier
|
|
1586
|
+
# # `buyer_identifier_echo: boolean` shorthand
|
|
1587
|
+
# # (which left "what counts as an identifier" to
|
|
1588
|
+
# # convention) with an explicit enumeration.
|
|
1589
|
+
# # Example:
|
|
1590
|
+
# # identifier_paths:
|
|
1591
|
+
# # - "audiences[*].add[*].hashed_email"
|
|
1592
|
+
# # - "audiences[*].add[*].external_id"
|
|
1593
|
+
# purpose_filter: array of string (optional)
|
|
1594
|
+
# # Restrict the assertion to recorded_calls whose
|
|
1595
|
+
# # `purpose` field matches one of the listed
|
|
1596
|
+
# # values. Allowed values:
|
|
1597
|
+
# # "platform_primary" | "measurement" |
|
|
1598
|
+
# # "attribution" | "creative_serving" |
|
|
1599
|
+
# # "identity" | "other"
|
|
1600
|
+
# # Use this when an adapter legitimately makes
|
|
1601
|
+
# # multiple kinds of upstream calls during a step
|
|
1602
|
+
# # — e.g., a sales-non-guaranteed buyer agent
|
|
1603
|
+
# # creating a campaign AND calling a measurement
|
|
1604
|
+
# # vendor AND posting an attribution event during
|
|
1605
|
+
# # the same create_media_buy. The storyboard
|
|
1606
|
+
# # scopes to `purpose_filter: [platform_primary]`
|
|
1607
|
+
# # so the other calls don't muddy the
|
|
1608
|
+
# # campaign-creation assertion.
|
|
1609
|
+
# # When omitted, the assertion considers all
|
|
1610
|
+
# # recorded_calls regardless of purpose. Calls
|
|
1611
|
+
# # without a `purpose` field (adopter didn't
|
|
1612
|
+
# # classify) are treated as `purpose: other` for
|
|
1613
|
+
# # filter matching — they're included by filters
|
|
1614
|
+
# # listing `other` and excluded by filters that
|
|
1615
|
+
# # don't. Runners MUST include a count of
|
|
1616
|
+
# # unclassified calls in the validation_result's
|
|
1617
|
+
# # `actual` when `purpose_filter` is set and
|
|
1618
|
+
# # zero recorded_calls match the filter, so a
|
|
1619
|
+
# # façade that misclassifies-into-`other` to
|
|
1620
|
+
# # dodge filtering produces a noisy zero rather
|
|
1621
|
+
# # than a silent one.
|
|
1622
|
+
# since: prior_step_id (optional, default: this step's request timestamp)
|
|
1623
|
+
# # Bound the lookup window. By default, the
|
|
1624
|
+
# # runner queries traffic recorded since the
|
|
1625
|
+
# # storyboard step issued its AdCP request, so
|
|
1626
|
+
# # only side-effects of THIS step are counted.
|
|
1627
|
+
# # Set to a prior step's id to include traffic
|
|
1628
|
+
# # caused by an earlier step (e.g., for a
|
|
1629
|
+
# # cumulative-effect assertion). The runner uses
|
|
1630
|
+
# # its own wall-clock timestamp from when it
|
|
1631
|
+
# # issued the AdCP request as the lower bound —
|
|
1632
|
+
# # the controller's `since_timestamp` echo is
|
|
1633
|
+
# # informational, not authoritative. See
|
|
1634
|
+
# # runner-output-contract.yaml > validation_result
|
|
1635
|
+
# # for the timestamp boundary semantics
|
|
1636
|
+
# # (inclusive lower bound, clock-skew tolerance).
|
|
1637
|
+
# attestation_mode_required: string (optional)
|
|
1638
|
+
# # When set to "raw", the storyboard requires
|
|
1639
|
+
# # raw payload introspection. Recorded calls
|
|
1640
|
+
# # returned in `attestation_mode: digest` grade
|
|
1641
|
+
# # the upstream_traffic check `not_applicable`
|
|
1642
|
+
# # rather than failing — the storyboard is
|
|
1643
|
+
# # asserting against payload contents the
|
|
1644
|
+
# # adopter's privacy policy doesn't permit
|
|
1645
|
+
# # disclosing.
|
|
1646
|
+
# # When omitted, the storyboard accepts either
|
|
1647
|
+
# # mode: assertions degrade gracefully in digest
|
|
1648
|
+
# # mode (`payload_must_contain` grades
|
|
1649
|
+
# # not_applicable per call; `identifier_paths`
|
|
1650
|
+
# # works via the digest-of-identifier echo
|
|
1651
|
+
# # mechanism, see "Digest-mode behavior" below).
|
|
1652
|
+
# # Use "raw" sparingly — the digest mode exists
|
|
1653
|
+
# # so EU/privacy-conscious adopters can support
|
|
1654
|
+
# # upstream_traffic conformance without returning
|
|
1655
|
+
# # hashed-PII payloads. Forcing raw excludes
|
|
1656
|
+
# # those adopters; only use it when the
|
|
1657
|
+
# # storyboard's assertion genuinely cannot be
|
|
1658
|
+
# # expressed in identifier-digest form.
|
|
1659
|
+
#
|
|
1660
|
+
# Digest-mode behavior:
|
|
1661
|
+
# `attestation_mode: digest` per recorded_call swaps payload introspection
|
|
1662
|
+
# for digest-based echo verification (see comply-test-controller-response.json
|
|
1663
|
+
# > UpstreamTrafficSuccess > recorded_calls > DigestAttestation):
|
|
1664
|
+
# - `payload_must_contain` arbitrary path assertions: NOT supported in
|
|
1665
|
+
# digest mode. Each entry grades `not_applicable` per affected call,
|
|
1666
|
+
# with a note explaining the mode constraint.
|
|
1667
|
+
# - JCS non-finite numbers: when a digest-mode upstream_traffic response
|
|
1668
|
+
# cannot be produced because the parsed JSON-like value tree contains
|
|
1669
|
+
# a non-finite numeric value (`NaN`, `+Infinity`, or `-Infinity`), the
|
|
1670
|
+
# controller MUST NOT coerce that value to `null`, a string, or any
|
|
1671
|
+
# other placeholder for digest computation; it returns ControllerError
|
|
1672
|
+
# with error code `JCS_NON_FINITE_NUMBER`; the runner grades the
|
|
1673
|
+
# affected validation `not_applicable`, not failed, with a note citing
|
|
1674
|
+
# the RFC 8785/JCS constraint.
|
|
1675
|
+
# - `payload_length`: MUST equal the exact byte length covered by
|
|
1676
|
+
# `payload_digest_sha256` — RFC 8785 (JCS) canonical bytes for
|
|
1677
|
+
# JSON-shaped content after redaction, or post-redaction raw body bytes
|
|
1678
|
+
# for non-JSON content. It is not the original outbound body length
|
|
1679
|
+
# before JSON parsing, redaction, or canonicalization.
|
|
1680
|
+
# - `identifier_paths` echo verification: supported via the controller's
|
|
1681
|
+
# `identifier_match_proofs[]`. The runner computes SHA-256 of each
|
|
1682
|
+
# resolved identifier value, sends the digests in
|
|
1683
|
+
# `params.identifier_value_digests`, and reads the per-digest
|
|
1684
|
+
# `found: true | false` proof from the response. Plaintext identifiers
|
|
1685
|
+
# never reach the controller; the controller never returns plaintext
|
|
1686
|
+
# payloads. The privacy boundary holds in both directions.
|
|
1687
|
+
# - `min_count` and `endpoint_pattern`: unchanged — count and URL
|
|
1688
|
+
# matching work identically in both modes.
|
|
1689
|
+
#
|
|
1690
|
+
# Grading output (see runner-output-contract.yaml > validation_result):
|
|
1691
|
+
# `expected` echoes the declared assertion fields. `actual` is a
|
|
1692
|
+
# diagnostic summary: { matched_count, total_calls, missing_payload_paths,
|
|
1693
|
+
# missing_identifier_values }. The full recorded_calls array lives in
|
|
1694
|
+
# `response.payload` so an implementor can see the exact upstream
|
|
1695
|
+
# payloads the runner observed.
|
|
1696
|
+
#
|
|
1697
|
+
# Adopter integration:
|
|
1698
|
+
# The adopter implements `query_upstream_traffic` in its
|
|
1699
|
+
# comply_test_controller. The controller records outbound HTTP calls in a
|
|
1700
|
+
# caller-scoped buffer (sandbox-only — production builds MUST 404 the
|
|
1701
|
+
# comply_test_controller tool entirely; multi-tenant sandboxes MUST key
|
|
1702
|
+
# the buffer on the calling principal so cross-caller traffic does not
|
|
1703
|
+
# leak). See comply-test-controller-request.json > query_upstream_traffic
|
|
1704
|
+
# for the controller-side query contract.
|
|
1705
|
+
#
|
|
1706
|
+
# Example — sync_audiences must POST hashed members upstream:
|
|
1707
|
+
# - check: upstream_traffic
|
|
1708
|
+
# description: "sync_audiences caused upstream traffic with hashed members"
|
|
1709
|
+
# endpoint_pattern: "POST *"
|
|
1710
|
+
# min_count: 1
|
|
1711
|
+
# payload_must_contain:
|
|
1712
|
+
# - path: "users[*].hashed_email"
|
|
1713
|
+
# match: present
|
|
1714
|
+
# identifier_paths:
|
|
1715
|
+
# - "audiences[*].add[*].hashed_email"
|
|
1716
|
+
# - "audiences[*].add[*].external_id"
|
|
1717
|
+
#
|
|
1718
|
+
# Cross-response assertions — `check: cross_response_field_equal` / `cross_response_count_distinct`:
|
|
1719
|
+
# These check kinds operate on the RESPONSE SET of a step that dispatches
|
|
1720
|
+
# multiple parallel requests under the `parallel_dispatch_runner` contract
|
|
1721
|
+
# (see test-kits/parallel-dispatch-runner.yaml). They are NOT applicable to
|
|
1722
|
+
# single-dispatch steps: a single response trivially has cardinality 1 on any
|
|
1723
|
+
# field. Runners MUST gate these checks on the step declaring
|
|
1724
|
+
# `requires_contract: parallel_dispatch_runner` and a `parallel_dispatch`
|
|
1725
|
+
# block; outside that gate the check MUST grade `not_applicable`. The
|
|
1726
|
+
# canonical use case is rule-9 testing (concurrent retries / first-insert-wins
|
|
1727
|
+
# from L1/security.mdx#idempotency) — fire N parallel calls with the same
|
|
1728
|
+
# `idempotency_key`, assert exactly one resource was created.
|
|
1729
|
+
#
|
|
1730
|
+
# `parallel_dispatch` is a step-level block that travels alongside `requires_contract`:
|
|
1731
|
+
#
|
|
1732
|
+
# - id: concurrent_create
|
|
1733
|
+
# task: create_media_buy
|
|
1734
|
+
# requires_contract: parallel_dispatch_runner
|
|
1735
|
+
# parallel_dispatch:
|
|
1736
|
+
# count: 2 # required, integer 2..10. The runner fires
|
|
1737
|
+
# # `count` logically-simultaneous requests
|
|
1738
|
+
# # using the SDK's batch primitive.
|
|
1739
|
+
# same_idempotency_key: true # required for rule-9 tests; future
|
|
1740
|
+
# # non-idempotency parallel tests MAY set
|
|
1741
|
+
# # this false.
|
|
1742
|
+
# barrier_timeout_ms: 5000 # optional; default 5000 — how long the
|
|
1743
|
+
# # runner waits at the dispatch barrier
|
|
1744
|
+
# # before firing requests anyway.
|
|
1745
|
+
# validations:
|
|
1746
|
+
# - check: cross_response_count_distinct
|
|
1747
|
+
# path: "media_buy_id"
|
|
1748
|
+
# allowed_values: [1]
|
|
1749
|
+
# description: "Exactly one media_buy_id across all dispatches"
|
|
1750
|
+
# - check: cross_response_field_equal
|
|
1751
|
+
# path: "media_buy_id"
|
|
1752
|
+
# description: "All dispatches converge on the same media_buy_id"
|
|
1753
|
+
#
|
|
1754
|
+
# `cross_response_field_equal`:
|
|
1755
|
+
# - path (string, required): JSON path checked on every resolved response.
|
|
1756
|
+
# All non-null observed values at `path` MUST be deeply equal. A missing
|
|
1757
|
+
# value on a response is treated as a non-equal observation and fails the
|
|
1758
|
+
# check.
|
|
1759
|
+
#
|
|
1760
|
+
# `cross_response_count_distinct`:
|
|
1761
|
+
# - path (string, required): JSON path scanned on every resolved response.
|
|
1762
|
+
# - allowed_values (array of integers, required): permitted cardinalities of
|
|
1763
|
+
# distinct values observed at `path` across the resolved response set.
|
|
1764
|
+
# For rule-9 tests, `allowed_values: [1]` — exactly one resource was
|
|
1765
|
+
# created across all dispatches.
|
|
1766
|
+
#
|
|
1767
|
+
# Resolution semantics. A seller adopting the reject-and-redirect policy under
|
|
1768
|
+
# rule 9 may return `IDEMPOTENCY_IN_FLIGHT` on the second (or later) dispatch
|
|
1769
|
+
# rather than block. The runner MUST resolve `IDEMPOTENCY_IN_FLIGHT` responses
|
|
1770
|
+
# to their eventual cached response before applying these checks: wait
|
|
1771
|
+
# `error.details.retry_after` seconds and retry with the SAME idempotency_key,
|
|
1772
|
+
# exactly as the buyer-side normative behavior specifies. The cross-response
|
|
1773
|
+
# check operates on the resolved response set, not the raw initial set. If
|
|
1774
|
+
# resolution exceeds the runner's step-timeout budget, the step grades
|
|
1775
|
+
# `step_timeout` (a runner-synthesized code), not a rule-9 violation.
|
|
1776
|
+
#
|
|
1777
|
+
# Grading output. `expected` carries the declared assertion fields
|
|
1778
|
+
# (`path`, plus `allowed_values` for `count_distinct`). `actual` summarizes
|
|
1779
|
+
# what was observed across the resolved response set:
|
|
1780
|
+
# cross_response_field_equal → { observed_values: [...] }
|
|
1781
|
+
# cross_response_count_distinct → { observed_cardinality, observed_values: [...] }
|
|
1782
|
+
# See runner-output-contract.yaml > validation_result > expected/actual for
|
|
1783
|
+
# the canonical shapes.
|
|
1784
|
+
#
|
|
1785
|
+
# --- Runner output ---
|
|
1786
|
+
#
|
|
1787
|
+
# How a runner MUST report validation results — including which fields a
|
|
1788
|
+
# failing validation carries (exact request, response, JSON Pointer, expected
|
|
1789
|
+
# vs. actual, schema $id, schema URL) and how skipped storyboards MUST
|
|
1790
|
+
# distinguish not_applicable vs. no_phases vs. missing_tool — is defined in
|
|
1791
|
+
# runner-output-contract.yaml. Storyboard authors SHOULD assume failures will
|
|
1792
|
+
# be rendered with that detail and write descriptions accordingly.
|
|
1793
|
+
#
|
|
1794
|
+
# Runner grading codes introduced by the context accumulator and fixture
|
|
1795
|
+
# seeding:
|
|
1796
|
+
#
|
|
1797
|
+
# capture_path_not_resolvable
|
|
1798
|
+
# Emitted on the capturing step when a `context_outputs:` entry's
|
|
1799
|
+
# `path` does not resolve to a usable value in the step's response body.
|
|
1800
|
+
# The step grades as failed — the capture declared a contract that the
|
|
1801
|
+
# response did not meet. Distinct from unresolved_substitution so tooling
|
|
1802
|
+
# can discriminate "producer contract breach" from "consumer missing
|
|
1803
|
+
# state."
|
|
1804
|
+
#
|
|
1805
|
+
# "Does not resolve to a usable value" covers all three cases:
|
|
1806
|
+
# 1. Path structurally absent (e.g. `signals[0]` when array is empty)
|
|
1807
|
+
# 2. Path present but value is null
|
|
1808
|
+
# 3. Path present but value is "" (empty string)
|
|
1809
|
+
# Capturing null or "" produces fabricated downstream state and is as
|
|
1810
|
+
# incorrect as a missing path; runners MUST treat all three as failures.
|
|
1811
|
+
#
|
|
1812
|
+
# Output shape: runner-output-contract.yaml > validation_result, with
|
|
1813
|
+
# check: capture_path_not_resolvable, expected: the declared path string,
|
|
1814
|
+
# actual: the resolved value (or null if absent), json_pointer: RFC 6901
|
|
1815
|
+
# form of the path.
|
|
1816
|
+
#
|
|
1817
|
+
# unresolved_substitution
|
|
1818
|
+
# Emitted on a consumer step when a referenced $context.<name> or
|
|
1819
|
+
# {{prior_step.<id>.<field>}} value is not populated at step-execution
|
|
1820
|
+
# time (either because the producer step failed, was skipped, or never
|
|
1821
|
+
# ran). The step grades as failed. Also used at preflight when a
|
|
1822
|
+
# substitution is statically unresolvable (e.g., {{runner.webhook_url:*}}
|
|
1823
|
+
# on a run with no webhook receiver) to grade the entire storyboard
|
|
1824
|
+
# not_applicable before execution.
|
|
1825
|
+
#
|
|
1826
|
+
# Output shape: runner-output-contract.yaml > validation_result, with
|
|
1827
|
+
# check: unresolved_substitution, expected: the unresolved token string,
|
|
1828
|
+
# actual: null, json_pointer: null (pre-wire failure — no response
|
|
1829
|
+
# payload available).
|
|
1830
|
+
#
|
|
1831
|
+
# fixture_seed_unsupported
|
|
1832
|
+
# Emitted when a storyboard's `prerequisites.controller_seeding` requires
|
|
1833
|
+
# a seed_* scenario the seller does not implement (returned as
|
|
1834
|
+
# UNKNOWN_SCENARIO). The storyboard grades not_applicable (coverage gap),
|
|
1835
|
+
# not failed.
|
|
1836
|
+
#
|
|
1837
|
+
# unresolved_scenario_reference
|
|
1838
|
+
# A detailed sub-reason under the canonical skip `reason:
|
|
1839
|
+
# not_applicable`. Emitted when a parent storyboard's
|
|
1840
|
+
# `requires_scenarios:` entry cannot be resolved against the source
|
|
1841
|
+
# tree (no file declares that `id:`). Per the
|
|
1842
|
+
# `detailed_reason_mapping` convention in runner-output-contract.yaml,
|
|
1843
|
+
# runners MUST populate the canonical `reason: not_applicable` and
|
|
1844
|
+
# encode `unresolved_scenario_reference` in `detail`. detail MUST
|
|
1845
|
+
# follow the shape
|
|
1846
|
+
# 'requires_scenarios reference "<scenario_id>" did not resolve
|
|
1847
|
+
# against the source tree' — and when multiple references are
|
|
1848
|
+
# unresolved, detail MUST enumerate every unresolved id with the
|
|
1849
|
+
# same shape (comma-separated or newline-delimited) so downstream
|
|
1850
|
+
# tooling can parse each. Runners MUST NOT silently pass the parent
|
|
1851
|
+
# as if the missing scenario had contributed.
|
|
1852
|
+
#
|
|
1853
|
+
# Distinct from `fixture_seed_unsupported`: that reason indicates an
|
|
1854
|
+
# agent-coverage gap (seller doesn't implement a seed scenario);
|
|
1855
|
+
# `unresolved_scenario_reference` indicates a source-tree authoring
|
|
1856
|
+
# bug that the build-time lint SHOULD have caught. Dashboards and
|
|
1857
|
+
# summary reports SHOULD NOT conflate the two when aggregating
|
|
1858
|
+
# `not_applicable` counts — they are categorically different
|
|
1859
|
+
# signals.
|
|
1860
|
+
#
|
|
1861
|
+
# The `lint:storyboard-branch-sets` script surfaces this as a
|
|
1862
|
+
# build-time error via the `unresolved_scenario_reference` rule so
|
|
1863
|
+
# unresolved references in a well-linted corpus never reach a
|
|
1864
|
+
# runner. The runner-side grading is defensive for corpora that
|
|
1865
|
+
# bypass the lint or come from pre-lint CI states.
|
|
1866
|
+
#
|
|
1867
|
+
# --- Webhook receiver (optional, for outbound-webhook conformance) ---
|
|
1868
|
+
#
|
|
1869
|
+
# Storyboards that verify outbound webhook conformance (signing, idempotency_key
|
|
1870
|
+
# presence, idempotency_key stability across retries) require the runner to host
|
|
1871
|
+
# a webhook receiver during test execution. The receiver URL is injected into
|
|
1872
|
+
# push_notification_config on steps that trigger webhooks, and a subsequent
|
|
1873
|
+
# expect_webhook* step asserts on what arrived.
|
|
1874
|
+
#
|
|
1875
|
+
# Receiver behavior is specified by a test-kit contract referenced from the
|
|
1876
|
+
# storyboard's prerequisites.test_kit field — see test-kits/webhook-receiver-runner.yaml
|
|
1877
|
+
# for the reference contract (endpoint modes, retry-replay shape, client-primitive
|
|
1878
|
+
# hooks). Storyboard authors MUST declare the contract in prerequisites; the
|
|
1879
|
+
# webhook-emission universal (universal/webhook-emission.yaml) is the reference
|
|
1880
|
+
# consumer, and universal/idempotency.yaml uses it for the "no duplicate webhooks
|
|
1881
|
+
# on replay" assertion.
|
|
1882
|
+
#
|
|
1883
|
+
# --- Substitution variables (webhook-receiver enabled) ---
|
|
1884
|
+
#
|
|
1885
|
+
# When a storyboard's test-kit contract declares a webhook_receiver, the runner
|
|
1886
|
+
# exposes these substitutions for use in sample_request payloads and expectations:
|
|
1887
|
+
#
|
|
1888
|
+
# {{runner.webhook_base}} → HTTPS base of the runner's receiver for this run
|
|
1889
|
+
# {{runner.webhook_url:<step_id>}} → per-step URL; unique per step
|
|
1890
|
+
#
|
|
1891
|
+
# Per-step receivers are the only supported shape in v1 so each step's
|
|
1892
|
+
# expect_webhook matches only its own deliveries. Fan-in scenarios are deferred
|
|
1893
|
+
# (see "shared_receiver" note below).
|
|
1894
|
+
#
|
|
1895
|
+
# --- Webhook-assertion steps ---
|
|
1896
|
+
#
|
|
1897
|
+
# The following step `task` values are only valid when the storyboard's test-kit
|
|
1898
|
+
# declares a webhook_receiver. They are scheduled alongside regular task steps
|
|
1899
|
+
# and reference earlier steps by id.
|
|
1900
|
+
#
|
|
1901
|
+
# task: expect_webhook
|
|
1902
|
+
# Wait up to `timeout_seconds` (default 30) for a webhook matching the filter,
|
|
1903
|
+
# then validate. Runner delegates to the `@adcp/client` AsyncHandler; the webhook
|
|
1904
|
+
# is observed via onActivity with type webhook_received, and WebhookMetadata
|
|
1905
|
+
# (including idempotency_key) is provided by the client.
|
|
1906
|
+
#
|
|
1907
|
+
# Fields:
|
|
1908
|
+
# triggered_by: <step_id> # earlier step whose task triggered this webhook
|
|
1909
|
+
# filter: # match by payload fields
|
|
1910
|
+
# operation_id: "{{prior_step.<id>.operation_id}}"
|
|
1911
|
+
# status: "completed" # optional; match any status if omitted
|
|
1912
|
+
# timeout_seconds: 30 # optional; fail if no match within window
|
|
1913
|
+
# expect_idempotency_key: true # default true; assert the key is present and pattern-valid
|
|
1914
|
+
# webhook_payload_schema_ref: <path>
|
|
1915
|
+
# # optional — validate payload against the webhook schema
|
|
1916
|
+
# # (distinct from step-level `schema_ref` and
|
|
1917
|
+
# # `response_schema_ref`, which apply to the caller→agent
|
|
1918
|
+
# # request/response. Webhook steps have no caller request,
|
|
1919
|
+
# # so the payload schema is named explicitly to avoid
|
|
1920
|
+
# # overload of schema_ref.)
|
|
1921
|
+
# expect_max_deliveries_per_logical_event: 1
|
|
1922
|
+
# # optional — if set, runner asserts at most N distinct
|
|
1923
|
+
# # logical webhook events (grouped by idempotency_key)
|
|
1924
|
+
# # arrive within the window. Used by idempotency storyboards
|
|
1925
|
+
# # to catch duplicate-side-effect bugs where a seller
|
|
1926
|
+
# # re-executes on replay with a fresh idempotency_key.
|
|
1927
|
+
# requires_contract: <contract_id>
|
|
1928
|
+
# # optional — step grades as not_applicable (not fail)
|
|
1929
|
+
# # when the named test-kit contract is not in scope.
|
|
1930
|
+
# # Used on cross-specialism assertions that depend on
|
|
1931
|
+
# # webhook_receiver_runner being active.
|
|
1932
|
+
#
|
|
1933
|
+
# Error modes: no_webhook_received, schema_violation, missing_idempotency_key,
|
|
1934
|
+
# invalid_idempotency_key_format, signature_invalid (when signature assertion on),
|
|
1935
|
+
# duplicate_webhook_on_replay (when expect_max_deliveries_per_logical_event exceeded).
|
|
1936
|
+
#
|
|
1937
|
+
# task: expect_webhook_retry_keys_stable
|
|
1938
|
+
# The runner's webhook receiver deterministically returns 5xx for the first
|
|
1939
|
+
# `retry_trigger.count` deliveries of the matching webhook, then 2xx. The
|
|
1940
|
+
# runner asserts that every delivery within the retry window carries the
|
|
1941
|
+
# byte-identical idempotency_key (the sender regenerating a key on retry is a
|
|
1942
|
+
# conformance failure).
|
|
1943
|
+
#
|
|
1944
|
+
# Signature verification on every delivery in the retry loop: when 9421 is in
|
|
1945
|
+
# effect (i.e., push_notification_config was registered without `authentication`),
|
|
1946
|
+
# the runner MUST verify the 9421 signature on EVERY delivery including retries
|
|
1947
|
+
# and MUST reuse the run-scoped (keyid, nonce) replay store (see test-kit
|
|
1948
|
+
# client_primitives.signature_replay_store). A publisher that stably reuses
|
|
1949
|
+
# idempotency_key (correct) but also reuses the 9421 `nonce` sig-param
|
|
1950
|
+
# (incorrect — nonce MUST be fresh per delivery) would pass retry-stability
|
|
1951
|
+
# but MUST fail signature-replay dedup on the second delivery. Running the two
|
|
1952
|
+
# checks together catches this class of bug; a retry-stability step that skips
|
|
1953
|
+
# signature verification hides it.
|
|
1954
|
+
#
|
|
1955
|
+
# Fields:
|
|
1956
|
+
# triggered_by: <step_id>
|
|
1957
|
+
# filter: { ... } # same shape as expect_webhook
|
|
1958
|
+
# retry_trigger:
|
|
1959
|
+
# count: 3 # runner returns 5xx for the first N deliveries.
|
|
1960
|
+
# # MUST be 1 ≤ count ≤ 10 (test-kit
|
|
1961
|
+
# # retry_replay_contract.max_count). Counts outside
|
|
1962
|
+
# # the range fail storyboard validation; sellers
|
|
1963
|
+
# # legitimately exercise N=3 for back-off sanity,
|
|
1964
|
+
# # higher counts risk becoming DoS amplifiers in
|
|
1965
|
+
# # proxy_url mode.
|
|
1966
|
+
# http_status: 503 # optional; default 503. MUST be in
|
|
1967
|
+
# # {429, 500, 502, 503, 504} (test-kit
|
|
1968
|
+
# # retry_replay_contract.allowed_statuses). Other
|
|
1969
|
+
# # statuses are not retryable signals for
|
|
1970
|
+
# # at-least-once senders and fail validation.
|
|
1971
|
+
# timeout_seconds: 90 # longer default — must cover sender's retry back-off
|
|
1972
|
+
# expect_min_deliveries: 2 # fail if fewer deliveries observed within window
|
|
1973
|
+
# verify_signature_on_every_delivery: true
|
|
1974
|
+
# # default true when 9421 is in effect. Set false
|
|
1975
|
+
# # ONLY when the storyboard is explicitly registered
|
|
1976
|
+
# # for legacy HMAC mode; in 9421 mode, silently
|
|
1977
|
+
# # disabling retry-delivery signature verification
|
|
1978
|
+
# # hides nonce-replay bugs.
|
|
1979
|
+
#
|
|
1980
|
+
# Error modes: insufficient_retries, idempotency_key_rotated,
|
|
1981
|
+
# idempotency_key_format_changed, signature_replayed (nonce reused across
|
|
1982
|
+
# retries), signature_invalid (any retry delivery fails 9421).
|
|
1983
|
+
#
|
|
1984
|
+
# task: expect_webhook_signature_valid
|
|
1985
|
+
# Assert that the inbound webhook passed the 9421 webhook-signing verifier
|
|
1986
|
+
# checklist (see docs/building/implementation/security.mdx#verifier-checklist-for-webhooks).
|
|
1987
|
+
# Runner delegates verification to the `@adcp/client` 9421 webhook verifier;
|
|
1988
|
+
# this step is gated on the client library having that verifier available
|
|
1989
|
+
# (see test-kit contract). Negative conformance — signatures that MUST be
|
|
1990
|
+
# rejected with a specific webhook_signature_* code — is tested via static
|
|
1991
|
+
# conformance vectors under /compliance/{version}/test-vectors/webhook-signing/
|
|
1992
|
+
# when those land, not by this step type.
|
|
1993
|
+
#
|
|
1994
|
+
# Cross-step replay dedup: the runner MUST share a single (keyid, nonce) replay
|
|
1995
|
+
# store across ALL expect_webhook_signature_valid invocations (and all retry
|
|
1996
|
+
# deliveries under expect_webhook_retry_keys_stable) in a storyboard run — see
|
|
1997
|
+
# test-kit client_primitives.signature_replay_store. A per-step store would let
|
|
1998
|
+
# a publisher reuse the same (keyid, nonce) bytes at two expect_webhook_signature_valid
|
|
1999
|
+
# steps and pass both in isolation, silently missing cross-step replay.
|
|
2000
|
+
#
|
|
2001
|
+
# Fields:
|
|
2002
|
+
# triggered_by: <step_id>
|
|
2003
|
+
# filter: { ... } # same shape as expect_webhook
|
|
2004
|
+
# timeout_seconds: 30
|
|
2005
|
+
# require_tag: "adcp/webhook-signing/v1" # default; sanity check
|
|
2006
|
+
#
|
|
2007
|
+
# Error modes: signature_invalid, signature_expired, signature_key_unknown,
|
|
2008
|
+
# signature_key_purpose_invalid, signature_digest_mismatch, signature_tag_invalid,
|
|
2009
|
+
# signature_replayed.
|
|
2010
|
+
#
|
|
2011
|
+
# task: expect_substitution_safe
|
|
2012
|
+
# Assert that a creative preview (preview_html or preview_url) contains
|
|
2013
|
+
# substituted catalog-item macro values that are percent-encoded per RFC 3986
|
|
2014
|
+
# (unreserved-whitelist), per docs/creative/universal-macros#substitution-safety-catalog-item-macros.
|
|
2015
|
+
# Runner delegates HTML parsing, URL extraction, and the encoding check to
|
|
2016
|
+
# the `@adcp/client` SubstitutionObserver primitive (see test-kit
|
|
2017
|
+
# substitution-observer-runner.yaml). Catches implementations that pass raw
|
|
2018
|
+
# attacker bytes through substitution, including CRLF injection, bidi-override
|
|
2019
|
+
# spoofing, reserved-character URL breakout, and javascript:-scheme injection.
|
|
2020
|
+
#
|
|
2021
|
+
# Fields:
|
|
2022
|
+
# source: html_inline | url_fetch # default html_inline
|
|
2023
|
+
# source_path: "<JSON pointer into previous step's response>"
|
|
2024
|
+
# macro_template: "<URL template with {MACRO} placeholders>"
|
|
2025
|
+
# catalog_bindings:
|
|
2026
|
+
# - macro: "{SKU}"
|
|
2027
|
+
# catalog_item_id: "<item_id in the synced catalog>"
|
|
2028
|
+
# vector_name: "<fixture entry name, e.g. reserved-character-breakout>"
|
|
2029
|
+
# # Optional overrides for custom (non-canonical-fixture) vectors:
|
|
2030
|
+
# # raw_value: "<attacker-shaped input>"
|
|
2031
|
+
# # expected_encoded: "<RFC 3986 unreserved-encoded form>"
|
|
2032
|
+
# require_every_binding_observed: true # default true. Opt to false only
|
|
2033
|
+
# # when preview shows a partial
|
|
2034
|
+
# # catalog and partial observation
|
|
2035
|
+
# # is legitimate (document why in
|
|
2036
|
+
# # the narrative). The old field
|
|
2037
|
+
# # name require_all_bindings_observed
|
|
2038
|
+
# # is deprecated.
|
|
2039
|
+
# requires_contract: substitution_observer_runner
|
|
2040
|
+
#
|
|
2041
|
+
# Error modes: substitution_encoding_violation, nested_macro_re_expansion,
|
|
2042
|
+
# substitution_scheme_injection, substitution_binding_missing,
|
|
2043
|
+
# preview_source_unavailable, preview_url_unusable (with sub-reason:
|
|
2044
|
+
# http_status | content_type | size_exceeded | redirect_returned |
|
|
2045
|
+
# ssrf_blocked | fetch_timeout).
|
|
2046
|
+
#
|
|
2047
|
+
# task: expect_rate_limit_not_replayed
|
|
2048
|
+
# Assert that a `RATE_LIMITED` response on idempotency-cache insert MUST NOT
|
|
2049
|
+
# be cached as the canonical replay for that idempotency_key, per
|
|
2050
|
+
# L1/security.mdx#idempotency rule 3 ("Only successful responses are cached")
|
|
2051
|
+
# and bullet 8 (insert-rate ceiling). The runner drives a sequential fresh-
|
|
2052
|
+
# key burst against the named mutating-op target until a RATE_LIMITED
|
|
2053
|
+
# response appears, then re-submits the same idempotency_key after waiting
|
|
2054
|
+
# `error.details.retry_after` seconds. The cross-step assertion fails if the
|
|
2055
|
+
# replay returned the cached RATE_LIMITED response shape.
|
|
2056
|
+
#
|
|
2057
|
+
# When `max_attempts` requests complete without producing a RATE_LIMITED
|
|
2058
|
+
# response, the step grades as `not_applicable` with reason
|
|
2059
|
+
# `rate_limit_not_triggered` — a seller's sandbox limiter may legitimately
|
|
2060
|
+
# sit above the contract's burst ceiling. Runner delegates the burst,
|
|
2061
|
+
# wait, and replay mechanics to the `@adcp/client` RateLimitTripObserver
|
|
2062
|
+
# primitive (see test-kit rate-limit-trip-runner.yaml).
|
|
2063
|
+
#
|
|
2064
|
+
# Fields:
|
|
2065
|
+
# rate_limit_trip:
|
|
2066
|
+
# trip_target_task: "<task name, e.g. create_media_buy>"
|
|
2067
|
+
# # the mutating-op the burst targets
|
|
2068
|
+
# trip_target_sample_request: { ... }
|
|
2069
|
+
# # full canonical request body; the runner
|
|
2070
|
+
# # rewrites `idempotency_key` per attempt
|
|
2071
|
+
# # and on the replay reuses the captured key
|
|
2072
|
+
# max_attempts: 200 # 50–500; see test-kit max_attempts bounds
|
|
2073
|
+
# replay_max_wait_seconds: 30 # optional; caps the post-trip wait. Sellers
|
|
2074
|
+
# # returning retry_after > this fail with
|
|
2075
|
+
# # `replay_wait_exceeded` rather than the
|
|
2076
|
+
# # invariant itself.
|
|
2077
|
+
# requires_contract: rate_limit_trip_runner
|
|
2078
|
+
#
|
|
2079
|
+
# Error modes: rate_limit_response_cached_as_replay,
|
|
2080
|
+
# missing_retry_after, replay_wait_exceeded, rate_limit_not_triggered
|
|
2081
|
+
# (the not_applicable case, surfaced for runner output transparency).
|
|
2082
|
+
#
|
|
2083
|
+
# --- shared_receiver (deferred / out of scope v1) ---
|
|
2084
|
+
#
|
|
2085
|
+
# Storyboards with multiple webhook-triggering steps MUST use per-step receiver
|
|
2086
|
+
# URLs; the default substitution `{{runner.webhook_url:<step_id>}}` gives each
|
|
2087
|
+
# step its own receiver so expect_webhook scopes cleanly. A `shared_receiver: true`
|
|
2088
|
+
# flag on an emitting step would route all emissions from that step to a shared
|
|
2089
|
+
# bucket (for fan-in dedup tests), but the mechanics — filter matching across
|
|
2090
|
+
# multiple emitters, retry-replay policy precedence, shared replay store scoping —
|
|
2091
|
+
# are underspecified. shared_receiver is OUT OF SCOPE for the first
|
|
2092
|
+
# webhook-emission universal release; storyboard authors MUST NOT set it. Future
|
|
2093
|
+
# fan-in storyboards will either flesh out the semantics or adopt a different
|
|
2094
|
+
# idiom (e.g., per-step receivers with cross-step assertions on aggregate
|
|
2095
|
+
# observations).
|
|
2096
|
+
#
|
|
2097
|
+
# --- Context accumulator and substitution ---
|
|
2098
|
+
#
|
|
2099
|
+
# The runner maintains a storyboard-run-scoped context accumulator populated from
|
|
2100
|
+
# three sources (evaluated in this precedence, high to low):
|
|
2101
|
+
#
|
|
2102
|
+
# 1. Step `context_outputs:` captures (populated after a step's validations pass)
|
|
2103
|
+
# 2. Storyboard-root `context:` block (literal values fixed at run start)
|
|
2104
|
+
# 3. Test-kit substitutions where explicitly scoped into context
|
|
2105
|
+
#
|
|
2106
|
+
# Two substitution syntaxes are supported in `sample_request` payloads:
|
|
2107
|
+
#
|
|
2108
|
+
# $context.<name> — single-token substitution. The runner replaces
|
|
2109
|
+
# the literal string "$context.<name>" with the
|
|
2110
|
+
# captured value. Example:
|
|
2111
|
+
# account_id: '$context.account_id'
|
|
2112
|
+
# resolves to the account_id captured from an
|
|
2113
|
+
# earlier step. Works inside scalar values and
|
|
2114
|
+
# inside array elements.
|
|
2115
|
+
#
|
|
2116
|
+
# {{prior_step.<id>.<field>}} — Mustache-style interpolation for webhook
|
|
2117
|
+
# filters, operation_id matching, and any other
|
|
2118
|
+
# cross-step reference. Resolves against the
|
|
2119
|
+
# named prior step's captured response fields.
|
|
2120
|
+
# Used by expect_webhook and expect_*_webhook_*
|
|
2121
|
+
# assertion steps (see Webhook-assertion steps
|
|
2122
|
+
# above) and by storyboards correlating an
|
|
2123
|
+
# async operation_id across steps.
|
|
2124
|
+
#
|
|
2125
|
+
# Runner requirements:
|
|
2126
|
+
# - Substitution runs at step-execution time, after resolving the test-kit
|
|
2127
|
+
# overlay and before applying auth. The agent under test receives the
|
|
2128
|
+
# substituted value — it MUST NOT see the literal `$context.foo` or `{{...}}`
|
|
2129
|
+
# token on the wire (see Unresolved substitution behavior below for the one
|
|
2130
|
+
# legitimate exception: preflight not_applicable grading when a substitution
|
|
2131
|
+
# is statically unresolvable).
|
|
2132
|
+
# - $context.<name> referencing an unpopulated name (the capturing step failed
|
|
2133
|
+
# or did not run) grades the referencing step as failed with
|
|
2134
|
+
# `unresolved_substitution`. The storyboard MUST NOT proceed with a fabricated
|
|
2135
|
+
# default — test outcomes on synthesized state are not signal.
|
|
2136
|
+
# - Captured values are typed as the JSON path resolved against the response;
|
|
2137
|
+
# numeric and object captures are preserved (they do not round-trip through
|
|
2138
|
+
# string coercion).
|
|
2139
|
+
# - When the per-task request builder discards body fields and
|
|
2140
|
+
# `$context.<name>` inside `sample_request` never reaches the wire (the
|
|
2141
|
+
# observable symptom is the agent answering with an unfiltered result
|
|
2142
|
+
# and the round-trip assertion grading on fabricated state), use
|
|
2143
|
+
# `context_inputs:` as the post-builder injection path. See the
|
|
2144
|
+
# `context_inputs:` field definition above for the full semantics and
|
|
2145
|
+
# when it is acceptable to reach for.
|
|
2146
|
+
#
|
|
2147
|
+
# --- Context echo contract (agent-facing) ---
|
|
2148
|
+
#
|
|
2149
|
+
# Agents under test MUST obey the following when a storyboard sends a `context:`
|
|
2150
|
+
# block on its sample_request:
|
|
2151
|
+
#
|
|
2152
|
+
# 1. Successful responses MUST echo the `context` object verbatim. The agent
|
|
2153
|
+
# MUST NOT mutate, filter, or reorder fields. The echo applies whether the
|
|
2154
|
+
# response is synchronous (`completed`) or asynchronous (`submitted` /
|
|
2155
|
+
# `working`).
|
|
2156
|
+
# 2. Error responses MUST also echo the `context` object verbatim. Context is
|
|
2157
|
+
# used as a correlation surface; dropping it on failure prevents buyers from
|
|
2158
|
+
# correlating errors with the originating call.
|
|
2159
|
+
# 3. Agents MUST NOT synthesize a `context` object when the caller did not
|
|
2160
|
+
# send one. A storyboard whose `sample_request` lacks `context:` MUST see
|
|
2161
|
+
# the response lack `context:` — fabricating `{ correlation_id: "..." }` on
|
|
2162
|
+
# the agent side is a conformance failure because the storyboard validator
|
|
2163
|
+
# is asserting on what-the-caller-sent, not what-the-agent-invented.
|
|
2164
|
+
# 4. Agents MUST NOT parse or act on `context` contents — see
|
|
2165
|
+
# docs/building/integration/context-sessions.mdx for the normative
|
|
2166
|
+
# agent-facing rules. The storyboard runner's validators rely on verbatim
|
|
2167
|
+
# echo and will fail any mutation.
|
|
2168
|
+
#
|
|
2169
|
+
# Runners MUST NOT auto-inject an implicit `context:` block on sample_request
|
|
2170
|
+
# payloads the storyboard did not declare. Storyboards that want to assert on
|
|
2171
|
+
# `context.correlation_id` MUST declare the context block explicitly on each
|
|
2172
|
+
# sample_request. This keeps the sample_request an honest shape of what the
|
|
2173
|
+
# agent will receive, and prevents an agent from passing only because the
|
|
2174
|
+
# runner compensated for its missing contract.
|
|
2175
|
+
#
|
|
2176
|
+
# --- Unresolved substitution behavior ---
|
|
2177
|
+
#
|
|
2178
|
+
# When a storyboard references a substitution variable the runner cannot
|
|
2179
|
+
# resolve — most commonly {{runner.webhook_base}} or {{runner.webhook_url:<id>}}
|
|
2180
|
+
# on a run with no webhook receiver configured — the runner MUST NOT ship the
|
|
2181
|
+
# literal `{{...}}` token to the agent under test. Shipping unresolved
|
|
2182
|
+
# substitutions is a conformance bug on the runner side, not a grading signal
|
|
2183
|
+
# about the agent.
|
|
2184
|
+
#
|
|
2185
|
+
# Runners MUST resolve substitutions at one of two preflight points:
|
|
2186
|
+
# 1. Before the storyboard run starts — if any step references an unresolvable
|
|
2187
|
+
# substitution, grade the storyboard as not_applicable with
|
|
2188
|
+
# `unresolved_substitution` as the reason, and skip execution entirely.
|
|
2189
|
+
# This is the preferred behavior for absent webhook receivers.
|
|
2190
|
+
# 2. Before individual step execution — if a later-computed substitution
|
|
2191
|
+
# (e.g., {{prior_step.<id>.operation_id}}) fails to resolve, grade the
|
|
2192
|
+
# step as failed with `unresolved_substitution`.
|
|
2193
|
+
#
|
|
2194
|
+
# Neither path may ship a literal `{{...}}` token on the wire.
|