@adcp/sdk 12.0.0 → 12.0.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.
Files changed (942) hide show
  1. package/dist/lib/adapters/content-standards-adapter.mjs +0 -6
  2. package/dist/lib/adapters/content-standards-adapter.mjs.map +1 -1
  3. package/dist/lib/adapters/derived-account-store.mjs +0 -6
  4. package/dist/lib/adapters/derived-account-store.mjs.map +1 -1
  5. package/dist/lib/adapters/governance-adapter.mjs +0 -6
  6. package/dist/lib/adapters/governance-adapter.mjs.map +1 -1
  7. package/dist/lib/adapters/implicit-account-store.mjs +0 -6
  8. package/dist/lib/adapters/implicit-account-store.mjs.map +1 -1
  9. package/dist/lib/adapters/index.mjs +0 -6
  10. package/dist/lib/adapters/index.mjs.map +1 -1
  11. package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs +0 -6
  12. package/dist/lib/adapters/legacy/v2-5/create_media_buy.mjs.map +1 -1
  13. package/dist/lib/adapters/legacy/v2-5/get_products.mjs +0 -6
  14. package/dist/lib/adapters/legacy/v2-5/get_products.mjs.map +1 -1
  15. package/dist/lib/adapters/legacy/v2-5/index.mjs +0 -6
  16. package/dist/lib/adapters/legacy/v2-5/index.mjs.map +1 -1
  17. package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs +0 -6
  18. package/dist/lib/adapters/legacy/v2-5/list_creative_formats.mjs.map +1 -1
  19. package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs +0 -6
  20. package/dist/lib/adapters/legacy/v2-5/preview_creative.mjs.map +1 -1
  21. package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs +0 -6
  22. package/dist/lib/adapters/legacy/v2-5/sync_creatives.mjs.map +1 -1
  23. package/dist/lib/adapters/legacy/v2-5/types.mjs +0 -6
  24. package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs +0 -6
  25. package/dist/lib/adapters/legacy/v2-5/update_media_buy.mjs.map +1 -1
  26. package/dist/lib/adapters/oauth-passthrough-resolver.mjs +0 -6
  27. package/dist/lib/adapters/oauth-passthrough-resolver.mjs.map +1 -1
  28. package/dist/lib/adapters/property-list-adapter.mjs +0 -6
  29. package/dist/lib/adapters/property-list-adapter.mjs.map +1 -1
  30. package/dist/lib/adapters/roster-account-store.mjs +0 -6
  31. package/dist/lib/adapters/roster-account-store.mjs.map +1 -1
  32. package/dist/lib/adapters/si-session-manager.mjs +0 -6
  33. package/dist/lib/adapters/si-session-manager.mjs.map +1 -1
  34. package/dist/lib/adapters/version/3.0/brand-fields.mjs +0 -6
  35. package/dist/lib/adapters/version/3.0/brand-fields.mjs.map +1 -1
  36. package/dist/lib/adapters/version/3.0/get-products.mjs +0 -6
  37. package/dist/lib/adapters/version/3.0/get-products.mjs.map +1 -1
  38. package/dist/lib/adapters/version/3.0/index.mjs +0 -6
  39. package/dist/lib/adapters/version/3.0/index.mjs.map +1 -1
  40. package/dist/lib/adapters/version/3.0/sync-accounts.mjs +0 -6
  41. package/dist/lib/adapters/version/3.0/sync-accounts.mjs.map +1 -1
  42. package/dist/lib/adapters/version/index.mjs +0 -6
  43. package/dist/lib/adapters/version/index.mjs.map +1 -1
  44. package/dist/lib/adapters/version/types.mjs +0 -6
  45. package/dist/lib/advanced.mjs +0 -6
  46. package/dist/lib/advanced.mjs.map +1 -1
  47. package/dist/lib/agents/index.generated.mjs +0 -6
  48. package/dist/lib/agents/index.generated.mjs.map +1 -1
  49. package/dist/lib/auth/index.mjs +0 -6
  50. package/dist/lib/auth/index.mjs.map +1 -1
  51. package/dist/lib/auth/oauth/CLIFlowHandler.mjs +0 -6
  52. package/dist/lib/auth/oauth/CLIFlowHandler.mjs.map +1 -1
  53. package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +0 -6
  54. package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs.map +1 -1
  55. package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +0 -6
  56. package/dist/lib/auth/oauth/MCPOAuthProvider.mjs.map +1 -1
  57. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +0 -6
  58. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs.map +1 -1
  59. package/dist/lib/auth/oauth/authorization-required.mjs +0 -6
  60. package/dist/lib/auth/oauth/authorization-required.mjs.map +1 -1
  61. package/dist/lib/auth/oauth/diagnose.mjs +0 -6
  62. package/dist/lib/auth/oauth/diagnose.mjs.map +1 -1
  63. package/dist/lib/auth/oauth/diagnostics.mjs +0 -6
  64. package/dist/lib/auth/oauth/diagnostics.mjs.map +1 -1
  65. package/dist/lib/auth/oauth/discovery.mjs +0 -6
  66. package/dist/lib/auth/oauth/discovery.mjs.map +1 -1
  67. package/dist/lib/auth/oauth/file-storage.mjs +0 -6
  68. package/dist/lib/auth/oauth/file-storage.mjs.map +1 -1
  69. package/dist/lib/auth/oauth/index.mjs +0 -6
  70. package/dist/lib/auth/oauth/index.mjs.map +1 -1
  71. package/dist/lib/auth/oauth/secret-resolver.mjs +0 -6
  72. package/dist/lib/auth/oauth/secret-resolver.mjs.map +1 -1
  73. package/dist/lib/auth/oauth/storage-registry.mjs +0 -6
  74. package/dist/lib/auth/oauth/storage-registry.mjs.map +1 -1
  75. package/dist/lib/auth/oauth/types.mjs +0 -6
  76. package/dist/lib/auth/oauth/types.mjs.map +1 -1
  77. package/dist/lib/auth/oauth/web-flow.mjs +0 -6
  78. package/dist/lib/auth/oauth/web-flow.mjs.map +1 -1
  79. package/dist/lib/brand/index.mjs +0 -6
  80. package/dist/lib/brand/index.mjs.map +1 -1
  81. package/dist/lib/canonical-references/index.mjs +6 -7
  82. package/dist/lib/canonical-references/index.mjs.map +1 -1
  83. package/dist/lib/client/index.mjs +0 -6
  84. package/dist/lib/client/index.mjs.map +1 -1
  85. package/dist/lib/compliance/index.mjs +0 -6
  86. package/dist/lib/compliance/index.mjs.map +1 -1
  87. package/dist/lib/compliance-fixtures/index.mjs +0 -6
  88. package/dist/lib/compliance-fixtures/index.mjs.map +1 -1
  89. package/dist/lib/compliance-fixtures/test-authorization-server.mjs +0 -6
  90. package/dist/lib/compliance-fixtures/test-authorization-server.mjs.map +1 -1
  91. package/dist/lib/conformance/index.mjs +0 -6
  92. package/dist/lib/conformance/index.mjs.map +1 -1
  93. package/dist/lib/conformance/invariants/uniformError.mjs +0 -6
  94. package/dist/lib/conformance/invariants/uniformError.mjs.map +1 -1
  95. package/dist/lib/conformance/invariants/uniformErrorComparator.mjs +0 -6
  96. package/dist/lib/conformance/invariants/uniformErrorComparator.mjs.map +1 -1
  97. package/dist/lib/conformance/oracle.mjs +0 -6
  98. package/dist/lib/conformance/oracle.mjs.map +1 -1
  99. package/dist/lib/conformance/runConformance.mjs +0 -6
  100. package/dist/lib/conformance/runConformance.mjs.map +1 -1
  101. package/dist/lib/conformance/runners.mjs +0 -6
  102. package/dist/lib/conformance/runners.mjs.map +1 -1
  103. package/dist/lib/conformance/schemaArbitrary.mjs +0 -6
  104. package/dist/lib/conformance/schemaArbitrary.mjs.map +1 -1
  105. package/dist/lib/conformance/schemaLoader.mjs +5 -6
  106. package/dist/lib/conformance/schemaLoader.mjs.map +1 -1
  107. package/dist/lib/conformance/seeder.mjs +0 -6
  108. package/dist/lib/conformance/seeder.mjs.map +1 -1
  109. package/dist/lib/conformance/types.mjs +0 -6
  110. package/dist/lib/conformance/types.mjs.map +1 -1
  111. package/dist/lib/conformance/webhook.mjs +0 -6
  112. package/dist/lib/conformance/webhook.mjs.map +1 -1
  113. package/dist/lib/core/ADCPMultiAgentClient.mjs +0 -6
  114. package/dist/lib/core/ADCPMultiAgentClient.mjs.map +1 -1
  115. package/dist/lib/core/AgentClient.mjs +0 -6
  116. package/dist/lib/core/AgentClient.mjs.map +1 -1
  117. package/dist/lib/core/AsyncHandler.mjs +0 -6
  118. package/dist/lib/core/AsyncHandler.mjs.map +1 -1
  119. package/dist/lib/core/ConfigurationManager.mjs +0 -6
  120. package/dist/lib/core/ConfigurationManager.mjs.map +1 -1
  121. package/dist/lib/core/ConversationTypes.mjs +0 -6
  122. package/dist/lib/core/CreativeAgentClient.mjs +0 -6
  123. package/dist/lib/core/CreativeAgentClient.mjs.map +1 -1
  124. package/dist/lib/core/GovernanceMiddleware.mjs +0 -6
  125. package/dist/lib/core/GovernanceMiddleware.mjs.map +1 -1
  126. package/dist/lib/core/GovernanceTypes.mjs +0 -6
  127. package/dist/lib/core/GovernanceTypes.mjs.map +1 -1
  128. package/dist/lib/core/ProtocolResponseParser.mjs +0 -6
  129. package/dist/lib/core/ProtocolResponseParser.mjs.map +1 -1
  130. package/dist/lib/core/ResponseValidator.mjs +0 -6
  131. package/dist/lib/core/ResponseValidator.mjs.map +1 -1
  132. package/dist/lib/core/SingleAgentClient.mjs +0 -6
  133. package/dist/lib/core/SingleAgentClient.mjs.map +1 -1
  134. package/dist/lib/core/TaskEventTypes.mjs +0 -6
  135. package/dist/lib/core/TaskEventTypes.mjs.map +1 -1
  136. package/dist/lib/core/TaskExecutor.mjs +0 -6
  137. package/dist/lib/core/TaskExecutor.mjs.map +1 -1
  138. package/dist/lib/core/match.mjs +0 -6
  139. package/dist/lib/core/match.mjs.map +1 -1
  140. package/dist/lib/core/task-status.mjs +0 -6
  141. package/dist/lib/core/task-status.mjs.map +1 -1
  142. package/dist/lib/core/webhook-url.mjs +0 -6
  143. package/dist/lib/core/webhook-url.mjs.map +1 -1
  144. package/dist/lib/discovery/adagents-redirects.mjs +0 -6
  145. package/dist/lib/discovery/adagents-redirects.mjs.map +1 -1
  146. package/dist/lib/discovery/agent-directory.mjs +0 -6
  147. package/dist/lib/discovery/agent-directory.mjs.map +1 -1
  148. package/dist/lib/discovery/inline-publisher-properties.mjs +0 -6
  149. package/dist/lib/discovery/inline-publisher-properties.mjs.map +1 -1
  150. package/dist/lib/discovery/network-consistency-checker.mjs +0 -6
  151. package/dist/lib/discovery/network-consistency-checker.mjs.map +1 -1
  152. package/dist/lib/discovery/property-crawler.mjs +0 -6
  153. package/dist/lib/discovery/property-crawler.mjs.map +1 -1
  154. package/dist/lib/discovery/property-index.mjs +0 -6
  155. package/dist/lib/discovery/property-index.mjs.map +1 -1
  156. package/dist/lib/discovery/publisher-property-selector.mjs +0 -6
  157. package/dist/lib/discovery/publisher-property-selector.mjs.map +1 -1
  158. package/dist/lib/discovery/resolve-agent-properties.mjs +0 -6
  159. package/dist/lib/discovery/resolve-agent-properties.mjs.map +1 -1
  160. package/dist/lib/discovery/types.mjs +0 -6
  161. package/dist/lib/discovery/validate-adagents.mjs +0 -6
  162. package/dist/lib/discovery/validate-adagents.mjs.map +1 -1
  163. package/dist/lib/enums.mjs +0 -6
  164. package/dist/lib/enums.mjs.map +1 -1
  165. package/dist/lib/errors/index.mjs +0 -6
  166. package/dist/lib/errors/index.mjs.map +1 -1
  167. package/dist/lib/express-mcp/index.mjs +0 -6
  168. package/dist/lib/express-mcp/index.mjs.map +1 -1
  169. package/dist/lib/governance/index.mjs +0 -6
  170. package/dist/lib/governance/index.mjs.map +1 -1
  171. package/dist/lib/handlers/types.mjs +0 -6
  172. package/dist/lib/handlers/types.mjs.map +1 -1
  173. package/dist/lib/index.mjs +0 -6
  174. package/dist/lib/index.mjs.map +1 -1
  175. package/dist/lib/media-buy/available-actions.mjs +0 -6
  176. package/dist/lib/media-buy/available-actions.mjs.map +1 -1
  177. package/dist/lib/media-buy/index.mjs +0 -6
  178. package/dist/lib/media-buy/index.mjs.map +1 -1
  179. package/dist/lib/media-buy/preflight.mjs +0 -6
  180. package/dist/lib/media-buy/preflight.mjs.map +1 -1
  181. package/dist/lib/media-buy/property-policy.mjs +0 -6
  182. package/dist/lib/media-buy/property-policy.mjs.map +1 -1
  183. package/dist/lib/media-buy/types.mjs +0 -6
  184. package/dist/lib/media-buy/types.mjs.map +1 -1
  185. package/dist/lib/media-buy/update-fields.generated.mjs +0 -6
  186. package/dist/lib/media-buy/update-fields.generated.mjs.map +1 -1
  187. package/dist/lib/mock-server/creative-ad-server/seed-data.mjs +0 -6
  188. package/dist/lib/mock-server/creative-ad-server/seed-data.mjs.map +1 -1
  189. package/dist/lib/mock-server/creative-ad-server/server.mjs +0 -6
  190. package/dist/lib/mock-server/creative-ad-server/server.mjs.map +1 -1
  191. package/dist/lib/mock-server/creative-template/seed-data.mjs +0 -6
  192. package/dist/lib/mock-server/creative-template/seed-data.mjs.map +1 -1
  193. package/dist/lib/mock-server/creative-template/server.mjs +0 -6
  194. package/dist/lib/mock-server/creative-template/server.mjs.map +1 -1
  195. package/dist/lib/mock-server/index.mjs +0 -6
  196. package/dist/lib/mock-server/index.mjs.map +1 -1
  197. package/dist/lib/mock-server/sales-guaranteed/recipe.mjs +0 -6
  198. package/dist/lib/mock-server/sales-guaranteed/recipe.mjs.map +1 -1
  199. package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs +0 -6
  200. package/dist/lib/mock-server/sales-guaranteed/seed-data.mjs.map +1 -1
  201. package/dist/lib/mock-server/sales-guaranteed/server.mjs +0 -6
  202. package/dist/lib/mock-server/sales-guaranteed/server.mjs.map +1 -1
  203. package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs +0 -6
  204. package/dist/lib/mock-server/sales-non-guaranteed/recipe.mjs.map +1 -1
  205. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs +0 -6
  206. package/dist/lib/mock-server/sales-non-guaranteed/seed-data.mjs.map +1 -1
  207. package/dist/lib/mock-server/sales-non-guaranteed/server.mjs +0 -6
  208. package/dist/lib/mock-server/sales-non-guaranteed/server.mjs.map +1 -1
  209. package/dist/lib/mock-server/sales-social/seed-data.mjs +0 -6
  210. package/dist/lib/mock-server/sales-social/seed-data.mjs.map +1 -1
  211. package/dist/lib/mock-server/sales-social/server.mjs +0 -6
  212. package/dist/lib/mock-server/sales-social/server.mjs.map +1 -1
  213. package/dist/lib/mock-server/scenario.mjs +0 -6
  214. package/dist/lib/mock-server/scenario.mjs.map +1 -1
  215. package/dist/lib/mock-server/signal-marketplace/seed-data.mjs +0 -6
  216. package/dist/lib/mock-server/signal-marketplace/seed-data.mjs.map +1 -1
  217. package/dist/lib/mock-server/signal-marketplace/server.mjs +0 -6
  218. package/dist/lib/mock-server/signal-marketplace/server.mjs.map +1 -1
  219. package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs +0 -6
  220. package/dist/lib/mock-server/sponsored-intelligence/seed-data.mjs.map +1 -1
  221. package/dist/lib/mock-server/sponsored-intelligence/server.mjs +0 -6
  222. package/dist/lib/mock-server/sponsored-intelligence/server.mjs.map +1 -1
  223. package/dist/lib/net/address-guards.mjs +0 -6
  224. package/dist/lib/net/address-guards.mjs.map +1 -1
  225. package/dist/lib/net/index.mjs +0 -6
  226. package/dist/lib/net/index.mjs.map +1 -1
  227. package/dist/lib/net/ssrf-fetch.mjs +0 -6
  228. package/dist/lib/net/ssrf-fetch.mjs.map +1 -1
  229. package/dist/lib/observability/index.mjs +0 -6
  230. package/dist/lib/observability/index.mjs.map +1 -1
  231. package/dist/lib/observability/tracing.mjs +6 -7
  232. package/dist/lib/observability/tracing.mjs.map +1 -1
  233. package/dist/lib/protocols/a2a.mjs +0 -6
  234. package/dist/lib/protocols/a2a.mjs.map +1 -1
  235. package/dist/lib/protocols/abort.mjs +0 -6
  236. package/dist/lib/protocols/abort.mjs.map +1 -1
  237. package/dist/lib/protocols/index.mjs +0 -6
  238. package/dist/lib/protocols/index.mjs.map +1 -1
  239. package/dist/lib/protocols/mcp-modern.mjs +0 -6
  240. package/dist/lib/protocols/mcp-modern.mjs.map +1 -1
  241. package/dist/lib/protocols/mcp-tasks.mjs +0 -6
  242. package/dist/lib/protocols/mcp-tasks.mjs.map +1 -1
  243. package/dist/lib/protocols/mcp.mjs +0 -6
  244. package/dist/lib/protocols/mcp.mjs.map +1 -1
  245. package/dist/lib/protocols/rawResponseCapture.mjs +0 -6
  246. package/dist/lib/protocols/rawResponseCapture.mjs.map +1 -1
  247. package/dist/lib/protocols/responseSizeLimit.mjs +0 -6
  248. package/dist/lib/protocols/responseSizeLimit.mjs.map +1 -1
  249. package/dist/lib/protocols/transportDiagnostics.mjs +0 -6
  250. package/dist/lib/protocols/transportDiagnostics.mjs.map +1 -1
  251. package/dist/lib/registry/cursor-store.mjs +0 -6
  252. package/dist/lib/registry/cursor-store.mjs.map +1 -1
  253. package/dist/lib/registry/feed-stream.mjs +0 -6
  254. package/dist/lib/registry/feed-stream.mjs.map +1 -1
  255. package/dist/lib/registry/index.mjs +0 -6
  256. package/dist/lib/registry/index.mjs.map +1 -1
  257. package/dist/lib/registry/property-registry.mjs +0 -6
  258. package/dist/lib/registry/property-registry.mjs.map +1 -1
  259. package/dist/lib/registry/sync.mjs +0 -6
  260. package/dist/lib/registry/sync.mjs.map +1 -1
  261. package/dist/lib/registry/types.generated.mjs +0 -6
  262. package/dist/lib/registry/types.mjs +0 -6
  263. package/dist/lib/schemas/index.mjs +0 -6
  264. package/dist/lib/schemas/index.mjs.map +1 -1
  265. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  266. package/dist/lib/server/a2a-adapter.mjs +0 -6
  267. package/dist/lib/server/a2a-adapter.mjs.map +1 -1
  268. package/dist/lib/server/account-mode.mjs +0 -6
  269. package/dist/lib/server/account-mode.mjs.map +1 -1
  270. package/dist/lib/server/adcp-server.mjs +0 -6
  271. package/dist/lib/server/adcp-server.mjs.map +1 -1
  272. package/dist/lib/server/auth-introspection.mjs +0 -6
  273. package/dist/lib/server/auth-introspection.mjs.map +1 -1
  274. package/dist/lib/server/auth-signature.mjs +0 -6
  275. package/dist/lib/server/auth-signature.mjs.map +1 -1
  276. package/dist/lib/server/auth.mjs +0 -6
  277. package/dist/lib/server/auth.mjs.map +1 -1
  278. package/dist/lib/server/create-adcp-server.mjs +0 -6
  279. package/dist/lib/server/create-adcp-server.mjs.map +1 -1
  280. package/dist/lib/server/credential-policy.mjs +0 -6
  281. package/dist/lib/server/credential-policy.mjs.map +1 -1
  282. package/dist/lib/server/ctx-metadata/backends/memory.mjs +0 -6
  283. package/dist/lib/server/ctx-metadata/backends/memory.mjs.map +1 -1
  284. package/dist/lib/server/ctx-metadata/backends/pg.mjs +0 -6
  285. package/dist/lib/server/ctx-metadata/backends/pg.mjs.map +1 -1
  286. package/dist/lib/server/ctx-metadata/backends/redis.mjs +0 -6
  287. package/dist/lib/server/ctx-metadata/backends/redis.mjs.map +1 -1
  288. package/dist/lib/server/ctx-metadata/index.mjs +0 -6
  289. package/dist/lib/server/ctx-metadata/index.mjs.map +1 -1
  290. package/dist/lib/server/ctx-metadata/store.mjs +0 -6
  291. package/dist/lib/server/ctx-metadata/store.mjs.map +1 -1
  292. package/dist/lib/server/ctx-metadata/wire-shape.mjs +0 -6
  293. package/dist/lib/server/ctx-metadata/wire-shape.mjs.map +1 -1
  294. package/dist/lib/server/decisioning/account.mjs +0 -6
  295. package/dist/lib/server/decisioning/account.mjs.map +1 -1
  296. package/dist/lib/server/decisioning/admin-router.mjs +0 -6
  297. package/dist/lib/server/decisioning/admin-router.mjs.map +1 -1
  298. package/dist/lib/server/decisioning/assembly-helpers.mjs +0 -6
  299. package/dist/lib/server/decisioning/assembly-helpers.mjs.map +1 -1
  300. package/dist/lib/server/decisioning/async-outcome.mjs +0 -6
  301. package/dist/lib/server/decisioning/async-outcome.mjs.map +1 -1
  302. package/dist/lib/server/decisioning/buyer-agent.mjs +0 -6
  303. package/dist/lib/server/decisioning/buyer-agent.mjs.map +1 -1
  304. package/dist/lib/server/decisioning/capabilities.mjs +0 -6
  305. package/dist/lib/server/decisioning/capabilities.mjs.map +1 -1
  306. package/dist/lib/server/decisioning/compose.mjs +0 -6
  307. package/dist/lib/server/decisioning/compose.mjs.map +1 -1
  308. package/dist/lib/server/decisioning/context.mjs +0 -6
  309. package/dist/lib/server/decisioning/errors-typed.mjs +0 -6
  310. package/dist/lib/server/decisioning/errors-typed.mjs.map +1 -1
  311. package/dist/lib/server/decisioning/helpers.mjs +0 -6
  312. package/dist/lib/server/decisioning/helpers.mjs.map +1 -1
  313. package/dist/lib/server/decisioning/index.mjs +0 -6
  314. package/dist/lib/server/decisioning/index.mjs.map +1 -1
  315. package/dist/lib/server/decisioning/list-helpers.mjs +0 -6
  316. package/dist/lib/server/decisioning/list-helpers.mjs.map +1 -1
  317. package/dist/lib/server/decisioning/manifest-helpers.mjs +0 -6
  318. package/dist/lib/server/decisioning/manifest-helpers.mjs.map +1 -1
  319. package/dist/lib/server/decisioning/pagination.mjs +0 -6
  320. package/dist/lib/server/decisioning/platform-helpers.mjs +0 -6
  321. package/dist/lib/server/decisioning/platform-helpers.mjs.map +1 -1
  322. package/dist/lib/server/decisioning/platform.mjs +0 -6
  323. package/dist/lib/server/decisioning/proposal/dispatch.mjs +0 -6
  324. package/dist/lib/server/decisioning/proposal/dispatch.mjs.map +1 -1
  325. package/dist/lib/server/decisioning/proposal/index.mjs +0 -6
  326. package/dist/lib/server/decisioning/proposal/index.mjs.map +1 -1
  327. package/dist/lib/server/decisioning/proposal/lifecycle.mjs +0 -6
  328. package/dist/lib/server/decisioning/proposal/lifecycle.mjs.map +1 -1
  329. package/dist/lib/server/decisioning/proposal/mock-manager.mjs +0 -6
  330. package/dist/lib/server/decisioning/proposal/mock-manager.mjs.map +1 -1
  331. package/dist/lib/server/decisioning/proposal/store.mjs +0 -6
  332. package/dist/lib/server/decisioning/proposal/store.mjs.map +1 -1
  333. package/dist/lib/server/decisioning/proposal/types.mjs +0 -6
  334. package/dist/lib/server/decisioning/proposal/types.mjs.map +1 -1
  335. package/dist/lib/server/decisioning/resolve-presets.mjs +0 -6
  336. package/dist/lib/server/decisioning/resolve-presets.mjs.map +1 -1
  337. package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs +0 -6
  338. package/dist/lib/server/decisioning/runtime/entity-hydration.generated.mjs.map +1 -1
  339. package/dist/lib/server/decisioning/runtime/from-platform.mjs +0 -6
  340. package/dist/lib/server/decisioning/runtime/from-platform.mjs.map +1 -1
  341. package/dist/lib/server/decisioning/runtime/observed-modes.mjs +0 -6
  342. package/dist/lib/server/decisioning/runtime/observed-modes.mjs.map +1 -1
  343. package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs +0 -6
  344. package/dist/lib/server/decisioning/runtime/postgres-task-registry.mjs.map +1 -1
  345. package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs +0 -6
  346. package/dist/lib/server/decisioning/runtime/protocol-for-tool.mjs.map +1 -1
  347. package/dist/lib/server/decisioning/runtime/task-registry.mjs +0 -6
  348. package/dist/lib/server/decisioning/runtime/task-registry.mjs.map +1 -1
  349. package/dist/lib/server/decisioning/runtime/to-context.mjs +0 -6
  350. package/dist/lib/server/decisioning/runtime/to-context.mjs.map +1 -1
  351. package/dist/lib/server/decisioning/runtime/validate-platform.mjs +0 -6
  352. package/dist/lib/server/decisioning/runtime/validate-platform.mjs.map +1 -1
  353. package/dist/lib/server/decisioning/specialisms/audiences.mjs +0 -6
  354. package/dist/lib/server/decisioning/specialisms/brand-rights.mjs +0 -6
  355. package/dist/lib/server/decisioning/specialisms/campaign-governance.mjs +0 -6
  356. package/dist/lib/server/decisioning/specialisms/content-standards.mjs +0 -6
  357. package/dist/lib/server/decisioning/specialisms/creative-ad-server.mjs +0 -6
  358. package/dist/lib/server/decisioning/specialisms/creative.mjs +0 -6
  359. package/dist/lib/server/decisioning/specialisms/lists.mjs +0 -6
  360. package/dist/lib/server/decisioning/specialisms/sales.mjs +0 -6
  361. package/dist/lib/server/decisioning/specialisms/signals.mjs +0 -6
  362. package/dist/lib/server/decisioning/specialisms/sponsored-intelligence.mjs +0 -6
  363. package/dist/lib/server/decisioning/start-time.mjs +0 -6
  364. package/dist/lib/server/decisioning/start-time.mjs.map +1 -1
  365. package/dist/lib/server/decisioning/status-changes.mjs +0 -6
  366. package/dist/lib/server/decisioning/status-changes.mjs.map +1 -1
  367. package/dist/lib/server/decisioning/status-mappers.mjs +0 -6
  368. package/dist/lib/server/decisioning/status-mappers.mjs.map +1 -1
  369. package/dist/lib/server/decisioning/tenant-registry.mjs +0 -6
  370. package/dist/lib/server/decisioning/tenant-registry.mjs.map +1 -1
  371. package/dist/lib/server/decisioning/tenant-store.mjs +0 -6
  372. package/dist/lib/server/decisioning/tenant-store.mjs.map +1 -1
  373. package/dist/lib/server/decisioning/validate-specialisms.mjs +0 -6
  374. package/dist/lib/server/decisioning/validate-specialisms.mjs.map +1 -1
  375. package/dist/lib/server/dynamic-registry.mjs +0 -6
  376. package/dist/lib/server/dynamic-registry.mjs.map +1 -1
  377. package/dist/lib/server/envelope-allowlist.mjs +0 -6
  378. package/dist/lib/server/envelope-allowlist.mjs.map +1 -1
  379. package/dist/lib/server/error-arm-tools.mjs +5 -6
  380. package/dist/lib/server/error-arm-tools.mjs.map +1 -1
  381. package/dist/lib/server/errors.mjs +0 -6
  382. package/dist/lib/server/errors.mjs.map +1 -1
  383. package/dist/lib/server/example-tld-guard.mjs +0 -6
  384. package/dist/lib/server/example-tld-guard.mjs.map +1 -1
  385. package/dist/lib/server/express-adapter.mjs +0 -6
  386. package/dist/lib/server/express-adapter.mjs.map +1 -1
  387. package/dist/lib/server/governance.mjs +0 -6
  388. package/dist/lib/server/governance.mjs.map +1 -1
  389. package/dist/lib/server/idempotency/backends/lazy.mjs +0 -6
  390. package/dist/lib/server/idempotency/backends/lazy.mjs.map +1 -1
  391. package/dist/lib/server/idempotency/backends/memory.mjs +0 -6
  392. package/dist/lib/server/idempotency/backends/memory.mjs.map +1 -1
  393. package/dist/lib/server/idempotency/backends/pg.mjs +0 -6
  394. package/dist/lib/server/idempotency/backends/pg.mjs.map +1 -1
  395. package/dist/lib/server/idempotency/backends/redis.mjs +0 -6
  396. package/dist/lib/server/idempotency/backends/redis.mjs.map +1 -1
  397. package/dist/lib/server/idempotency/index.mjs +0 -6
  398. package/dist/lib/server/idempotency/index.mjs.map +1 -1
  399. package/dist/lib/server/idempotency/store.mjs +0 -6
  400. package/dist/lib/server/idempotency/store.mjs.map +1 -1
  401. package/dist/lib/server/index.mjs +0 -6
  402. package/dist/lib/server/index.mjs.map +1 -1
  403. package/dist/lib/server/legacy/v5/index.mjs +0 -6
  404. package/dist/lib/server/legacy/v5/index.mjs.map +1 -1
  405. package/dist/lib/server/mcp-modern-server.mjs +0 -6
  406. package/dist/lib/server/mcp-modern-server.mjs.map +1 -1
  407. package/dist/lib/server/media-buy-actions.mjs +0 -6
  408. package/dist/lib/server/media-buy-actions.mjs.map +1 -1
  409. package/dist/lib/server/media-buy-helpers.mjs +0 -6
  410. package/dist/lib/server/media-buy-helpers.mjs.map +1 -1
  411. package/dist/lib/server/media-buy-store.mjs +0 -6
  412. package/dist/lib/server/media-buy-store.mjs.map +1 -1
  413. package/dist/lib/server/normalize-errors.mjs +0 -6
  414. package/dist/lib/server/normalize-errors.mjs.map +1 -1
  415. package/dist/lib/server/operational-platform.mjs +0 -6
  416. package/dist/lib/server/operational-platform.mjs.map +1 -1
  417. package/dist/lib/server/pick-safe-details.mjs +0 -6
  418. package/dist/lib/server/pick-safe-details.mjs.map +1 -1
  419. package/dist/lib/server/pin-and-bind-fetch.mjs +0 -6
  420. package/dist/lib/server/pin-and-bind-fetch.mjs.map +1 -1
  421. package/dist/lib/server/postgres-state-store.mjs +0 -6
  422. package/dist/lib/server/postgres-state-store.mjs.map +1 -1
  423. package/dist/lib/server/postgres-task-store.mjs +0 -6
  424. package/dist/lib/server/postgres-task-store.mjs.map +1 -1
  425. package/dist/lib/server/product-defaults.mjs +0 -6
  426. package/dist/lib/server/product-defaults.mjs.map +1 -1
  427. package/dist/lib/server/redact.mjs +0 -6
  428. package/dist/lib/server/redact.mjs.map +1 -1
  429. package/dist/lib/server/responses.mjs +0 -6
  430. package/dist/lib/server/responses.mjs.map +1 -1
  431. package/dist/lib/server/serve.mjs +0 -6
  432. package/dist/lib/server/serve.mjs.map +1 -1
  433. package/dist/lib/server/socket-mode/conformance-client.mjs +0 -6
  434. package/dist/lib/server/socket-mode/conformance-client.mjs.map +1 -1
  435. package/dist/lib/server/socket-mode/index.mjs +0 -6
  436. package/dist/lib/server/socket-mode/index.mjs.map +1 -1
  437. package/dist/lib/server/socket-mode/ws-transport.mjs +0 -6
  438. package/dist/lib/server/socket-mode/ws-transport.mjs.map +1 -1
  439. package/dist/lib/server/state-machine.mjs +0 -6
  440. package/dist/lib/server/state-machine.mjs.map +1 -1
  441. package/dist/lib/server/state-store.mjs +0 -6
  442. package/dist/lib/server/state-store.mjs.map +1 -1
  443. package/dist/lib/server/structured-serialize.mjs +0 -6
  444. package/dist/lib/server/structured-serialize.mjs.map +1 -1
  445. package/dist/lib/server/targeting-helpers.mjs +0 -6
  446. package/dist/lib/server/targeting-helpers.mjs.map +1 -1
  447. package/dist/lib/server/tasks.mjs +0 -6
  448. package/dist/lib/server/tasks.mjs.map +1 -1
  449. package/dist/lib/server/test-controller-bridge.mjs +0 -6
  450. package/dist/lib/server/test-controller-bridge.mjs.map +1 -1
  451. package/dist/lib/server/test-controller.mjs +0 -6
  452. package/dist/lib/server/test-controller.mjs.map +1 -1
  453. package/dist/lib/server/upstream-helpers.mjs +0 -6
  454. package/dist/lib/server/upstream-helpers.mjs.map +1 -1
  455. package/dist/lib/server/webhook-emitter.mjs +0 -6
  456. package/dist/lib/server/webhook-emitter.mjs.map +1 -1
  457. package/dist/lib/server/wire-safe.mjs +0 -6
  458. package/dist/lib/server/wire-safe.mjs.map +1 -1
  459. package/dist/lib/server/wire-spec-fields.generated.mjs +0 -6
  460. package/dist/lib/server/wire-spec-fields.generated.mjs.map +1 -1
  461. package/dist/lib/server/wrap-envelope.mjs +0 -6
  462. package/dist/lib/server/wrap-envelope.mjs.map +1 -1
  463. package/dist/lib/signing/agent-context.mjs +0 -6
  464. package/dist/lib/signing/agent-context.mjs.map +1 -1
  465. package/dist/lib/signing/agent-fetch.mjs +0 -6
  466. package/dist/lib/signing/agent-fetch.mjs.map +1 -1
  467. package/dist/lib/signing/agent-resolver/canonicalize.mjs +0 -6
  468. package/dist/lib/signing/agent-resolver/canonicalize.mjs.map +1 -1
  469. package/dist/lib/signing/agent-resolver/capabilities-types.mjs +0 -6
  470. package/dist/lib/signing/agent-resolver/capabilities-types.mjs.map +1 -1
  471. package/dist/lib/signing/agent-resolver/consistency.mjs +0 -6
  472. package/dist/lib/signing/agent-resolver/consistency.mjs.map +1 -1
  473. package/dist/lib/signing/agent-resolver/errors.mjs +0 -6
  474. package/dist/lib/signing/agent-resolver/errors.mjs.map +1 -1
  475. package/dist/lib/signing/agent-resolver/etld.mjs +0 -6
  476. package/dist/lib/signing/agent-resolver/etld.mjs.map +1 -1
  477. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +0 -6
  478. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs.map +1 -1
  479. package/dist/lib/signing/agent-resolver/index.mjs +0 -6
  480. package/dist/lib/signing/agent-resolver/index.mjs.map +1 -1
  481. package/dist/lib/signing/agent-resolver/jwks-set.mjs +0 -6
  482. package/dist/lib/signing/agent-resolver/jwks-set.mjs.map +1 -1
  483. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +0 -6
  484. package/dist/lib/signing/agent-resolver/resolve-agent.mjs.map +1 -1
  485. package/dist/lib/signing/agent-resolver/select-agent.mjs +0 -6
  486. package/dist/lib/signing/agent-resolver/select-agent.mjs.map +1 -1
  487. package/dist/lib/signing/agent-resolver/strict-json.mjs +0 -6
  488. package/dist/lib/signing/agent-resolver/strict-json.mjs.map +1 -1
  489. package/dist/lib/signing/brand-jwks.mjs +0 -6
  490. package/dist/lib/signing/brand-jwks.mjs.map +1 -1
  491. package/dist/lib/signing/canonicalize.mjs +0 -6
  492. package/dist/lib/signing/canonicalize.mjs.map +1 -1
  493. package/dist/lib/signing/capability-cache.mjs +0 -6
  494. package/dist/lib/signing/capability-cache.mjs.map +1 -1
  495. package/dist/lib/signing/capability-priming.mjs +0 -6
  496. package/dist/lib/signing/capability-priming.mjs.map +1 -1
  497. package/dist/lib/signing/client.mjs +0 -6
  498. package/dist/lib/signing/client.mjs.map +1 -1
  499. package/dist/lib/signing/content-digest.mjs +0 -6
  500. package/dist/lib/signing/content-digest.mjs.map +1 -1
  501. package/dist/lib/signing/crypto.mjs +0 -6
  502. package/dist/lib/signing/crypto.mjs.map +1 -1
  503. package/dist/lib/signing/ecdsa-encoding.mjs +0 -6
  504. package/dist/lib/signing/ecdsa-encoding.mjs.map +1 -1
  505. package/dist/lib/signing/errors.mjs +0 -6
  506. package/dist/lib/signing/errors.mjs.map +1 -1
  507. package/dist/lib/signing/fetch-async.mjs +0 -6
  508. package/dist/lib/signing/fetch-async.mjs.map +1 -1
  509. package/dist/lib/signing/fetch.mjs +0 -6
  510. package/dist/lib/signing/fetch.mjs.map +1 -1
  511. package/dist/lib/signing/index.mjs +0 -6
  512. package/dist/lib/signing/index.mjs.map +1 -1
  513. package/dist/lib/signing/jwks-helpers.mjs +0 -6
  514. package/dist/lib/signing/jwks-helpers.mjs.map +1 -1
  515. package/dist/lib/signing/jwks-https.mjs +0 -6
  516. package/dist/lib/signing/jwks-https.mjs.map +1 -1
  517. package/dist/lib/signing/jwks.mjs +0 -6
  518. package/dist/lib/signing/jwks.mjs.map +1 -1
  519. package/dist/lib/signing/middleware.mjs +0 -6
  520. package/dist/lib/signing/middleware.mjs.map +1 -1
  521. package/dist/lib/signing/parser.mjs +0 -6
  522. package/dist/lib/signing/parser.mjs.map +1 -1
  523. package/dist/lib/signing/postgres-replay-store.mjs +0 -6
  524. package/dist/lib/signing/postgres-replay-store.mjs.map +1 -1
  525. package/dist/lib/signing/protocol-response.mjs +0 -6
  526. package/dist/lib/signing/protocol-response.mjs.map +1 -1
  527. package/dist/lib/signing/provider.mjs +0 -6
  528. package/dist/lib/signing/redis-replay-store.mjs +0 -6
  529. package/dist/lib/signing/redis-replay-store.mjs.map +1 -1
  530. package/dist/lib/signing/replay.mjs +0 -6
  531. package/dist/lib/signing/replay.mjs.map +1 -1
  532. package/dist/lib/signing/request-context.mjs +0 -6
  533. package/dist/lib/signing/request-context.mjs.map +1 -1
  534. package/dist/lib/signing/revocation-https.mjs +0 -6
  535. package/dist/lib/signing/revocation-https.mjs.map +1 -1
  536. package/dist/lib/signing/revocation.mjs +0 -6
  537. package/dist/lib/signing/revocation.mjs.map +1 -1
  538. package/dist/lib/signing/server.mjs +0 -6
  539. package/dist/lib/signing/server.mjs.map +1 -1
  540. package/dist/lib/signing/signer-async.mjs +0 -6
  541. package/dist/lib/signing/signer-async.mjs.map +1 -1
  542. package/dist/lib/signing/signer.mjs +0 -6
  543. package/dist/lib/signing/signer.mjs.map +1 -1
  544. package/dist/lib/signing/testing.mjs +0 -6
  545. package/dist/lib/signing/testing.mjs.map +1 -1
  546. package/dist/lib/signing/types.mjs +0 -6
  547. package/dist/lib/signing/types.mjs.map +1 -1
  548. package/dist/lib/signing/verifier.mjs +0 -6
  549. package/dist/lib/signing/verifier.mjs.map +1 -1
  550. package/dist/lib/signing/webhook-auth-detection.mjs +0 -6
  551. package/dist/lib/signing/webhook-auth-detection.mjs.map +1 -1
  552. package/dist/lib/signing/webhook-verifier.mjs +0 -6
  553. package/dist/lib/signing/webhook-verifier.mjs.map +1 -1
  554. package/dist/lib/storage/MemoryStorage.mjs +0 -6
  555. package/dist/lib/storage/MemoryStorage.mjs.map +1 -1
  556. package/dist/lib/storage/interfaces.mjs +0 -6
  557. package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs +0 -6
  558. package/dist/lib/substitution/encoder/SubstitutionEncoder.mjs.map +1 -1
  559. package/dist/lib/substitution/encoder/index.mjs +0 -6
  560. package/dist/lib/substitution/encoder/index.mjs.map +1 -1
  561. package/dist/lib/substitution/index.mjs +0 -6
  562. package/dist/lib/substitution/index.mjs.map +1 -1
  563. package/dist/lib/substitution/observer/SubstitutionObserver.mjs +0 -6
  564. package/dist/lib/substitution/observer/SubstitutionObserver.mjs.map +1 -1
  565. package/dist/lib/substitution/observer/alignment.mjs +0 -6
  566. package/dist/lib/substitution/observer/alignment.mjs.map +1 -1
  567. package/dist/lib/substitution/observer/assertions.mjs +0 -6
  568. package/dist/lib/substitution/observer/assertions.mjs.map +1 -1
  569. package/dist/lib/substitution/observer/html-parser.mjs +0 -6
  570. package/dist/lib/substitution/observer/html-parser.mjs.map +1 -1
  571. package/dist/lib/substitution/observer/index.mjs +0 -6
  572. package/dist/lib/substitution/observer/index.mjs.map +1 -1
  573. package/dist/lib/substitution/observer/ssrf.mjs +0 -6
  574. package/dist/lib/substitution/observer/ssrf.mjs.map +1 -1
  575. package/dist/lib/substitution/rfc3986.mjs +0 -6
  576. package/dist/lib/substitution/rfc3986.mjs.map +1 -1
  577. package/dist/lib/substitution/translate.mjs +0 -6
  578. package/dist/lib/substitution/translate.mjs.map +1 -1
  579. package/dist/lib/substitution/types.mjs +0 -6
  580. package/dist/lib/substitution/vectors.mjs +0 -6
  581. package/dist/lib/substitution/vectors.mjs.map +1 -1
  582. package/dist/lib/testing/agent-tester.mjs +0 -6
  583. package/dist/lib/testing/agent-tester.mjs.map +1 -1
  584. package/dist/lib/testing/client.mjs +0 -6
  585. package/dist/lib/testing/client.mjs.map +1 -1
  586. package/dist/lib/testing/compliance/briefs.mjs +0 -6
  587. package/dist/lib/testing/compliance/briefs.mjs.map +1 -1
  588. package/dist/lib/testing/compliance/comply.mjs +0 -6
  589. package/dist/lib/testing/compliance/comply.mjs.map +1 -1
  590. package/dist/lib/testing/compliance/index.mjs +0 -6
  591. package/dist/lib/testing/compliance/index.mjs.map +1 -1
  592. package/dist/lib/testing/compliance/spec-conformance.mjs +0 -6
  593. package/dist/lib/testing/compliance/spec-conformance.mjs.map +1 -1
  594. package/dist/lib/testing/compliance/storyboard-tracks.mjs +0 -6
  595. package/dist/lib/testing/compliance/storyboard-tracks.mjs.map +1 -1
  596. package/dist/lib/testing/compliance/summary.mjs +0 -6
  597. package/dist/lib/testing/compliance/summary.mjs.map +1 -1
  598. package/dist/lib/testing/compliance/types.mjs +0 -6
  599. package/dist/lib/testing/comply-controller.mjs +0 -6
  600. package/dist/lib/testing/comply-controller.mjs.map +1 -1
  601. package/dist/lib/testing/controller-assertions.mjs +0 -6
  602. package/dist/lib/testing/controller-assertions.mjs.map +1 -1
  603. package/dist/lib/testing/formatter.mjs +0 -6
  604. package/dist/lib/testing/formatter.mjs.map +1 -1
  605. package/dist/lib/testing/index.mjs +0 -6
  606. package/dist/lib/testing/index.mjs.map +1 -1
  607. package/dist/lib/testing/local-agent-runner.mjs +0 -6
  608. package/dist/lib/testing/local-agent-runner.mjs.map +1 -1
  609. package/dist/lib/testing/orchestrator.mjs +0 -6
  610. package/dist/lib/testing/orchestrator.mjs.map +1 -1
  611. package/dist/lib/testing/personas/index.mjs +0 -6
  612. package/dist/lib/testing/personas/index.mjs.map +1 -1
  613. package/dist/lib/testing/scenarios/brand-rights.mjs +0 -6
  614. package/dist/lib/testing/scenarios/brand-rights.mjs.map +1 -1
  615. package/dist/lib/testing/scenarios/capabilities.mjs +0 -6
  616. package/dist/lib/testing/scenarios/capabilities.mjs.map +1 -1
  617. package/dist/lib/testing/scenarios/creative.mjs +0 -6
  618. package/dist/lib/testing/scenarios/creative.mjs.map +1 -1
  619. package/dist/lib/testing/scenarios/deterministic.mjs +0 -6
  620. package/dist/lib/testing/scenarios/deterministic.mjs.map +1 -1
  621. package/dist/lib/testing/scenarios/discovery.mjs +0 -6
  622. package/dist/lib/testing/scenarios/discovery.mjs.map +1 -1
  623. package/dist/lib/testing/scenarios/edge-cases.mjs +0 -6
  624. package/dist/lib/testing/scenarios/edge-cases.mjs.map +1 -1
  625. package/dist/lib/testing/scenarios/error-compliance.mjs +0 -6
  626. package/dist/lib/testing/scenarios/error-compliance.mjs.map +1 -1
  627. package/dist/lib/testing/scenarios/governance.mjs +0 -6
  628. package/dist/lib/testing/scenarios/governance.mjs.map +1 -1
  629. package/dist/lib/testing/scenarios/health.mjs +0 -6
  630. package/dist/lib/testing/scenarios/health.mjs.map +1 -1
  631. package/dist/lib/testing/scenarios/index.mjs +0 -6
  632. package/dist/lib/testing/scenarios/index.mjs.map +1 -1
  633. package/dist/lib/testing/scenarios/media-buy.mjs +0 -6
  634. package/dist/lib/testing/scenarios/media-buy.mjs.map +1 -1
  635. package/dist/lib/testing/scenarios/schema-compliance.mjs +0 -6
  636. package/dist/lib/testing/scenarios/schema-compliance.mjs.map +1 -1
  637. package/dist/lib/testing/scenarios/signals.mjs +0 -6
  638. package/dist/lib/testing/scenarios/signals.mjs.map +1 -1
  639. package/dist/lib/testing/scenarios/sponsored-intelligence.mjs +0 -6
  640. package/dist/lib/testing/scenarios/sponsored-intelligence.mjs.map +1 -1
  641. package/dist/lib/testing/scenarios/trusted-match.mjs +0 -6
  642. package/dist/lib/testing/scenarios/trusted-match.mjs.map +1 -1
  643. package/dist/lib/testing/seed-merge.mjs +0 -6
  644. package/dist/lib/testing/seed-merge.mjs.map +1 -1
  645. package/dist/lib/testing/storyboard/agent-routing.mjs +0 -6
  646. package/dist/lib/testing/storyboard/agent-routing.mjs.map +1 -1
  647. package/dist/lib/testing/storyboard/assertions.mjs +0 -6
  648. package/dist/lib/testing/storyboard/assertions.mjs.map +1 -1
  649. package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs +0 -6
  650. package/dist/lib/testing/storyboard/canonical-format-satisfaction.mjs.map +1 -1
  651. package/dist/lib/testing/storyboard/compliance.mjs +5 -6
  652. package/dist/lib/testing/storyboard/compliance.mjs.map +1 -1
  653. package/dist/lib/testing/storyboard/context.mjs +0 -6
  654. package/dist/lib/testing/storyboard/context.mjs.map +1 -1
  655. package/dist/lib/testing/storyboard/default-invariants.mjs +0 -6
  656. package/dist/lib/testing/storyboard/default-invariants.mjs.map +1 -1
  657. package/dist/lib/testing/storyboard/index.mjs +0 -6
  658. package/dist/lib/testing/storyboard/index.mjs.map +1 -1
  659. package/dist/lib/testing/storyboard/junit.mjs +0 -6
  660. package/dist/lib/testing/storyboard/junit.mjs.map +1 -1
  661. package/dist/lib/testing/storyboard/loader.mjs +0 -6
  662. package/dist/lib/testing/storyboard/loader.mjs.map +1 -1
  663. package/dist/lib/testing/storyboard/parallel-dispatch.mjs +0 -6
  664. package/dist/lib/testing/storyboard/parallel-dispatch.mjs.map +1 -1
  665. package/dist/lib/testing/storyboard/path.mjs +0 -6
  666. package/dist/lib/testing/storyboard/path.mjs.map +1 -1
  667. package/dist/lib/testing/storyboard/probes.mjs +0 -6
  668. package/dist/lib/testing/storyboard/probes.mjs.map +1 -1
  669. package/dist/lib/testing/storyboard/rate-limit-trip.mjs +0 -6
  670. package/dist/lib/testing/storyboard/rate-limit-trip.mjs.map +1 -1
  671. package/dist/lib/testing/storyboard/rejection-hints.mjs +0 -6
  672. package/dist/lib/testing/storyboard/rejection-hints.mjs.map +1 -1
  673. package/dist/lib/testing/storyboard/request-builder.mjs +0 -6
  674. package/dist/lib/testing/storyboard/request-builder.mjs.map +1 -1
  675. package/dist/lib/testing/storyboard/request-signing/builder.mjs +0 -6
  676. package/dist/lib/testing/storyboard/request-signing/builder.mjs.map +1 -1
  677. package/dist/lib/testing/storyboard/request-signing/grader.mjs +0 -6
  678. package/dist/lib/testing/storyboard/request-signing/grader.mjs.map +1 -1
  679. package/dist/lib/testing/storyboard/request-signing/index.mjs +0 -6
  680. package/dist/lib/testing/storyboard/request-signing/index.mjs.map +1 -1
  681. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +0 -6
  682. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs.map +1 -1
  683. package/dist/lib/testing/storyboard/request-signing/probe.mjs +0 -6
  684. package/dist/lib/testing/storyboard/request-signing/probe.mjs.map +1 -1
  685. package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +0 -6
  686. package/dist/lib/testing/storyboard/request-signing/synthesize.mjs.map +1 -1
  687. package/dist/lib/testing/storyboard/request-signing/test-kit.mjs +0 -6
  688. package/dist/lib/testing/storyboard/request-signing/test-kit.mjs.map +1 -1
  689. package/dist/lib/testing/storyboard/request-signing/types.mjs +0 -6
  690. package/dist/lib/testing/storyboard/request-signing/types.mjs.map +1 -1
  691. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +0 -6
  692. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs.map +1 -1
  693. package/dist/lib/testing/storyboard/runner.mjs +0 -6
  694. package/dist/lib/testing/storyboard/runner.mjs.map +1 -1
  695. package/dist/lib/testing/storyboard/sandbox-entities.mjs +0 -6
  696. package/dist/lib/testing/storyboard/sandbox-entities.mjs.map +1 -1
  697. package/dist/lib/testing/storyboard/seeding.mjs +0 -6
  698. package/dist/lib/testing/storyboard/seeding.mjs.map +1 -1
  699. package/dist/lib/testing/storyboard/shape-drift-hints.mjs +0 -6
  700. package/dist/lib/testing/storyboard/shape-drift-hints.mjs.map +1 -1
  701. package/dist/lib/testing/storyboard/signer-grader/grader.mjs +0 -6
  702. package/dist/lib/testing/storyboard/signer-grader/grader.mjs.map +1 -1
  703. package/dist/lib/testing/storyboard/signer-grader/index.mjs +0 -6
  704. package/dist/lib/testing/storyboard/signer-grader/index.mjs.map +1 -1
  705. package/dist/lib/testing/storyboard/signer-grader/types.mjs +0 -6
  706. package/dist/lib/testing/storyboard/strict-validation-hints.mjs +0 -6
  707. package/dist/lib/testing/storyboard/strict-validation-hints.mjs.map +1 -1
  708. package/dist/lib/testing/storyboard/task-map.mjs +0 -6
  709. package/dist/lib/testing/storyboard/task-map.mjs.map +1 -1
  710. package/dist/lib/testing/storyboard/test-kit.mjs +0 -6
  711. package/dist/lib/testing/storyboard/test-kit.mjs.map +1 -1
  712. package/dist/lib/testing/storyboard/types.mjs +0 -6
  713. package/dist/lib/testing/storyboard/types.mjs.map +1 -1
  714. package/dist/lib/testing/storyboard/validations.mjs +0 -6
  715. package/dist/lib/testing/storyboard/validations.mjs.map +1 -1
  716. package/dist/lib/testing/storyboard/webhook-assertions.mjs +0 -6
  717. package/dist/lib/testing/storyboard/webhook-assertions.mjs.map +1 -1
  718. package/dist/lib/testing/storyboard/webhook-receiver.mjs +0 -6
  719. package/dist/lib/testing/storyboard/webhook-receiver.mjs.map +1 -1
  720. package/dist/lib/testing/stubs/governance-agent-stub.mjs +0 -6
  721. package/dist/lib/testing/stubs/governance-agent-stub.mjs.map +1 -1
  722. package/dist/lib/testing/stubs/index.mjs +0 -6
  723. package/dist/lib/testing/stubs/index.mjs.map +1 -1
  724. package/dist/lib/testing/test-controller.mjs +0 -6
  725. package/dist/lib/testing/test-controller.mjs.map +1 -1
  726. package/dist/lib/testing/test-helpers.mjs +0 -6
  727. package/dist/lib/testing/test-helpers.mjs.map +1 -1
  728. package/dist/lib/testing/types.mjs +0 -6
  729. package/dist/lib/types/adcp.mjs +0 -6
  730. package/dist/lib/types/adcp.mjs.map +1 -1
  731. package/dist/lib/types/asset-instances.mjs +0 -6
  732. package/dist/lib/types/compat.mjs +0 -6
  733. package/dist/lib/types/compat.mjs.map +1 -1
  734. package/dist/lib/types/core.generated.mjs +0 -6
  735. package/dist/lib/types/enums.generated.mjs +0 -6
  736. package/dist/lib/types/enums.generated.mjs.map +1 -1
  737. package/dist/lib/types/error-codes.mjs +0 -6
  738. package/dist/lib/types/error-codes.mjs.map +1 -1
  739. package/dist/lib/types/error-details.aliases.mjs +0 -6
  740. package/dist/lib/types/error-details.aliases.mjs.map +1 -1
  741. package/dist/lib/types/format-asset-slots.mjs +0 -6
  742. package/dist/lib/types/forward-compat-error-codes.mjs +0 -6
  743. package/dist/lib/types/forward-compat-error-codes.mjs.map +1 -1
  744. package/dist/lib/types/index.mjs +0 -6
  745. package/dist/lib/types/index.mjs.map +1 -1
  746. package/dist/lib/types/inline-enums.aliases.mjs +0 -6
  747. package/dist/lib/types/inline-enums.aliases.mjs.map +1 -1
  748. package/dist/lib/types/inline-enums.generated.mjs +0 -6
  749. package/dist/lib/types/inline-enums.generated.mjs.map +1 -1
  750. package/dist/lib/types/manifest.generated.mjs +0 -6
  751. package/dist/lib/types/manifest.generated.mjs.map +1 -1
  752. package/dist/lib/types/schemas.generated.mjs +0 -6
  753. package/dist/lib/types/schemas.generated.mjs.map +1 -1
  754. package/dist/lib/types/server-payload-aliases.mjs +0 -6
  755. package/dist/lib/types/server-payload.mjs +0 -6
  756. package/dist/lib/types/sync-rows.mjs +0 -6
  757. package/dist/lib/types/tools.generated.mjs +0 -6
  758. package/dist/lib/types/v2-5/index.mjs +0 -6
  759. package/dist/lib/types/v2-5/index.mjs.map +1 -1
  760. package/dist/lib/types/v2-5/tools.generated.mjs +0 -6
  761. package/dist/lib/types/v3-1-beta/index.mjs +0 -6
  762. package/dist/lib/types/v3-1-beta/index.mjs.map +1 -1
  763. package/dist/lib/types/v3-1-beta/tools.generated.mjs +0 -6
  764. package/dist/lib/types/wellknown-schemas.generated.mjs +0 -6
  765. package/dist/lib/types/wellknown-schemas.generated.mjs.map +1 -1
  766. package/dist/lib/upstream-recorder/constants.mjs +0 -6
  767. package/dist/lib/upstream-recorder/constants.mjs.map +1 -1
  768. package/dist/lib/upstream-recorder/index.mjs +0 -6
  769. package/dist/lib/upstream-recorder/index.mjs.map +1 -1
  770. package/dist/lib/upstream-recorder/recorder.mjs +0 -6
  771. package/dist/lib/upstream-recorder/recorder.mjs.map +1 -1
  772. package/dist/lib/upstream-recorder/types.mjs +0 -6
  773. package/dist/lib/upstream-recorder/types.mjs.map +1 -1
  774. package/dist/lib/utils/a2a-artifacts.mjs +0 -6
  775. package/dist/lib/utils/a2a-artifacts.mjs.map +1 -1
  776. package/dist/lib/utils/a2a-discovery.mjs +0 -6
  777. package/dist/lib/utils/a2a-discovery.mjs.map +1 -1
  778. package/dist/lib/utils/activation-key-builders.mjs +0 -6
  779. package/dist/lib/utils/activation-key-builders.mjs.map +1 -1
  780. package/dist/lib/utils/adcp-version-config.mjs +0 -6
  781. package/dist/lib/utils/adcp-version-config.mjs.map +1 -1
  782. package/dist/lib/utils/asset-builders.mjs +0 -6
  783. package/dist/lib/utils/asset-builders.mjs.map +1 -1
  784. package/dist/lib/utils/build-creative-return-builders.mjs +0 -6
  785. package/dist/lib/utils/build-creative-return-builders.mjs.map +1 -1
  786. package/dist/lib/utils/buyer-retry-policy.mjs +0 -6
  787. package/dist/lib/utils/buyer-retry-policy.mjs.map +1 -1
  788. package/dist/lib/utils/capabilities.mjs +0 -6
  789. package/dist/lib/utils/capabilities.mjs.map +1 -1
  790. package/dist/lib/utils/capability-rollups.mjs +0 -6
  791. package/dist/lib/utils/capability-rollups.mjs.map +1 -1
  792. package/dist/lib/utils/creative-adapter.mjs +0 -6
  793. package/dist/lib/utils/creative-adapter.mjs.map +1 -1
  794. package/dist/lib/utils/creative-delivery.mjs +0 -6
  795. package/dist/lib/utils/creative-delivery.mjs.map +1 -1
  796. package/dist/lib/utils/deprecation.mjs +0 -6
  797. package/dist/lib/utils/deprecation.mjs.map +1 -1
  798. package/dist/lib/utils/envelope-status-compat.mjs +0 -6
  799. package/dist/lib/utils/envelope-status-compat.mjs.map +1 -1
  800. package/dist/lib/utils/error-extraction.mjs +0 -6
  801. package/dist/lib/utils/error-extraction.mjs.map +1 -1
  802. package/dist/lib/utils/extract-result.mjs +0 -6
  803. package/dist/lib/utils/extract-result.mjs.map +1 -1
  804. package/dist/lib/utils/format-asset-slot-builders.mjs +0 -6
  805. package/dist/lib/utils/format-asset-slot-builders.mjs.map +1 -1
  806. package/dist/lib/utils/format-assets.mjs +0 -6
  807. package/dist/lib/utils/format-assets.mjs.map +1 -1
  808. package/dist/lib/utils/format-render-builders.mjs +0 -6
  809. package/dist/lib/utils/format-render-builders.mjs.map +1 -1
  810. package/dist/lib/utils/format-renders.mjs +0 -6
  811. package/dist/lib/utils/format-renders.mjs.map +1 -1
  812. package/dist/lib/utils/get-products-cache-scope.mjs +0 -6
  813. package/dist/lib/utils/get-products-cache-scope.mjs.map +1 -1
  814. package/dist/lib/utils/glob.mjs +0 -6
  815. package/dist/lib/utils/glob.mjs.map +1 -1
  816. package/dist/lib/utils/global-async-local-storage.mjs +0 -6
  817. package/dist/lib/utils/global-async-local-storage.mjs.map +1 -1
  818. package/dist/lib/utils/idempotency.mjs +0 -6
  819. package/dist/lib/utils/idempotency.mjs.map +1 -1
  820. package/dist/lib/utils/index.mjs +0 -6
  821. package/dist/lib/utils/index.mjs.map +1 -1
  822. package/dist/lib/utils/jcs.mjs +0 -6
  823. package/dist/lib/utils/jcs.mjs.map +1 -1
  824. package/dist/lib/utils/json-depth.mjs +0 -6
  825. package/dist/lib/utils/json-depth.mjs.map +1 -1
  826. package/dist/lib/utils/logger.mjs +0 -6
  827. package/dist/lib/utils/logger.mjs.map +1 -1
  828. package/dist/lib/utils/media-buy-delivery-notification-builders.mjs +0 -6
  829. package/dist/lib/utils/media-buy-delivery-notification-builders.mjs.map +1 -1
  830. package/dist/lib/utils/media-buy-status.mjs +0 -6
  831. package/dist/lib/utils/media-buy-status.mjs.map +1 -1
  832. package/dist/lib/utils/pagination.mjs +0 -6
  833. package/dist/lib/utils/pagination.mjs.map +1 -1
  834. package/dist/lib/utils/preview-creative-builders.mjs +0 -6
  835. package/dist/lib/utils/preview-creative-builders.mjs.map +1 -1
  836. package/dist/lib/utils/preview-normalizer.mjs +0 -6
  837. package/dist/lib/utils/preview-normalizer.mjs.map +1 -1
  838. package/dist/lib/utils/preview-utils.mjs +0 -6
  839. package/dist/lib/utils/preview-utils.mjs.map +1 -1
  840. package/dist/lib/utils/pricing-adapter.mjs +0 -6
  841. package/dist/lib/utils/pricing-adapter.mjs.map +1 -1
  842. package/dist/lib/utils/probe-policy.mjs +0 -6
  843. package/dist/lib/utils/probe-policy.mjs.map +1 -1
  844. package/dist/lib/utils/protocol-detection.mjs +0 -6
  845. package/dist/lib/utils/protocol-detection.mjs.map +1 -1
  846. package/dist/lib/utils/redact-secrets.mjs +0 -6
  847. package/dist/lib/utils/redact-secrets.mjs.map +1 -1
  848. package/dist/lib/utils/redis-default-prefix-warn.mjs +0 -6
  849. package/dist/lib/utils/redis-default-prefix-warn.mjs.map +1 -1
  850. package/dist/lib/utils/render-builders.mjs +0 -6
  851. package/dist/lib/utils/render-builders.mjs.map +1 -1
  852. package/dist/lib/utils/request-normalizer.mjs +0 -6
  853. package/dist/lib/utils/request-normalizer.mjs.map +1 -1
  854. package/dist/lib/utils/response-schemas.mjs +0 -6
  855. package/dist/lib/utils/response-schemas.mjs.map +1 -1
  856. package/dist/lib/utils/response-unwrapper.mjs +0 -6
  857. package/dist/lib/utils/response-unwrapper.mjs.map +1 -1
  858. package/dist/lib/utils/retry.mjs +0 -6
  859. package/dist/lib/utils/retry.mjs.map +1 -1
  860. package/dist/lib/utils/signal-discovery-helpers.mjs +0 -6
  861. package/dist/lib/utils/signal-discovery-helpers.mjs.map +1 -1
  862. package/dist/lib/utils/signal-id-builders.mjs +0 -6
  863. package/dist/lib/utils/signal-id-builders.mjs.map +1 -1
  864. package/dist/lib/utils/sync-creatives-adapter.mjs +0 -6
  865. package/dist/lib/utils/sync-creatives-adapter.mjs.map +1 -1
  866. package/dist/lib/utils/task-state.mjs +0 -6
  867. package/dist/lib/utils/task-state.mjs.map +1 -1
  868. package/dist/lib/utils/tool-request-schemas.mjs +0 -6
  869. package/dist/lib/utils/tool-request-schemas.mjs.map +1 -1
  870. package/dist/lib/utils/typeGuards.mjs +0 -6
  871. package/dist/lib/utils/typeGuards.mjs.map +1 -1
  872. package/dist/lib/utils/union-errors.mjs +0 -6
  873. package/dist/lib/utils/union-errors.mjs.map +1 -1
  874. package/dist/lib/utils/validate-user-agent.mjs +0 -6
  875. package/dist/lib/utils/validate-user-agent.mjs.map +1 -1
  876. package/dist/lib/v2/format-schema/fetch.mjs +0 -6
  877. package/dist/lib/v2/format-schema/fetch.mjs.map +1 -1
  878. package/dist/lib/v2/format-schema/index.mjs +0 -6
  879. package/dist/lib/v2/format-schema/index.mjs.map +1 -1
  880. package/dist/lib/v2/format-schema/regex-safety.mjs +0 -6
  881. package/dist/lib/v2/format-schema/regex-safety.mjs.map +1 -1
  882. package/dist/lib/v2/format-schema/resolver.mjs +7 -8
  883. package/dist/lib/v2/format-schema/resolver.mjs.map +1 -1
  884. package/dist/lib/v2/format-schema/sandbox-refs.mjs +0 -6
  885. package/dist/lib/v2/format-schema/sandbox-refs.mjs.map +1 -1
  886. package/dist/lib/v2/projection/augment-response.mjs +0 -6
  887. package/dist/lib/v2/projection/augment-response.mjs.map +1 -1
  888. package/dist/lib/v2/projection/builders.mjs +0 -6
  889. package/dist/lib/v2/projection/builders.mjs.map +1 -1
  890. package/dist/lib/v2/projection/cache-versions.mjs +0 -6
  891. package/dist/lib/v2/projection/cache-versions.mjs.map +1 -1
  892. package/dist/lib/v2/projection/canonical-properties.mjs +5 -6
  893. package/dist/lib/v2/projection/canonical-properties.mjs.map +1 -1
  894. package/dist/lib/v2/projection/catalog.mjs +5 -6
  895. package/dist/lib/v2/projection/catalog.mjs.map +1 -1
  896. package/dist/lib/v2/projection/constants.mjs +0 -6
  897. package/dist/lib/v2/projection/constants.mjs.map +1 -1
  898. package/dist/lib/v2/projection/index.mjs +0 -6
  899. package/dist/lib/v2/projection/index.mjs.map +1 -1
  900. package/dist/lib/v2/projection/registry.mjs +5 -6
  901. package/dist/lib/v2/projection/registry.mjs.map +1 -1
  902. package/dist/lib/v2/projection/types.mjs +0 -6
  903. package/dist/lib/v2/projection/v1-to-v2.mjs +0 -6
  904. package/dist/lib/v2/projection/v1-to-v2.mjs.map +1 -1
  905. package/dist/lib/v2/projection/v2-to-v1.mjs +0 -6
  906. package/dist/lib/v2/projection/v2-to-v1.mjs.map +1 -1
  907. package/dist/lib/v2/projection/write-side.mjs +0 -6
  908. package/dist/lib/v2/projection/write-side.mjs.map +1 -1
  909. package/dist/lib/v2/publisher-catalog/formats.mjs +0 -6
  910. package/dist/lib/v2/publisher-catalog/formats.mjs.map +1 -1
  911. package/dist/lib/v2/publisher-catalog/index.mjs +0 -6
  912. package/dist/lib/v2/publisher-catalog/index.mjs.map +1 -1
  913. package/dist/lib/validation/client-hooks.mjs +0 -6
  914. package/dist/lib/validation/client-hooks.mjs.map +1 -1
  915. package/dist/lib/validation/hints.mjs +0 -6
  916. package/dist/lib/validation/hints.mjs.map +1 -1
  917. package/dist/lib/validation/index.mjs +0 -6
  918. package/dist/lib/validation/index.mjs.map +1 -1
  919. package/dist/lib/validation/schema-errors.mjs +0 -6
  920. package/dist/lib/validation/schema-errors.mjs.map +1 -1
  921. package/dist/lib/validation/schema-loader.mjs +5 -6
  922. package/dist/lib/validation/schema-loader.mjs.map +1 -1
  923. package/dist/lib/validation/schema-validator.mjs +0 -6
  924. package/dist/lib/validation/schema-validator.mjs.map +1 -1
  925. package/dist/lib/validation/sync-creatives.mjs +0 -6
  926. package/dist/lib/validation/sync-creatives.mjs.map +1 -1
  927. package/dist/lib/version.d.mts +3 -3
  928. package/dist/lib/version.d.ts +3 -3
  929. package/dist/lib/version.js +3 -3
  930. package/dist/lib/version.js.map +1 -1
  931. package/dist/lib/version.mjs +3 -9
  932. package/dist/lib/version.mjs.map +1 -1
  933. package/dist/lib/webhooks/index.mjs +0 -6
  934. package/dist/lib/webhooks/index.mjs.map +1 -1
  935. package/dist/lib/wholesale-feed-sync/index.mjs +0 -6
  936. package/dist/lib/wholesale-feed-sync/index.mjs.map +1 -1
  937. package/dist/lib/wholesale-feed-sync/sync.mjs +0 -6
  938. package/dist/lib/wholesale-feed-sync/sync.mjs.map +1 -1
  939. package/dist/lib/wholesale-feed-sync/types.mjs +0 -6
  940. package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +0 -6
  941. package/dist/lib/wholesale-feed-sync/webhook-notification.mjs.map +1 -1
  942. package/package.json +2 -1
