@adcp/sdk 6.9.0 → 6.11.0

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.
Files changed (261) hide show
  1. package/bin/adcp.js +285 -5
  2. package/compliance/cache/3.0.6.previous/domains/brand/index.yaml +163 -0
  3. package/compliance/cache/3.0.6.previous/domains/creative/index.yaml +412 -0
  4. package/compliance/cache/3.0.6.previous/domains/governance/index.yaml +683 -0
  5. package/compliance/cache/3.0.6.previous/domains/media-buy/creative-reception.yaml +247 -0
  6. package/compliance/cache/3.0.6.previous/domains/media-buy/index.yaml +769 -0
  7. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/create_media_buy_async.yaml +232 -0
  8. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml +414 -0
  9. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/delivery_reporting.yaml +205 -0
  10. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/governance_approved.yaml +211 -0
  11. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/governance_conditions.yaml +196 -0
  12. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/governance_denied.yaml +192 -0
  13. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/governance_denied_recovery.yaml +244 -0
  14. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/invalid_transitions.yaml +284 -0
  15. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/inventory_list_no_match.yaml +143 -0
  16. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/inventory_list_targeting.yaml +271 -0
  17. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/measurement_terms_rejected.yaml +195 -0
  18. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/pending_creatives_to_start.yaml +250 -0
  19. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/proposal_finalize.yaml +243 -0
  20. package/compliance/cache/3.0.6.previous/domains/media-buy/scenarios/refine_products.yaml +148 -0
  21. package/compliance/cache/3.0.6.previous/domains/media-buy/state-machine.yaml +442 -0
  22. package/compliance/cache/3.0.6.previous/domains/signals/index.yaml +266 -0
  23. package/compliance/cache/3.0.6.previous/domains/sponsored-intelligence/index.yaml +256 -0
  24. package/compliance/cache/3.0.6.previous/index.json +324 -0
  25. package/compliance/cache/3.0.6.previous/protocols/brand/index.yaml +163 -0
  26. package/compliance/cache/3.0.6.previous/protocols/creative/index.yaml +412 -0
  27. package/compliance/cache/3.0.6.previous/protocols/governance/index.yaml +683 -0
  28. package/compliance/cache/3.0.6.previous/protocols/media-buy/creative-reception.yaml +247 -0
  29. package/compliance/cache/3.0.6.previous/protocols/media-buy/index.yaml +769 -0
  30. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/create_media_buy_async.yaml +232 -0
  31. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml +414 -0
  32. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/delivery_reporting.yaml +205 -0
  33. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/governance_approved.yaml +211 -0
  34. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/governance_conditions.yaml +196 -0
  35. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/governance_denied.yaml +192 -0
  36. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/governance_denied_recovery.yaml +244 -0
  37. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/invalid_transitions.yaml +284 -0
  38. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/inventory_list_no_match.yaml +143 -0
  39. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/inventory_list_targeting.yaml +271 -0
  40. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/measurement_terms_rejected.yaml +195 -0
  41. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/pending_creatives_to_start.yaml +250 -0
  42. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/proposal_finalize.yaml +243 -0
  43. package/compliance/cache/3.0.6.previous/protocols/media-buy/scenarios/refine_products.yaml +148 -0
  44. package/compliance/cache/3.0.6.previous/protocols/media-buy/state-machine.yaml +442 -0
  45. package/compliance/cache/3.0.6.previous/protocols/signals/index.yaml +266 -0
  46. package/compliance/cache/3.0.6.previous/protocols/sponsored-intelligence/index.yaml +256 -0
  47. package/compliance/cache/3.0.6.previous/specialisms/audience-sync/index.yaml +280 -0
  48. package/compliance/cache/3.0.6.previous/specialisms/brand-rights/index.yaml +350 -0
  49. package/compliance/cache/3.0.6.previous/specialisms/brand-rights/scenarios/governance_denied.yaml +204 -0
  50. package/compliance/cache/3.0.6.previous/specialisms/collection-lists/index.yaml +359 -0
  51. package/compliance/cache/3.0.6.previous/specialisms/content-standards/index.yaml +572 -0
  52. package/compliance/cache/3.0.6.previous/specialisms/creative-ad-server/index.yaml +383 -0
  53. package/compliance/cache/3.0.6.previous/specialisms/creative-generative/generative-seller.yaml +758 -0
  54. package/compliance/cache/3.0.6.previous/specialisms/creative-generative/index.yaml +746 -0
  55. package/compliance/cache/3.0.6.previous/specialisms/creative-template/index.yaml +413 -0
  56. package/compliance/cache/3.0.6.previous/specialisms/governance-aware-seller/index.yaml +136 -0
  57. package/compliance/cache/3.0.6.previous/specialisms/governance-delivery-monitor/index.yaml +441 -0
  58. package/compliance/cache/3.0.6.previous/specialisms/governance-spend-authority/denied.yaml +221 -0
  59. package/compliance/cache/3.0.6.previous/specialisms/governance-spend-authority/index.yaml +330 -0
  60. package/compliance/cache/3.0.6.previous/specialisms/property-lists/index.yaml +482 -0
  61. package/compliance/cache/3.0.6.previous/specialisms/sales-broadcast-tv/index.yaml +689 -0
  62. package/compliance/cache/3.0.6.previous/specialisms/sales-catalog-driven/index.yaml +779 -0
  63. package/compliance/cache/3.0.6.previous/specialisms/sales-guaranteed/index.yaml +504 -0
  64. package/compliance/cache/3.0.6.previous/specialisms/sales-non-guaranteed/index.yaml +428 -0
  65. package/compliance/cache/3.0.6.previous/specialisms/sales-proposal-mode/index.yaml +520 -0
  66. package/compliance/cache/3.0.6.previous/specialisms/sales-social/index.yaml +584 -0
  67. package/compliance/cache/3.0.6.previous/specialisms/signal-marketplace/index.yaml +415 -0
  68. package/compliance/cache/3.0.6.previous/specialisms/signal-marketplace/scenarios/governance_denied.yaml +207 -0
  69. package/compliance/cache/3.0.6.previous/specialisms/signal-owned/index.yaml +316 -0
  70. package/compliance/cache/3.0.6.previous/test-kits/acme-outdoor.yaml +210 -0
  71. package/compliance/cache/3.0.6.previous/test-kits/bistro-oranje.yaml +126 -0
  72. package/compliance/cache/3.0.6.previous/test-kits/nova-motors.yaml +262 -0
  73. package/compliance/cache/3.0.6.previous/test-kits/osei-natural.yaml +126 -0
  74. package/compliance/cache/3.0.6.previous/test-kits/signed-requests-runner.yaml +155 -0
  75. package/compliance/cache/3.0.6.previous/test-kits/substitution-observer-runner.yaml +690 -0
  76. package/compliance/cache/3.0.6.previous/test-kits/summit-foods.yaml +125 -0
  77. package/compliance/cache/3.0.6.previous/test-kits/webhook-receiver-runner.yaml +265 -0
  78. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/001-minimal-plan.json +43 -0
  79. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/002-full-plan.json +217 -0
  80. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/003-bookkeeping-stripped.json +60 -0
  81. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/004a-human-review-omitted.json +43 -0
  82. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/004b-human-review-explicit-null.json +49 -0
  83. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/005a-policy-categories-order-1.json +53 -0
  84. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/005b-policy-categories-order-2.json +57 -0
  85. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/006a-ext-trace-v1.json +49 -0
  86. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/006b-ext-trace-v2.json +53 -0
  87. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/007-unicode-objectives.json +43 -0
  88. package/compliance/cache/3.0.6.previous/test-vectors/plan-hash/008-numeric-canonicalization.json +65 -0
  89. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/README.md +219 -0
  90. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/canonicalization.json +241 -0
  91. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/keys.json +60 -0
  92. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/001-no-signature-header.json +24 -0
  93. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/002-wrong-tag.json +26 -0
  94. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/003-expired-signature.json +26 -0
  95. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/004-window-too-long.json +26 -0
  96. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/005-alg-not-allowed.json +26 -0
  97. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/006-missing-covered-component.json +26 -0
  98. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/007-missing-content-digest.json +26 -0
  99. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/008-unknown-keyid.json +26 -0
  100. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/009-key-ops-missing-verify.json +27 -0
  101. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/010-content-digest-mismatch.json +33 -0
  102. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/011-malformed-header.json +27 -0
  103. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/012-missing-expires-param.json +26 -0
  104. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/013-expires-le-created.json +27 -0
  105. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/014-missing-nonce-param.json +27 -0
  106. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/015-signature-invalid.json +28 -0
  107. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/016-replayed-nonce.json +35 -0
  108. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/017-key-revoked.json +38 -0
  109. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/018-digest-covered-when-forbidden.json +28 -0
  110. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/019-signature-without-signature-input.json +26 -0
  111. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/020-rate-abuse.json +34 -0
  112. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/021-duplicate-signature-input-label.json +31 -0
  113. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/022-multi-valued-content-type.json +31 -0
  114. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/023-multi-valued-content-digest.json +32 -0
  115. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/024-unquoted-string-param.json +31 -0
  116. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/025-jwk-alg-crv-mismatch.json +43 -0
  117. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/026-non-ascii-host.json +31 -0
  118. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/negative/027-webhook-registration-authentication-unsigned.json +25 -0
  119. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/001-basic-post.json +30 -0
  120. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/002-post-with-content-digest.json +31 -0
  121. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/003-es256-post.json +30 -0
  122. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/004-multiple-signature-labels.json +26 -0
  123. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/005-default-port-stripped.json +30 -0
  124. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/006-dot-segment-path.json +30 -0
  125. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/007-query-byte-preserved.json +30 -0
  126. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/008-percent-encoded-path.json +30 -0
  127. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/009-percent-encoded-unreserved-decoded.json +30 -0
  128. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/010-percent-encoded-slash-preserved.json +30 -0
  129. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/011-ipv6-authority.json +30 -0
  130. package/compliance/cache/3.0.6.previous/test-vectors/request-signing/positive/012-ipv6-authority-default-port-stripped.json +30 -0
  131. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/README.md +211 -0
  132. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/keys.json +61 -0
  133. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/001-wrong-tag.json +26 -0
  134. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/002-expired-signature.json +26 -0
  135. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/003-window-too-long.json +26 -0
  136. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/004-alg-not-allowed.json +26 -0
  137. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/005-missing-authority-component.json +26 -0
  138. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/006-missing-content-digest.json +25 -0
  139. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/007-unknown-keyid.json +26 -0
  140. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/008-wrong-adcp-use.json +26 -0
  141. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/009-content-digest-mismatch.json +26 -0
  142. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/010-malformed-signature-input.json +26 -0
  143. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/011-signature-without-input.json +25 -0
  144. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/012-missing-expires-param.json +26 -0
  145. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/013-expires-le-created.json +26 -0
  146. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/014-missing-nonce-param.json +26 -0
  147. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/015-signature-invalid.json +26 -0
  148. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/016-replayed-nonce.json +37 -0
  149. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/017-key-revoked.json +32 -0
  150. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/018-rate-abuse.json +33 -0
  151. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/019-revocation-stale.json +32 -0
  152. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/020-key-ops-missing-verify.json +41 -0
  153. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/negative/021-base64-alphabet-mixing.json +26 -0
  154. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/001-basic-post.json +24 -0
  155. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/002-es256-post.json +24 -0
  156. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/003-multiple-signature-labels.json +24 -0
  157. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/004-default-port-stripped.json +24 -0
  158. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/005-percent-encoded-path.json +24 -0
  159. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/006-query-byte-preserved.json +24 -0
  160. package/compliance/cache/3.0.6.previous/test-vectors/webhook-signing/positive/007-body-without-idempotency-key.json +25 -0
  161. package/compliance/cache/3.0.6.previous/universal/capability-discovery.yaml +125 -0
  162. package/compliance/cache/3.0.6.previous/universal/collection-lists-pagination-integrity.yaml +306 -0
  163. package/compliance/cache/3.0.6.previous/universal/content-standards-pagination-integrity.yaml +326 -0
  164. package/compliance/cache/3.0.6.previous/universal/deterministic-testing.yaml +1343 -0
  165. package/compliance/cache/3.0.6.previous/universal/error-compliance.yaml +474 -0
  166. package/compliance/cache/3.0.6.previous/universal/fictional-entities.yaml +307 -0
  167. package/compliance/cache/3.0.6.previous/universal/get-media-buys-pagination-integrity.yaml +160 -0
  168. package/compliance/cache/3.0.6.previous/universal/get-signals-pagination-integrity.yaml +211 -0
  169. package/compliance/cache/3.0.6.previous/universal/idempotency.yaml +593 -0
  170. package/compliance/cache/3.0.6.previous/universal/pagination-integrity-creative-formats.yaml +258 -0
  171. package/compliance/cache/3.0.6.previous/universal/pagination-integrity-list-accounts.yaml +262 -0
  172. package/compliance/cache/3.0.6.previous/universal/pagination-integrity.yaml +263 -0
  173. package/compliance/cache/3.0.6.previous/universal/property-lists-pagination-integrity.yaml +307 -0
  174. package/compliance/cache/3.0.6.previous/universal/runner-output-contract.yaml +358 -0
  175. package/compliance/cache/3.0.6.previous/universal/schema-validation.yaml +526 -0
  176. package/compliance/cache/3.0.6.previous/universal/security.yaml +431 -0
  177. package/compliance/cache/3.0.6.previous/universal/signed-requests.yaml +205 -0
  178. package/compliance/cache/3.0.6.previous/universal/storyboard-schema.yaml +1176 -0
  179. package/compliance/cache/3.0.6.previous/universal/v3-envelope-integrity.yaml +106 -0
  180. package/compliance/cache/3.0.6.previous/universal/webhook-emission.yaml +337 -0
  181. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  182. package/dist/lib/server/create-adcp-server.d.ts +33 -0
  183. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  184. package/dist/lib/server/create-adcp-server.js +127 -1
  185. package/dist/lib/server/create-adcp-server.js.map +1 -1
  186. package/dist/lib/server/credential-policy.d.ts +221 -0
  187. package/dist/lib/server/credential-policy.d.ts.map +1 -0
  188. package/dist/lib/server/credential-policy.js +260 -0
  189. package/dist/lib/server/credential-policy.js.map +1 -0
  190. package/dist/lib/server/decisioning/async-outcome.d.ts +17 -0
  191. package/dist/lib/server/decisioning/async-outcome.d.ts.map +1 -1
  192. package/dist/lib/server/decisioning/async-outcome.js +23 -18
  193. package/dist/lib/server/decisioning/async-outcome.js.map +1 -1
  194. package/dist/lib/server/decisioning/context.d.ts +8 -2
  195. package/dist/lib/server/decisioning/context.d.ts.map +1 -1
  196. package/dist/lib/server/decisioning/index.d.ts +1 -0
  197. package/dist/lib/server/decisioning/index.d.ts.map +1 -1
  198. package/dist/lib/server/decisioning/index.js.map +1 -1
  199. package/dist/lib/server/decisioning/runtime/from-platform.js +6 -4
  200. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -1
  201. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts.map +1 -1
  202. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js +5 -2
  203. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js.map +1 -1
  204. package/dist/lib/server/decisioning/runtime/task-registry.d.ts +5 -0
  205. package/dist/lib/server/decisioning/runtime/task-registry.d.ts.map +1 -1
  206. package/dist/lib/server/decisioning/runtime/task-registry.js +4 -1
  207. package/dist/lib/server/decisioning/runtime/task-registry.js.map +1 -1
  208. package/dist/lib/server/decisioning/runtime/to-context.d.ts.map +1 -1
  209. package/dist/lib/server/decisioning/runtime/to-context.js +10 -2
  210. package/dist/lib/server/decisioning/runtime/to-context.js.map +1 -1
  211. package/dist/lib/server/dynamic-registry.d.ts +219 -0
  212. package/dist/lib/server/dynamic-registry.d.ts.map +1 -0
  213. package/dist/lib/server/dynamic-registry.js +245 -0
  214. package/dist/lib/server/dynamic-registry.js.map +1 -0
  215. package/dist/lib/server/index.d.ts +8 -0
  216. package/dist/lib/server/index.d.ts.map +1 -1
  217. package/dist/lib/server/index.js +15 -4
  218. package/dist/lib/server/index.js.map +1 -1
  219. package/dist/lib/server/operational-platform.d.ts +239 -0
  220. package/dist/lib/server/operational-platform.d.ts.map +1 -0
  221. package/dist/lib/server/operational-platform.js +94 -0
  222. package/dist/lib/server/operational-platform.js.map +1 -0
  223. package/dist/lib/server/test-controller.d.ts +2 -0
  224. package/dist/lib/server/test-controller.d.ts.map +1 -1
  225. package/dist/lib/server/test-controller.js +6 -11
  226. package/dist/lib/server/test-controller.js.map +1 -1
  227. package/dist/lib/server/wire-safe.d.ts +211 -0
  228. package/dist/lib/server/wire-safe.d.ts.map +1 -0
  229. package/dist/lib/server/wire-safe.js +231 -0
  230. package/dist/lib/server/wire-safe.js.map +1 -0
  231. package/dist/lib/server/wire-spec-fields.generated.d.ts +168 -0
  232. package/dist/lib/server/wire-spec-fields.generated.d.ts.map +1 -0
  233. package/dist/lib/server/wire-spec-fields.generated.js +172 -0
  234. package/dist/lib/server/wire-spec-fields.generated.js.map +1 -0
  235. package/dist/lib/testing/compliance/index.d.ts +2 -0
  236. package/dist/lib/testing/compliance/index.d.ts.map +1 -1
  237. package/dist/lib/testing/compliance/index.js +6 -1
  238. package/dist/lib/testing/compliance/index.js.map +1 -1
  239. package/dist/lib/testing/compliance/summary.d.ts +77 -0
  240. package/dist/lib/testing/compliance/summary.d.ts.map +1 -0
  241. package/dist/lib/testing/compliance/summary.js +176 -0
  242. package/dist/lib/testing/compliance/summary.js.map +1 -0
  243. package/dist/lib/testing/comply-controller.d.ts +2 -0
  244. package/dist/lib/testing/comply-controller.d.ts.map +1 -1
  245. package/dist/lib/testing/comply-controller.js.map +1 -1
  246. package/dist/lib/testing/storyboard/compliance.d.ts +26 -0
  247. package/dist/lib/testing/storyboard/compliance.d.ts.map +1 -1
  248. package/dist/lib/testing/storyboard/compliance.js +51 -0
  249. package/dist/lib/testing/storyboard/compliance.js.map +1 -1
  250. package/dist/lib/testing/storyboard/index.d.ts +2 -2
  251. package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
  252. package/dist/lib/testing/storyboard/index.js +4 -2
  253. package/dist/lib/testing/storyboard/index.js.map +1 -1
  254. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  255. package/dist/lib/testing/storyboard/runner.js +58 -5
  256. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  257. package/dist/lib/version.d.ts +3 -3
  258. package/dist/lib/version.d.ts.map +1 -1
  259. package/dist/lib/version.js +3 -3
  260. package/dist/lib/version.js.map +1 -1
  261. package/package.json +2 -2