@@ -1,9 +1,3 @@
1
- import { fileURLToPath as __adcpFileURLToPath } from 'node:url';
2
- import { dirname as __adcpDirname } from 'node:path';
3
- import { createRequire as __adcpCreateRequire } from 'node:module';
4
- const __filename = __adcpFileURLToPath(import.meta.url);
5
- const __dirname = __adcpDirname(__filename);
6
- const require = __adcpCreateRequire(import.meta.url);
7
1
  import { z } from "zod";
8
2
  const BrandJsonSchema = z.union([z.object({ "$schema": z.string().optional(), "authoritative_location": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL of the authoritative brand.json file"), "redirect_reason": z.enum(["acquisition", "divestiture", "rebrand", "regional", "legacy", "consolidation", "other"]).describe("Optional structured signal indicating why this redirect was put in place. Consumers SHOULD use this to inform cache TTL decisions: 'acquisition' / 'divestiture' / 'rebrand' / 'consolidation' suggest the resolved target is in transition and consumers SHOULD shorten cache TTL until stable. 'regional' / 'legacy' suggest a stable redirect with no special cache handling needed. Free-text rationale belongs in 'note'.").optional(), "redirect_effective_at": z.string().datetime().describe("Optional timestamp when this redirect became effective. Caches MUST treat any entry cached before this timestamp as stale and re-fetch through the redirect.").optional(), "note": z.string().describe("Optional human-readable rationale for the redirect.").optional(), "last_updated": z.string().datetime().optional() }).strict().describe("Redirects to a hosted brand.json file at another URL"), z.object({ "$schema": z.string().optional(), "house": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("House domain to fetch brand portfolio from"), "region": z.string().regex(new RegExp("^[A-Z]{2}$")).describe("ISO 3166-1 alpha-2 country code if this is a regional domain").optional(), "redirect_reason": z.enum(["acquisition", "divestiture", "rebrand", "regional", "legacy", "consolidation", "other"]).describe("Optional structured signal indicating why this redirect was put in place. Consumers SHOULD use this to inform cache TTL decisions: 'acquisition' / 'divestiture' / 'rebrand' / 'consolidation' suggest the resolved target is in transition and consumers SHOULD shorten cache TTL until stable. 'regional' / 'legacy' suggest a stable redirect with no special cache handling needed. Free-text rationale belongs in 'note'.").optional(), "redirect_effective_at": z.string().datetime().describe("Optional timestamp when this redirect became effective. Caches MUST treat any entry cached before this timestamp as stale and re-fetch through the redirect.").optional(), "note": z.string().describe("Optional human-readable rationale for the redirect.").optional(), "last_updated": z.string().datetime().optional() }).strict().describe("Redirects to the house domain that contains the full brand portfolio"), z.object({ "$schema": z.string().optional(), "version": z.string().optional(), "agents": z.array(z.object({ "type": z.enum(["brand", "rights", "measurement", "governance", "creative", "sales", "buying", "signals"]).describe("Functional role of this agent"), "url": z.string().url().regex(new RegExp("^https://")).describe("Agent endpoint URL (MCP or A2A). Callers comparing a brand's declared agent URL against another value (e.g., resolving 'is this the agent that signed this artifact?' or matching against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).max(100).describe("Agent identifier (useful for logging, multi-tenant platforms)"), "description": z.string().max(500).describe("Human-readable description of this agent's capabilities or scope").optional(), "jwks_uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL of the agent's JWKS (RFC 7517) containing public keys used to verify artifacts this agent signs or requests it sends. Verified artifacts include signed governance_context tokens (for governance agents) and RFC 9421 HTTP Signatures on outgoing requests (for any agent). When absent, verifiers MUST default to /.well-known/jwks.json on the origin of `url`. Keys are identified by `kid` in the JWS header or RFC 9421 `keyid` parameter; JWKS MAY contain multiple keys to support rotation and per-purpose separation via `key_ops` and `use`.").optional(), "available_uses": z.array(z.enum(["likeness", "voice", "name", "endorsement", "motion_capture", "signature", "catchphrase", "sync", "background_music", "editorial", "commercial", "ai_generated_image"]).describe("Types of rights usage that can be licensed through the brand protocol. Aligned with DDEX UseType direction for interoperability with music and media rights systems.")).describe("For rights agents: rights uses available for licensing").optional(), "right_types": z.array(z.enum(["talent", "character", "brand_ip", "music", "stock_media"]).describe("Categories of intellectual property rights that can be licensed through the brand protocol.")).describe("For rights agents: types of rights available").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where this agent operates. Omit for global scope.").optional() }).catchall(z.any()).describe("An agent declared by a brand or house. Each entry identifies one agent endpoint and its functional role in the advertising ecosystem.")).describe("Agents declared by this brand or house. Multiple entries with the same type are permitted when they have distinct url values, such as one endpoint URL per tenant or property scope. Agent url values MUST be unique within this array; duplicate urls are invalid because signature verifiers resolve a signing key by matching one agent url to one agents[] entry and reject ambiguous matches.").optional(), "brand_agent": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("Brand agent MCP endpoint URL. Callers comparing this URL against another value (e.g., resolving 'is this the brand's declared agent?' against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Agent identifier (useful for logging, multi-tenant DAMs)") }).catchall(z.any()).describe("Reference to a brand agent that provides brand data via MCP").optional(), "contact": z.object({ "name": z.string().min(1).max(255), "email": z.string().email().max(255).optional(), "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("A valid domain name").optional() }).catchall(z.any()).describe("Contact information").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to a human-accessible contestation form or information page.").optional(), "email": z.string().email().describe("Email address for contestation requests. Deployer MUST monitor and respond within the timelines set by applicable law (e.g., 1 month under GDPR Art. 12(3)).").optional(), "languages": z.array(z.string()).describe("BCP 47 language tags the contestation channel supports (e.g., ['en', 'de', 'fr']). At minimum SHOULD include a language spoken in every jurisdiction where the brand runs regulated-vertical campaigns.").optional() }).strict().and(z.union([z.any(), z.any()])).describe("Contact point where a data subject can request human intervention, express their view, or contest an automated decision \u2014 satisfying GDPR Article 22(3) and EU AI Act Article 26(11) transparency obligations. This is a contact reference (URL, email, or both), not a machine-callable API. AdCP surfaces the pointer; the deployer runs the contestation workflow.").optional(), "last_updated": z.string().datetime().optional() }).strict().and(z.union([z.any(), z.any()])).describe("Brand represented by agents that provide brand info via MCP"), z.object({ "$schema": z.string().optional(), "version": z.string().optional(), "house": z.object({ "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("The house's domain where brand.json is hosted"), "name": z.string().min(1).describe("Primary display name of the house"), "names": z.array(z.record(z.string(), z.string().min(1)).describe("A localized name with BCP 47 locale code key (e.g., 'en_US', 'fr_CA', 'zh_CN') and name value. Bare language codes ('en') are accepted as wildcards for backwards compatibility.")).describe("Localized house names including legal name, stock symbol, etc.").optional(), "architecture": z.enum(["branded_house", "house_of_brands", "hybrid"]).describe("Brand architecture model: branded_house (Google), house_of_brands (P&G), hybrid (Nike)").optional(), "agents": z.array(z.object({ "type": z.enum(["brand", "rights", "measurement", "governance", "creative", "sales", "buying", "signals"]).describe("Functional role of this agent"), "url": z.string().url().regex(new RegExp("^https://")).describe("Agent endpoint URL (MCP or A2A). Callers comparing a brand's declared agent URL against another value (e.g., resolving 'is this the agent that signed this artifact?' or matching against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).max(100).describe("Agent identifier (useful for logging, multi-tenant platforms)"), "description": z.string().max(500).describe("Human-readable description of this agent's capabilities or scope").optional(), "jwks_uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL of the agent's JWKS (RFC 7517) containing public keys used to verify artifacts this agent signs or requests it sends. Verified artifacts include signed governance_context tokens (for governance agents) and RFC 9421 HTTP Signatures on outgoing requests (for any agent). When absent, verifiers MUST default to /.well-known/jwks.json on the origin of `url`. Keys are identified by `kid` in the JWS header or RFC 9421 `keyid` parameter; JWKS MAY contain multiple keys to support rotation and per-purpose separation via `key_ops` and `use`.").optional(), "available_uses": z.array(z.enum(["likeness", "voice", "name", "endorsement", "motion_capture", "signature", "catchphrase", "sync", "background_music", "editorial", "commercial", "ai_generated_image"]).describe("Types of rights usage that can be licensed through the brand protocol. Aligned with DDEX UseType direction for interoperability with music and media rights systems.")).describe("For rights agents: rights uses available for licensing").optional(), "right_types": z.array(z.enum(["talent", "character", "brand_ip", "music", "stock_media"]).describe("Categories of intellectual property rights that can be licensed through the brand protocol.")).describe("For rights agents: types of rights available").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where this agent operates. Omit for global scope.").optional() }).catchall(z.any()).describe("An agent declared by a brand or house. Each entry identifies one agent endpoint and its functional role in the advertising ecosystem.")).describe("House-level agents that apply to all brands unless overridden at the brand level").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to a human-accessible contestation form or information page.").optional(), "email": z.string().email().describe("Email address for contestation requests. Deployer MUST monitor and respond within the timelines set by applicable law (e.g., 1 month under GDPR Art. 12(3)).").optional(), "languages": z.array(z.string()).describe("BCP 47 language tags the contestation channel supports (e.g., ['en', 'de', 'fr']). At minimum SHOULD include a language spoken in every jurisdiction where the brand runs regulated-vertical campaigns.").optional() }).strict().and(z.union([z.any(), z.any()])).describe("House-level fallback contestation contact. Governance agents resolve in order: brand.data_subject_contestation \u2192 house.data_subject_contestation \u2192 missing (critical finding when human review required).").optional(), "identity_relying_parties": z.array(z.object({ "issuer": z.object({ "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where /.well-known/brand.json is hosted, or the brand's operating domain"), "brand_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Brand identifier within the house portfolio. Optional for single-brand domains.").optional(), "industries": z.array(z.string()).describe("Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json.").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).optional(), "email": z.string().email().optional(), "languages": z.array(z.string()).optional() }).strict().and(z.union([z.any(), z.any()])).describe("Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand \u2192 house \u2192 missing.").optional(), "brand_kit_override": z.object({ "logo": z.object({ "asset_type": z.literal("image").describe("Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry."), "url": z.string().url().describe("URL to the image asset"), "width": z.number().int().gte(1).describe("Width in pixels"), "height": z.number().int().gte(1).describe("Height in pixels"), "format": z.string().describe("Image file format (jpg, png, gif, webp, etc.)").optional(), "alt_text": z.string().describe("Alternative text for accessibility").optional(), "provenance": z.object({ "digital_source_type": z.enum(["digital_capture", "digital_creation", "trained_algorithmic_media", "composite_with_trained_algorithmic_media", "algorithmic_media", "composite_capture", "composite_synthetic", "human_edits", "data_driven_media"]).describe("IPTC-aligned classification of AI involvement in producing this content").optional(), "ai_tool": z.object({ "name": z.string().describe("Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')"), "version": z.string().describe("Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version.").optional(), "provider": z.string().describe("Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')").optional() }).catchall(z.any()).describe("AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.").optional(), "human_oversight": z.enum(["none", "prompt_only", "selected", "edited", "directed"]).describe("Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` \u2014 the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.").optional(), "declared_by": z.object({ "agent_url": z.string().url().describe("URL of the agent or service that declared this provenance").optional(), "role": z.enum(["creator", "advertiser", "agency", "platform", "tool"]).describe("Role of the declaring party in the supply chain") }).catchall(z.any()).describe("Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.").optional(), "declared_at": z.string().datetime().describe("When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.").optional(), "created_time": z.string().datetime().describe("When this content was created or generated (ISO 8601)").optional(), "c2pa": z.object({ "manifest_url": z.string().url().describe("URL to the C2PA manifest store for this content") }).catchall(z.any()).describe("C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.").optional(), "embedded_provenance": z.array(z.object({ "method": z.enum(["manifest_wrapper", "provenance_markers"]).describe("How provenance data is carried within the content"), "standard": z.string().describe("Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)").optional(), "provider": z.string().describe("Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context \u2014 not a wire identifier."), "verify_agent": z.object({ "agent_url": z.string().url().regex(new RegExp("^https://")).describe("URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed."), "feature_id": z.string().describe("Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog.").optional() }).strict().describe("Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing \u2014 the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts).").optional(), "embedded_at": z.string().datetime().describe("When the provenance data was embedded (ISO 8601)").optional() }).catchall(z.any())).describe("Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).").optional(), "watermarks": z.array(z.object({ "media_type": z.enum(["audio", "image", "video", "text"]).describe("Media category of the watermarked content"), "provider": z.string().describe("Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context \u2014 not a wire identifier."), "verify_agent": z.object({ "agent_url": z.string().url().regex(new RegExp("^https://")).describe("URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed."), "feature_id": z.string().describe("Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog.").optional() }).strict().describe("Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing \u2014 the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist).").optional(), "c2pa_action": z.enum(["c2pa.watermarked.bound", "c2pa.watermarked.unbound"]).describe("C2PA action classification for this watermark").optional(), "embedded_at": z.string().datetime().describe("When the watermark was applied (ISO 8601)").optional() }).catchall(z.any())).describe("Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).").optional(), "disclosure": z.object({ "required": z.boolean().describe("The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain \u2014 useful as a routing and audit input \u2014 not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover."), "jurisdictions": z.array(z.object({ "country": z.string().describe("ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')"), "region": z.string().describe("Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)").optional(), "regulation": z.string().describe("Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')"), "label_text": z.string().describe("Required disclosure label text for this jurisdiction, in the local language").optional(), "render_guidance": z.object({ "persistence": z.enum(["continuous", "initial", "flexible"]).describe("How long the disclosure must persist during content playback or display").optional(), "min_duration_ms": z.number().int().gte(1).describe("Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial \u2014 without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.").optional(), "positions": z.array(z.enum(["prominent", "footer", "audio", "subtitle", "overlay", "end_card", "pre_roll", "companion"]).describe("Where a required disclosure should appear within a creative. Used by creative briefs to specify disclosure placement and by formats to declare which positions they can render.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Preferred disclosure positions in priority order. The first position a format supports should be used.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed.").optional() }).catchall(z.any())).describe("Jurisdictions where disclosure obligations apply").optional() }).catchall(z.any()).describe("Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.").optional(), "verification": z.array(z.object({ "verified_by": z.string().describe("Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')"), "verified_time": z.string().datetime().describe("When the verification was performed (ISO 8601)").optional(), "result": z.enum(["authentic", "ai_generated", "ai_modified", "inconclusive"]).describe("Verification outcome"), "confidence": z.number().gte(0).lte(1).describe("Confidence score of the verification result (0.0 to 1.0)").optional(), "details_url": z.string().url().describe("URL to the full verification report").optional() }).catchall(z.any())).describe("Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim \u2014 verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("Provenance metadata for this asset, overrides manifest-level provenance").optional() }).catchall(z.any()).describe("Override logo asset.").optional(), "colors": z.object({ "primary": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional(), "secondary": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional(), "accent": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional() }).catchall(z.any()).describe("Override brand colors (hex strings).").optional(), "voice": z.string().describe("Override brand-voice description for surface-composed text/audio output.").optional(), "tagline": z.string().describe("Override tagline.").optional() }).catchall(z.any()).describe("Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call \u2014 e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` \u2014 the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently \u2014 when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently \u2014 a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload.").optional() }).strict().describe(`Identity issuer / attestation authority, referenced as a vendor BrandRef (e.g. {"domain": "world.org"}) \u2014 the same vendor-reference shape AdCP uses for measurement and signals vendors. The issuer's canonical domain is the anchor; it need not host a brand.json, but if it does, that is where its verifier metadata (scheme versions, verify endpoint, JWKs) lives. Issuer-agnostic: World ID, ISO 18013-5 mDL, and W3C-VC issuers all reference by domain. The relying party is namespaced by the issuer \u2014 identity is the tuple (issuer.domain, issuer.brand_id, relying_party_id), mirroring (vendor.domain, vendor.brand_id, metric_id).`), "scheme": z.string().describe('Proof scheme and version, e.g. "world_id_v4".').optional(), "relying_party_id": z.string().describe("The relying-party id registered with the issuer (and, for on-chain issuers like World ID, with the issuer's registry). One entity may operate many relying parties (scope=entity vs scope=property); this is the attestation's audience / linkability boundary, not an entity identifier."), "scope": z.enum(["entity", "property"]).describe("Whether this relying_party_id is shared across the entity's properties (entity \u2192 a within-entity unique-human graph) or scoped to a single property (property \u2192 per-property pseudonyms, unlinkable across the entity).").optional() }).strict().describe("A verified-identity relying party an entity or brand operates. Used for attestation provenance in TMP Identity Match (the buyer checks a forwarded attestation's relying_party_id against the owner's published list). Issuer-agnostic; World ID is the first issuer.")).describe("Verified-identity relying parties this house/entity operates, so a buyer can verify that a forwarded TMP identity attestation's relying_party_id genuinely belongs to this entity (provenance) rather than being replayed under another owner. For network-as-RP, the network publishes its own relying_party_id here. See specs/tmp-verified-identity-attestation.md.").optional() }).catchall(z.any()).describe("Corporate or organizational entity that owns brands"), "brands": z.array(z.object({ "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Brand identifier within the house. House chooses this ID."), "url": z.string().url().describe("Primary brand URL for context and asset discovery").optional(), "identity_relying_parties": z.array(z.object({ "issuer": z.object({ "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where /.well-known/brand.json is hosted, or the brand's operating domain"), "brand_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Brand identifier within the house portfolio. Optional for single-brand domains.").optional(), "industries": z.array(z.string()).describe("Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json.").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).optional(), "email": z.string().email().optional(), "languages": z.array(z.string()).optional() }).strict().and(z.union([z.any(), z.any()])).describe("Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand \u2192 house \u2192 missing.").optional(), "brand_kit_override": z.object({ "logo": z.object({ "asset_type": z.literal("image").describe("Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry."), "url": z.string().url().describe("URL to the image asset"), "width": z.number().int().gte(1).describe("Width in pixels"), "height": z.number().int().gte(1).describe("Height in pixels"), "format": z.string().describe("Image file format (jpg, png, gif, webp, etc.)").optional(), "alt_text": z.string().describe("Alternative text for accessibility").optional(), "provenance": z.object({ "digital_source_type": z.enum(["digital_capture", "digital_creation", "trained_algorithmic_media", "composite_with_trained_algorithmic_media", "algorithmic_media", "composite_capture", "composite_synthetic", "human_edits", "data_driven_media"]).describe("IPTC-aligned classification of AI involvement in producing this content").optional(), "ai_tool": z.object({ "name": z.string().describe("Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')"), "version": z.string().describe("Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version.").optional(), "provider": z.string().describe("Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')").optional() }).catchall(z.any()).describe("AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.").optional(), "human_oversight": z.enum(["none", "prompt_only", "selected", "edited", "directed"]).describe("Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` \u2014 the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.").optional(), "declared_by": z.object({ "agent_url": z.string().url().describe("URL of the agent or service that declared this provenance").optional(), "role": z.enum(["creator", "advertiser", "agency", "platform", "tool"]).describe("Role of the declaring party in the supply chain") }).catchall(z.any()).describe("Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.").optional(), "declared_at": z.string().datetime().describe("When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.").optional(), "created_time": z.string().datetime().describe("When this content was created or generated (ISO 8601)").optional(), "c2pa": z.object({ "manifest_url": z.string().url().describe("URL to the C2PA manifest store for this content") }).catchall(z.any()).describe("C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.").optional(), "embedded_provenance": z.array(z.object({ "method": z.enum(["manifest_wrapper", "provenance_markers"]).describe("How provenance data is carried within the content"), "standard": z.string().describe("Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)").optional(), "provider": z.string().describe("Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context \u2014 not a wire identifier."), "verify_agent": z.object({ "agent_url": z.string().url().regex(new RegExp("^https://")).describe("URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed."), "feature_id": z.string().describe("Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog.").optional() }).strict().describe("Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing \u2014 the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts).").optional(), "embedded_at": z.string().datetime().describe("When the provenance data was embedded (ISO 8601)").optional() }).catchall(z.any())).describe("Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).").optional(), "watermarks": z.array(z.object({ "media_type": z.enum(["audio", "image", "video", "text"]).describe("Media category of the watermarked content"), "provider": z.string().describe("Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context \u2014 not a wire identifier."), "verify_agent": z.object({ "agent_url": z.string().url().regex(new RegExp("^https://")).describe("URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed."), "feature_id": z.string().describe("Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog.").optional() }).strict().describe("Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing \u2014 the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist).").optional(), "c2pa_action": z.enum(["c2pa.watermarked.bound", "c2pa.watermarked.unbound"]).describe("C2PA action classification for this watermark").optional(), "embedded_at": z.string().datetime().describe("When the watermark was applied (ISO 8601)").optional() }).catchall(z.any())).describe("Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).").optional(), "disclosure": z.object({ "required": z.boolean().describe("The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain \u2014 useful as a routing and audit input \u2014 not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover."), "jurisdictions": z.array(z.object({ "country": z.string().describe("ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')"), "region": z.string().describe("Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)").optional(), "regulation": z.string().describe("Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')"), "label_text": z.string().describe("Required disclosure label text for this jurisdiction, in the local language").optional(), "render_guidance": z.object({ "persistence": z.enum(["continuous", "initial", "flexible"]).describe("How long the disclosure must persist during content playback or display").optional(), "min_duration_ms": z.number().int().gte(1).describe("Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial \u2014 without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.").optional(), "positions": z.array(z.enum(["prominent", "footer", "audio", "subtitle", "overlay", "end_card", "pre_roll", "companion"]).describe("Where a required disclosure should appear within a creative. Used by creative briefs to specify disclosure placement and by formats to declare which positions they can render.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Preferred disclosure positions in priority order. The first position a format supports should be used.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed.").optional() }).catchall(z.any())).describe("Jurisdictions where disclosure obligations apply").optional() }).catchall(z.any()).describe("Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.").optional(), "verification": z.array(z.object({ "verified_by": z.string().describe("Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')"), "verified_time": z.string().datetime().describe("When the verification was performed (ISO 8601)").optional(), "result": z.enum(["authentic", "ai_generated", "ai_modified", "inconclusive"]).describe("Verification outcome"), "confidence": z.number().gte(0).lte(1).describe("Confidence score of the verification result (0.0 to 1.0)").optional(), "details_url": z.string().url().describe("URL to the full verification report").optional() }).catchall(z.any())).describe("Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim \u2014 verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("Provenance metadata for this asset, overrides manifest-level provenance").optional() }).catchall(z.any()).describe("Override logo asset.").optional(), "colors": z.object({ "primary": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional(), "secondary": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional(), "accent": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional() }).catchall(z.any()).describe("Override brand colors (hex strings).").optional(), "voice": z.string().describe("Override brand-voice description for surface-composed text/audio output.").optional(), "tagline": z.string().describe("Override tagline.").optional() }).catchall(z.any()).describe("Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call \u2014 e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` \u2014 the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently \u2014 when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently \u2014 a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload.").optional() }).strict().describe(`Identity issuer / attestation authority, referenced as a vendor BrandRef (e.g. {"domain": "world.org"}) \u2014 the same vendor-reference shape AdCP uses for measurement and signals vendors. The issuer's canonical domain is the anchor; it need not host a brand.json, but if it does, that is where its verifier metadata (scheme versions, verify endpoint, JWKs) lives. Issuer-agnostic: World ID, ISO 18013-5 mDL, and W3C-VC issuers all reference by domain. The relying party is namespaced by the issuer \u2014 identity is the tuple (issuer.domain, issuer.brand_id, relying_party_id), mirroring (vendor.domain, vendor.brand_id, metric_id).`), "scheme": z.string().describe('Proof scheme and version, e.g. "world_id_v4".').optional(), "relying_party_id": z.string().describe("The relying-party id registered with the issuer (and, for on-chain issuers like World ID, with the issuer's registry). One entity may operate many relying parties (scope=entity vs scope=property); this is the attestation's audience / linkability boundary, not an entity identifier."), "scope": z.enum(["entity", "property"]).describe("Whether this relying_party_id is shared across the entity's properties (entity \u2192 a within-entity unique-human graph) or scoped to a single property (property \u2192 per-property pseudonyms, unlinkable across the entity).").optional() }).strict().describe("A verified-identity relying party an entity or brand operates. Used for attestation provenance in TMP Identity Match (the buyer checks a forwarded attestation's relying_party_id against the owner's published list). Issuer-agnostic; World ID is the first issuer.")).describe("Verified-identity relying parties scoped to this brand/property, for attestation provenance in TMP Identity Match. Use when a brand or property runs its own relying_party_id (per-property pseudonyms); entity-wide relying parties live on the house object. See specs/tmp-verified-identity-attestation.md.").optional(), "names": z.array(z.record(z.string(), z.string().min(1)).describe("A localized name with BCP 47 locale code key (e.g., 'en_US', 'fr_CA', 'zh_CN') and name value. Bare language codes ('en') are accepted as wildcards for backwards compatibility.")).describe("Localized brand names. Multiple entries per language allowed for aliases."), "keller_type": z.enum(["master", "sub_brand", "endorsed", "independent"]).describe("Brand architecture type from Keller's theory. master: primary brand of house. sub_brand: carries parent name (Nike SB). endorsed: independent identity backed by parent (Air Jordan 'by Nike'). independent: operates separately (Converse under Nike, Inc.)").optional(), "parent_brand": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Parent brand ID for sub-brands and endorsed brands").optional(), "description": z.string().describe("Brand description").optional(), "industries": z.array(z.string()).describe("Brand industries (e.g., ['automotive'] or ['pharmaceutical', 'cpg'] for a consumer health company). Describes what the company does \u2014 not what regulatory regimes apply (use policy_categories for that).").optional(), "target_audience": z.string().describe("Primary target audience").optional(), "logos": z.array(z.object({ "id": z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable identifier for this logo entry. Recommended when logo usage rules or mark lockups need to bind to a specific logo asset.").optional(), "url": z.string().url().describe("URL to the logo asset"), "orientation": z.enum(["square", "horizontal", "vertical", "stacked"]).describe("Logo aspect ratio orientation. square: ~1:1, horizontal: wide, vertical: tall, stacked: vertically arranged elements").optional(), "background": z.enum(["dark-bg", "light-bg", "transparent-bg"]).describe("Background compatibility. dark-bg: use on dark backgrounds, light-bg: use on light backgrounds, transparent-bg: has transparent background").optional(), "variant": z.enum(["primary", "secondary", "icon", "wordmark", "full-lockup"]).describe("Logo variant type. primary: main logo, secondary: alternative, icon: symbol only, wordmark: text only, full-lockup: complete logo").optional(), "tags": z.array(z.string()).describe("Additional semantic tags for custom categorization beyond the standard orientation, background, and variant fields").optional(), "slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Canonical renderer slots where this logo is appropriate. Consumers SHOULD prefer this over inferring from tags or usage prose when selecting a logo for a specific UI surface.").optional(), "usage": z.string().describe("Human-readable description of when to use this logo variant (e.g., 'Primary logo for use on light backgrounds')").optional(), "width": z.number().int().describe("Width in pixels").optional(), "height": z.number().int().describe("Height in pixels").optional() }).catchall(z.any()).describe("Brand logo asset with structured fields for orientation, background compatibility, and variant type")).describe("Brand logo assets").optional(), "colors": z.object({ "primary": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "secondary": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "accent": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "background": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "text": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "heading": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "body": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "label": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "border": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "divider": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "surface_1": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "surface_2": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional() }).catchall(z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))])).describe("Brand color palette. Each role accepts a single hex color or an array of hex colors for brands with multiple values per role. Beyond the core five roles, brands can provide additional color roles for finer granularity \u2014 heading, body, label, border, divider, surface_1, surface_2, etc.").optional(), "fonts": z.object({ "primary": z.union([z.string().describe("CSS font-family name (e.g., 'Montserrat', 'Arial, sans-serif')"), z.object({ "family": z.string().describe("CSS font-family name (e.g., 'Brand Sans')"), "files": z.array(z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to the font file (WOFF2, TTF, or OTF)"), "weight": z.number().int().gte(100).lte(900).describe("CSS numeric font-weight for static fonts (100-900)").optional(), "weight_range": z.array(z.number().int().gte(100).lte(900)).max(2).describe("Variable font weight axis range as [min, max] (e.g., [100, 900]). Use instead of weight for variable fonts.").optional(), "style": z.enum(["normal", "italic", "oblique"]).describe("CSS font-style").optional() }).catchall(z.any()).describe("A font file with weight, style, and variable font metadata")).max(36).describe("Font files for different weights and styles").optional(), "opentype_features": z.array(z.string().regex(new RegExp("^[a-z0-9]{4}$"))).max(20).describe("OpenType feature tags to enable (e.g., ['ss01', 'tnum', 'cv01']). These are four-character tags per the OpenType spec.").optional(), "fallbacks": z.array(z.string().max(100)).max(10).describe("Ordered fallback font-family names for when the primary font is unavailable or does not support the required script (e.g., ['Noto Sans Arabic', 'Noto Sans SC', 'sans-serif'])").optional() }).catchall(z.any()).describe("Structured font with family name, downloadable font files, and typographic metadata")]).describe("Primary font family").optional(), "secondary": z.union([z.string().describe("CSS font-family name (e.g., 'Montserrat', 'Arial, sans-serif')"), z.object({ "family": z.string().describe("CSS font-family name (e.g., 'Brand Sans')"), "files": z.array(z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to the font file (WOFF2, TTF, or OTF)"), "weight": z.number().int().gte(100).lte(900).describe("CSS numeric font-weight for static fonts (100-900)").optional(), "weight_range": z.array(z.number().int().gte(100).lte(900)).max(2).describe("Variable font weight axis range as [min, max] (e.g., [100, 900]). Use instead of weight for variable fonts.").optional(), "style": z.enum(["normal", "italic", "oblique"]).describe("CSS font-style").optional() }).catchall(z.any()).describe("A font file with weight, style, and variable font metadata")).max(36).describe("Font files for different weights and styles").optional(), "opentype_features": z.array(z.string().regex(new RegExp("^[a-z0-9]{4}$"))).max(20).describe("OpenType feature tags to enable (e.g., ['ss01', 'tnum', 'cv01']). These are four-character tags per the OpenType spec.").optional(), "fallbacks": z.array(z.string().max(100)).max(10).describe("Ordered fallback font-family names for when the primary font is unavailable or does not support the required script (e.g., ['Noto Sans Arabic', 'Noto Sans SC', 'sans-serif'])").optional() }).catchall(z.any()).describe("Structured font with family name, downloadable font files, and typographic metadata")]).describe("Secondary font family").optional() }).catchall(z.union([z.string().describe("CSS font-family name (e.g., 'Montserrat', 'Arial, sans-serif')"), z.object({ "family": z.string().describe("CSS font-family name (e.g., 'Brand Sans')"), "files": z.array(z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to the font file (WOFF2, TTF, or OTF)"), "weight": z.number().int().gte(100).lte(900).describe("CSS numeric font-weight for static fonts (100-900)").optional(), "weight_range": z.array(z.number().int().gte(100).lte(900)).max(2).describe("Variable font weight axis range as [min, max] (e.g., [100, 900]). Use instead of weight for variable fonts.").optional(), "style": z.enum(["normal", "italic", "oblique"]).describe("CSS font-style").optional() }).catchall(z.any()).describe("A font file with weight, style, and variable font metadata")).max(36).describe("Font files for different weights and styles").optional(), "opentype_features": z.array(z.string().regex(new RegExp("^[a-z0-9]{4}$"))).max(20).describe("OpenType feature tags to enable (e.g., ['ss01', 'tnum', 'cv01']). These are four-character tags per the OpenType spec.").optional(), "fallbacks": z.array(z.string().max(100)).max(10).describe("Ordered fallback font-family names for when the primary font is unavailable or does not support the required script (e.g., ['Noto Sans Arabic', 'Noto Sans SC', 'sans-serif'])").optional() }).catchall(z.any()).describe("Structured font with family name, downloadable font files, and typographic metadata")]).describe("A font role entry. Either a CSS font-family string (simple) or a structured object with family name and font files (rich).")).describe("Brand typography. Each key is a role name (e.g., 'primary', 'secondary') referenced by type_scale entries. Values are either a CSS font-family string or a structured object with font files for reliable resolution.").optional(), "tone": z.union([z.string().describe("Simple tone descriptors for backwards compatibility"), z.object({ "voice": z.string().describe("High-level voice descriptor (e.g., 'warm and inviting', 'professional and trustworthy')").optional(), "attributes": z.array(z.string()).describe("Personality traits that characterize the brand voice").optional(), "dos": z.array(z.string()).describe("Guidance for copy generation - what TO do").optional(), "donts": z.array(z.string()).describe("Guardrails to avoid brand violations - what NOT to do").optional() }).describe("Structured brand voice guidelines")]).describe("Brand voice and messaging tone guidelines").optional(), "tagline": z.union([z.string().describe("Plain tagline string for backwards compatibility"), z.array(z.record(z.string(), z.string().min(1)).describe("A localized name with BCP 47 locale code key (e.g., 'en_US', 'fr_CA', 'zh_CN') and name value. Bare language codes ('en') are accepted as wildcards for backwards compatibility.")).describe("Localized taglines with BCP 47 locale codes")]).describe("Brand tagline or slogan. Accepts a plain string or a localized array matching the names pattern.").optional(), "assets": z.array(z.object({ "asset_id": z.string().describe("Unique identifier"), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "html", "css", "javascript", "vast", "daast", "url", "webhook", "brief", "catalog", "published_post"]).describe("Type of asset content"), "url": z.string().url().describe("URL to CDN-hosted asset file"), "tags": z.array(z.string()).describe("Tags for discovery (e.g., 'hero', 'lifestyle', 'product', 'holiday')").optional(), "name": z.string().describe("Human-readable name").optional(), "description": z.string().describe("Asset description or usage notes").optional(), "width": z.number().int().describe("Image/video width in pixels").optional(), "height": z.number().int().describe("Image/video height in pixels").optional(), "duration_seconds": z.number().describe("Video/audio duration in seconds").optional(), "file_size_bytes": z.number().int().describe("File size in bytes").optional(), "format": z.string().describe("File format (e.g., 'jpg', 'mp4', 'mp3')").optional(), "metadata": z.record(z.string(), z.any()).describe("Additional asset-specific metadata").optional() }).catchall(z.any()).describe("Brand asset (image, video, audio, text)")).describe("Brand asset library").optional(), "properties": z.array(z.object({ "type": z.enum(["website", "mobile_app", "ctv_app", "desktop_app", "dooh", "podcast", "radio", "streaming_audio"]).describe("Property type"), "identifier": z.string().min(1).describe("Property identifier - domain for websites, bundle ID for apps"), "store": z.enum(["apple", "google", "amazon", "roku", "samsung", "lg", "other"]).describe("App store for mobile/CTV apps").optional(), "region": z.string().regex(new RegExp("^([A-Z]{2}|global)$")).describe("ISO 3166-1 alpha-2 country code or 'global'").optional(), "primary": z.boolean().describe("Whether this is the primary property for the brand").default(false), "relationship": z.enum(["owned", "direct", "delegated", "ad_network"]).describe("How this brand relates to the property. 'owned': the brand owns and operates this property (default) and has no adagents.json delegation_type counterpart. 'direct': the brand is the direct sales path for this property, even if a third party operates the software (e.g., a publisher's in-house ad team using a vendor's tech). 'delegated': the brand manages monetization for this property \u2014 they are in charge of ad sales (e.g., Mediavine managing a food blog). 'ad_network': the brand sells this property's inventory as part of a network or exchange \u2014 they are a path to the inventory, not the path (e.g., PubMatic as an SSP). For non-owned properties, the publisher confirms the relationship by setting the matching delegation_type on the agent's authorization in their adagents.json.").default("owned") }).catchall(z.any()).describe("A digital property associated with a brand. Defaults to owned; use 'relationship' to declare direct, delegated, or ad_network properties. For delegated and network paths, these values match the delegation_type field in adagents.json, creating a bilateral verification chain: the operator declares the relationship here, the publisher confirms by setting the same delegation_type on the agent's authorization in their adagents.json. 'owned' is an inline ownership declaration with no adagents.json counterpart.")).describe("Digital properties associated with this brand \u2014 owned, managed, or represented").optional(), "product_catalog": z.object({ "feed_url": z.string().url().describe("URL to product catalog feed"), "feed_format": z.enum(["google_merchant_center", "facebook_catalog", "shopify", "linkedin_jobs", "tiktok_shop", "pinterest_catalog", "openai_product_feed", "custom"]).describe("Format of the product feed").optional(), "categories": z.array(z.string()).describe("Product categories available in the catalog").optional(), "last_updated": z.string().datetime().describe("When the product catalog was last updated").optional(), "update_frequency": z.enum(["realtime", "hourly", "daily", "weekly"]).describe("How frequently the product catalog is updated").optional(), "agentic_checkout": z.object({ "endpoint": z.string().url().describe("Base URL for checkout session API"), "spec": z.string().describe("Checkout API specification identifier. Use a namespaced string to identify the checkout protocol (e.g., vendor-prefixed or custom). Vendor-specific values belong under ext.{vendor}."), "supported_payment_providers": z.array(z.string()).describe("Payment providers supported by this checkout endpoint").optional() }).describe("Agentic checkout endpoint configuration").optional() }).catchall(z.any()).describe("Product catalog for e-commerce brands").optional(), "privacy_policy_url": z.string().url().describe("URL to the brand's privacy policy").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to a human-accessible contestation form or information page.").optional(), "email": z.string().email().describe("Email address for contestation requests. Deployer MUST monitor and respond within the timelines set by applicable law (e.g., 1 month under GDPR Art. 12(3)).").optional(), "languages": z.array(z.string()).describe("BCP 47 language tags the contestation channel supports (e.g., ['en', 'de', 'fr']). At minimum SHOULD include a language spoken in every jurisdiction where the brand runs regulated-vertical campaigns.").optional() }).strict().and(z.union([z.any(), z.any()])).describe("Contact point where a data subject can request human intervention, express their view, or contest an automated decision \u2014 satisfying GDPR Article 22(3) and EU AI Act Article 26(11) transparency obligations. This is a contact reference (URL, email, or both), not a machine-callable API. AdCP surfaces the pointer; the deployer runs the contestation workflow.").optional(), "disclaimers": z.array(z.object({ "text": z.string(), "context": z.string().optional(), "required": z.boolean().default(true) })).describe("Legal disclaimers for creatives").optional(), "trademarks": z.array(z.object({ "registry": z.string().describe("Trademark registry (e.g., 'USPTO', 'EUIPO', 'JPO', 'CNIPA')"), "number": z.string().describe("Registration number as issued by the registry"), "mark": z.string().describe("The registered mark as published"), "status": z.enum(["active", "pending", "abandoned", "cancelled", "expired"]).describe("Registration status. Omit for active marks if status tracking is not maintained.").optional(), "license_type": z.enum(["owned", "licensed_in", "licensed_out"]).describe("Whether the publisher owns the mark, licenses it from another entity, or licenses it to others. 'owned' is the default if omitted.").optional(), "licensor_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain of the entity that licenses this mark to the publisher. Meaningful when license_type=licensed_in; omit otherwise.").optional(), "countries": z.array(z.string().min(2).max(2)).describe("ISO 3166-1 alpha-2 country codes where this registration applies. Omit for global or where the registry's jurisdiction is implicit.").optional(), "nice_classes": z.array(z.number().int().gte(1).lte(45)).describe("Nice Classification class numbers (1-45) covered by this registration. Disambiguates marks across industries (e.g., Delta-airline vs Delta-faucet). Omit if scope is implicit from registry.").optional() }).catchall(z.any()).describe("A registered trademark. May appear at house level (corporate marks, e.g., 'NIKE' owned by Nike, Inc.) or at brand level (brand-specific marks, e.g., 'CONVERSE' owned by Converse). Resolution between house- and brand-level trademarks is union \u2014 both lists are valid claims about marks the publisher controls.")).describe("Brand-level registered trademarks. Use for marks the brand owns or controls (e.g., a sub-brand's own marks distinct from the corporate parent). House-level trademarks live on the house object; resolution between the two is union \u2014 both lists are valid claims.").optional(), "voice_synthesis": z.object({ "provider": z.string().optional(), "voice_id": z.string().optional(), "settings": z.record(z.string(), z.any()).optional() }).catchall(z.any()).describe("TTS voice synthesis configuration for AI-generated audio").optional(), "avatar": z.object({ "provider": z.string().optional(), "avatar_id": z.string().optional(), "settings": z.record(z.string(), z.any()).optional() }).catchall(z.any()).describe("Visual avatar configuration").optional(), "visual_guidelines": z.object({ "photography": z.object({ "realism": z.enum(["natural", "stylized", "hyperreal", "abstract"]).describe("Level of photographic realism").optional(), "lighting": z.string().describe("Lighting style (e.g., 'soft daylight', 'studio', 'golden hour', 'high-key', 'low-key')").optional(), "color_temperature": z.enum(["warm", "neutral", "cool"]).describe("Overall color temperature of photography").optional(), "contrast": z.enum(["low", "medium", "high"]).describe("Contrast level in photography").optional(), "depth_of_field": z.enum(["shallow", "medium", "deep"]).describe("Depth of field preference. shallow: blurred background with subject isolation, deep: everything in focus").optional(), "subject": z.object({ "people": z.object({ "age_range": z.string().describe("Target age range (e.g., '20-35')").optional(), "diversity": z.string().describe("Diversity representation (e.g., 'mixed', 'varied')").optional(), "mood": z.array(z.string()).describe("Mood descriptors (e.g., ['confident', 'relaxed'])").optional() }).catchall(z.any()).describe("People photography guidelines").optional(), "product_focus": z.enum(["in-use", "isolated", "lifestyle", "detail"]).describe("How products are shown").optional(), "setting": z.string().describe("Environmental context for photography (e.g., 'indoor', 'outdoor', 'studio', 'urban', 'nature', 'workplace')").optional() }).catchall(z.any()).describe("Subject matter guidelines").optional(), "framing": z.object({ "subject_position": z.string().describe("Where the subject sits in frame (e.g., 'center', 'center-left', 'rule-of-thirds')").optional(), "crop_style": z.string().describe("Cropping convention (e.g., 'waist-up', 'full-body', 'close-up', 'wide')").optional(), "perspective": z.string().describe("Camera perspective (e.g., 'eye-level', 'overhead', 'low-angle')").optional() }).catchall(z.any()).describe("Camera framing rules").optional(), "preferred_aspect_ratios": z.array(z.string().regex(new RegExp("^\\d+:\\d+$"))).describe("Preferred aspect ratios for brand photography (e.g., '16:9', '4:5', '1:1')").optional(), "tags": z.array(z.string()).describe("Additional style descriptors").optional() }).catchall(z.any()).describe("Photography style rules for generative creative systems. Defines how brand photography should look when selected or generated.").optional(), "graphic_style": z.object({ "style_type": z.enum(["flat_illustration", "geometric", "gradient_mesh", "editorial_collage", "hand_drawn", "minimal_line_art", "3d_render", "isometric", "photographic_composite"]).describe("Primary graphic style").optional(), "stroke_style": z.enum(["rounded", "square", "mixed", "none"]).describe("Stroke end/join style").optional(), "stroke_weight": z.string().describe("Stroke weight (e.g., '2px', 'thin', 'bold')").optional(), "corner_radius": z.string().describe("Default corner radius for graphic and illustration elements (e.g., '12px', '8px', 'sharp'). For UI component radii (buttons, cards, inputs), see visual_guidelines.border_radius.").optional(), "tags": z.array(z.string()).describe("Additional style descriptors").optional() }).catchall(z.any()).describe("Visual language for brand graphics and illustrations").optional(), "shapes": z.object({ "primary_shape": z.string().describe("Primary brand shape (e.g., 'rounded_rectangle', 'circle', 'hexagon')").optional(), "secondary_shapes": z.array(z.string()).describe("Secondary shapes in the brand vocabulary").optional(), "usage": z.object({ "max_per_layout": z.number().int().describe("Maximum distinct shapes per layout").optional(), "overlap_allowed": z.boolean().describe("Whether shapes may overlap").optional() }).catchall(z.any()).describe("Shape usage rules").optional() }).catchall(z.any()).describe("Distinctive shapes used as part of brand visual identity").optional(), "iconography": z.object({ "style": z.enum(["outline", "filled", "duotone", "flat", "glyph", "hand_drawn"]).describe("Icon rendering style").optional(), "stroke_weight": z.string().describe("Icon stroke weight (e.g., '2px', '1.5px')").optional(), "corner_style": z.enum(["rounded", "square", "mixed"]).describe("Corner style for icon paths").optional(), "usage": z.object({ "max_per_frame": z.number().int().describe("Maximum icons per creative frame").optional(), "size_ratio": z.string().describe("Icon-to-layout size ratio (e.g., '1:8')").optional() }).catchall(z.any()).describe("Icon usage rules").optional() }).catchall(z.any()).describe("Icon style system and usage rules").optional(), "composition": z.object({ "overlays": z.object({ "gradient_style": z.enum(["linear", "radial", "conic", "none"]).describe("Gradient type for overlays").optional(), "gradient_direction": z.string().describe("Gradient direction (e.g., '45deg', 'to-bottom-right')").optional(), "opacity": z.string().describe("Overlay opacity (e.g., '70%')").optional() }).catchall(z.any()).describe("Graphic overlay rules").optional(), "texture": z.object({ "style": z.enum(["none", "subtle_grain", "noise", "paper", "fabric", "concrete"]).describe("Texture style applied to creative assets").optional(), "intensity": z.enum(["low", "medium", "high"]).describe("Texture intensity").optional() }).catchall(z.any()).describe("Texture treatment rules").optional(), "backgrounds": z.object({ "types_allowed": z.array(z.enum(["solid_color", "gradient", "blurred_photo", "image", "video", "pattern", "transparent"])).describe("Permitted background types").optional() }).catchall(z.any()).describe("Background treatment rules").optional() }).catchall(z.any()).describe("Layout composition rules including overlays, textures, and backgrounds").optional(), "border_radius": z.object({ "none": z.string().describe("Explicitly sharp corners (e.g., '0')").optional(), "default": z.string().describe("Default border radius for UI components (e.g., '8px', '12px', '0'). For graphic/illustration elements, see graphic_style.corner_radius.").optional(), "small": z.string().describe("Small border radius for compact elements (e.g., '4px')").optional(), "large": z.string().describe("Large border radius for cards and containers (e.g., '16px', '24px')").optional(), "pill": z.string().describe("Fully rounded / pill shape (e.g., '999px')").optional() }).catchall(z.string()).describe("Named border radius presets for UI components and layout elements. One of the most visible brand differentiators \u2014 Airbnb uses generous 20px, Stripe uses precise 4\u20138px, Spotify uses pill/999px.").optional(), "elevation": z.object({ "none": z.string().describe("No shadow (e.g., 'none')").optional(), "subtle": z.string().describe("Subtle shadow for slight lift (e.g., '0 1px 2px rgba(0,0,0,0.05)')").optional(), "card": z.string().describe("Card-level shadow (e.g., '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)')").optional(), "modal": z.string().describe("Modal/overlay shadow (e.g., '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)')").optional() }).catchall(z.string()).describe("Named shadow/elevation levels. Brands use elevation as identity \u2014 from Stripe's blue-tinted multi-layer shadows to Apple's single diffuse shadow. Values are CSS box-shadow syntax.").optional(), "spacing": z.object({ "unit": z.string().describe("Base grid unit this scale was designed from (e.g., '8px', '4px'). Informational \u2014 agents should use the named scale values, not compute from this.").optional(), "scale": z.object({ "xs": z.string().describe("Extra small spacing (e.g., '4px')").optional(), "sm": z.string().describe("Small spacing (e.g., '8px')").optional(), "md": z.string().describe("Medium spacing (e.g., '16px')").optional(), "lg": z.string().describe("Large spacing (e.g., '24px')").optional(), "xl": z.string().describe("Extra large spacing (e.g., '32px')").optional(), "2xl": z.string().describe("Section-level spacing (e.g., '48px', '64px')").optional() }).catchall(z.string()).describe("Named spacing scale built from the base unit").optional() }).strict().describe("Spacing system for consistent layout rhythm. Most design systems use an 8px base grid.").optional(), "graphic_elements": z.array(z.object({ "name": z.string().describe("Element name (e.g., 'Paper Tear', 'Brand Watermark', 'Section Divider')"), "type": z.enum(["border", "divider", "frame", "watermark", "pattern", "texture_overlay", "decorative"]).describe("Element type").optional(), "description": z.string().describe("How the element is used in layouts").optional(), "orientation": z.enum(["horizontal", "vertical", "any"]).describe("Preferred orientation when used in layouts").optional(), "colors": z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value")).describe("Colors this element may appear in").optional(), "max_per_layout": z.number().int().describe("Maximum instances per layout").optional() }).catchall(z.any()).describe("A reusable decorative or structural visual element that is part of the brand identity (e.g., torn paper edges, watermarks, dividers, background patterns)")).describe("Reusable decorative elements that are part of the brand visual identity (e.g., torn paper edges, watermarks, dividers)").optional(), "motion": z.object({ "transition_style": z.enum(["cut", "dissolve", "slide", "wipe", "zoom", "fade"]).describe("Primary transition style between scenes").optional(), "animation_speed": z.enum(["slow", "moderate", "fast"]).describe("Overall animation pacing").optional(), "easing": z.string().describe("Default easing function (e.g., 'ease-in-out', 'spring', 'linear')").optional(), "text_entrance": z.enum(["fade", "typewriter", "slide_up", "slide_left", "scale", "none"]).describe("How text enters the frame").optional(), "pacing": z.enum(["lingering", "moderate", "fast_cuts"]).describe("Overall editing rhythm").optional(), "kinetic_typography": z.boolean().describe("Whether animated/kinetic typography is allowed").optional(), "tags": z.array(z.string()).describe("Additional motion style descriptors").optional() }).catchall(z.any()).describe("Motion and animation rules for video, animated display, and interactive formats").optional(), "logo_placement": z.object({ "preferred_position": z.enum(["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right", "center"]).describe("Preferred logo position in layouts").optional(), "min_clear_space": z.string().describe("Minimum clear space around the logo, expressed as a multiple of logo height (e.g., '0.5x', '1x') or fixed value (e.g., '16px')").optional(), "min_height": z.string().describe("Minimum logo height to maintain legibility (e.g., '40px', '24px')").optional(), "background_contrast": z.enum(["light_only", "dark_only", "any"]).describe("Permitted background contrast behind logo").optional() }).catchall(z.any()).describe("Logo placement and clear space rules for automated creative production").optional(), "colorways": z.array(z.object({ "name": z.string().describe("Colorway name (e.g., 'primary', 'inverted', 'subtle')"), "foreground": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value"), "background": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value"), "accent": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value").optional(), "border": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value").optional(), "cta_foreground": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("CTA text/icon color, if different from foreground").optional(), "cta_background": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("CTA button/container color, if different from accent").optional(), "channels": z.array(z.string()).describe("Channels or contexts where this colorway applies (e.g., 'online', 'print', 'pos', 'social', 'outdoor'). Omit for universal colorways.").optional() }).catchall(z.any()).describe("A named color pairing that defines how colors work together. Colorways ensure foreground/background combinations are always on-brand and accessible.")).describe("Named color pairings for consistent foreground/background combinations").optional(), "color_constraints": z.array(z.object({ "color": z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Color role or value this constraint governs."), "applies_to": z.array(z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"])).describe("Surfaces where this color may be used.").optional(), "allowed_on": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Backgrounds, surfaces, or color roles where this color is allowed.").optional(), "forbidden_on": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Backgrounds, surfaces, or color roles where this color is forbidden.").optional(), "never_pair_with": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Color roles or values that must not be paired with this color.").optional(), "contexts": z.array(z.string()).describe("Channels or creative contexts where this constraint applies, such as digital, print, social, or ctv_end_card.").optional(), "severity": z.enum(["must", "should"]).describe("Strength of a machine-readable brand guideline constraint.").optional(), "description": z.string().describe("Human-readable rationale or source-language summary for the rule.").optional() }).catchall(z.any()).describe("Machine-readable rule constraining how a brand color may be used or paired. Use for accent-only colors, forbidden foreground/background combinations, and palette pairs that should never appear together.")).describe("Machine-readable constraints for color usage and pairings, such as accent-only rules or forbidden foreground/background combinations.").optional(), "logo_usage_rules": z.array(z.object({ "logo_url": z.string().url().describe("Specific logo asset URL this rule applies to. Omit when the rule applies by variant or tags.").optional(), "logo_id": z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable `logos[].id` this rule applies to. Prefer this over logo_url when the rule targets a specific logo entry.").optional(), "logo_variant": z.enum(["primary", "secondary", "icon", "wordmark", "full-lockup"]).describe("Logo variant this rule applies to.").optional(), "logo_tags": z.array(z.string()).describe("Logo tags this rule applies to.").optional(), "contexts": z.array(z.string()).describe("Creative contexts where this rule applies.").optional(), "slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Canonical renderer slots where this rule applies. Use this for deterministic logo-card, profile-mark, end-card, and lockup selection.").optional(), "minimum_size": z.object({ "width": z.string().describe("Minimum width, such as 48px or 12mm.").optional(), "height": z.string().describe("Minimum height, such as 18px or 6mm.").optional() }).strict().describe("Minimum rendered size needed for legibility.").optional(), "clear_space": z.string().describe("Minimum clear space around the logo, expressed in brand terms or units.").optional(), "allowed_backgrounds": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Background color roles, values, or surfaces where this logo may be placed.").optional(), "forbidden_backgrounds": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Background color roles, values, or surfaces where this logo must not be placed.").optional(), "forbidden_contexts": z.array(z.string()).describe("Contexts where this logo must not be used, such as photography_without_knockout.").optional(), "severity": z.enum(["must", "should"]).describe("Strength of a machine-readable brand guideline constraint.").optional(), "description": z.string().describe("Human-readable rationale or source-language summary for the rule.").optional() }).catchall(z.any()).and(z.union([z.any(), z.any(), z.any(), z.any(), z.any()])).describe("Machine-readable logo selection and placement rule. Complements logos[].usage by making enforceable minimum size, clear-space, background, and context constraints queryable.")).describe("Machine-readable logo selection and placement constraints for minimum size, clear space, backgrounds, and contexts.").optional(), "mark_lockups": z.array(z.object({ "lockup_type": z.enum(["co_brand", "secondary_mark", "partner", "sponsor", "program", "talent", "custom"]).describe("Type of mark relationship governed by this lockup rule."), "ordering": z.enum(["brand_first", "partner_first", "equal", "contextual"]).describe("Required visual ordering of the brand mark relative to partner or secondary marks.").optional(), "contexts": z.array(z.string()).describe("Creative contexts where this lockup rule applies.").optional(), "brand_logo_id": z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable `logos[].id` for the brand logo this lockup rule is anchored on.").optional(), "secondary_logo_ids": z.array(z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable identifier for a logo entry within this brand.json document. Use lowercase words separated by underscores or hyphens; do not key integrations on mutable asset URLs.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Stable `logos[].id` values for secondary, program, sponsor, or partner marks governed by this lockup rule when those marks are represented in this brand.json.").optional(), "separator": z.object({ "type": z.enum(["none", "keyline", "space", "divider"]).describe("Separator style."), "color": z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.").optional(), "width": z.string().describe("Separator width, such as 1px.").optional() }).catchall(z.any()).describe("Separator between marks, when required.").optional(), "min_gap": z.string().describe("Minimum gap between marks, expressed in brand terms or units.").optional(), "brand_min_optical_weight_ratio": z.number().gt(0).describe("Minimum optical weight of the brand mark relative to partner marks. 1 means at least equal.").optional(), "partner_max_optical_weight_ratio": z.number().gt(0).describe("Maximum optical weight of partner marks relative to the brand mark. 1 means no larger than the brand mark. Enforcement is at layout time, not parse time \u2014 this value signals to renderers and creative agents how much space to provision for each mark.").optional(), "severity": z.enum(["must", "should"]).describe("Strength of a machine-readable brand guideline constraint.").optional(), "description": z.string().describe("Human-readable rationale or source-language summary for the lockup rule.").optional() }).catchall(z.any()).describe("Machine-readable layout constraints for co-brand, partner, sponsor, program, or secondary-mark lockups.")).describe("Machine-readable co-brand, partner, sponsor, program, or secondary-mark lockup rules.").optional(), "type_scale": z.object({ "base_width": z.string().describe("Reference canvas width these sizes were designed for (e.g., '1080px'). Generative systems should scale proportionally for other canvas sizes.").optional(), "heading": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "subheading": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "body": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "caption": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "cta": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional() }).catchall(z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale")).describe("Typography scale defining sizes and weights for different text roles. When sizes are in px, use base_width to indicate the reference canvas.").optional(), "asset_libraries": z.array(z.object({ "name": z.string().describe("Display name of the asset library"), "type": z.enum(["icon_set", "illustration_system", "image_library", "video_library", "template_library"]).describe("Type of asset library").optional(), "url": z.string().url().describe("URL to the asset library (for human access)"), "description": z.string().describe("Description of the library contents and usage").optional(), "color_guide": z.object({ "roles": z.array(z.string()).describe("Named color roles used in the library (e.g., base, shadow_1, highlight_1, stroke)").optional(), "palettes": z.array(z.object({ "name": z.string().describe("Palette name"), "colors": z.record(z.string(), z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value")).describe("Map of role names to hex color values") }).catchall(z.any())).describe("Named color palettes mapping roles to specific colors").optional() }).catchall(z.any()).describe("Color guide for the asset library defining roles and palettes").optional() }).catchall(z.any()).describe("A managed asset library (icon set, illustration system, image collection). The URL is for human access; agent-facing DAM integration is under investigation.")).describe("References to managed asset libraries (icon sets, illustration systems, image collections). URLs are intended for human access; agent-facing DAM integration is under investigation.").optional(), "restrictions": z.array(z.string()).describe("Visual prohibitions and guardrails (e.g., 'Never use black backgrounds', 'Do not crop the logo', 'No stock photography of people on phones')").optional() }).catchall(z.any()).describe("Structured visual rules for generative creative systems").optional(), "agents": z.array(z.object({ "type": z.enum(["brand", "rights", "measurement", "governance", "creative", "sales", "buying", "signals"]).describe("Functional role of this agent"), "url": z.string().url().regex(new RegExp("^https://")).describe("Agent endpoint URL (MCP or A2A). Callers comparing a brand's declared agent URL against another value (e.g., resolving 'is this the agent that signed this artifact?' or matching against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).max(100).describe("Agent identifier (useful for logging, multi-tenant platforms)"), "description": z.string().max(500).describe("Human-readable description of this agent's capabilities or scope").optional(), "jwks_uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL of the agent's JWKS (RFC 7517) containing public keys used to verify artifacts this agent signs or requests it sends. Verified artifacts include signed governance_context tokens (for governance agents) and RFC 9421 HTTP Signatures on outgoing requests (for any agent). When absent, verifiers MUST default to /.well-known/jwks.json on the origin of `url`. Keys are identified by `kid` in the JWS header or RFC 9421 `keyid` parameter; JWKS MAY contain multiple keys to support rotation and per-purpose separation via `key_ops` and `use`.").optional(), "available_uses": z.array(z.enum(["likeness", "voice", "name", "endorsement", "motion_capture", "signature", "catchphrase", "sync", "background_music", "editorial", "commercial", "ai_generated_image"]).describe("Types of rights usage that can be licensed through the brand protocol. Aligned with DDEX UseType direction for interoperability with music and media rights systems.")).describe("For rights agents: rights uses available for licensing").optional(), "right_types": z.array(z.enum(["talent", "character", "brand_ip", "music", "stock_media"]).describe("Categories of intellectual property rights that can be licensed through the brand protocol.")).describe("For rights agents: types of rights available").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where this agent operates. Omit for global scope.").optional() }).catchall(z.any()).describe("An agent declared by a brand or house. Each entry identifies one agent endpoint and its functional role in the advertising ecosystem.")).describe("Agents authorized to act on behalf of this brand. Consumers resolving an agent by URL use the matching brand-level entry; do not infer a type-wide override of unrelated house-level entries when multiple same-type entries exist.").optional(), "brand_agent": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("Brand agent MCP endpoint URL. Callers comparing this URL against another value (e.g., resolving 'is this the brand's declared agent?' against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Agent identifier (useful for logging, multi-tenant DAMs)") }).catchall(z.any()).describe("Deprecated: use agents array with type 'brand' instead. Brand agent that provides dynamic brand data via MCP.").optional(), "rights_agent": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("Rights agent MCP endpoint URL. Callers comparing this URL against another value (e.g., matching against a brand's declared rights endpoint) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Agent identifier"), "available_uses": z.array(z.enum(["likeness", "voice", "name", "endorsement", "motion_capture", "signature", "catchphrase", "sync", "background_music", "editorial", "commercial", "ai_generated_image"]).describe("Types of rights usage that can be licensed through the brand protocol. Aligned with DDEX UseType direction for interoperability with music and media rights systems.")).describe("Rights uses available for licensing through this agent"), "right_types": z.array(z.enum(["talent", "character", "brand_ip", "music", "stock_media"]).describe("Categories of intellectual property rights that can be licensed through the brand protocol.")).describe("Types of rights available").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("Countries where rights are available (ISO 3166-1 alpha-2)").optional() }).catchall(z.any()).describe("Deprecated: use agents array with type 'rights' instead. Rights licensing agent for this brand.").optional(), "contact": z.object({ "email": z.string().email().describe("Contact email").optional(), "phone": z.string().describe("Contact phone number").optional() }).describe("Brand-level contact information").optional(), "collections": z.array(z.object({ "collection_id": z.string().describe("Collection identifier as used in the seller's get_products responses").optional(), "name": z.string().describe("Human-readable collection name"), "role": z.enum(["host", "guest", "creator", "cast", "narrator", "producer", "correspondent", "commentator", "analyst"]).describe("This person's role on the collection").optional(), "seller_agent_url": z.string().url().describe("URL of the sales agent that sells inventory for this collection. Buyer agents can query this agent for collection products.").optional() }).catchall(z.any())).describe("Collections this person or brand is associated with. Enables bidirectional linking: a collection's talent references brand.json via brand_url, and brand.json links back to collections.").optional() }).catchall(z.any()).describe("A brand within a house portfolio. Combines identity (who) with creative assets (how to represent). Referenced as domain + brand_id.")).describe("Inline brands owned by this house (parent-owned data). Use for sub-brands without their own canonical document \u2014 typically those without a dedicated domain or that the holdco wants to manage centrally. A brand_id MUST NOT appear in both brands[] and brand_refs[].").optional(), "brand_refs": z.array(z.object({ "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where the child's canonical brand.json lives"), "brand_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Stable brand identifier within the house portfolio. Required so the cross-array uniqueness invariant (brand_id MUST NOT appear in both brands[] and brand_refs[]) is enforceable."), "managed_by": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Optional domain of the entity that operationally manages this brand (e.g., an agency network within a holdco). House-declared. Consumers MUST NOT use it for trust or authorization decisions. Aggregation across houses ('show me everything BBH manages') is the intended use; trust is unaffected.").optional(), "effective_at": z.string().datetime().describe("ISO 8601 timestamp when the house established this ownership claim. Consumers age mutual-assertion edges from this date for TTL purposes. Optional; absent means the consumer ages from its own first observation.").optional() }).strict().describe("A house's ownership entry for a brand that publishes its own canonical brand.json elsewhere. The publisher (the house) asserts 'I own this brand, hosted at this domain, effective on this date.' Mutual-assertion trust requires the child's house_domain to reciprocate. Distinct from core/brand-ref.json (which identifies brands in media-buy plans). See docs/brand-protocol/brand-json.mdx")).describe("Portfolio entries for brands owned by this house that publish their own canonical brand.json elsewhere (child-owned data). Each entry asserts ownership plus where the child's document lives. Mutual-assertion trust: the pointed-to document's house_domain must equal this house's domain. Invariants: a brand_id MUST NOT appear in both brands[] and brand_refs[]; brand_id and domain MUST each be unique within brand_refs[]. See docs/brand-protocol/brand-json.mdx").optional(), "contact": z.object({ "name": z.string().min(1).max(255), "email": z.string().email().max(255).optional(), "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("A valid domain name").optional() }).catchall(z.any()).describe("Contact information").optional(), "authorized_operators": z.array(z.object({ "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain of the authorized operator (e.g., 'groupm.com')"), "brands": z.array(z.string().regex(new RegExp("^([a-z0-9_]+|\\*)$"))).describe("Brand IDs this operator is authorized for. Use ['*'] for all brands in the portfolio."), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where this authorization applies. Omit for global authorization.").optional(), "scopes": z.array(z.enum(["all", "media_buying", "creative_generation", "rights_clearance", "governance", "measurement", "agent_operations"])).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Activities this operator is authorized to perform for the listed brands and countries. Omit for backwards-compatible broad authorization. Use ['all'] only when every listed scope is delegated.").optional(), "valid_from": z.string().datetime().describe("ISO 8601 timestamp when this operator authorization starts. Omit when authorization is already active or the start date is not tracked.").optional(), "valid_until": z.string().datetime().describe("ISO 8601 timestamp when this operator authorization expires. Consumers MUST treat entries at or after this timestamp as inactive for authorization decisions.").optional() }).catchall(z.any()).describe("An entity authorized to represent brands from this house. Verified by resolving the operator's domain. Optional validity fields let houses time-box agency-of-record and delegated-operator relationships without changing historical entries.")).describe("Entities authorized to represent brands from this house. Third parties (sellers, platforms) can verify an operator's authorization by checking this list. Operators are identified by domain.").optional(), "trademarks": z.array(z.object({ "registry": z.string().describe("Trademark registry (e.g., 'USPTO', 'EUIPO', 'JPO', 'CNIPA')"), "number": z.string().describe("Registration number as issued by the registry"), "mark": z.string().describe("The registered mark as published"), "status": z.enum(["active", "pending", "abandoned", "cancelled", "expired"]).describe("Registration status. Omit for active marks if status tracking is not maintained.").optional(), "license_type": z.enum(["owned", "licensed_in", "licensed_out"]).describe("Whether the publisher owns the mark, licenses it from another entity, or licenses it to others. 'owned' is the default if omitted.").optional(), "licensor_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain of the entity that licenses this mark to the publisher. Meaningful when license_type=licensed_in; omit otherwise.").optional(), "countries": z.array(z.string().min(2).max(2)).describe("ISO 3166-1 alpha-2 country codes where this registration applies. Omit for global or where the registry's jurisdiction is implicit.").optional(), "nice_classes": z.array(z.number().int().gte(1).lte(45)).describe("Nice Classification class numbers (1-45) covered by this registration. Disambiguates marks across industries (e.g., Delta-airline vs Delta-faucet). Omit if scope is implicit from registry.").optional() }).catchall(z.any()).describe("A registered trademark. May appear at house level (corporate marks, e.g., 'NIKE' owned by Nike, Inc.) or at brand level (brand-specific marks, e.g., 'CONVERSE' owned by Converse). Resolution between house- and brand-level trademarks is union \u2014 both lists are valid claims about marks the publisher controls.")).describe("House-level (corporate) registered trademarks. Brand-level marks live on individual brand entries; resolution is union.").optional(), "last_updated": z.string().datetime().optional() }).strict().and(z.union([z.any(), z.any()])).describe("Full house/brand portfolio with hierarchy, creative assets, and properties. May carry inline brands (parent-owned, brands[]) and/or pointer brands (child-owned canonical documents, brand_refs[]). At least one of brands[] or brand_refs[] is required. A brand_id MUST NOT appear in both. See docs/brand-protocol/brand-json.mdx"), z.record(z.string(), z.any()).and(z.intersection(z.object({ "$schema": z.string().optional(), "version": z.string().optional(), "house_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Optional pointer to the corporate house this brand belongs to. The named house's brand_refs[] MUST reciprocate for mutual-assertion trust. Single-hop only \u2014 a brand cannot itself declare brand_refs[]. Omit for standalone brands (no house).").optional(), "last_updated": z.string().datetime().optional() }), z.object({ "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Brand identifier within the house. House chooses this ID."), "url": z.string().url().describe("Primary brand URL for context and asset discovery").optional(), "identity_relying_parties": z.array(z.object({ "issuer": z.object({ "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain where /.well-known/brand.json is hosted, or the brand's operating domain"), "brand_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Brand identifier within the house portfolio. Optional for single-brand domains.").optional(), "industries": z.array(z.string()).describe("Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json.").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).optional(), "email": z.string().email().optional(), "languages": z.array(z.string()).optional() }).strict().and(z.union([z.any(), z.any()])).describe("Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand \u2192 house \u2192 missing.").optional(), "brand_kit_override": z.object({ "logo": z.object({ "asset_type": z.literal("image").describe("Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry."), "url": z.string().url().describe("URL to the image asset"), "width": z.number().int().gte(1).describe("Width in pixels"), "height": z.number().int().gte(1).describe("Height in pixels"), "format": z.string().describe("Image file format (jpg, png, gif, webp, etc.)").optional(), "alt_text": z.string().describe("Alternative text for accessibility").optional(), "provenance": z.object({ "digital_source_type": z.enum(["digital_capture", "digital_creation", "trained_algorithmic_media", "composite_with_trained_algorithmic_media", "algorithmic_media", "composite_capture", "composite_synthetic", "human_edits", "data_driven_media"]).describe("IPTC-aligned classification of AI involvement in producing this content").optional(), "ai_tool": z.object({ "name": z.string().describe("Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')"), "version": z.string().describe("Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version.").optional(), "provider": z.string().describe("Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')").optional() }).catchall(z.any()).describe("AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.").optional(), "human_oversight": z.enum(["none", "prompt_only", "selected", "edited", "directed"]).describe("Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` \u2014 the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.").optional(), "declared_by": z.object({ "agent_url": z.string().url().describe("URL of the agent or service that declared this provenance").optional(), "role": z.enum(["creator", "advertiser", "agency", "platform", "tool"]).describe("Role of the declaring party in the supply chain") }).catchall(z.any()).describe("Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.").optional(), "declared_at": z.string().datetime().describe("When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.").optional(), "created_time": z.string().datetime().describe("When this content was created or generated (ISO 8601)").optional(), "c2pa": z.object({ "manifest_url": z.string().url().describe("URL to the C2PA manifest store for this content") }).catchall(z.any()).describe("C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.").optional(), "embedded_provenance": z.array(z.object({ "method": z.enum(["manifest_wrapper", "provenance_markers"]).describe("How provenance data is carried within the content"), "standard": z.string().describe("Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)").optional(), "provider": z.string().describe("Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context \u2014 not a wire identifier."), "verify_agent": z.object({ "agent_url": z.string().url().regex(new RegExp("^https://")).describe("URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed."), "feature_id": z.string().describe("Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog.").optional() }).strict().describe("Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing \u2014 the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts).").optional(), "embedded_at": z.string().datetime().describe("When the provenance data was embedded (ISO 8601)").optional() }).catchall(z.any())).describe("Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).").optional(), "watermarks": z.array(z.object({ "media_type": z.enum(["audio", "image", "video", "text"]).describe("Media category of the watermarked content"), "provider": z.string().describe("Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context \u2014 not a wire identifier."), "verify_agent": z.object({ "agent_url": z.string().url().regex(new RegExp("^https://")).describe("URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed."), "feature_id": z.string().describe("Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog.").optional() }).strict().describe("Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing \u2014 the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist).").optional(), "c2pa_action": z.enum(["c2pa.watermarked.bound", "c2pa.watermarked.unbound"]).describe("C2PA action classification for this watermark").optional(), "embedded_at": z.string().datetime().describe("When the watermark was applied (ISO 8601)").optional() }).catchall(z.any())).describe("Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).").optional(), "disclosure": z.object({ "required": z.boolean().describe("The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain \u2014 useful as a routing and audit input \u2014 not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover."), "jurisdictions": z.array(z.object({ "country": z.string().describe("ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')"), "region": z.string().describe("Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)").optional(), "regulation": z.string().describe("Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')"), "label_text": z.string().describe("Required disclosure label text for this jurisdiction, in the local language").optional(), "render_guidance": z.object({ "persistence": z.enum(["continuous", "initial", "flexible"]).describe("How long the disclosure must persist during content playback or display").optional(), "min_duration_ms": z.number().int().gte(1).describe("Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial \u2014 without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.").optional(), "positions": z.array(z.enum(["prominent", "footer", "audio", "subtitle", "overlay", "end_card", "pre_roll", "companion"]).describe("Where a required disclosure should appear within a creative. Used by creative briefs to specify disclosure placement and by formats to declare which positions they can render.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Preferred disclosure positions in priority order. The first position a format supports should be used.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed.").optional() }).catchall(z.any())).describe("Jurisdictions where disclosure obligations apply").optional() }).catchall(z.any()).describe("Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.").optional(), "verification": z.array(z.object({ "verified_by": z.string().describe("Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')"), "verified_time": z.string().datetime().describe("When the verification was performed (ISO 8601)").optional(), "result": z.enum(["authentic", "ai_generated", "ai_modified", "inconclusive"]).describe("Verification outcome"), "confidence": z.number().gte(0).lte(1).describe("Confidence score of the verification result (0.0 to 1.0)").optional(), "details_url": z.string().url().describe("URL to the full verification report").optional() }).catchall(z.any())).describe("Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim \u2014 verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("Provenance metadata for this asset, overrides manifest-level provenance").optional() }).catchall(z.any()).describe("Override logo asset.").optional(), "colors": z.object({ "primary": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional(), "secondary": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional(), "accent": z.string().regex(new RegExp("^#[0-9a-fA-F]{6}$")).optional() }).catchall(z.any()).describe("Override brand colors (hex strings).").optional(), "voice": z.string().describe("Override brand-voice description for surface-composed text/audio output.").optional(), "tagline": z.string().describe("Override tagline.").optional() }).catchall(z.any()).describe("Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call \u2014 e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` \u2014 the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently \u2014 when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently \u2014 a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload.").optional() }).strict().describe(`Identity issuer / attestation authority, referenced as a vendor BrandRef (e.g. {"domain": "world.org"}) \u2014 the same vendor-reference shape AdCP uses for measurement and signals vendors. The issuer's canonical domain is the anchor; it need not host a brand.json, but if it does, that is where its verifier metadata (scheme versions, verify endpoint, JWKs) lives. Issuer-agnostic: World ID, ISO 18013-5 mDL, and W3C-VC issuers all reference by domain. The relying party is namespaced by the issuer \u2014 identity is the tuple (issuer.domain, issuer.brand_id, relying_party_id), mirroring (vendor.domain, vendor.brand_id, metric_id).`), "scheme": z.string().describe('Proof scheme and version, e.g. "world_id_v4".').optional(), "relying_party_id": z.string().describe("The relying-party id registered with the issuer (and, for on-chain issuers like World ID, with the issuer's registry). One entity may operate many relying parties (scope=entity vs scope=property); this is the attestation's audience / linkability boundary, not an entity identifier."), "scope": z.enum(["entity", "property"]).describe("Whether this relying_party_id is shared across the entity's properties (entity \u2192 a within-entity unique-human graph) or scoped to a single property (property \u2192 per-property pseudonyms, unlinkable across the entity).").optional() }).strict().describe("A verified-identity relying party an entity or brand operates. Used for attestation provenance in TMP Identity Match (the buyer checks a forwarded attestation's relying_party_id against the owner's published list). Issuer-agnostic; World ID is the first issuer.")).describe("Verified-identity relying parties scoped to this brand/property, for attestation provenance in TMP Identity Match. Use when a brand or property runs its own relying_party_id (per-property pseudonyms); entity-wide relying parties live on the house object. See specs/tmp-verified-identity-attestation.md.").optional(), "names": z.array(z.record(z.string(), z.string().min(1)).describe("A localized name with BCP 47 locale code key (e.g., 'en_US', 'fr_CA', 'zh_CN') and name value. Bare language codes ('en') are accepted as wildcards for backwards compatibility.")).describe("Localized brand names. Multiple entries per language allowed for aliases."), "keller_type": z.enum(["master", "sub_brand", "endorsed", "independent"]).describe("Brand architecture type from Keller's theory. master: primary brand of house. sub_brand: carries parent name (Nike SB). endorsed: independent identity backed by parent (Air Jordan 'by Nike'). independent: operates separately (Converse under Nike, Inc.)").optional(), "parent_brand": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Parent brand ID for sub-brands and endorsed brands").optional(), "description": z.string().describe("Brand description").optional(), "industries": z.array(z.string()).describe("Brand industries (e.g., ['automotive'] or ['pharmaceutical', 'cpg'] for a consumer health company). Describes what the company does \u2014 not what regulatory regimes apply (use policy_categories for that).").optional(), "target_audience": z.string().describe("Primary target audience").optional(), "logos": z.array(z.object({ "id": z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable identifier for this logo entry. Recommended when logo usage rules or mark lockups need to bind to a specific logo asset.").optional(), "url": z.string().url().describe("URL to the logo asset"), "orientation": z.enum(["square", "horizontal", "vertical", "stacked"]).describe("Logo aspect ratio orientation. square: ~1:1, horizontal: wide, vertical: tall, stacked: vertically arranged elements").optional(), "background": z.enum(["dark-bg", "light-bg", "transparent-bg"]).describe("Background compatibility. dark-bg: use on dark backgrounds, light-bg: use on light backgrounds, transparent-bg: has transparent background").optional(), "variant": z.enum(["primary", "secondary", "icon", "wordmark", "full-lockup"]).describe("Logo variant type. primary: main logo, secondary: alternative, icon: symbol only, wordmark: text only, full-lockup: complete logo").optional(), "tags": z.array(z.string()).describe("Additional semantic tags for custom categorization beyond the standard orientation, background, and variant fields").optional(), "slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Canonical renderer slots where this logo is appropriate. Consumers SHOULD prefer this over inferring from tags or usage prose when selecting a logo for a specific UI surface.").optional(), "usage": z.string().describe("Human-readable description of when to use this logo variant (e.g., 'Primary logo for use on light backgrounds')").optional(), "width": z.number().int().describe("Width in pixels").optional(), "height": z.number().int().describe("Height in pixels").optional() }).catchall(z.any()).describe("Brand logo asset with structured fields for orientation, background compatibility, and variant type")).describe("Brand logo assets").optional(), "colors": z.object({ "primary": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "secondary": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "accent": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "background": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "text": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "heading": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "body": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "label": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "border": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "divider": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "surface_1": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional(), "surface_2": z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))]).optional() }).catchall(z.union([z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")), z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")))])).describe("Brand color palette. Each role accepts a single hex color or an array of hex colors for brands with multiple values per role. Beyond the core five roles, brands can provide additional color roles for finer granularity \u2014 heading, body, label, border, divider, surface_1, surface_2, etc.").optional(), "fonts": z.object({ "primary": z.union([z.string().describe("CSS font-family name (e.g., 'Montserrat', 'Arial, sans-serif')"), z.object({ "family": z.string().describe("CSS font-family name (e.g., 'Brand Sans')"), "files": z.array(z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to the font file (WOFF2, TTF, or OTF)"), "weight": z.number().int().gte(100).lte(900).describe("CSS numeric font-weight for static fonts (100-900)").optional(), "weight_range": z.array(z.number().int().gte(100).lte(900)).max(2).describe("Variable font weight axis range as [min, max] (e.g., [100, 900]). Use instead of weight for variable fonts.").optional(), "style": z.enum(["normal", "italic", "oblique"]).describe("CSS font-style").optional() }).catchall(z.any()).describe("A font file with weight, style, and variable font metadata")).max(36).describe("Font files for different weights and styles").optional(), "opentype_features": z.array(z.string().regex(new RegExp("^[a-z0-9]{4}$"))).max(20).describe("OpenType feature tags to enable (e.g., ['ss01', 'tnum', 'cv01']). These are four-character tags per the OpenType spec.").optional(), "fallbacks": z.array(z.string().max(100)).max(10).describe("Ordered fallback font-family names for when the primary font is unavailable or does not support the required script (e.g., ['Noto Sans Arabic', 'Noto Sans SC', 'sans-serif'])").optional() }).catchall(z.any()).describe("Structured font with family name, downloadable font files, and typographic metadata")]).describe("Primary font family").optional(), "secondary": z.union([z.string().describe("CSS font-family name (e.g., 'Montserrat', 'Arial, sans-serif')"), z.object({ "family": z.string().describe("CSS font-family name (e.g., 'Brand Sans')"), "files": z.array(z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to the font file (WOFF2, TTF, or OTF)"), "weight": z.number().int().gte(100).lte(900).describe("CSS numeric font-weight for static fonts (100-900)").optional(), "weight_range": z.array(z.number().int().gte(100).lte(900)).max(2).describe("Variable font weight axis range as [min, max] (e.g., [100, 900]). Use instead of weight for variable fonts.").optional(), "style": z.enum(["normal", "italic", "oblique"]).describe("CSS font-style").optional() }).catchall(z.any()).describe("A font file with weight, style, and variable font metadata")).max(36).describe("Font files for different weights and styles").optional(), "opentype_features": z.array(z.string().regex(new RegExp("^[a-z0-9]{4}$"))).max(20).describe("OpenType feature tags to enable (e.g., ['ss01', 'tnum', 'cv01']). These are four-character tags per the OpenType spec.").optional(), "fallbacks": z.array(z.string().max(100)).max(10).describe("Ordered fallback font-family names for when the primary font is unavailable or does not support the required script (e.g., ['Noto Sans Arabic', 'Noto Sans SC', 'sans-serif'])").optional() }).catchall(z.any()).describe("Structured font with family name, downloadable font files, and typographic metadata")]).describe("Secondary font family").optional() }).catchall(z.union([z.string().describe("CSS font-family name (e.g., 'Montserrat', 'Arial, sans-serif')"), z.object({ "family": z.string().describe("CSS font-family name (e.g., 'Brand Sans')"), "files": z.array(z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to the font file (WOFF2, TTF, or OTF)"), "weight": z.number().int().gte(100).lte(900).describe("CSS numeric font-weight for static fonts (100-900)").optional(), "weight_range": z.array(z.number().int().gte(100).lte(900)).max(2).describe("Variable font weight axis range as [min, max] (e.g., [100, 900]). Use instead of weight for variable fonts.").optional(), "style": z.enum(["normal", "italic", "oblique"]).describe("CSS font-style").optional() }).catchall(z.any()).describe("A font file with weight, style, and variable font metadata")).max(36).describe("Font files for different weights and styles").optional(), "opentype_features": z.array(z.string().regex(new RegExp("^[a-z0-9]{4}$"))).max(20).describe("OpenType feature tags to enable (e.g., ['ss01', 'tnum', 'cv01']). These are four-character tags per the OpenType spec.").optional(), "fallbacks": z.array(z.string().max(100)).max(10).describe("Ordered fallback font-family names for when the primary font is unavailable or does not support the required script (e.g., ['Noto Sans Arabic', 'Noto Sans SC', 'sans-serif'])").optional() }).catchall(z.any()).describe("Structured font with family name, downloadable font files, and typographic metadata")]).describe("A font role entry. Either a CSS font-family string (simple) or a structured object with family name and font files (rich).")).describe("Brand typography. Each key is a role name (e.g., 'primary', 'secondary') referenced by type_scale entries. Values are either a CSS font-family string or a structured object with font files for reliable resolution.").optional(), "tone": z.union([z.string().describe("Simple tone descriptors for backwards compatibility"), z.object({ "voice": z.string().describe("High-level voice descriptor (e.g., 'warm and inviting', 'professional and trustworthy')").optional(), "attributes": z.array(z.string()).describe("Personality traits that characterize the brand voice").optional(), "dos": z.array(z.string()).describe("Guidance for copy generation - what TO do").optional(), "donts": z.array(z.string()).describe("Guardrails to avoid brand violations - what NOT to do").optional() }).describe("Structured brand voice guidelines")]).describe("Brand voice and messaging tone guidelines").optional(), "tagline": z.union([z.string().describe("Plain tagline string for backwards compatibility"), z.array(z.record(z.string(), z.string().min(1)).describe("A localized name with BCP 47 locale code key (e.g., 'en_US', 'fr_CA', 'zh_CN') and name value. Bare language codes ('en') are accepted as wildcards for backwards compatibility.")).describe("Localized taglines with BCP 47 locale codes")]).describe("Brand tagline or slogan. Accepts a plain string or a localized array matching the names pattern.").optional(), "assets": z.array(z.object({ "asset_id": z.string().describe("Unique identifier"), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "html", "css", "javascript", "vast", "daast", "url", "webhook", "brief", "catalog", "published_post"]).describe("Type of asset content"), "url": z.string().url().describe("URL to CDN-hosted asset file"), "tags": z.array(z.string()).describe("Tags for discovery (e.g., 'hero', 'lifestyle', 'product', 'holiday')").optional(), "name": z.string().describe("Human-readable name").optional(), "description": z.string().describe("Asset description or usage notes").optional(), "width": z.number().int().describe("Image/video width in pixels").optional(), "height": z.number().int().describe("Image/video height in pixels").optional(), "duration_seconds": z.number().describe("Video/audio duration in seconds").optional(), "file_size_bytes": z.number().int().describe("File size in bytes").optional(), "format": z.string().describe("File format (e.g., 'jpg', 'mp4', 'mp3')").optional(), "metadata": z.record(z.string(), z.any()).describe("Additional asset-specific metadata").optional() }).catchall(z.any()).describe("Brand asset (image, video, audio, text)")).describe("Brand asset library").optional(), "properties": z.array(z.object({ "type": z.enum(["website", "mobile_app", "ctv_app", "desktop_app", "dooh", "podcast", "radio", "streaming_audio"]).describe("Property type"), "identifier": z.string().min(1).describe("Property identifier - domain for websites, bundle ID for apps"), "store": z.enum(["apple", "google", "amazon", "roku", "samsung", "lg", "other"]).describe("App store for mobile/CTV apps").optional(), "region": z.string().regex(new RegExp("^([A-Z]{2}|global)$")).describe("ISO 3166-1 alpha-2 country code or 'global'").optional(), "primary": z.boolean().describe("Whether this is the primary property for the brand").default(false), "relationship": z.enum(["owned", "direct", "delegated", "ad_network"]).describe("How this brand relates to the property. 'owned': the brand owns and operates this property (default) and has no adagents.json delegation_type counterpart. 'direct': the brand is the direct sales path for this property, even if a third party operates the software (e.g., a publisher's in-house ad team using a vendor's tech). 'delegated': the brand manages monetization for this property \u2014 they are in charge of ad sales (e.g., Mediavine managing a food blog). 'ad_network': the brand sells this property's inventory as part of a network or exchange \u2014 they are a path to the inventory, not the path (e.g., PubMatic as an SSP). For non-owned properties, the publisher confirms the relationship by setting the matching delegation_type on the agent's authorization in their adagents.json.").default("owned") }).catchall(z.any()).describe("A digital property associated with a brand. Defaults to owned; use 'relationship' to declare direct, delegated, or ad_network properties. For delegated and network paths, these values match the delegation_type field in adagents.json, creating a bilateral verification chain: the operator declares the relationship here, the publisher confirms by setting the same delegation_type on the agent's authorization in their adagents.json. 'owned' is an inline ownership declaration with no adagents.json counterpart.")).describe("Digital properties associated with this brand \u2014 owned, managed, or represented").optional(), "product_catalog": z.object({ "feed_url": z.string().url().describe("URL to product catalog feed"), "feed_format": z.enum(["google_merchant_center", "facebook_catalog", "shopify", "linkedin_jobs", "tiktok_shop", "pinterest_catalog", "openai_product_feed", "custom"]).describe("Format of the product feed").optional(), "categories": z.array(z.string()).describe("Product categories available in the catalog").optional(), "last_updated": z.string().datetime().describe("When the product catalog was last updated").optional(), "update_frequency": z.enum(["realtime", "hourly", "daily", "weekly"]).describe("How frequently the product catalog is updated").optional(), "agentic_checkout": z.object({ "endpoint": z.string().url().describe("Base URL for checkout session API"), "spec": z.string().describe("Checkout API specification identifier. Use a namespaced string to identify the checkout protocol (e.g., vendor-prefixed or custom). Vendor-specific values belong under ext.{vendor}."), "supported_payment_providers": z.array(z.string()).describe("Payment providers supported by this checkout endpoint").optional() }).describe("Agentic checkout endpoint configuration").optional() }).catchall(z.any()).describe("Product catalog for e-commerce brands").optional(), "privacy_policy_url": z.string().url().describe("URL to the brand's privacy policy").optional(), "data_subject_contestation": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL to a human-accessible contestation form or information page.").optional(), "email": z.string().email().describe("Email address for contestation requests. Deployer MUST monitor and respond within the timelines set by applicable law (e.g., 1 month under GDPR Art. 12(3)).").optional(), "languages": z.array(z.string()).describe("BCP 47 language tags the contestation channel supports (e.g., ['en', 'de', 'fr']). At minimum SHOULD include a language spoken in every jurisdiction where the brand runs regulated-vertical campaigns.").optional() }).strict().and(z.union([z.any(), z.any()])).describe("Contact point where a data subject can request human intervention, express their view, or contest an automated decision \u2014 satisfying GDPR Article 22(3) and EU AI Act Article 26(11) transparency obligations. This is a contact reference (URL, email, or both), not a machine-callable API. AdCP surfaces the pointer; the deployer runs the contestation workflow.").optional(), "disclaimers": z.array(z.object({ "text": z.string(), "context": z.string().optional(), "required": z.boolean().default(true) })).describe("Legal disclaimers for creatives").optional(), "trademarks": z.array(z.object({ "registry": z.string().describe("Trademark registry (e.g., 'USPTO', 'EUIPO', 'JPO', 'CNIPA')"), "number": z.string().describe("Registration number as issued by the registry"), "mark": z.string().describe("The registered mark as published"), "status": z.enum(["active", "pending", "abandoned", "cancelled", "expired"]).describe("Registration status. Omit for active marks if status tracking is not maintained.").optional(), "license_type": z.enum(["owned", "licensed_in", "licensed_out"]).describe("Whether the publisher owns the mark, licenses it from another entity, or licenses it to others. 'owned' is the default if omitted.").optional(), "licensor_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Domain of the entity that licenses this mark to the publisher. Meaningful when license_type=licensed_in; omit otherwise.").optional(), "countries": z.array(z.string().min(2).max(2)).describe("ISO 3166-1 alpha-2 country codes where this registration applies. Omit for global or where the registry's jurisdiction is implicit.").optional(), "nice_classes": z.array(z.number().int().gte(1).lte(45)).describe("Nice Classification class numbers (1-45) covered by this registration. Disambiguates marks across industries (e.g., Delta-airline vs Delta-faucet). Omit if scope is implicit from registry.").optional() }).catchall(z.any()).describe("A registered trademark. May appear at house level (corporate marks, e.g., 'NIKE' owned by Nike, Inc.) or at brand level (brand-specific marks, e.g., 'CONVERSE' owned by Converse). Resolution between house- and brand-level trademarks is union \u2014 both lists are valid claims about marks the publisher controls.")).describe("Brand-level registered trademarks. Use for marks the brand owns or controls (e.g., a sub-brand's own marks distinct from the corporate parent). House-level trademarks live on the house object; resolution between the two is union \u2014 both lists are valid claims.").optional(), "voice_synthesis": z.object({ "provider": z.string().optional(), "voice_id": z.string().optional(), "settings": z.record(z.string(), z.any()).optional() }).catchall(z.any()).describe("TTS voice synthesis configuration for AI-generated audio").optional(), "avatar": z.object({ "provider": z.string().optional(), "avatar_id": z.string().optional(), "settings": z.record(z.string(), z.any()).optional() }).catchall(z.any()).describe("Visual avatar configuration").optional(), "visual_guidelines": z.object({ "photography": z.object({ "realism": z.enum(["natural", "stylized", "hyperreal", "abstract"]).describe("Level of photographic realism").optional(), "lighting": z.string().describe("Lighting style (e.g., 'soft daylight', 'studio', 'golden hour', 'high-key', 'low-key')").optional(), "color_temperature": z.enum(["warm", "neutral", "cool"]).describe("Overall color temperature of photography").optional(), "contrast": z.enum(["low", "medium", "high"]).describe("Contrast level in photography").optional(), "depth_of_field": z.enum(["shallow", "medium", "deep"]).describe("Depth of field preference. shallow: blurred background with subject isolation, deep: everything in focus").optional(), "subject": z.object({ "people": z.object({ "age_range": z.string().describe("Target age range (e.g., '20-35')").optional(), "diversity": z.string().describe("Diversity representation (e.g., 'mixed', 'varied')").optional(), "mood": z.array(z.string()).describe("Mood descriptors (e.g., ['confident', 'relaxed'])").optional() }).catchall(z.any()).describe("People photography guidelines").optional(), "product_focus": z.enum(["in-use", "isolated", "lifestyle", "detail"]).describe("How products are shown").optional(), "setting": z.string().describe("Environmental context for photography (e.g., 'indoor', 'outdoor', 'studio', 'urban', 'nature', 'workplace')").optional() }).catchall(z.any()).describe("Subject matter guidelines").optional(), "framing": z.object({ "subject_position": z.string().describe("Where the subject sits in frame (e.g., 'center', 'center-left', 'rule-of-thirds')").optional(), "crop_style": z.string().describe("Cropping convention (e.g., 'waist-up', 'full-body', 'close-up', 'wide')").optional(), "perspective": z.string().describe("Camera perspective (e.g., 'eye-level', 'overhead', 'low-angle')").optional() }).catchall(z.any()).describe("Camera framing rules").optional(), "preferred_aspect_ratios": z.array(z.string().regex(new RegExp("^\\d+:\\d+$"))).describe("Preferred aspect ratios for brand photography (e.g., '16:9', '4:5', '1:1')").optional(), "tags": z.array(z.string()).describe("Additional style descriptors").optional() }).catchall(z.any()).describe("Photography style rules for generative creative systems. Defines how brand photography should look when selected or generated.").optional(), "graphic_style": z.object({ "style_type": z.enum(["flat_illustration", "geometric", "gradient_mesh", "editorial_collage", "hand_drawn", "minimal_line_art", "3d_render", "isometric", "photographic_composite"]).describe("Primary graphic style").optional(), "stroke_style": z.enum(["rounded", "square", "mixed", "none"]).describe("Stroke end/join style").optional(), "stroke_weight": z.string().describe("Stroke weight (e.g., '2px', 'thin', 'bold')").optional(), "corner_radius": z.string().describe("Default corner radius for graphic and illustration elements (e.g., '12px', '8px', 'sharp'). For UI component radii (buttons, cards, inputs), see visual_guidelines.border_radius.").optional(), "tags": z.array(z.string()).describe("Additional style descriptors").optional() }).catchall(z.any()).describe("Visual language for brand graphics and illustrations").optional(), "shapes": z.object({ "primary_shape": z.string().describe("Primary brand shape (e.g., 'rounded_rectangle', 'circle', 'hexagon')").optional(), "secondary_shapes": z.array(z.string()).describe("Secondary shapes in the brand vocabulary").optional(), "usage": z.object({ "max_per_layout": z.number().int().describe("Maximum distinct shapes per layout").optional(), "overlap_allowed": z.boolean().describe("Whether shapes may overlap").optional() }).catchall(z.any()).describe("Shape usage rules").optional() }).catchall(z.any()).describe("Distinctive shapes used as part of brand visual identity").optional(), "iconography": z.object({ "style": z.enum(["outline", "filled", "duotone", "flat", "glyph", "hand_drawn"]).describe("Icon rendering style").optional(), "stroke_weight": z.string().describe("Icon stroke weight (e.g., '2px', '1.5px')").optional(), "corner_style": z.enum(["rounded", "square", "mixed"]).describe("Corner style for icon paths").optional(), "usage": z.object({ "max_per_frame": z.number().int().describe("Maximum icons per creative frame").optional(), "size_ratio": z.string().describe("Icon-to-layout size ratio (e.g., '1:8')").optional() }).catchall(z.any()).describe("Icon usage rules").optional() }).catchall(z.any()).describe("Icon style system and usage rules").optional(), "composition": z.object({ "overlays": z.object({ "gradient_style": z.enum(["linear", "radial", "conic", "none"]).describe("Gradient type for overlays").optional(), "gradient_direction": z.string().describe("Gradient direction (e.g., '45deg', 'to-bottom-right')").optional(), "opacity": z.string().describe("Overlay opacity (e.g., '70%')").optional() }).catchall(z.any()).describe("Graphic overlay rules").optional(), "texture": z.object({ "style": z.enum(["none", "subtle_grain", "noise", "paper", "fabric", "concrete"]).describe("Texture style applied to creative assets").optional(), "intensity": z.enum(["low", "medium", "high"]).describe("Texture intensity").optional() }).catchall(z.any()).describe("Texture treatment rules").optional(), "backgrounds": z.object({ "types_allowed": z.array(z.enum(["solid_color", "gradient", "blurred_photo", "image", "video", "pattern", "transparent"])).describe("Permitted background types").optional() }).catchall(z.any()).describe("Background treatment rules").optional() }).catchall(z.any()).describe("Layout composition rules including overlays, textures, and backgrounds").optional(), "border_radius": z.object({ "none": z.string().describe("Explicitly sharp corners (e.g., '0')").optional(), "default": z.string().describe("Default border radius for UI components (e.g., '8px', '12px', '0'). For graphic/illustration elements, see graphic_style.corner_radius.").optional(), "small": z.string().describe("Small border radius for compact elements (e.g., '4px')").optional(), "large": z.string().describe("Large border radius for cards and containers (e.g., '16px', '24px')").optional(), "pill": z.string().describe("Fully rounded / pill shape (e.g., '999px')").optional() }).catchall(z.string()).describe("Named border radius presets for UI components and layout elements. One of the most visible brand differentiators \u2014 Airbnb uses generous 20px, Stripe uses precise 4\u20138px, Spotify uses pill/999px.").optional(), "elevation": z.object({ "none": z.string().describe("No shadow (e.g., 'none')").optional(), "subtle": z.string().describe("Subtle shadow for slight lift (e.g., '0 1px 2px rgba(0,0,0,0.05)')").optional(), "card": z.string().describe("Card-level shadow (e.g., '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)')").optional(), "modal": z.string().describe("Modal/overlay shadow (e.g., '0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)')").optional() }).catchall(z.string()).describe("Named shadow/elevation levels. Brands use elevation as identity \u2014 from Stripe's blue-tinted multi-layer shadows to Apple's single diffuse shadow. Values are CSS box-shadow syntax.").optional(), "spacing": z.object({ "unit": z.string().describe("Base grid unit this scale was designed from (e.g., '8px', '4px'). Informational \u2014 agents should use the named scale values, not compute from this.").optional(), "scale": z.object({ "xs": z.string().describe("Extra small spacing (e.g., '4px')").optional(), "sm": z.string().describe("Small spacing (e.g., '8px')").optional(), "md": z.string().describe("Medium spacing (e.g., '16px')").optional(), "lg": z.string().describe("Large spacing (e.g., '24px')").optional(), "xl": z.string().describe("Extra large spacing (e.g., '32px')").optional(), "2xl": z.string().describe("Section-level spacing (e.g., '48px', '64px')").optional() }).catchall(z.string()).describe("Named spacing scale built from the base unit").optional() }).strict().describe("Spacing system for consistent layout rhythm. Most design systems use an 8px base grid.").optional(), "graphic_elements": z.array(z.object({ "name": z.string().describe("Element name (e.g., 'Paper Tear', 'Brand Watermark', 'Section Divider')"), "type": z.enum(["border", "divider", "frame", "watermark", "pattern", "texture_overlay", "decorative"]).describe("Element type").optional(), "description": z.string().describe("How the element is used in layouts").optional(), "orientation": z.enum(["horizontal", "vertical", "any"]).describe("Preferred orientation when used in layouts").optional(), "colors": z.array(z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value")).describe("Colors this element may appear in").optional(), "max_per_layout": z.number().int().describe("Maximum instances per layout").optional() }).catchall(z.any()).describe("A reusable decorative or structural visual element that is part of the brand identity (e.g., torn paper edges, watermarks, dividers, background patterns)")).describe("Reusable decorative elements that are part of the brand visual identity (e.g., torn paper edges, watermarks, dividers)").optional(), "motion": z.object({ "transition_style": z.enum(["cut", "dissolve", "slide", "wipe", "zoom", "fade"]).describe("Primary transition style between scenes").optional(), "animation_speed": z.enum(["slow", "moderate", "fast"]).describe("Overall animation pacing").optional(), "easing": z.string().describe("Default easing function (e.g., 'ease-in-out', 'spring', 'linear')").optional(), "text_entrance": z.enum(["fade", "typewriter", "slide_up", "slide_left", "scale", "none"]).describe("How text enters the frame").optional(), "pacing": z.enum(["lingering", "moderate", "fast_cuts"]).describe("Overall editing rhythm").optional(), "kinetic_typography": z.boolean().describe("Whether animated/kinetic typography is allowed").optional(), "tags": z.array(z.string()).describe("Additional motion style descriptors").optional() }).catchall(z.any()).describe("Motion and animation rules for video, animated display, and interactive formats").optional(), "logo_placement": z.object({ "preferred_position": z.enum(["top-left", "top-center", "top-right", "bottom-left", "bottom-center", "bottom-right", "center"]).describe("Preferred logo position in layouts").optional(), "min_clear_space": z.string().describe("Minimum clear space around the logo, expressed as a multiple of logo height (e.g., '0.5x', '1x') or fixed value (e.g., '16px')").optional(), "min_height": z.string().describe("Minimum logo height to maintain legibility (e.g., '40px', '24px')").optional(), "background_contrast": z.enum(["light_only", "dark_only", "any"]).describe("Permitted background contrast behind logo").optional() }).catchall(z.any()).describe("Logo placement and clear space rules for automated creative production").optional(), "colorways": z.array(z.object({ "name": z.string().describe("Colorway name (e.g., 'primary', 'inverted', 'subtle')"), "foreground": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value"), "background": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value"), "accent": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value").optional(), "border": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value").optional(), "cta_foreground": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("CTA text/icon color, if different from foreground").optional(), "cta_background": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("CTA button/container color, if different from accent").optional(), "channels": z.array(z.string()).describe("Channels or contexts where this colorway applies (e.g., 'online', 'print', 'pos', 'social', 'outdoor'). Omit for universal colorways.").optional() }).catchall(z.any()).describe("A named color pairing that defines how colors work together. Colorways ensure foreground/background combinations are always on-brand and accessible.")).describe("Named color pairings for consistent foreground/background combinations").optional(), "color_constraints": z.array(z.object({ "color": z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Color role or value this constraint governs."), "applies_to": z.array(z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"])).describe("Surfaces where this color may be used.").optional(), "allowed_on": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Backgrounds, surfaces, or color roles where this color is allowed.").optional(), "forbidden_on": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Backgrounds, surfaces, or color roles where this color is forbidden.").optional(), "never_pair_with": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Color roles or values that must not be paired with this color.").optional(), "contexts": z.array(z.string()).describe("Channels or creative contexts where this constraint applies, such as digital, print, social, or ctv_end_card.").optional(), "severity": z.enum(["must", "should"]).describe("Strength of a machine-readable brand guideline constraint.").optional(), "description": z.string().describe("Human-readable rationale or source-language summary for the rule.").optional() }).catchall(z.any()).describe("Machine-readable rule constraining how a brand color may be used or paired. Use for accent-only colors, forbidden foreground/background combinations, and palette pairs that should never appear together.")).describe("Machine-readable constraints for color usage and pairings, such as accent-only rules or forbidden foreground/background combinations.").optional(), "logo_usage_rules": z.array(z.object({ "logo_url": z.string().url().describe("Specific logo asset URL this rule applies to. Omit when the rule applies by variant or tags.").optional(), "logo_id": z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable `logos[].id` this rule applies to. Prefer this over logo_url when the rule targets a specific logo entry.").optional(), "logo_variant": z.enum(["primary", "secondary", "icon", "wordmark", "full-lockup"]).describe("Logo variant this rule applies to.").optional(), "logo_tags": z.array(z.string()).describe("Logo tags this rule applies to.").optional(), "contexts": z.array(z.string()).describe("Creative contexts where this rule applies.").optional(), "slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Canonical renderer slots where this rule applies. Use this for deterministic logo-card, profile-mark, end-card, and lockup selection.").optional(), "minimum_size": z.object({ "width": z.string().describe("Minimum width, such as 48px or 12mm.").optional(), "height": z.string().describe("Minimum height, such as 18px or 6mm.").optional() }).strict().describe("Minimum rendered size needed for legibility.").optional(), "clear_space": z.string().describe("Minimum clear space around the logo, expressed in brand terms or units.").optional(), "allowed_backgrounds": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Background color roles, values, or surfaces where this logo may be placed.").optional(), "forbidden_backgrounds": z.array(z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.")).describe("Background color roles, values, or surfaces where this logo must not be placed.").optional(), "forbidden_contexts": z.array(z.string()).describe("Contexts where this logo must not be used, such as photography_without_knockout.").optional(), "severity": z.enum(["must", "should"]).describe("Strength of a machine-readable brand guideline constraint.").optional(), "description": z.string().describe("Human-readable rationale or source-language summary for the rule.").optional() }).catchall(z.any()).and(z.union([z.any(), z.any(), z.any(), z.any(), z.any()])).describe("Machine-readable logo selection and placement rule. Complements logos[].usage by making enforceable minimum size, clear-space, background, and context constraints queryable.")).describe("Machine-readable logo selection and placement constraints for minimum size, clear space, backgrounds, and contexts.").optional(), "mark_lockups": z.array(z.object({ "lockup_type": z.enum(["co_brand", "secondary_mark", "partner", "sponsor", "program", "talent", "custom"]).describe("Type of mark relationship governed by this lockup rule."), "ordering": z.enum(["brand_first", "partner_first", "equal", "contextual"]).describe("Required visual ordering of the brand mark relative to partner or secondary marks.").optional(), "contexts": z.array(z.string()).describe("Creative contexts where this lockup rule applies.").optional(), "brand_logo_id": z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable `logos[].id` for the brand logo this lockup rule is anchored on.").optional(), "secondary_logo_ids": z.array(z.string().regex(new RegExp("^[a-z0-9][a-z0-9_-]*$")).describe("Stable identifier for a logo entry within this brand.json document. Use lowercase words separated by underscores or hyphens; do not key integrations on mutable asset URLs.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Stable `logos[].id` values for secondary, program, sponsor, or partner marks governed by this lockup rule when those marks are represented in this brand.json.").optional(), "separator": z.object({ "type": z.enum(["none", "keyline", "space", "divider"]).describe("Separator style."), "color": z.object({ "kind": z.enum(["name", "value", "surface"]).describe("Discriminator for the color reference variant.").optional(), "name": z.string().describe("Key from the colors object, such as market_yellow, river_green, or any custom palette key. Unambiguous lookup into colors{}.").optional(), "value": z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("Literal hex color when no palette key exists.").optional(), "surface": z.enum(["background", "foreground", "text", "logo_background", "cta", "accent", "border", "icon", "graphic_element"]).describe("Usage surface rather than a specific color value.").optional() }).strict().and(z.union([z.object({ "kind": z.literal("name") }), z.object({ "kind": z.literal("value") }), z.object({ "kind": z.literal("surface") })])).describe("Reference to a brand color or usage surface for machine-readable guideline constraints. Use kind=name for palette lookup keys in colors, kind=value for a literal hex color, and kind=surface for layout surfaces such as background or text.").optional(), "width": z.string().describe("Separator width, such as 1px.").optional() }).catchall(z.any()).describe("Separator between marks, when required.").optional(), "min_gap": z.string().describe("Minimum gap between marks, expressed in brand terms or units.").optional(), "brand_min_optical_weight_ratio": z.number().gt(0).describe("Minimum optical weight of the brand mark relative to partner marks. 1 means at least equal.").optional(), "partner_max_optical_weight_ratio": z.number().gt(0).describe("Maximum optical weight of partner marks relative to the brand mark. 1 means no larger than the brand mark. Enforcement is at layout time, not parse time \u2014 this value signals to renderers and creative agents how much space to provision for each mark.").optional(), "severity": z.enum(["must", "should"]).describe("Strength of a machine-readable brand guideline constraint.").optional(), "description": z.string().describe("Human-readable rationale or source-language summary for the lockup rule.").optional() }).catchall(z.any()).describe("Machine-readable layout constraints for co-brand, partner, sponsor, program, or secondary-mark lockups.")).describe("Machine-readable co-brand, partner, sponsor, program, or secondary-mark lockup rules.").optional(), "type_scale": z.object({ "base_width": z.string().describe("Reference canvas width these sizes were designed for (e.g., '1080px'). Generative systems should scale proportionally for other canvas sizes.").optional(), "heading": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "subheading": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "body": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "caption": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional(), "cta": z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale").optional() }).catchall(z.object({ "font": z.string().describe("Font reference. Use a key from the fonts object (e.g., 'primary', 'secondary') to reference a defined font role, or a literal CSS font-family string as a fallback.").optional(), "size": z.string().describe("Font size (e.g., '48px', '2rem')").optional(), "weight": z.string().describe("Font weight (e.g., '700', 'bold')").optional(), "line_height": z.string().describe("Line height (e.g., '1.2', '56px')").optional(), "letter_spacing": z.string().describe("Letter spacing (e.g., '-0.02em', '0.5px')").optional(), "text_transform": z.enum(["none", "uppercase", "lowercase", "capitalize"]).describe("Text transformation").optional() }).catchall(z.any()).describe("A single entry in the type scale")).describe("Typography scale defining sizes and weights for different text roles. When sizes are in px, use base_width to indicate the reference canvas.").optional(), "asset_libraries": z.array(z.object({ "name": z.string().describe("Display name of the asset library"), "type": z.enum(["icon_set", "illustration_system", "image_library", "video_library", "template_library"]).describe("Type of asset library").optional(), "url": z.string().url().describe("URL to the asset library (for human access)"), "description": z.string().describe("Description of the library contents and usage").optional(), "color_guide": z.object({ "roles": z.array(z.string()).describe("Named color roles used in the library (e.g., base, shadow_1, highlight_1, stroke)").optional(), "palettes": z.array(z.object({ "name": z.string().describe("Palette name"), "colors": z.record(z.string(), z.string().regex(new RegExp("^#[0-9A-Fa-f]{6}$")).describe("A single hex color value")).describe("Map of role names to hex color values") }).catchall(z.any())).describe("Named color palettes mapping roles to specific colors").optional() }).catchall(z.any()).describe("Color guide for the asset library defining roles and palettes").optional() }).catchall(z.any()).describe("A managed asset library (icon set, illustration system, image collection). The URL is for human access; agent-facing DAM integration is under investigation.")).describe("References to managed asset libraries (icon sets, illustration systems, image collections). URLs are intended for human access; agent-facing DAM integration is under investigation.").optional(), "restrictions": z.array(z.string()).describe("Visual prohibitions and guardrails (e.g., 'Never use black backgrounds', 'Do not crop the logo', 'No stock photography of people on phones')").optional() }).catchall(z.any()).describe("Structured visual rules for generative creative systems").optional(), "agents": z.array(z.object({ "type": z.enum(["brand", "rights", "measurement", "governance", "creative", "sales", "buying", "signals"]).describe("Functional role of this agent"), "url": z.string().url().regex(new RegExp("^https://")).describe("Agent endpoint URL (MCP or A2A). Callers comparing a brand's declared agent URL against another value (e.g., resolving 'is this the agent that signed this artifact?' or matching against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).max(100).describe("Agent identifier (useful for logging, multi-tenant platforms)"), "description": z.string().max(500).describe("Human-readable description of this agent's capabilities or scope").optional(), "jwks_uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL of the agent's JWKS (RFC 7517) containing public keys used to verify artifacts this agent signs or requests it sends. Verified artifacts include signed governance_context tokens (for governance agents) and RFC 9421 HTTP Signatures on outgoing requests (for any agent). When absent, verifiers MUST default to /.well-known/jwks.json on the origin of `url`. Keys are identified by `kid` in the JWS header or RFC 9421 `keyid` parameter; JWKS MAY contain multiple keys to support rotation and per-purpose separation via `key_ops` and `use`.").optional(), "available_uses": z.array(z.enum(["likeness", "voice", "name", "endorsement", "motion_capture", "signature", "catchphrase", "sync", "background_music", "editorial", "commercial", "ai_generated_image"]).describe("Types of rights usage that can be licensed through the brand protocol. Aligned with DDEX UseType direction for interoperability with music and media rights systems.")).describe("For rights agents: rights uses available for licensing").optional(), "right_types": z.array(z.enum(["talent", "character", "brand_ip", "music", "stock_media"]).describe("Categories of intellectual property rights that can be licensed through the brand protocol.")).describe("For rights agents: types of rights available").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("ISO 3166-1 alpha-2 country codes where this agent operates. Omit for global scope.").optional() }).catchall(z.any()).describe("An agent declared by a brand or house. Each entry identifies one agent endpoint and its functional role in the advertising ecosystem.")).describe("Agents authorized to act on behalf of this brand. Consumers resolving an agent by URL use the matching brand-level entry; do not infer a type-wide override of unrelated house-level entries when multiple same-type entries exist.").optional(), "brand_agent": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("Brand agent MCP endpoint URL. Callers comparing this URL against another value (e.g., resolving 'is this the brand's declared agent?' against a discovery cache) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Agent identifier (useful for logging, multi-tenant DAMs)") }).catchall(z.any()).describe("Deprecated: use agents array with type 'brand' instead. Brand agent that provides dynamic brand data via MCP.").optional(), "rights_agent": z.object({ "url": z.string().url().regex(new RegExp("^https://")).describe("Rights agent MCP endpoint URL. Callers comparing this URL against another value (e.g., matching against a brand's declared rights endpoint) MUST canonicalize both sides per the AdCP URL canonicalization rules, not byte-equality. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Agent identifier"), "available_uses": z.array(z.enum(["likeness", "voice", "name", "endorsement", "motion_capture", "signature", "catchphrase", "sync", "background_music", "editorial", "commercial", "ai_generated_image"]).describe("Types of rights usage that can be licensed through the brand protocol. Aligned with DDEX UseType direction for interoperability with music and media rights systems.")).describe("Rights uses available for licensing through this agent"), "right_types": z.array(z.enum(["talent", "character", "brand_ip", "music", "stock_media"]).describe("Categories of intellectual property rights that can be licensed through the brand protocol.")).describe("Types of rights available").optional(), "countries": z.array(z.string().regex(new RegExp("^[A-Z]{2}$"))).describe("Countries where rights are available (ISO 3166-1 alpha-2)").optional() }).catchall(z.any()).describe("Deprecated: use agents array with type 'rights' instead. Rights licensing agent for this brand.").optional(), "contact": z.object({ "email": z.string().email().describe("Contact email").optional(), "phone": z.string().describe("Contact phone number").optional() }).describe("Brand-level contact information").optional(), "collections": z.array(z.object({ "collection_id": z.string().describe("Collection identifier as used in the seller's get_products responses").optional(), "name": z.string().describe("Human-readable collection name"), "role": z.enum(["host", "guest", "creator", "cast", "narrator", "producer", "correspondent", "commentator", "analyst"]).describe("This person's role on the collection").optional(), "seller_agent_url": z.string().url().describe("URL of the sales agent that sells inventory for this collection. Buyer agents can query this agent for collection products.").optional() }).catchall(z.any())).describe("Collections this person or brand is associated with. Enables bidirectional linking: a collection's talent references brand.json via brand_url, and brand.json links back to collections.").optional() }).catchall(z.any()).describe("A brand within a house portfolio. Combines identity (who) with creative assets (how to represent). Referenced as domain + brand_id."))).describe("Self-published brand document where the brand owns its own identity attributes. Optionally declares its house via house_domain; for trust, the named house's brand_refs[] must reciprocate (mutual assertion). Standalone brands (no parent house) omit house_domain. Hosted at the brand's own /.well-known/brand.json (or via authoritative_location indirection). See docs/brand-protocol/brand-json.mdx")]).describe("Brand identity and discovery file. Hosted at /.well-known/brand.json on house domains. Contains the full brand portfolio with identity, creative assets, and digital properties. Brands are identified by house + brand_id (like properties are identified by publisher + property_id). Supports variants: house portfolio (full brand data), brand agent (agent provides brand info via MCP), house redirect (pointer to house domain), or authoritative location redirect.");
9
3
  const AdagentsJsonSchema = z.union([z.object({ "$schema": z.string().describe("JSON Schema identifier for this adagents.json file").optional(), "authoritative_location": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL of the authoritative adagents.json file. When present, this file is a reference and the authoritative location contains the actual agent authorization data. Because one deploy can change authorization across every publisher in the network, validators MUST cap response size, refuse redirects on the fetch, enforce short timeouts, and serve the previously cached file on transient 5xx. Two-tier size cap: pointer files served at `/.well-known/adagents.json` use the general 5 MB SSRF cap; dereferenced authoritative files (this URL's response, after the indirection) use a recommended 20 MB cap because the origin has explicitly opted in to fanning out across a publisher network. See docs/governance/property/managed-networks#security-considerations."), "last_updated": z.string().datetime().describe("ISO 8601 timestamp indicating when this reference was last updated").optional() }).catchall(z.any()).describe("URL reference variant - points to the authoritative location of the adagents.json file"), z.object({ "$schema": z.string().describe("JSON Schema identifier for this adagents.json file").optional(), "contact": z.object({ "name": z.string().min(1).max(255).describe("Name of the entity managing this file (e.g., 'Meta Advertising Operations', 'Clear Channel Digital')"), "email": z.string().email().min(1).max(255).describe("Contact email for questions or issues with this authorization file").optional(), "domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Primary domain of the entity managing this file").optional(), "seller_id": z.string().min(1).max(255).describe("Seller ID from IAB Tech Lab sellers.json (if applicable)").optional(), "tag_id": z.string().min(1).max(100).describe("TAG Certified Against Fraud ID for verification (if applicable)").optional(), "privacy_policy_url": z.string().url().describe("URL to the entity's privacy policy. Used for consumer consent flows when interacting with this sales agent.").optional() }).catchall(z.any()).describe("Contact information for the entity managing this adagents.json file (may be publisher or third-party operator)").optional(), "catalog_etag": z.string().min(1).max(255).describe("Opaque publisher-controlled cache validator for the public catalog portions of this file (`properties[]`, `collections[]`, `placements[]`, `formats[]`, `signals[]`, and tag metadata). Publishers SHOULD change this value whenever any catalog entry or catalog-scoped authorization changes, even when the hosting URL and HTTP validators stay the same. Buyer SDKs SHOULD cache resolved catalog lookups by URL plus `catalog_etag` (falling back to HTTP ETag/Last-Modified, then bounded TTL when absent) and re-resolve placement, format, collection, property, and signal references when it changes. This value is not a cryptographic digest; it is a compact version token such as a deployment hash, revision ID, or ISO timestamp.").optional(), "properties": z.array(z.object({ "property_id": z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Unique identifier for this property (optional). Enables referencing properties by ID instead of repeating full objects.").optional(), "property_type": z.enum(["website", "mobile_app", "ctv_app", "desktop_app", "dooh", "podcast", "radio", "linear_tv", "streaming_audio", "ai_assistant"]).describe("Type of advertising property"), "name": z.string().describe("Human-readable property name"), "identifiers": z.array(z.object({ "type": z.enum(["domain", "subdomain", "network_id", "ios_bundle", "android_package", "apple_app_store_id", "google_play_id", "roku_store_id", "fire_tv_asin", "samsung_app_id", "apple_tv_bundle", "bundle_id", "venue_id", "screen_id", "openooh_venue_type", "rss_url", "apple_podcast_id", "spotify_collection_id", "podcast_guid", "station_id", "facility_id"]).describe("Type of identifier for this property"), "value": z.string().describe("The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain") }).catchall(z.any())).describe("Array of identifiers for this property"), "tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Tags for categorization and grouping (e.g., network membership, content categories)").optional(), "supported_channels": z.array(z.enum(["display", "olv", "social", "search", "ctv", "linear_tv", "radio", "streaming_audio", "podcast", "dooh", "ooh", "print", "cinema", "email", "gaming", "retail_media", "influencer", "affiliate", "product_placement", "sponsored_intelligence"]).describe("Standardized advertising media channels describing how buyers allocate budget. Channels are planning abstractions, not technical substrates. See the Media Channel Taxonomy specification for detailed definitions.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Advertising channels this property supports (e.g., ['display', 'olv', 'social']). Publishers declare which channels their inventory aligns with. Properties may support multiple channels. See the Media Channel Taxonomy for definitions.").optional(), "publisher_domain": z.string().describe("Domain where adagents.json should be checked for authorization validation. Optional in adagents.json (file location implies domain).").optional() }).catchall(z.any()).describe("An advertising property that can be validated via adagents.json")).describe("Array of all properties covered by this adagents.json file. Defines the canonical property list that authorized agents reference.").optional(), "revoked_publisher_domains": z.array(z.object({ "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Publisher domain being revoked. Matches against the same canonicalized form used in `publisher_properties[].publisher_domain`."), "revoked_at": z.string().datetime().describe("ISO 8601 timestamp when this publisher was revoked. Validators MAY use this to order revocations against their own cached state."), "reason": z.enum(["relationship_ended", "compliance_violation", "publisher_request", "other"]).describe("Reason for revocation. **Operator-internal self-classification for review routing \u2014 not a public accusation.** `relationship_ended` is the routine commercial case. `compliance_violation` SHOULD be used only when the network has itself determined the publisher is out of policy; for un-adjudicated third-party allegations (regulator inquiries, advertiser complaints, ongoing investigations), use `other` to avoid making a discoverable adverse statement. `publisher_request` is for publisher-initiated exits. Compare to sellers.json, which deliberately carries no reason field for the same exposure concern.").optional() }).catchall(z.any())).describe("Publisher domains explicitly removed from this managed network. Validators MUST treat any publisher domain listed here as no-longer-authorized, taking precedence over any appearance of the same domain in `authorized_agents[].publisher_properties[].publisher_domain` / `.publisher_domains[]`, in `authorized_agents[].properties[].publisher_domain` (`inline_properties` authorization type), or in top-level `properties[].publisher_domain`. Lets a network propagate per-publisher revocations on the next refresh instead of waiting for the file-level 7-day cache cap. Validators MUST hold previously-observed `(publisher_domain, revoked_at)` tuples for 7 days from the validator's first observation, even if the entry vanishes from a subsequent fetch \u2014 this closes the rollback gap where an attacker re-serves a stale file with the revocation removed. Networks SHOULD retain entries for at least 7 days after `revoked_at` so validators that didn't observe the original entry still pick it up on refresh.").optional(), "collections": z.array(z.object({ "collection_id": z.string().describe("Publisher-assigned identifier for this collection. Declared in the publisher's adagents.json collections array. Products reference collections via collection selectors with publisher_domain and collection_ids. Use distribution identifiers for cross-seller matching across publishers."), "name": z.string().describe("Human-readable collection name"), "kind": z.enum(["series", "publication", "event_series", "rotation"]).describe("What kind of content program this is. Helps agents interpret installments correctly. Defaults to 'series' when absent.").optional(), "description": z.string().describe("What the collection is about").optional(), "genre": z.array(z.string()).describe("Genre tags. When genre_taxonomy is present, values are taxonomy IDs (e.g., IAB Content Taxonomy 3.0 codes). Otherwise free-form.").optional(), "genre_taxonomy": z.string().describe("Taxonomy system for genre values (e.g., 'iab_content_3.0'). When present, genre values should be valid taxonomy IDs. Recommended for machine-readable brand safety evaluation.").optional(), "language": z.string().describe("Primary language (BCP 47 tag, e.g., 'en', 'es-MX')").optional(), "content_rating": z.object({ "system": z.enum(["tv_parental", "mpaa", "podcast", "esrb", "bbfc", "fsk", "acb", "chvrs", "csa", "pegi", "custom"]).describe("Rating system used"), "rating": z.string().describe("Rating value within the system (e.g., 'TV-PG', 'R', 'explicit')") }).catchall(z.any()).describe("Baseline content rating for the collection. Individual installments may override this.").optional(), "cadence": z.enum(["daily", "weekly", "monthly", "seasonal", "event", "irregular"]).describe("How frequently the collection releases new installments").optional(), "season": z.string().describe("Current or most recent season identifier (e.g., '3', '2026', 'spring_2026'). A lightweight label \u2014 not a full season object.").optional(), "status": z.enum(["active", "hiatus", "ended", "upcoming"]).describe("Lifecycle status of the collection").optional(), "production_quality": z.enum(["professional", "prosumer", "ugc"]).describe("Production quality tier. Seller-declared. Maps to OpenRTB content.prodq (professional=1, prosumer=2, ugc=3).").optional(), "talent": z.array(z.object({ "role": z.enum(["host", "guest", "creator", "cast", "narrator", "producer", "correspondent", "commentator", "analyst"]).describe("Role of this person on the collection or installment"), "name": z.string().describe("Person's name as credited on the collection"), "brand_url": z.string().url().describe("URL to this person's brand.json entry. Enables buyer agents to evaluate the talent's brand identity and associations.").optional() }).catchall(z.any()).describe("A person associated with a collection or installment, with an optional link to their brand.json identity")).describe("Hosts, recurring cast, creators associated with the collection. Each talent entry may include a brand_url linking to their brand.json identity.").optional(), "special": z.object({ "name": z.string().describe("Name of the event (e.g., 'Olympics 2028', 'Super Bowl LXI')"), "category": z.enum(["awards", "championship", "concert", "conference", "election", "festival", "gala", "holiday", "premiere", "product_launch", "reunion", "tribute"]).describe("Category of the event").optional(), "starts": z.string().datetime().describe("When the event starts (ISO 8601)").optional(), "ends": z.string().datetime().describe("When the event ends (ISO 8601). Omit for single-day events.").optional() }).catchall(z.any()).describe("When present, this collection is a special \u2014 content anchored to a real-world event or occasion. Individual installments may override with their own event context.").optional(), "limited_series": z.object({ "total_installments": z.number().int().gte(1).describe("Planned number of installments in the series"), "starts": z.string().datetime().describe("When the series begins (ISO 8601)").optional(), "ends": z.string().datetime().describe("When the series ends (ISO 8601)").optional() }).catchall(z.any()).describe("When present, this collection is a limited series \u2014 a bounded run with a defined arc, installment count, and end date.").optional(), "distribution": z.array(z.object({ "publisher_domain": z.string().describe("Domain of the publisher platform where the collection is distributed (e.g., 'youtube.com', 'spotify.com')"), "identifiers": z.array(z.object({ "type": z.enum(["apple_podcast_id", "spotify_collection_id", "rss_url", "podcast_guid", "amazon_music_id", "iheart_id", "podcast_index_id", "youtube_channel_id", "youtube_channel_handle", "youtube_channel_url", "youtube_playlist_id", "amazon_title_id", "roku_channel_id", "pluto_channel_id", "tubi_id", "peacock_id", "tiktok_id", "twitch_channel", "imdb_id", "gracenote_id", "eidr_id", "domain", "substack_id"]).describe("Type of distribution identifier"), "value": z.string().describe("The identifier value") }).strict()).describe("Platform-specific identifiers for the collection on this publisher") }).catchall(z.any()).describe("A collection's presence on a specific publisher platform, identified by platform-specific identifiers. Enables cross-seller matching when the same collection is sold by different agents.")).describe("Where this collection is distributed. Each entry maps the collection to a publisher platform with platform-specific identifiers. Collections SHOULD include at least one platform-independent identifier (imdb_id, gracenote_id, eidr_id) when available.").optional(), "deadline_policy": z.object({ "booking_lead_days": z.number().int().gte(0).describe("Days before scheduled_at by which the placement must be booked").optional(), "cancellation_lead_days": z.number().int().gte(0).describe("Days before scheduled_at by which cancellation is penalty-free").optional(), "material_stages": z.array(z.object({ "stage": z.string().describe("Stage identifier. Standard values: 'draft' (needs seller processing), 'final' (production-ready)."), "lead_days": z.number().int().gte(0).describe("Days before scheduled_at this stage is due"), "label": z.string().describe("What the seller needs at this stage").optional() }).catchall(z.any())).describe("Default material submission stages. Items MUST be in chronological order (earliest due first). Agents compute due_at as: installment.scheduled_at minus lead_days.").optional(), "business_days_only": z.boolean().describe("When true, lead_days counts business days (Mon-Fri) rather than calendar days. Defaults to false.").default(false) }).catchall(z.any()).describe("Default deadline rules for installments of this collection. Agents compute absolute deadlines from each installment's scheduled_at and these lead times. Installments with explicit deadlines override this policy.").optional(), "related_collections": z.array(z.object({ "collection_id": z.string().describe("The related collection's collection_id within this seller's response"), "relationship": z.enum(["spinoff", "companion", "sequel", "prequel", "crossover"]).describe("How the collections are related") }).strict()).describe("Relationships to other collections (spin-offs, companion collections, etc.). Each entry references another collection by collection_id within the same publisher's adagents.json.").optional(), "ext": z.record(z.string(), z.any()).describe("Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.").optional() }).catchall(z.any()).describe("A recurring inventory container \u2014 a named program, publication, event series, or rotation that produces bookable installments on a defined cadence. The kind field indicates how to interpret this collection: 'series' for TV/podcast programs, 'publication' for print/newsletter titles, 'event_series' for live events, 'rotation' for DOOH scheduling. Declared in the publisher's adagents.json and referenced by products via collection selectors.")).describe("Collections produced or distributed by this publisher. Declares the content programs whose inventory is sold through authorized agents. Products in get_products responses reference these collections by collection_id.").optional(), "placements": z.array(z.object({ "placement_id": z.string().describe("Stable placement identifier unique within this adagents.json file."), "name": z.string().describe("Human-readable placement name (e.g., 'Homepage Banner', 'Pre-roll', 'Sponsored Listing Slot 1')."), "description": z.string().describe("Description of where and how this placement appears.").optional(), "tags": z.array(z.string()).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Tags for grouping and querying placements across properties and products (e.g., 'homepage', 'native', 'premium', 'pre_roll').").optional(), "property_ids": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.")).describe("Property IDs in this adagents.json where this placement can appear.").optional(), "property_tags": z.array(z.string().regex(new RegExp("^[a-z0-9_]+$")).describe("Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.")).describe("Property tags in this adagents.json where this placement can appear. Useful for network-wide positions such as 'pre_roll' or 'homepage_native_feed'.").optional(), "collection_ids": z.array(z.string()).describe("Optional collection IDs in this adagents.json where this placement is valid. Use to narrow a placement to specific content programs carried on the selected properties.").optional(), "channels": z.array(z.enum(["display", "olv", "social", "search", "ctv", "linear_tv", "radio", "streaming_audio", "podcast", "dooh", "ooh", "print", "cinema", "email", "gaming", "retail_media", "influencer", "affiliate", "product_placement", "sponsored_intelligence"]).describe("Standardized advertising media channels describing how buyers allocate budget. Channels are planning abstractions, not technical substrates. See the Media Channel Taxonomy specification for detailed definitions.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Advertising channels where this placement can run. Products that reference the placement may narrow this set but should not broaden it.").optional(), "format_options": z.array(z.union([z.object({ "format_option_id": z.string().describe("Matches a `format_option_id` in the file's top-level `formats[]`.") }).catchall(z.any()).describe("Reference an entry in the file's top-level `formats[]` by `format_option_id`. Resolved at validation time. additionalProperties: true so placement-local fields (display_name, etc.) carry through without forcing a full inline declaration."), z.object({ "format_option_id": z.string().describe("Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry \u2014 not just when structurally required to break a `format_kind` collision \u2014 so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'.").optional(), "publisher_domain": z.string().regex(new RegExp("^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$")).describe("Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.").optional(), "display_name": z.string().describe("Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics \u2014 buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn.").optional(), "applies_to_channels": z.array(z.enum(["display", "olv", "social", "search", "ctv", "linear_tv", "radio", "streaming_audio", "podcast", "dooh", "ooh", "print", "cinema", "email", "gaming", "retail_media", "influencer", "affiliate", "product_placement", "sponsored_intelligence"]).describe("Standardized advertising media channels describing how buyers allocate budget. Channels are planning abstractions, not technical substrates. See the Media Channel Taxonomy specification for detailed definitions.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel \u2014 `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`.").optional(), "seller_preference": z.enum(["preferred", "accepted", "discouraged"]).describe("Optional soft routing hint *within* a product's accepted set of formats \u2014 NOT an enforcement axis. `preferred` \u2014 seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` \u2014 supported on equal footing with other format_options (default when omitted); `discouraged` \u2014 supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry \u2014 the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it.").optional(), "canonical_formats_only": z.boolean().describe('When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations \u2014 the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` \u2014 explicit v2-only is more useful than silent absence.').default(false), "experimental": z.boolean().describe("When true, THIS seller's specific product declaration may not work as declared \u2014 even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag \u2014 a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) \u2014 same semantic ('use with caution') without the cognitive overhead of two stability axes.").default(false), "format_shape": z.string().describe('REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, \u2026). Non-canonical values valid (validators MAY soft-warn) \u2014 adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.').optional(), "v1_format_ref": z.array(z.object({ "agent_url": z.string().url().describe("URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization."), "id": z.string().regex(new RegExp("^[a-zA-Z0-9_-]+$")).describe("Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant."), "width": z.number().int().gte(1).describe("Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.").optional(), "height": z.number().int().gte(1).describe("Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.").optional(), "duration_ms": z.number().gte(1).describe("Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.").optional() }).catchall(z.any()).describe('A JSON object \u2014 never a plain string \u2014 that identifies a creative format by its declaring agent and local slug. Required properties: agent_url (URI of the agent that owns the format) and id (slug matching [a-zA-Z0-9_-]+). Example: {"agent_url": "https://creative.adcontextprotocol.org", "id": "display_300x250"}. Can reference: (1) a concrete format with fixed dimensions (id only), (2) a template format without parameters (id only), or (3) a template format with parameters (id + dimensions/duration). Template formats accept parameters in format_id while concrete formats have fixed dimensions in their definition. Parameterized format IDs create unique, specific format variants. Using a plain string here is a schema violation.')).describe("Authoritative v2 \u2192 v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape \u2014 adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` \u2014 see the 'Narrows \u2014 formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` \u2287 entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit \u2014 NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N \u2014 opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` \u2014 a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file \u2192 registry glob \u2192 structural match \u2192 fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration \u2192 v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs \u2026) and the v1 wire fragments into per-publisher namespaces \u2014 exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror \u2014 `https://creative.adcontextprotocol.org/translated/<platform>` + `id: <platform-format-name>` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for \u22651 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/<platform>`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/<platform>` \u2014 sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.").optional(), "format_schema": z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("REQUIRED when `format_kind: \"custom\"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape's actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally \u2014 same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that's why the schema is required, not optional.\n\n**Fetch contract (normative)** \u2014 `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields \u2014 any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden \u2014 these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout \u22645 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient \u2014 retry policy at the SDK's discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** \u2014 the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 \xA76 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document's parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth \u22648 AND `$ref` count \u2264256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes \u2014 depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count \u226410 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget \u2264250 ms (exceeded budget \u2192 treat manifest as invalid, surface telemetry signal). Without these, a 'valid' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.").optional() }).and(z.union([z.object({ "format_kind": z.literal("image"), "params": z.intersection(z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints)."), z.union([z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any(), z.any(), z.any()]), z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema")]).describe("Exactly one of: (a) fixed (`width` + `height` both set), (b) multi-size (`sizes` set), (c) responsive (any of `min_width`/`max_width`/`min_height`/`max_height` set), (d) none (no size constraint declared \u2014 accepts any dimensions). Combining modes (e.g., `width` + `sizes`) is rejected at schema layer; same rule on `html5` and `display_tag` canonicals.")).describe("Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters \u2014 covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.") }), z.object({ "format_kind": z.literal("html5"), "params": z.intersection(z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints)."), z.union([z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any(), z.any(), z.any()]), z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema")]).describe("Exactly one of: (a) fixed (`width` + `height` both set), (b) multi-size (`sizes` set), (c) responsive (any of `min_width`/`max_width`/`min_height`/`max_height` set), (d) none (no size constraint declared \u2014 accepts any dimensions). Combining modes is rejected at schema layer.")).describe("Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.") }), z.object({ "format_kind": z.literal("display_tag"), "params": z.intersection(z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints)."), z.union([z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema"), z.union([z.any(), z.any(), z.any(), z.any()]), z.any().refine((value) => !z.union([z.any(), z.any(), z.any(), z.any(), z.any(), z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema")]).describe("Exactly one of: (a) fixed (`width` + `height` both set), (b) multi-size (`sizes` set), (c) responsive (any of `min_width`/`max_width`/`min_height`/`max_height` set), (d) none (no size constraint declared \u2014 accepts any dimensions). Combining modes is rejected at schema layer.")).describe("Third-party-served display tag (JS, iframe, or 1\xD71 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller \u2014 third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).") }), z.object({ "format_kind": z.literal("image_carousel"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe('Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value \u2014 NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.') }), z.object({ "format_kind": z.literal("video_hosted"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) \u2014 receivers fire impression and click pixels at delivery time.") }), z.object({ "format_kind": z.literal("video_vast"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec \u2014 `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).") }), z.object({ "format_kind": z.literal("audio_hosted"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("Direct audio creative \u2014 buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) \u2014 there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.") }), z.object({ "format_kind": z.literal("audio_daast"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec \u2014 `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).") }), z.object({ "format_kind": z.literal("sponsored_placement"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset \u2014 product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** \u2014 buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative \u2014 buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot \u2014 products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) \u2014 use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) \u2014 use `responsive_creative`.\n- Single-image or single-video creative \u2014 use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing \u2014 a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).") }), z.object({ "format_kind": z.literal("native_in_feed"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types \u2014 `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 \u2014 visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 \u2014 app/product rating), `price` (Data Asset type 6 \u2014 product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes \u2014 type 4, downloads \u2014 type 5, saleprice \u2014 type 7, phone_number \u2014 type 8, address \u2014 type 9, desc2 \u2014 type 10, etc.) or to remove slots the surface doesn't render. The publisher's renderer assembles these into its own look-and-feel \u2014 feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative \u2014 buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) \u2014 use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) \u2014 use `responsive_creative`.\n- Multi-card carousel \u2014 use `image_carousel`.\n- Video-first native units where the asset is a hosted video file \u2014 use `video_hosted` with `applies_to_channels: [\"native\"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.") }), z.object({ "format_kind": z.literal("responsive_creative"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe('Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** \u2014 surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).') }), z.object({ "format_kind": z.literal("agent_placement"), "params": z.object({ "experimental": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) may not work as declared \u2014 adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: 'this is shipping but may break, evolve, or fail.' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration's `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** \u2014 the canonical's tracking model or parameter shape is still being settled (`agent_placement`'s tracking macros, `sponsored_placement`'s per-adapter contracts, `responsive_creative`'s algorithmic composition).\n2. **Adopter runtime gap** \u2014 the seller has declared the canonical in their catalog but their runtime doesn't yet honor it cleanly.\n3. **Custom shapes** \u2014 `format_kind: \"custom\"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) \u2014 two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as 'try at my own risk.' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren't yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).").default(false), "deprecated": z.boolean().describe("When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path.").default(false), "v1_translatable": z.boolean().describe("Whether this canonical has any v1 named-format equivalent. `true` (default) \u2014 the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1\u2192v2 projection via `v1-canonical-mapping.json` is meaningful. `false` \u2014 the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable \u2014 seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational \u2014 buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) \u2014 instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`.").default(true), "since_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected \u2014 canonicals are introduced at minor-version boundaries.").optional(), "migration_target_version": z.string().regex(new RegExp("^[1-9]\\d*\\.(0|[1-9]\\d*)$")).describe("AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected \u2014 canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').").optional(), "composition_model": z.enum(["deterministic", "algorithmic"]).describe("Whether the surface composes deterministically (buyer can predict per-slot rendering \u2014 sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing \u2014 responsive_creative, agent_placement).").optional(), "provenance_required": z.boolean().describe("When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.").optional(), "platform_extensions": z.array(z.object({ "uri": z.string().url().regex(new RegExp("^https://")).describe("HTTPS URL identifying the extension. `https://` is mandatory \u2014 `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract \u2014 SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds \u2014 is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ."), "digest": z.string().regex(new RegExp("^sha256:[a-f0-9]{64}$")).describe("SHA-256 content digest of the extension definition (sha256:<hex>). Used to detect drift \u2014 if the agent revises the extension, the digest changes and cached definitions become invalid.") }).catchall(z.any()).describe("Reference to a platform extension definition. The agent that owns the URI is authoritative for the extension's schema. Buyers fetch the definition once per content digest and cache it. Platform extensions are typically bundled in `get_products` responses under an `extensions` map keyed by `uri@digest`, eliminating the need for a separate fetch.\n\n**Within a single response**, multiple references to the same `uri` MUST carry the same `digest` \u2014 divergent digests in one response indicate producer-side error (e.g., concurrent extension revision mid-render). Buyers encountering divergent digests for the same URI MUST fail closed: treat all references to that URI as unresolved and surface a validation error rather than picking one branch silently. **Across responses**, digest divergence is normal \u2014 extension authors revise their schemas, the new digest differs, the cache key changes, and the buyer refetches. Cache by `uri@digest`, not by `uri` alone.")).describe('Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative \u2014 later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.').optional(), "synthesis_nondeterministic": z.boolean().describe("When true, the format's production pipeline is genuinely nondeterministic \u2014 the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific \u2014 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error.").default(false), "slots": z.array(z.object({ "asset_group_id": z.string().describe("Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings."), "asset_type": z.enum(["image", "video", "audio", "text", "markdown", "url", "html", "css", "javascript", "vast", "daast", "webhook", "brief", "catalog", "published_post", "zip", "card", "object", "pixel_tracker", "vast_tracker", "daast_tracker"]).describe("Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives \u2014 see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type \u2014 prefer specific types whenever possible."), "required": z.boolean().describe("Whether this slot is required for a valid manifest.").default(false), "min": z.number().int().gte(0).describe("Minimum count for repeatable / pool slots.").optional(), "max": z.number().int().gte(1).describe("Maximum count for repeatable / pool slots.").optional(), "max_chars": z.number().int().gte(1).describe("Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "max_size_kb": z.number().int().gte(1).describe("Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.").optional(), "logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.").optional(), "required_logo_slots": z.array(z.enum(["logo_card_light", "logo_card_dark", "profile_mark", "favicon", "app_icon", "social_profile_mark", "nav_header", "footer", "email_header", "watermark", "ad_end_card", "co_brand_lockup", "marketplace_listing"]).describe("Canonical renderer-facing logo slot. Use when selecting a logo variant from brand.json for a specific UI or creative placement.")).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.").optional(), "description": z.string().describe("Human-readable description of what the slot expects from the buyer.").optional(), "consumed_for_production": z.boolean().describe("Dispatch hint for `build_creative` and v1\u2194v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1\u2194v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted.").default(false) }).catchall(z.any()).and(z.intersection(z.intersection(z.any(), z.any()), z.intersection(z.any(), z.any())))).describe("Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry.").optional(), "required_connections": z.array(z.object({ "provider": z.string().describe("Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.").optional(), "connection_type": z.enum(["advertiser_account", "publisher_identity", "post_authorization"]).describe("Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity."), "required_for": z.array(z.string().min(1)).refine((arr) => arr.every((item, i) => arr.indexOf(item) == i), "All items must be unique!").describe("Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.").optional(), "scope": z.enum(["account", "identity", "post", "unknown"]).describe("Granularity of the downstream grant.").optional(), "status": z.enum(["connected", "missing", "pending", "expired", "revoked", "not_required", "unknown"]).describe("Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.").optional(), "connection_id": z.string().describe("Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.").optional(), "resource_ref": z.object({ "platform_account_id": z.string().describe("Provider-native advertiser or business account id, when safe to disclose.").optional(), "identity_id": z.string().describe("Provider-native creator, page, channel, organization, or profile id, when safe to disclose.").optional(), "handle": z.string().describe("Provider-native public handle for the owning identity, when available.").optional(), "profile_url": z.string().url().describe("Public URL for the owning identity, when available.").optional(), "post_id": z.string().describe("Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.").optional(), "post_url": z.string().url().describe("Public URL for the referenced post, when available.").optional() }).catchall(z.any()).describe("Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.").optional(), "authorization_url": z.string().url().describe("Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.").optional(), "authorization_instructions": z.string().describe("Human-readable instructions for completing or restoring this downstream connection.").optional(), "expires_at": z.string().datetime().describe("Expiration time for the downstream grant, when known.").optional() }).catchall(z.any()).and(z.any()).describe("A seller/platform-side connection or grant required by a product, format, or request. This is not the AdCP caller credential: the AdCP request is still authenticated once, and the seller uses these stored downstream connections to call a platform or service on the buyer's behalf. Use this shape for platforms that require more than one downstream grant, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.")).describe("Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.").optional(), "reference_mutability": z.enum(["immutable_snapshot", "mutable_requires_reapproval", "mutable_auto_recheck"]).describe("Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.").optional(), "production_window_business_days": z.number().int().gte(0).describe("Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).").optional() }).catchall(z.any()).describe("Shared parameter fields that apply across canonical formats. Each canonical format extends this base with format-specific parameters (dimensions, durations, codecs, slot constraints).").describe("**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** \u2014 the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering \u2014 but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern \u2014 brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.") }), z.object({ "format_kind": z.literal("custom"), "params": z.record(z.string(), z.any()).describe("Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`.") }).describe("Adopter-defined shape that doesn't fit the 12 canonicals. Requires `format_shape` (vocabulary-registered global pattern) and `format_schema` (URI+digest reference to a fetchable schema describing the actual params/slots). `params` shape is governed by the fetched schema rather than baked into AdCP \u2014 kept as `type: object` here with `additionalProperties: true` because the canonical schema validates dynamically post-fetch.")])).and(z.intersection(z.union([z.union([z.any(), z.any()]), z.any().refine((value) => !z.union([z.any(), z.any()]).safeParse(value).success, "Invalid input: Should NOT be valid against schema")]).superRefine((value, ctx) => {