@@ -0,0 +1,690 @@
1
+ # Substitution Observer Runner — Harness Contract Test Kit
2
+ #
3
+ # Applies to:
4
+ # - Any storyboard that exercises catalog-item macro substitution and needs
5
+ # to assert the RFC 3986 percent-encoding rule from
6
+ # docs/creative/universal-macros.mdx#substitution-safety-catalog-item-macros.
7
+ # - Current consumers (when phases are gated on this contract):
8
+ # - specialisms/sales-catalog-driven/index.yaml (substitution_safety phase)
9
+ # - specialisms/creative-generative/index.yaml (catalog_substitution_safety phase)
10
+ # - Deferred pending observation-hook design (#2651):
11
+ # - specialisms/sales-social/index.yaml — social platforms substitute at
12
+ # serve time in proprietary renderers; no AdCP-level preview hook.
13
+ # - Future consumers: sales-retail-media (post-retail-media-epic),
14
+ # sales-broadcast-tv dynamic-creative phases, and anywhere else catalog
15
+ # values expand into URL contexts through a previewable surface.
16
+ #
17
+ # The #2620 rule: sales agents MUST percent-encode catalog-item macro values
18
+ # such that only RFC 3986 `unreserved` characters remain unescaped before
19
+ # substitution into URL contexts. Nested macro expansion is prohibited.
20
+ #
21
+ # The ATTACK SURFACE is impression-time URL emission. AdCP's API surface does
22
+ # not normally expose substituted output — it happens at serve time outside
23
+ # the protocol. Two observable AdCP-layer hooks exist:
24
+ #
25
+ # preview_creative responses (preview_html / preview_url)
26
+ # build_creative responses with include_preview: true
27
+ #
28
+ # This contract defines how a runner consumes those preview artifacts,
29
+ # extracts tracker URLs with substituted catalog-item values, and asserts
30
+ # the values are encoded per the #2620 rule.
31
+ #
32
+ # Clean seam: the runner does NOT reimplement URL parsing, HTML extraction,
33
+ # or the encoding check. It delegates to @adcp/client primitives (proposed:
34
+ # `SubstitutionObserver` with `extract_tracker_urls` and `assert_rfc3986_safe`
35
+ # helpers) so the same library production receivers would use is what the
36
+ # conformance runner exercises. Library fixes cover both.
37
+
38
+ id: substitution_observer_runner
39
+ # Shape extension vs webhook-receiver-runner: this contract applies to
40
+ # specialisms rather than universals, because catalog-macro substitution is a
41
+ # specialism-scoped behavior (only catalog-accepting sellers emit it). The
42
+ # `applies_to.specialisms` key is a deliberate structural addition for this
43
+ # class of contract.
44
+ applies_to:
45
+ specialisms:
46
+ - sales_catalog_driven
47
+ - creative_generative
48
+ # sales_social deferred — no AdCP-level preview hook (see #2651).
49
+ universals: []
50
+
51
+ description: |
52
+ Coordination contract between a runner that observes substituted tracker
53
+ URLs in preview artifacts and an agent under test that emits catalog-driven
54
+ creatives. The runner ingests preview_html or follows preview_url, extracts
55
+ tracker URLs bound to catalog-item macros, and asserts RFC 3986 percent-
56
+ encoding of catalog-item values per docs/creative/universal-macros#substitution-safety-catalog-item-macros.
57
+
58
+ endpoint_scope: sandbox
59
+ # Storyboards consuming this contract synthesize attacker-shaped catalog
60
+ # values (e.g., title containing `abc&cmd=drop` or `\r\nHost: evil`) and
61
+ # push them via sync_catalogs. Running those against production would
62
+ # pollute live catalogs. Graders MUST target a sandbox/staging endpoint.
63
+
64
+ harness_mode: black_box
65
+
66
+ # --- Observation mechanism ---
67
+ #
68
+ # The runner captures preview output from two response shapes (either is
69
+ # sufficient — the storyboard author names which to observe):
70
+ #
71
+ # preview_html (inline HTML string in the response)
72
+ # The runner parses the HTML and extracts tracker URLs from the
73
+ # attribute set enumerated below. Zero network dependency.
74
+ #
75
+ # preview_url (HTTPS URL the runner fetches subject to the SSRF policy
76
+ # enumerated below)
77
+ # The runner performs a single GET against the URL and extracts
78
+ # identically.
79
+ #
80
+ # Both paths land at the same @adcp/client.SubstitutionObserver.extract_tracker_urls
81
+ # primitive, which returns a deterministic list of `{ url, source_attr, line_hint }`
82
+ # records that storyboards match against.
83
+
84
+ observation_modes:
85
+ - mode: html_inline
86
+ default_for: [lint, fast, full_conformance]
87
+ description: |
88
+ Runner reads preview_html from the response and parses it. No network
89
+ fetch. Appropriate for CI lint gates and SDK self-tests.
90
+ runner_config:
91
+ source_path: preview_html
92
+ html_parser: "@adcp/client.SubstitutionObserver.parse_html"
93
+
94
+ - mode: url_fetch
95
+ default_for: [adcp_verified]
96
+ description: |
97
+ Runner fetches preview_url over HTTPS, expects 200 + text/html, and
98
+ parses the body. Required for AdCP Verified grading where the preview
99
+ is a live asset.
100
+ runner_config:
101
+ source_path: preview_url
102
+ fetch:
103
+ method: GET
104
+ follow_redirects: false
105
+ max_body_bytes: 262144 # 256 KiB — matches typical creative preview sizes
106
+ max_connect_seconds: 3
107
+ timeout_seconds: 10
108
+ required_content_types: ["text/html", "application/xhtml+xml"]
109
+ # SSRF policy is NORMATIVE IN THIS CONTRACT (not deferred to a library).
110
+ # Verified graders MUST enforce every rule below. The runner MAY delegate
111
+ # the implementation to @adcp/client.SubstitutionObserver.enforce_ssrf_policy
112
+ # (or equivalent) provided the delegate implements this exact deny list.
113
+ ssrf_policy:
114
+ schemes_allowed: ["https"]
115
+ schemes_denied: ["http", "file", "gopher", "ftp", "ftps", "data", "javascript", "about", "ws", "wss"]
116
+ hosts_denied_ipv4_cidrs:
117
+ - "0.0.0.0/8" # "this network"
118
+ - "10.0.0.0/8" # RFC 1918 private
119
+ - "100.64.0.0/10" # CGNAT
120
+ - "127.0.0.0/8" # loopback
121
+ - "169.254.0.0/16" # link-local (incl. 169.254.169.254 IMDS v1/v2)
122
+ - "172.16.0.0/12" # RFC 1918 private
123
+ - "192.0.0.0/24" # IETF protocol assignments
124
+ - "192.168.0.0/16" # RFC 1918 private
125
+ - "224.0.0.0/4" # multicast
126
+ - "240.0.0.0/4" # reserved
127
+ hosts_denied_ipv6_cidrs:
128
+ - "::1/128" # loopback
129
+ - "::/128" # unspecified
130
+ - "::ffff:0:0/96" # IPv4-mapped (re-check as IPv4)
131
+ - "64:ff9b::/96" # IPv4/IPv6 translation
132
+ - "fc00::/7" # unique local
133
+ - "fe80::/10" # link-local
134
+ - "ff00::/8" # multicast
135
+ hosts_denied_metadata:
136
+ # Cloud metadata hosts by name — the IP CIDRs above catch the standard
137
+ # 169.254.169.254 cases, but some providers expose hostname aliases.
138
+ - "metadata.google.internal"
139
+ - "metadata"
140
+ - "metadata.packet.net"
141
+ - "fd00:ec2::254" # IMDS IPv6
142
+ host_literal_policy_verified: reject
143
+ # In AdCP Verified grading, reject ANY bare IP literal in preview_url
144
+ # (both IPv4 and IPv6) regardless of range — forces resolution through
145
+ # a public DNS name the grader can audit. Local-dev flags MAY relax this.
146
+ dns_revalidation: required
147
+ # After DNS resolution, EVERY resolved address MUST be re-checked against
148
+ # hosts_denied_*. Resolve once, bind to the resolved address for the
149
+ # request — do NOT pass the hostname to the HTTP client (closes DNS
150
+ # rebinding between resolve and connect).
151
+ redirects: follow_false_strict
152
+ # follow_redirects: false is already set above; this field documents
153
+ # the companion: if the origin returns 3xx, treat it as a failure
154
+ # (`preview_url_unusable` + sub-reason `redirect_returned`), do NOT
155
+ # chase — redirect chasing would require re-running the full SSRF
156
+ # policy at each hop and is out of scope for v1.
157
+
158
+ # --- HTML attribute extraction set (normative) ---
159
+ #
160
+ # The runner extracts tracker URLs from the following attributes only. This
161
+ # set is normative — runner MUST NOT under-extract (missing one of these
162
+ # attributes lets a seller hide an unencoded value); runner MUST NOT over-
163
+ # extract (e.g., arbitrary `data-*` attributes whose values happen to parse
164
+ # as URLs but are not trackers). Extension to additional attributes MAY
165
+ # happen in a future contract revision; today's set is closed.
166
+
167
+ html_attribute_extraction_set:
168
+ tag_attribute_pairs:
169
+ - { tag: "a", attr: "href" }
170
+ - { tag: "img", attr: "src" }
171
+ - { tag: "img", attr: "srcset" } # may contain multiple URLs
172
+ - { tag: "iframe", attr: "src" }
173
+ - { tag: "source", attr: "src" }
174
+ - { tag: "source", attr: "srcset" }
175
+ - { tag: "link", attr: "href" }
176
+ - { tag: "meta", attr: "content" } # refresh redirects, og:image, etc.
177
+ - { tag: "*", attr: "data-impression-url" }
178
+ - { tag: "*", attr: "data-click-url" }
179
+ - { tag: "*", attr: "data-tracker-url" }
180
+ - { tag: "*", attr: "data-vast-url" }
181
+ srcset_handling: parse_per_descriptor
182
+ # srcset values are space-separated URL+descriptor pairs (e.g.,
183
+ # "a.jpg 1x, b.jpg 2x"). The runner MUST extract every URL component.
184
+ comment_nodes: ignored
185
+ script_text_content: ignored
186
+ # Tracker URLs in `<script>`-emitted document.write or similar are out
187
+ # of scope v1 — if a seller hides substitution inside script text, the
188
+ # runner does not see it (covered under "preview/serve code-path
189
+ # divergence" in out-of-scope, below).
190
+
191
+ # --- Macro-position alignment algorithm (normative) ---
192
+ #
193
+ # The runner needs to map each extracted URL back to a position in the
194
+ # submitted macro_template so it can compare the substituted bytes against
195
+ # the expected_encoded form of the bound catalog-item value. The algorithm:
196
+ #
197
+ # 1. Parse `macro_template` with a WHATWG URL parser (absolute URLs only).
198
+ # Extract: scheme, host, port, path_segments[], query_pairs[{k,v}],
199
+ # fragment.
200
+ # 2. Parse each extracted observed URL with the SAME parser. If an
201
+ # extracted URL is relative in the preview HTML, resolve against a
202
+ # synthetic base (`https://observer.test/`) so relative-resolution
203
+ # does not introduce cross-runner variance.
204
+ # 3. Align query_pairs by key. A template pair {k: "sku", v: "{SKU}"}
205
+ # aligns to the observed pair whose key parses to the same sequence
206
+ # after percent-decoding `k`. If multiple observed pairs share a key,
207
+ # align positionally (template pair index N → observed pair index N
208
+ # among matching-key pairs).
209
+ # 4. Align path_segments positionally. A template segment "{JOB_ID}"
210
+ # aligns to the observed segment at the same index.
211
+ # 5. For each alignment, the observed value is the substituted output
212
+ # for that binding. Compare bytes against `expected_encoded` per the
213
+ # hex-case policy below.
214
+ #
215
+ # This algorithm is deterministic and does NOT depend on substring search
216
+ # or regex — substring alignment fails on attacker values that happen to
217
+ # equal other parts of the URL.
218
+
219
+ # --- Hex case policy (normative) ---
220
+ #
221
+ # RFC 3986 §2.1 states producers SHOULD use uppercase hex digits in
222
+ # percent-encoded triplets. This contract REQUIRES producers (sellers) to
223
+ # emit uppercase hex (`%C3%A9`, not `%c3%a9`) to match the fixture's
224
+ # expected_encoded values byte-for-byte.
225
+ #
226
+ # However, VERIFIERS (runners performing the byte comparison) MUST apply
227
+ # case-insensitive comparison on the hex digits only — the two hex digits
228
+ # inside a `%NN` triplet compare case-insensitively, every byte outside a
229
+ # triplet compares case-sensitively. This accommodates legitimate producer
230
+ # variation without silently accepting a non-conformant triplet encoding
231
+ # (e.g., `%g7` is invalid regardless of case).
232
+ #
233
+ # Implementation note: @adcp/client.SubstitutionObserver.assert_rfc3986_safe
234
+ # MUST implement the case-insensitive triplet comparison; runners MUST NOT
235
+ # do naive byte-equal comparison.
236
+
237
+ hex_case_policy:
238
+ producer_requirement: uppercase
239
+ verifier_comparison: case_insensitive_hex_digits_only
240
+
241
+ # --- Attacker-shaped catalog values ---
242
+ #
243
+ # The canonical vectors live in the unit-test fixture at
244
+ # static/test-vectors/catalog-macro-substitution.json. The fixture is the
245
+ # SINGLE SOURCE OF TRUTH for `raw_value` and `expected_encoded` — the
246
+ # runner MUST load the fixture at run time and look up each vector by name.
247
+ # This contract NAMES the vectors a consuming storyboard can reference;
248
+ # it does NOT duplicate the encoded values (they would drift).
249
+
250
+ attacker_value_catalog:
251
+ source_fixture: static/test-vectors/catalog-macro-substitution.json
252
+ canonical_vector_names:
253
+ - reserved-character-breakout
254
+ - nested-expansion-preserved-as-literal
255
+ - crlf-injection-neutralized
256
+ - non-ascii-utf8-percent-encoding
257
+ - nfc-normalization-before-encoding
258
+ - mixed-path-and-query-contexts
259
+ - bidi-override-neutralized
260
+ - url-scheme-injection-neutralized
261
+
262
+ # --- Storyboard-layer step task ---
263
+ #
264
+ # The contract exposes one primary step task:
265
+ #
266
+ # task: expect_substitution_safe
267
+ #
268
+ # Arguments:
269
+ # source:
270
+ # html_inline | url_fetch
271
+ # Which observation mode the runner uses. Default: html_inline.
272
+ # source_path:
273
+ # JSON-pointer into the previous step's response where the preview
274
+ # artifact lives. Example: "/creative_manifest/preview_html".
275
+ # macro_template:
276
+ # The URL template containing AdCP macros (the value submitted via
277
+ # sync_creatives). Example: "https://track.example/imp?sku={SKU}"
278
+ # catalog_bindings:
279
+ # Array of { macro, catalog_item_id, vector_name } entries. The runner
280
+ # loads the fixture (by source_fixture path above), looks up each
281
+ # vector_name, and binds `{macro}` to the vector's raw_value →
282
+ # expected_encoded pair. Storyboard authors DO NOT duplicate
283
+ # raw_value/expected_encoded inline; the fixture is authoritative.
284
+ #
285
+ # Optional override fields for non-canonical vectors (e.g., seller-
286
+ # specific payloads not in the fixture): `raw_value` and
287
+ # `expected_encoded` MAY be inlined, and when inlined the runner skips
288
+ # the fixture lookup. Runners MUST redact `raw_value` from error
289
+ # reports for inlined custom vectors (treat as potentially sensitive);
290
+ # canonical fixture values MAY be echoed verbatim because the fixture
291
+ # is public.
292
+ # require_every_binding_observed:
293
+ # Default `true`. When true, the step fails if any declared binding is
294
+ # not observed in the extracted URLs — catches a seller that silently
295
+ # strips a macro rather than substituting it. Storyboard authors MUST
296
+ # explicitly set `false` and document why (e.g., preview shows one
297
+ # item from a larger catalog) when partial observation is legitimate.
298
+ # The previous name `require_all_bindings_observed` is deprecated; the
299
+ # new name disambiguates "every declared binding" from "every URL
300
+ # matches some binding."
301
+ #
302
+ # Validations (runner-internal; storyboards reference them by name):
303
+ #
304
+ # substitution_encoded:
305
+ # For each binding, the observed value at the aligned macro position
306
+ # MUST equal the fixture's `expected_encoded` under the hex_case_policy
307
+ # comparison. Raw-value leakage (any byte of `raw_value` appearing
308
+ # literally at the macro position) fails with
309
+ # `substitution_encoding_violation` plus the binding, observed URL,
310
+ # byte offset of divergence, and expected form.
311
+ #
312
+ # nested_expansion_not_re_scanned:
313
+ # For the `nested-expansion-preserved-as-literal` vector specifically:
314
+ # the observed URL MUST contain `%7BDEVICE_ID%7D` (encoded braces) and
315
+ # MUST NOT contain any resolved value for {DEVICE_ID}. A second-round
316
+ # expansion fails with `nested_macro_re_expansion`.
317
+ #
318
+ # url_scheme_preserved:
319
+ # For the `url-scheme-injection-neutralized` vector at an
320
+ # href-whole-value macro binding (i.e., the macro occupies the entire
321
+ # attribute value, like `<a href="{CLICK}">`): the scheme of the
322
+ # parsed observed URL MUST equal the scheme of the template. A
323
+ # substituted value that changes the scheme (e.g., `javascript:`
324
+ # appearing as the scheme of the observed URL) fails with
325
+ # `substitution_scheme_injection`.
326
+ #
327
+ # rfc3986_unreserved_only_at_macro_position:
328
+ # A stricter check than substitution_encoded — verifies that every
329
+ # byte emitted at the macro position is either unreserved
330
+ # (ALPHA / DIGIT / "-" / "." / "_" / "~") or a percent-encoded triplet.
331
+ # Producers using a reserved-char allowlist (rather than
332
+ # unreserved-whitelist) fail specifically on the CRLF and bidi
333
+ # vectors.
334
+
335
+ step_task:
336
+ name: expect_substitution_safe
337
+ schema_ref: static/compliance/source/universal/storyboard-schema.yaml
338
+ error_modes:
339
+ - substitution_encoding_violation
340
+ - nested_macro_re_expansion
341
+ - substitution_scheme_injection
342
+ - substitution_binding_missing
343
+ - preview_source_unavailable
344
+ - preview_url_unusable # collapses fetch-failed + body-not-html + ssrf-blocked into one code with sub-reasons
345
+ preview_url_unusable_sub_reasons:
346
+ - http_status # non-200 response
347
+ - content_type # not text/html or application/xhtml+xml
348
+ - size_exceeded # body > max_body_bytes
349
+ - redirect_returned # 3xx without follow
350
+ - ssrf_blocked # ssrf_policy deny-hit, with specific rule id in the detail
351
+ - fetch_timeout # exceeded max_connect_seconds or timeout_seconds
352
+
353
+ # --- PHASE_TEMPLATE: canonical three-step substitution-safety phase ---
354
+ #
355
+ # This block is ADVISORY documentation, not a runtime-enforced schema. Future
356
+ # storyboard authors adding a third, fourth, or Nth consumer of this contract
357
+ # SHOULD copy the shape below and substitute the placeholders marked with
358
+ # `<<PLACEHOLDER>>` — domain, catalog_id prefix, correlation_id prefix, step-id
359
+ # slug stem, narrative pronoun. Deviating from the template is legitimate
360
+ # (different specialisms have different observation points and format needs);
361
+ # the value is that starting from the template avoids silent drift on the
362
+ # load-bearing fields (`require_every_binding_observed: true`, fixture-lookup
363
+ # binding shape, `requires_contract: substitution_observer_runner`).
364
+ #
365
+ # Existing consumers to reference:
366
+ # - specialisms/sales-catalog-driven/index.yaml (substitution_safety)
367
+ # - specialisms/creative-generative/index.yaml (catalog_substitution_safety)
368
+ #
369
+ # Placeholders:
370
+ # <<SPECIALISM_SLUG>> e.g., sales_catalog_driven, creative_generative
371
+ # <<BRAND_DOMAIN>> fictional brand domain from the chosen test-kit
372
+ # (amsterdam-steakhouse.example, acmeoutdoor.example, …)
373
+ # <<OPERATOR_DOMAIN>> typically pinnacle-agency.example across kits
374
+ # <<CATALOG_ID_PREFIX>> a stable prefix that will NOT collide with other
375
+ # catalogs synced earlier in the storyboard
376
+ # <<PHASE_ID>> substitution_safety | catalog_substitution_safety | …
377
+ # <<CORRELATION_ID_PREFIX>> kebab-slug form of <<SPECIALISM_SLUG>>
378
+ # <<BUILD_TASK>> preview-returning task — typically `build_creative`
379
+ # with `include_preview: true`, or `preview_creative`
380
+ # where native. Match the specialism's existing
381
+ # preview-producing step.
382
+ # <<BUILD_SCHEMA_REF>> schema_ref for the build task request
383
+ # <<BUILD_RESPONSE_SCHEMA_REF>> response_schema_ref for the build task
384
+ # <<BUILD_COMPLY_SCENARIO>> creative_flow (match the existing specialism)
385
+ # <<TARGET_FORMAT_ID>> an OBJECT `{ agent_url, id }` the agent advertises —
386
+ # e.g., `{ agent_url: "https://creative.adcontextprotocol.org",
387
+ # id: "product_carousel_3_to_10" }` for DPA shapes. Not a scalar.
388
+ # <<TEMPLATE_URL>> the URL template whose macro positions the runner
389
+ # will observe (must contain the macro used in
390
+ # catalog_bindings)
391
+ # <<MESSAGE_BRIEF>> natural-language brief. For generative specialisms,
392
+ # MUST explicitly ask for the literal macro token
393
+ # (unsubstituted) so the observer catches the template;
394
+ # for template-shaped specialisms, describe the
395
+ # catalog-driven render.
396
+ # <<IDEMPOTENCY_PREFIX>> convention: `<<SPECIALISM_SLUG>>_<<PHASE_ID>>_<step-id>`
397
+ # for `$generate:uuid_v4#<prefix>` inputs.
398
+ #
399
+ # phase_template:
400
+ # id: <<PHASE_ID>>
401
+ # title: "Catalog-item macro substitution safety"
402
+ # narrative: |
403
+ # Per docs/creative/universal-macros#substitution-safety-catalog-item-macros,
404
+ # sales/creative agents MUST normalize catalog-item values to Unicode NFC
405
+ # and then percent-encode them such that only RFC 3986 `unreserved`
406
+ # characters remain unescaped before substituting them into a URL context.
407
+ # Nested macro expansion is prohibited.
408
+ #
409
+ # This phase exercises the rule with attacker-shaped catalog values drawn
410
+ # from the fixture at `static/test-vectors/catalog-macro-substitution.json`.
411
+ # The phase is gated on the `substitution_observer_runner` test-kit
412
+ # contract — runners that do not advertise it grade the expect step as
413
+ # `not_applicable` while the earlier sync and build steps still run.
414
+ #
415
+ # Scope note: this phase validates substitution on the PREVIEW surface
416
+ # only. Sellers with divergent preview vs impression-time substitution
417
+ # paths MAY pass here while failing at serve time; serve-time attestation
418
+ # or log-introspection observability is tracked separately (#2651).
419
+ #
420
+ # steps:
421
+ # - id: sync_substitution_probe_catalog
422
+ # task: sync_catalogs
423
+ # schema_ref: "media-buy/sync-catalogs-request.json"
424
+ # response_schema_ref: "media-buy/sync-catalogs-response.json"
425
+ # doc_ref: "/media-buy/task-reference/sync_catalogs"
426
+ # stateful: true
427
+ # sample_request:
428
+ # account:
429
+ # brand: { domain: "<<BRAND_DOMAIN>>" }
430
+ # operator: "<<OPERATOR_DOMAIN>>"
431
+ # catalogs:
432
+ # - catalog_id: <<CATALOG_ID_PREFIX>>_substitution_probe_v1
433
+ # type: "product"
434
+ # content_id_type: "sku"
435
+ # items:
436
+ # # Minimum 3 vectors (canonical baseline). Specialisms with higher
437
+ # # risk profile (generative, user-text-in-copy) SHOULD add crlf +
438
+ # # bidi; specialisms with template-bound substitution MAY stay at 3.
439
+ # - { item_id: "reserved_char_breakout", sku: "00013&cmd=drop" , … }
440
+ # - { item_id: "nested_expansion", sku: "vacancy-{DEVICE_ID}-42", … }
441
+ # - { item_id: "non_ascii", sku: "café-amsterdam" , … }
442
+ # # OPTIONAL — add for higher-risk specialisms:
443
+ # # - { item_id: "crlf_injection", sku: "abc\r\nHost: evil.ex" , … }
444
+ # # - { item_id: "bidi_override", sku: "VIN-\u202E1234" , … }
445
+ # # - { item_id: "nfc_normalization", sku: "cafe\u0301-amsterdam" , … }
446
+ # # Specialisms with whole-value macro binding MAY add url_scheme_injection.
447
+ # idempotency_key: "$generate:uuid_v4#<<IDEMPOTENCY_PREFIX>>_sync_substitution_probe_catalog"
448
+ # context: { correlation_id: "<<CORRELATION_ID_PREFIX>>--sync_substitution_probe_catalog" }
449
+ #
450
+ # - id: build_substitution_probe_creative
451
+ # task: <<BUILD_TASK>>
452
+ # schema_ref: <<BUILD_SCHEMA_REF>>
453
+ # response_schema_ref: <<BUILD_RESPONSE_SCHEMA_REF>>
454
+ # comply_scenario: <<BUILD_COMPLY_SCENARIO>>
455
+ # stateful: true
456
+ # sample_request:
457
+ # message: <<MESSAGE_BRIEF>>
458
+ # target_format_id:
459
+ # agent_url: <<TARGET_FORMAT_ID.agent_url>>
460
+ # id: <<TARGET_FORMAT_ID.id>>
461
+ # account:
462
+ # brand: { domain: "<<BRAND_DOMAIN>>" }
463
+ # operator: "<<OPERATOR_DOMAIN>>"
464
+ # quality: "draft"
465
+ # include_preview: true
466
+ # idempotency_key: "$generate:uuid_v4#<<IDEMPOTENCY_PREFIX>>_build_substitution_probe_creative"
467
+ # context: { correlation_id: "<<CORRELATION_ID_PREFIX>>--build_substitution_probe_creative" }
468
+ #
469
+ # - id: expect_substitution_safe
470
+ # task: expect_substitution_safe
471
+ # requires_contract: substitution_observer_runner
472
+ # source: html_inline
473
+ # source_path: "/creative_manifest/preview_html"
474
+ # macro_template: <<TEMPLATE_URL>>
475
+ # # LOAD-BEARING: Setting `require_every_binding_observed: false` bypasses
476
+ # # the silent-strip detection called out in the #2647 security review —
477
+ # # a seller that emits `?sku=` (empty value) instead of substituting the
478
+ # # macro passes the test with zero observed bindings. Only legitimate
479
+ # # when partial-catalog preview is a documented specialism feature AND
480
+ # # the narrative explicitly justifies the deviation.
481
+ # require_every_binding_observed: true
482
+ # catalog_bindings:
483
+ # - { macro: "{SKU}", catalog_item_id: "reserved_char_breakout", vector_name: "reserved-character-breakout" }
484
+ # - { macro: "{SKU}", catalog_item_id: "nested_expansion", vector_name: "nested-expansion-preserved-as-literal" }
485
+ # - { macro: "{SKU}", catalog_item_id: "non_ascii", vector_name: "non-ascii-utf8-percent-encoding" }
486
+ # # Add the corresponding bindings when items above are expanded.
487
+
488
+ # --- @adcp/client primitives the runner consumes ---
489
+ #
490
+ # The runner does NOT reimplement HTML parsing, URL extraction, or the RFC
491
+ # 3986 encoding check. It consumes library primitives so production code
492
+ # paths match conformance paths.
493
+ #
494
+ # The library surface is deliberately split into `observer/` (runner-side)
495
+ # and `encoder/` (seller-side) modules sharing RFC 3986 primitives
496
+ # underneath. Runners import only observer/; sellers implementing the
497
+ # #2620 rule import only encoder/. Premature coupling is avoided.
498
+
499
+ client_primitives:
500
+ substitution_observer:
501
+ reference: SubstitutionObserver
502
+ methods:
503
+ - "parse_html(html: string) -> TrackerUrlRecord[]"
504
+ - "fetch_and_parse(url: URL) -> Promise<TrackerUrlRecord[]>"
505
+ - "match_bindings(records: TrackerUrlRecord[], template: URL, bindings: CatalogBinding[]) -> BindingMatch[]"
506
+ - "assert_rfc3986_safe(match: BindingMatch) -> AssertionResult"
507
+ - "assert_no_nested_expansion(match: BindingMatch, prohibited_pattern: RegExp) -> AssertionResult"
508
+ - "assert_scheme_preserved(match: BindingMatch, template_scheme: string) -> AssertionResult"
509
+ - "enforce_ssrf_policy(url: URL, policy: SsrfPolicy) -> PolicyResult"
510
+ proposed_location: "@adcp/client/src/substitution/observer/"
511
+ doc: "To be filed as an adcp-client PR when #2638 lands."
512
+
513
+ encoder_companion:
514
+ # Sibling module for seller-side production use. Shares RFC 3986
515
+ # primitives with observer/ so a single bug fix covers both.
516
+ reference: SubstitutionEncoder
517
+ methods:
518
+ - "encode_for_url_context(raw_value: string) -> string"
519
+ - "reject_if_contains_macro(raw_value: string) -> void | throw"
520
+ proposed_location: "@adcp/client/src/substitution/encoder/"
521
+ doc: "Seller-facing counterpart to observer/. Same library, disjoint API."
522
+
523
+ tracker_url_record:
524
+ reference: TrackerUrlRecord
525
+ shape: |
526
+ {
527
+ url: URL; // parsed URL object
528
+ source_attr: string; // 'href' | 'src' | 'srcset' | 'data-impression-url' | …
529
+ source_tag: string; // 'a' | 'img' | 'iframe' | 'meta' | …
530
+ line_hint: number | null; // best-effort line number in preview HTML
531
+ }
532
+
533
+ catalog_binding:
534
+ reference: CatalogBinding
535
+ shape: |
536
+ {
537
+ macro: string; // e.g., "{GTIN}"
538
+ catalog_item_id: string; // looks up raw_value from preceding
539
+ // sync_catalogs response + fixture
540
+ vector_name: string; // e.g., "reserved-character-breakout"
541
+ raw_value?: string; // optional override (custom vectors)
542
+ expected_encoded?: string; // optional override (custom vectors)
543
+ }
544
+
545
+ # --- Grading decision tree ---
546
+ #
547
+ # A step gated on this contract grades as follows:
548
+ #
549
+ # not_applicable:
550
+ # Runner does not advertise substitution_observer_runner in its
551
+ # capabilities, OR the specialism under test does not expose a preview
552
+ # surface (preview_html, preview_url, or equivalent). Recorded as
553
+ # not_applicable with a reason; does NOT contribute to the pass rate.
554
+ #
555
+ # pass:
556
+ # Every declared binding in `catalog_bindings` (when
557
+ # require_every_binding_observed is true — the default) is observed at
558
+ # an aligned macro position, each byte-equal (under hex_case_policy) to
559
+ # the fixture's `expected_encoded` for that vector. The nested-expansion
560
+ # vector additionally passes `nested_expansion_not_re_scanned`.
561
+ #
562
+ # fail with `substitution_encoding_violation`:
563
+ # At least one binding's observed value differs from expected_encoded.
564
+ # Runner MUST include the binding, the observed URL, the offending byte
565
+ # offset, and the expected encoding in the error report.
566
+ #
567
+ # fail with `nested_macro_re_expansion`:
568
+ # The nested-expansion-preserved-as-literal vector's macro position
569
+ # contains any byte sequence that resolves as a second-round AdCP
570
+ # macro. Includes the resolved macro name and its unexpected value.
571
+ #
572
+ # fail with `substitution_scheme_injection`:
573
+ # A binding at an href-whole-value position produced an observed URL
574
+ # whose scheme differs from the template's scheme. Includes the
575
+ # template scheme, observed scheme, and binding.
576
+ #
577
+ # fail with `substitution_binding_missing`:
578
+ # `require_every_binding_observed: true` (default) and one or more
579
+ # bindings are not present in the extracted URLs. Catches a seller
580
+ # that silently drops the macro.
581
+ #
582
+ # fail with `preview_source_unavailable`:
583
+ # The configured `source_path` resolves to null or an empty string in
584
+ # the observed response. The seller's preview shape does not satisfy
585
+ # the contract; specialism MAY grade not_applicable if the agent does
586
+ # not advertise preview support.
587
+ #
588
+ # fail with `preview_url_unusable`:
589
+ # The url_fetch path failed. Sub-reason set to one of http_status,
590
+ # content_type, size_exceeded, redirect_returned, ssrf_blocked, or
591
+ # fetch_timeout. `ssrf_blocked` includes the specific policy rule id
592
+ # (e.g., `hosts_denied_ipv4_cidrs:169.254.0.0/16`) so runner operators
593
+ # can distinguish grader-policy failures from seller bugs.
594
+
595
+ # --- Error-report payload handling ---
596
+ #
597
+ # Canonical fixture vectors in attacker_value_catalog.canonical_vector_names
598
+ # are public — runners MAY echo raw_value and expected_encoded verbatim in
599
+ # error reports, logs, and grader telemetry.
600
+ #
601
+ # CUSTOM vectors (a storyboard author inlining their own raw_value /
602
+ # expected_encoded for a seller-specific payload) MUST be redacted in error
603
+ # reports: runners MUST replace the raw_value with a SHA-256 hex digest
604
+ # prefixed `sha256:` UNLESS the grader was started with an explicit
605
+ # --include-raw-payloads flag (default-off, disabled in AdCP Verified).
606
+ # This prevents CI logs from becoming a searchable repository of seller-
607
+ # reported attack payloads.
608
+
609
+ error_report_payload_policy:
610
+ canonical_vectors: echo_verbatim
611
+ custom_vectors: redact_to_sha256_unless_flag
612
+ grader_flag_name: "--include-raw-payloads"
613
+ grader_flag_default: false
614
+ verified_mode: flag_disabled
615
+
616
+ # --- Out of scope v1 ---
617
+ #
618
+ # The following are deliberate non-goals for the initial contract:
619
+ #
620
+ # - Macros outside the catalog-item class. Trusted macros (MEDIA_BUY_ID,
621
+ # DEVICE_ID, GEO) are not observed by this contract. Extension to a
622
+ # universal URL-encoding check across all macros is tracked as a
623
+ # potential 3.2+ scope, not v1.
624
+ #
625
+ # - HTML-attribute context assertions for non-URL-bearing attributes.
626
+ # This contract DOES extract from URL-bearing attributes (the
627
+ # `html_attribute_extraction_set` above) because those are the
628
+ # substitution surfaces #2620 governs. Non-URL attribute contexts —
629
+ # e.g., `<div data-title="{PRODUCT_TITLE}">` where the value goes into
630
+ # a text context, not a URL context — are the publisher's
631
+ # HTML-attribute-escaping responsibility and out of AdCP's scope per
632
+ # the #2620 rule text. A runner that detects a catalog value in a
633
+ # non-URL attribute SHOULD warn but MUST NOT fail.
634
+ #
635
+ # - VAST XML bodies. VAST macros use [SQUARE_BRACKETS] and resolve in
636
+ # the player, not in AdCP. Runners MAY extract tracker URLs from VAST
637
+ # CDATA blocks for the AdCP-macro check but MUST NOT assert on VAST
638
+ # macro substitution.
639
+ #
640
+ # - Non-preview surfaces (post-impression log introspection, direct
641
+ # ad-server querying). Those require a different contract — not this
642
+ # one — and are a separate 3.2+ RFC.
643
+ #
644
+ # - Preview/serve code-path equivalence. This contract assumes the
645
+ # preview renderer shares substitution implementation with the
646
+ # impression-time renderer. Sellers with divergent paths (a separate
647
+ # serving tier, a compiled-ahead-of-time preview cache, etc.) MAY
648
+ # pass preview conformance while failing at serve time. Post-impression
649
+ # log introspection (above) is the complementary check. AdCP Verified
650
+ # grading SHOULD require sellers to attest to shared-path
651
+ # implementation; until that attestation exists, this contract's
652
+ # coverage claim is scoped to the preview surface only.
653
+ #
654
+ # - Script-emitted substitution. Tracker URLs written to the document
655
+ # via <script> content (document.write, innerHTML, attachShadow) are
656
+ # not extracted by `html_inline` or `url_fetch`. A seller hiding
657
+ # substitution inside script text is not caught by v1; the runner
658
+ # treats script text as opaque (see `html_attribute_extraction_set`
659
+ # above).
660
+
661
+ # --- Scope summary (machine-readable) ---
662
+
663
+ scope:
664
+ in_scope:
665
+ - catalog_item_macro_substitution
666
+ - url_contexts_impression_click_vast_landing
667
+ - preview_html_observation
668
+ - preview_url_fetch_observation_with_ssrf_policy
669
+ - html_attribute_extraction_bounded_set
670
+ - rfc3986_unreserved_only_byte_comparison
671
+ - nested_macro_expansion_prohibition
672
+ - url_scheme_preservation_at_whole_value_position
673
+ out_of_scope:
674
+ - non_catalog_macros
675
+ - non_url_html_attribute_contexts
676
+ - vast_xml_macro_substitution
677
+ - post_impression_log_introspection
678
+ - preview_serve_code_path_equivalence
679
+ - script_emitted_substitution
680
+
681
+ # --- References ---
682
+
683
+ references:
684
+ spec: docs/creative/universal-macros.mdx#substitution-safety-catalog-item-macros
685
+ upstream_rule_pr: "#2620"
686
+ contract_rfc: "#2638"
687
+ consumer_coverage_rfc: "#2640"
688
+ unit_test_fixture: static/test-vectors/catalog-macro-substitution.json
689
+ sibling_contract_webhook: static/compliance/source/test-kits/webhook-receiver-runner.yaml
690
+ sibling_contract_signed_requests: static/compliance/source/test-kits/signed-requests-runner.yaml