@adcp/sdk 5.25.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/README.md +45 -7
  2. package/dist/lib/compliance-fixtures/index.d.ts +1 -1
  3. package/dist/lib/compliance-fixtures/index.js +1 -1
  4. package/dist/lib/conformance/runners.d.ts.map +1 -1
  5. package/dist/lib/conformance/runners.js +13 -1
  6. package/dist/lib/conformance/runners.js.map +1 -1
  7. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  8. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  9. package/dist/lib/core/SingleAgentClient.js +15 -0
  10. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  11. package/dist/lib/core/TaskExecutor.d.ts +7 -0
  12. package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
  13. package/dist/lib/core/TaskExecutor.js +9 -2
  14. package/dist/lib/core/TaskExecutor.js.map +1 -1
  15. package/dist/lib/index.d.ts +1 -1
  16. package/dist/lib/index.d.ts.map +1 -1
  17. package/dist/lib/index.js +7 -8
  18. package/dist/lib/index.js.map +1 -1
  19. package/dist/lib/protocols/index.d.ts +3 -1
  20. package/dist/lib/protocols/index.d.ts.map +1 -1
  21. package/dist/lib/protocols/index.js +23 -14
  22. package/dist/lib/protocols/index.js.map +1 -1
  23. package/dist/lib/schemas/index.d.ts +1 -1
  24. package/dist/lib/schemas/index.js +1 -1
  25. package/dist/lib/server/create-adcp-server.d.ts +142 -11
  26. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  27. package/dist/lib/server/create-adcp-server.js +211 -2
  28. package/dist/lib/server/create-adcp-server.js.map +1 -1
  29. package/dist/lib/server/ctx-metadata/backends/memory.d.ts +27 -0
  30. package/dist/lib/server/ctx-metadata/backends/memory.d.ts.map +1 -0
  31. package/dist/lib/server/ctx-metadata/backends/memory.js +72 -0
  32. package/dist/lib/server/ctx-metadata/backends/memory.js.map +1 -0
  33. package/dist/lib/server/ctx-metadata/backends/pg.d.ts +62 -0
  34. package/dist/lib/server/ctx-metadata/backends/pg.d.ts.map +1 -0
  35. package/dist/lib/server/ctx-metadata/backends/pg.js +145 -0
  36. package/dist/lib/server/ctx-metadata/backends/pg.js.map +1 -0
  37. package/dist/lib/server/ctx-metadata/index.d.ts +15 -0
  38. package/dist/lib/server/ctx-metadata/index.d.ts.map +1 -0
  39. package/dist/lib/server/ctx-metadata/index.js +28 -0
  40. package/dist/lib/server/ctx-metadata/index.js.map +1 -0
  41. package/dist/lib/server/ctx-metadata/store.d.ts +177 -0
  42. package/dist/lib/server/ctx-metadata/store.d.ts.map +1 -0
  43. package/dist/lib/server/ctx-metadata/store.js +327 -0
  44. package/dist/lib/server/ctx-metadata/store.js.map +1 -0
  45. package/dist/lib/server/ctx-metadata/wire-shape.d.ts +55 -0
  46. package/dist/lib/server/ctx-metadata/wire-shape.d.ts.map +1 -0
  47. package/dist/lib/server/ctx-metadata/wire-shape.js +121 -0
  48. package/dist/lib/server/ctx-metadata/wire-shape.js.map +1 -0
  49. package/dist/lib/server/decisioning/account.d.ts +309 -0
  50. package/dist/lib/server/decisioning/account.d.ts.map +1 -0
  51. package/dist/lib/server/decisioning/account.js +102 -0
  52. package/dist/lib/server/decisioning/account.js.map +1 -0
  53. package/dist/lib/server/decisioning/admin-router.d.ts +75 -0
  54. package/dist/lib/server/decisioning/admin-router.d.ts.map +1 -0
  55. package/dist/lib/server/decisioning/admin-router.js +120 -0
  56. package/dist/lib/server/decisioning/admin-router.js.map +1 -0
  57. package/dist/lib/server/decisioning/assembly-helpers.d.ts +204 -0
  58. package/dist/lib/server/decisioning/assembly-helpers.d.ts.map +1 -0
  59. package/dist/lib/server/decisioning/assembly-helpers.js +173 -0
  60. package/dist/lib/server/decisioning/assembly-helpers.js.map +1 -0
  61. package/dist/lib/server/decisioning/async-outcome.d.ts +154 -0
  62. package/dist/lib/server/decisioning/async-outcome.d.ts.map +1 -0
  63. package/dist/lib/server/decisioning/async-outcome.js +239 -0
  64. package/dist/lib/server/decisioning/async-outcome.js.map +1 -0
  65. package/dist/lib/server/decisioning/capabilities.d.ts +251 -0
  66. package/dist/lib/server/decisioning/capabilities.d.ts.map +1 -0
  67. package/dist/lib/server/decisioning/capabilities.js +16 -0
  68. package/dist/lib/server/decisioning/capabilities.js.map +1 -0
  69. package/dist/lib/server/decisioning/context.d.ts +212 -0
  70. package/dist/lib/server/decisioning/context.d.ts.map +1 -0
  71. package/dist/lib/server/decisioning/context.js +26 -0
  72. package/dist/lib/server/decisioning/context.js.map +1 -0
  73. package/dist/lib/server/decisioning/errors-typed.d.ts +104 -0
  74. package/dist/lib/server/decisioning/errors-typed.d.ts.map +1 -0
  75. package/dist/lib/server/decisioning/errors-typed.js +304 -0
  76. package/dist/lib/server/decisioning/errors-typed.js.map +1 -0
  77. package/dist/lib/server/decisioning/helpers.d.ts +131 -0
  78. package/dist/lib/server/decisioning/helpers.d.ts.map +1 -0
  79. package/dist/lib/server/decisioning/helpers.js +134 -0
  80. package/dist/lib/server/decisioning/helpers.js.map +1 -0
  81. package/dist/lib/server/decisioning/index.d.ts +46 -0
  82. package/dist/lib/server/decisioning/index.d.ts.map +1 -0
  83. package/dist/lib/server/decisioning/index.js +120 -0
  84. package/dist/lib/server/decisioning/index.js.map +1 -0
  85. package/dist/lib/server/decisioning/list-helpers.d.ts +53 -0
  86. package/dist/lib/server/decisioning/list-helpers.d.ts.map +1 -0
  87. package/dist/lib/server/decisioning/list-helpers.js +96 -0
  88. package/dist/lib/server/decisioning/list-helpers.js.map +1 -0
  89. package/dist/lib/server/decisioning/manifest-helpers.d.ts +56 -0
  90. package/dist/lib/server/decisioning/manifest-helpers.d.ts.map +1 -0
  91. package/dist/lib/server/decisioning/manifest-helpers.js +78 -0
  92. package/dist/lib/server/decisioning/manifest-helpers.js.map +1 -0
  93. package/dist/lib/server/decisioning/pagination.d.ts +21 -0
  94. package/dist/lib/server/decisioning/pagination.d.ts.map +1 -0
  95. package/dist/lib/server/decisioning/pagination.js +12 -0
  96. package/dist/lib/server/decisioning/pagination.js.map +1 -0
  97. package/dist/lib/server/decisioning/platform.d.ts +188 -0
  98. package/dist/lib/server/decisioning/platform.d.ts.map +1 -0
  99. package/dist/lib/server/decisioning/platform.js +19 -0
  100. package/dist/lib/server/decisioning/platform.js.map +1 -0
  101. package/dist/lib/server/decisioning/runtime/from-platform.d.ts +510 -0
  102. package/dist/lib/server/decisioning/runtime/from-platform.d.ts.map +1 -0
  103. package/dist/lib/server/decisioning/runtime/from-platform.js +2196 -0
  104. package/dist/lib/server/decisioning/runtime/from-platform.js.map +1 -0
  105. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts +114 -0
  106. package/dist/lib/server/decisioning/runtime/postgres-task-registry.d.ts.map +1 -0
  107. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js +247 -0
  108. package/dist/lib/server/decisioning/runtime/postgres-task-registry.js.map +1 -0
  109. package/dist/lib/server/decisioning/runtime/protocol-for-tool.d.ts +32 -0
  110. package/dist/lib/server/decisioning/runtime/protocol-for-tool.d.ts.map +1 -0
  111. package/dist/lib/server/decisioning/runtime/protocol-for-tool.js +127 -0
  112. package/dist/lib/server/decisioning/runtime/protocol-for-tool.js.map +1 -0
  113. package/dist/lib/server/decisioning/runtime/task-registry.d.ts +105 -0
  114. package/dist/lib/server/decisioning/runtime/task-registry.d.ts.map +1 -0
  115. package/dist/lib/server/decisioning/runtime/task-registry.js +96 -0
  116. package/dist/lib/server/decisioning/runtime/task-registry.js.map +1 -0
  117. package/dist/lib/server/decisioning/runtime/to-context.d.ts +54 -0
  118. package/dist/lib/server/decisioning/runtime/to-context.d.ts.map +1 -0
  119. package/dist/lib/server/decisioning/runtime/to-context.js +166 -0
  120. package/dist/lib/server/decisioning/runtime/to-context.js.map +1 -0
  121. package/dist/lib/server/decisioning/runtime/validate-platform.d.ts +20 -0
  122. package/dist/lib/server/decisioning/runtime/validate-platform.d.ts.map +1 -0
  123. package/dist/lib/server/decisioning/runtime/validate-platform.js +93 -0
  124. package/dist/lib/server/decisioning/runtime/validate-platform.js.map +1 -0
  125. package/dist/lib/server/decisioning/specialisms/audiences.d.ts +72 -0
  126. package/dist/lib/server/decisioning/specialisms/audiences.d.ts.map +1 -0
  127. package/dist/lib/server/decisioning/specialisms/audiences.js +15 -0
  128. package/dist/lib/server/decisioning/specialisms/audiences.js.map +1 -0
  129. package/dist/lib/server/decisioning/specialisms/brand-rights.d.ts +92 -0
  130. package/dist/lib/server/decisioning/specialisms/brand-rights.d.ts.map +1 -0
  131. package/dist/lib/server/decisioning/specialisms/brand-rights.js +28 -0
  132. package/dist/lib/server/decisioning/specialisms/brand-rights.js.map +1 -0
  133. package/dist/lib/server/decisioning/specialisms/campaign-governance.d.ts +67 -0
  134. package/dist/lib/server/decisioning/specialisms/campaign-governance.d.ts.map +1 -0
  135. package/dist/lib/server/decisioning/specialisms/campaign-governance.js +31 -0
  136. package/dist/lib/server/decisioning/specialisms/campaign-governance.js.map +1 -0
  137. package/dist/lib/server/decisioning/specialisms/content-standards.d.ts +78 -0
  138. package/dist/lib/server/decisioning/specialisms/content-standards.d.ts.map +1 -0
  139. package/dist/lib/server/decisioning/specialisms/content-standards.js +35 -0
  140. package/dist/lib/server/decisioning/specialisms/content-standards.js.map +1 -0
  141. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts +81 -0
  142. package/dist/lib/server/decisioning/specialisms/creative-ad-server.d.ts.map +1 -0
  143. package/dist/lib/server/decisioning/specialisms/creative-ad-server.js +28 -0
  144. package/dist/lib/server/decisioning/specialisms/creative-ad-server.js.map +1 -0
  145. package/dist/lib/server/decisioning/specialisms/creative.d.ts +144 -0
  146. package/dist/lib/server/decisioning/specialisms/creative.d.ts.map +1 -0
  147. package/dist/lib/server/decisioning/specialisms/creative.js +19 -0
  148. package/dist/lib/server/decisioning/specialisms/creative.js.map +1 -0
  149. package/dist/lib/server/decisioning/specialisms/lists.d.ts +61 -0
  150. package/dist/lib/server/decisioning/specialisms/lists.d.ts.map +1 -0
  151. package/dist/lib/server/decisioning/specialisms/lists.js +30 -0
  152. package/dist/lib/server/decisioning/specialisms/lists.js.map +1 -0
  153. package/dist/lib/server/decisioning/specialisms/sales.d.ts +163 -0
  154. package/dist/lib/server/decisioning/specialisms/sales.d.ts.map +1 -0
  155. package/dist/lib/server/decisioning/specialisms/sales.js +64 -0
  156. package/dist/lib/server/decisioning/specialisms/sales.js.map +1 -0
  157. package/dist/lib/server/decisioning/specialisms/signals.d.ts +64 -0
  158. package/dist/lib/server/decisioning/specialisms/signals.d.ts.map +1 -0
  159. package/dist/lib/server/decisioning/specialisms/signals.js +28 -0
  160. package/dist/lib/server/decisioning/specialisms/signals.js.map +1 -0
  161. package/dist/lib/server/decisioning/start-time.d.ts +76 -0
  162. package/dist/lib/server/decisioning/start-time.d.ts.map +1 -0
  163. package/dist/lib/server/decisioning/start-time.js +81 -0
  164. package/dist/lib/server/decisioning/start-time.js.map +1 -0
  165. package/dist/lib/server/decisioning/status-changes.d.ts +165 -0
  166. package/dist/lib/server/decisioning/status-changes.d.ts.map +1 -0
  167. package/dist/lib/server/decisioning/status-changes.js +131 -0
  168. package/dist/lib/server/decisioning/status-changes.js.map +1 -0
  169. package/dist/lib/server/decisioning/status-mappers.d.ts +46 -0
  170. package/dist/lib/server/decisioning/status-mappers.d.ts.map +1 -0
  171. package/dist/lib/server/decisioning/status-mappers.js +46 -0
  172. package/dist/lib/server/decisioning/status-mappers.js.map +1 -0
  173. package/dist/lib/server/decisioning/tenant-registry.d.ts +289 -0
  174. package/dist/lib/server/decisioning/tenant-registry.d.ts.map +1 -0
  175. package/dist/lib/server/decisioning/tenant-registry.js +503 -0
  176. package/dist/lib/server/decisioning/tenant-registry.js.map +1 -0
  177. package/dist/lib/server/express-adapter.d.ts +1 -1
  178. package/dist/lib/server/express-adapter.js +1 -1
  179. package/dist/lib/server/governance.d.ts +1 -1
  180. package/dist/lib/server/governance.js +1 -1
  181. package/dist/lib/server/idempotency/store.d.ts +1 -1
  182. package/dist/lib/server/idempotency/store.js +1 -1
  183. package/dist/lib/server/index.d.ts +9 -2
  184. package/dist/lib/server/index.d.ts.map +1 -1
  185. package/dist/lib/server/index.js +79 -4
  186. package/dist/lib/server/index.js.map +1 -1
  187. package/dist/lib/server/legacy/v5/index.d.ts +38 -0
  188. package/dist/lib/server/legacy/v5/index.d.ts.map +1 -0
  189. package/dist/lib/server/legacy/v5/index.js +60 -0
  190. package/dist/lib/server/legacy/v5/index.js.map +1 -0
  191. package/dist/lib/server/normalize-errors.d.ts +88 -0
  192. package/dist/lib/server/normalize-errors.d.ts.map +1 -0
  193. package/dist/lib/server/normalize-errors.js +146 -0
  194. package/dist/lib/server/normalize-errors.js.map +1 -0
  195. package/dist/lib/server/pick-safe-details.d.ts +90 -0
  196. package/dist/lib/server/pick-safe-details.d.ts.map +1 -0
  197. package/dist/lib/server/pick-safe-details.js +148 -0
  198. package/dist/lib/server/pick-safe-details.js.map +1 -0
  199. package/dist/lib/server/postgres-state-store.d.ts +1 -1
  200. package/dist/lib/server/postgres-state-store.js +1 -1
  201. package/dist/lib/server/responses.d.ts +38 -0
  202. package/dist/lib/server/responses.d.ts.map +1 -1
  203. package/dist/lib/server/responses.js +38 -0
  204. package/dist/lib/server/responses.js.map +1 -1
  205. package/dist/lib/server/state-store.d.ts +1 -1
  206. package/dist/lib/server/state-store.js +1 -1
  207. package/dist/lib/server/test-controller.d.ts +10 -3
  208. package/dist/lib/server/test-controller.d.ts.map +1 -1
  209. package/dist/lib/server/test-controller.js +10 -3
  210. package/dist/lib/server/test-controller.js.map +1 -1
  211. package/dist/lib/testing/comply-controller.d.ts +47 -1
  212. package/dist/lib/testing/comply-controller.d.ts.map +1 -1
  213. package/dist/lib/testing/comply-controller.js +11 -4
  214. package/dist/lib/testing/comply-controller.js.map +1 -1
  215. package/dist/lib/testing/index.d.ts +1 -1
  216. package/dist/lib/testing/index.d.ts.map +1 -1
  217. package/dist/lib/testing/index.js.map +1 -1
  218. package/dist/lib/testing/personas/index.d.ts +143 -0
  219. package/dist/lib/testing/personas/index.d.ts.map +1 -0
  220. package/dist/lib/testing/personas/index.js +190 -0
  221. package/dist/lib/testing/personas/index.js.map +1 -0
  222. package/dist/lib/testing/storyboard/index.d.ts +1 -1
  223. package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
  224. package/dist/lib/testing/storyboard/index.js +3 -2
  225. package/dist/lib/testing/storyboard/index.js.map +1 -1
  226. package/dist/lib/testing/storyboard/runner.d.ts +13 -0
  227. package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
  228. package/dist/lib/testing/storyboard/runner.js +179 -7
  229. package/dist/lib/testing/storyboard/runner.js.map +1 -1
  230. package/dist/lib/types/adcp.d.ts.map +1 -1
  231. package/dist/lib/types/adcp.js +1 -0
  232. package/dist/lib/types/adcp.js.map +1 -1
  233. package/dist/lib/types/asset-instances.d.ts +1 -0
  234. package/dist/lib/types/asset-instances.d.ts.map +1 -1
  235. package/dist/lib/types/core.generated.d.ts +203 -98
  236. package/dist/lib/types/core.generated.d.ts.map +1 -1
  237. package/dist/lib/types/core.generated.js +1 -1
  238. package/dist/lib/types/index.d.ts +1 -0
  239. package/dist/lib/types/index.d.ts.map +1 -1
  240. package/dist/lib/types/index.js.map +1 -1
  241. package/dist/lib/types/schemas.generated.d.ts +599 -159
  242. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  243. package/dist/lib/types/schemas.generated.js +175 -94
  244. package/dist/lib/types/schemas.generated.js.map +1 -1
  245. package/dist/lib/types/tools.generated.d.ts +315 -46
  246. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  247. package/dist/lib/utils/capabilities.d.ts +1 -1
  248. package/dist/lib/utils/capabilities.d.ts.map +1 -1
  249. package/dist/lib/utils/capabilities.js +6 -0
  250. package/dist/lib/utils/capabilities.js.map +1 -1
  251. package/dist/lib/validation/schema-validator.d.ts +13 -0
  252. package/dist/lib/validation/schema-validator.d.ts.map +1 -1
  253. package/dist/lib/validation/schema-validator.js +240 -3
  254. package/dist/lib/validation/schema-validator.js.map +1 -1
  255. package/dist/lib/version.d.ts +3 -3
  256. package/dist/lib/version.d.ts.map +1 -1
  257. package/dist/lib/version.js +3 -3
  258. package/dist/lib/version.js.map +1 -1
  259. package/docs/guides/BUILD-AN-AGENT.md +30 -5
  260. package/docs/llms.txt +28 -17
  261. package/examples/README.md +3 -1
  262. package/examples/decisioning-platform-broadcast-tv.ts +300 -0
  263. package/examples/decisioning-platform-identity-graph.ts +214 -0
  264. package/examples/decisioning-platform-mock-seller.ts +332 -0
  265. package/examples/decisioning-platform-multi-tenant.ts +128 -0
  266. package/examples/decisioning-platform-programmatic.ts +254 -0
  267. package/examples/signals-agent.ts +1 -1
  268. package/package.json +13 -2
  269. package/skills/build-brand-rights-agent/SKILL.md +10 -3
  270. package/skills/build-creative-agent/SKILL.md +94 -64
  271. package/skills/build-decisioning-creative-template/SKILL.md +554 -0
  272. package/skills/build-decisioning-platform/SKILL.md +304 -0
  273. package/skills/build-decisioning-platform/advanced/BRAND-RIGHTS.md +25 -0
  274. package/skills/build-decisioning-platform/advanced/COMPLIANCE.md +23 -0
  275. package/skills/build-decisioning-platform/advanced/GOVERNANCE.md +24 -0
  276. package/skills/build-decisioning-platform/advanced/HITL.md +34 -0
  277. package/skills/build-decisioning-platform/advanced/IDEMPOTENCY.md +52 -0
  278. package/skills/build-decisioning-platform/advanced/MULTI-TENANT.md +47 -0
  279. package/skills/build-decisioning-platform/advanced/OAUTH.md +22 -0
  280. package/skills/build-decisioning-platform/advanced/REFERENCE.md +991 -0
  281. package/skills/build-decisioning-platform/advanced/SANDBOX.md +24 -0
  282. package/skills/build-decisioning-platform/advanced/STATE-MACHINE.md +52 -0
  283. package/skills/build-decisioning-signal-marketplace/SKILL.md +269 -0
  284. package/skills/build-generative-seller-agent/SKILL.md +89 -53
  285. package/skills/build-governance-agent/SKILL.md +76 -45
  286. package/skills/build-retail-media-agent/SKILL.md +87 -62
  287. package/skills/build-seller-agent/SKILL.md +384 -255
  288. package/skills/build-seller-agent/deployment.md +5 -3
  289. package/skills/build-seller-agent/specialisms/audience-sync.md +0 -2
  290. package/skills/build-seller-agent/specialisms/sales-broadcast-tv.md +0 -2
  291. package/skills/build-seller-agent/specialisms/sales-guaranteed.md +0 -2
  292. package/skills/build-seller-agent/specialisms/sales-non-guaranteed.md +0 -2
  293. package/skills/build-seller-agent/specialisms/sales-proposal-mode.md +0 -2
  294. package/skills/build-seller-agent/specialisms/sales-social.md +0 -2
  295. package/skills/build-seller-agent/specialisms/signed-requests.md +0 -2
  296. package/skills/build-si-agent/SKILL.md +40 -32
  297. package/skills/build-signals-agent/SKILL.md +139 -92
  298. package/skills/call-adcp-agent.previous/SKILL.md +5 -0
@@ -27,46 +27,61 @@ Every sales-_ specialism (including `sales-social`, `sales-broadcast-tv`, `sales
27
27
 
28
28
  **Required tools** (tested by the `media_buy_seller` storyboard bundle at `compliance/cache/3.0.0/protocols/media-buy/`):
29
29
 
30
- | Tool | Purpose | `createAdcpServer` group |
30
+ | Tool | Purpose | `SalesPlatform` method |
31
31
  | ------------------------ | ---------------------------------------------------------------------------------- | ------------------------ |
32
32
  | `get_adcp_capabilities` | Declare protocols + specialisms + features | auto (framework) |
33
- | `sync_accounts` | Advertiser onboarding, per-tenant account creation | `accounts` |
34
- | `list_accounts` | Account lookup by brand/operator; buyers listing their accounts on your platform | `accounts` |
35
- | `get_products` | Product catalog discovery from a brief; returns `{ products: [...] }` | `mediaBuy` |
36
- | `list_creative_formats` | Formats your agent accepts | `mediaBuy` |
37
- | `create_media_buy` | Accept a campaign with packages, budget, flight dates | `mediaBuy` |
38
- | `update_media_buy` | Bid, budget, status, package mutations over the campaign lifecycle | `mediaBuy` |
39
- | `get_media_buys` | Read campaigns back with full state (status, budget, packages, targeting overlays) | `mediaBuy` |
40
- | `sync_creatives` | Accept creative assets and return per-asset status | `mediaBuy` |
41
- | `list_creatives` | Read the creative library back with pagination | `mediaBuy` |
42
- | `get_media_buy_delivery` | Delivery + spend reporting with `reporting_period`, per-package billing rows | `mediaBuy` |
43
-
44
- **Minimum handler skeleton** — every sales-\* seller starts here, then adds specialism-specific behavior on top:
33
+ | `sync_accounts` | Advertiser onboarding, per-tenant account creation | `accounts.upsert` |
34
+ | `list_accounts` | Account lookup by brand/operator; buyers listing their accounts on your platform | `accounts.list` |
35
+ | `get_products` | Product catalog discovery from a brief; returns `{ products: [...] }` | `sales.getProducts` |
36
+ | `list_creative_formats` | Formats your agent accepts | `sales.listCreativeFormats` |
37
+ | `create_media_buy` | Accept a campaign with packages, budget, flight dates | `sales.createMediaBuy` |
38
+ | `update_media_buy` | Bid, budget, status, package mutations over the campaign lifecycle | `sales.updateMediaBuy` |
39
+ | `get_media_buys` | Read campaigns back with full state (status, budget, packages, targeting overlays) | `sales.getMediaBuys` |
40
+ | `sync_creatives` | Accept creative assets and return per-asset status | `sales.syncCreatives` |
41
+ | `list_creatives` | Read the creative library back with pagination | `sales.listCreatives` |
42
+ | `get_media_buy_delivery` | Delivery + spend reporting with `reporting_period`, per-package billing rows | `sales.getMediaBuyDelivery` |
43
+
44
+ **Minimum platform skeleton** — every sales-\* seller starts here, then adds specialism-specific behavior on top:
45
45
 
46
46
  ```ts
47
- createAdcpServer({
48
- name: 'my-seller',
49
- version: '1.0.0',
50
- stateStore,
51
- idempotency: createIdempotencyStore({ backend: memoryBackend() }),
52
- resolveSessionKey: ctx => ctx.account?.account_id,
53
- accounts: {
54
- syncAccounts: async (params, ctx) => { /* */ },
55
- listAccounts: async (params, ctx) => { /* … */ },
56
- },
57
- mediaBuy: {
47
+ import { createAdcpServerFromPlatform, type DecisioningPlatform, type SalesPlatform, type AccountStore } from '@adcp/sdk/server';
48
+
49
+ class MySeller implements DecisioningPlatform<{ networkId: string }, MyMeta> {
50
+ capabilities = {
51
+ specialisms: ['sales-non-guaranteed'] as const,
52
+ pricingModels: ['cpm'] as const,
53
+ channels: ['display'] as const,
54
+ config: { networkId: 'NET_42' },
55
+ };
56
+
57
+ accounts: AccountStore<MyMeta> = {
58
+ resolve: async (ref, ctx) => { /* … */ },
59
+ upsert: async (params, ctx) => { /* … */ },
60
+ list: async (params, ctx) => { /* … */ },
61
+ };
62
+
63
+ sales: SalesPlatform<MyMeta> = {
58
64
  getProducts: async (params, ctx) => { /* … */ },
59
65
  listCreativeFormats: async () => ({ formats: [...] }),
60
66
  createMediaBuy: async (params, ctx) => { /* … */ },
61
- updateMediaBuy: async (params, ctx) => { /* … */ },
67
+ updateMediaBuy: async (id, patch, ctx) => { /* … */ },
62
68
  getMediaBuys: async (params, ctx) => { /* … */ },
63
- syncCreatives: async (params, ctx) => { /* … */ },
69
+ syncCreatives: async (creatives, ctx) => { /* … */ },
64
70
  listCreatives: async (params, ctx) => { /* … */ },
65
- getMediaBuyDelivery: async (params, ctx) => { /* … */ },
66
- },
71
+ getMediaBuyDelivery: async (filter, ctx) => { /* … */ },
72
+ };
73
+ }
74
+
75
+ const server = createAdcpServerFromPlatform(new MySeller(), {
76
+ name: 'my-seller',
77
+ version: '1.0.0',
67
78
  });
68
79
  ```
69
80
 
81
+ The `createAdcpServerFromPlatform` path wraps a typed `DecisioningPlatform` with compile-time specialism enforcement (claim `sales-non-guaranteed`, miss a required `sales.*` method, fail compile), ctx_metadata round-trip + auto-hydration, idempotency-principal synthesis, status mappers, and webhook auto-emit. **Reach for the lower-level `createAdcpServer` from `@adcp/sdk/server/legacy/v5` only when you need fine control over individual handlers, are mid-migration from a v5 codebase, or have custom tools the platform interface doesn't yet model.**
82
+
83
+ > **On a hydration miss, the framework leaves the hydrated field undefined and runs the handler anyway** — the cache is a hint, not source-of-truth. Your handler keeps its existence check (`patch.media_buy ?? (await db.findMediaBuy(id))`) and throws a typed `MediaBuyNotFoundError` / `PackageNotFoundError` / `ProductNotFoundError` from `@adcp/sdk/server` when both the hydrate and the DB-fallback come up empty. Full rationale in [Auto-hydration error contract](../../docs/migration-5.x-to-6.x.md#auto-hydration-error-contract).
84
+
70
85
  If a specialism's storyboard doesn't exercise one of these tools, the tool is **not optional** — the storyboard is just focused elsewhere (e.g. `sales-social` covers audience sync + DPA + events; the media buy flow itself is covered by `sales-non-guaranteed` or `sales-guaranteed` which you also claim). See § [Tools and Required Response Shapes](#tools-and-required-response-shapes) below for the exact response shape each tool must return.
71
86
 
72
87
  ## Specialisms This Skill Covers
@@ -99,7 +114,7 @@ Three requirements apply to **every** production seller, regardless of which spe
99
114
 
100
115
  ### `idempotency_key` is required on every mutating request
101
116
 
102
- `create_media_buy`, `update_media_buy`, `sync_accounts`, `sync_creatives`, `sync_audiences`, `sync_catalogs`, `sync_event_sources`, `provide_performance_feedback` — every mutating call carries a client-supplied `idempotency_key`. Wire `createIdempotencyStore` into `createAdcpServer({ idempotency })` and the framework handles replay detection, payload-hash conflict (`IDEMPOTENCY_CONFLICT`), expiry (`IDEMPOTENCY_EXPIRED`), and in-flight parallelism. Don't implement this in handler code. See [§ Idempotency](#idempotency) below for the full wire-up.
117
+ `create_media_buy`, `update_media_buy`, `sync_accounts`, `sync_creatives`, `sync_audiences`, `sync_catalogs`, `sync_event_sources`, `provide_performance_feedback` — every mutating call carries a client-supplied `idempotency_key`. Wire `createIdempotencyStore` into `createAdcpServerFromPlatform(platform, { idempotency })` and the framework handles replay detection, payload-hash conflict (`IDEMPOTENCY_CONFLICT`), expiry (`IDEMPOTENCY_EXPIRED`), and in-flight parallelism. Don't implement this in handler code. See [§ Idempotency](#idempotency) below for the full wire-up.
103
118
 
104
119
  ### Authentication is mandatory
105
120
 
@@ -169,7 +184,7 @@ serve(createAgent, {
169
184
  publicUrl: 'https://seller.example.com/mcp',
170
185
 
171
186
  // 1. authenticate runs first. Bad/missing bearer → 401 Bearer challenge.
172
- // serve() populates extra.authInfo, which createAdcpServer surfaces as ctx.authInfo.
187
+ // serve() populates extra.authInfo, which the framework surfaces as ctx.authInfo.
173
188
  authenticate: verifyBearer({
174
189
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
175
190
  issuer: 'https://auth.example.com',
@@ -206,7 +221,7 @@ serve(createAgent, {
206
221
  return false; // continue to MCP dispatch
207
222
  },
208
223
 
209
- // 3. MCP transport parses JSON and dispatches to createAdcpServer.
224
+ // 3. MCP transport parses JSON and dispatches to the framework server.
210
225
  // 4. Framework applies the idempotency store per handler — you don't mount it.
211
226
  });
212
227
  ```
@@ -214,7 +229,7 @@ serve(createAgent, {
214
229
  **Principal threading.** `resolveSessionKey(ctx)` receives only `{toolName, params, account}` — no auth info. To compose the OAuth subject into the idempotency key you need `resolveIdempotencyPrincipal`, which receives the full `HandlerContext` including `ctx.authInfo` (populated by `verifyBearer` through MCP's `extra.authInfo`):
215
230
 
216
231
  ```typescript
217
- createAdcpServer({
232
+ createAdcpServerFromPlatform(myPlatform, {
218
233
  // ...
219
234
  // SessionKeyContext has no authInfo — use this for coarse per-account scoping:
220
235
  resolveSessionKey: ctx => ctx.account?.id,
@@ -277,10 +292,16 @@ This means: the `task_id` you return on a `sales-guaranteed` `create_media_buy`
277
292
 
278
293
  ## Webhooks (async completion, signed outbound)
279
294
 
280
- Most seller flows need outbound webhooks — `sales-guaranteed` fires on IO completion, `sales-broadcast-tv` fires `window_update` deliveries as C3/C7 data matures, `update_media_buy` fires on bid/budget application. **Don't hand-roll `fetch` with HMAC**. Wire `createAdcpServer({ webhooks: { signerKey } })` and call `ctx.emitWebhook(...)` from any handler — the framework handles RFC 9421 signing, nonce minting, stable `idempotency_key` across retries, 5xx/429 backoff, byte-identical JSON serialization, and the "don't retry on signature failures" terminal behavior.
295
+ Most seller flows need outbound webhooks — `sales-guaranteed` fires on IO completion, `sales-broadcast-tv` fires `window_update` deliveries as C3/C7 data matures, `update_media_buy` fires on bid/budget application. **Don't hand-roll `fetch` with HMAC**. Pass `webhooks: { signerKey }` to `createAdcpServerFromPlatform` and call `ctx.emitWebhook(...)` from any handler — the framework handles RFC 9421 signing, nonce minting, stable `idempotency_key` across retries, 5xx/429 backoff, byte-identical JSON serialization, and the "don't retry on signature failures" terminal behavior.
281
296
 
282
297
  ```typescript
283
- import { createAdcpServer, serve } from '@adcp/sdk';
298
+ import {
299
+ createAdcpServerFromPlatform,
300
+ serve,
301
+ type DecisioningPlatform,
302
+ type SalesPlatform,
303
+ type AccountStore,
304
+ } from '@adcp/sdk/server';
284
305
 
285
306
  // Dev: generate a signer JWK once at boot. Production: load from KMS/env with a stable `kid`,
286
307
  // and publish the public half at your `jwks_uri` so buyers can verify without OOB exchange.
@@ -294,41 +315,67 @@ const signerJwk = {
294
315
  key_ops: ['sign'],
295
316
  };
296
317
 
318
+ class WebhookSeller implements DecisioningPlatform {
319
+ capabilities = {
320
+ specialisms: ['sales-guaranteed'] as const,
321
+ pricingModels: ['cpm'] as const,
322
+ channels: ['display'] as const,
323
+ config: {},
324
+ };
325
+
326
+ accounts: AccountStore = {
327
+ resolve: async ref => ({
328
+ id: 'account_id' in ref ? ref.account_id : 'default',
329
+ operator: 'me',
330
+ ctx_metadata: {},
331
+ }),
332
+ upsert: async () => ({ ok: true, items: [] }),
333
+ list: async () => ({ items: [], nextCursor: null }),
334
+ };
335
+
336
+ sales: SalesPlatform = {
337
+ getProducts: async () => ({ products: [] }),
338
+ createMediaBuy: async (req, ctx) => {
339
+ // sales-guaranteed: IO signing completes async. Emit the final result on completion.
340
+ const taskId = `task_${randomUUID()}`;
341
+
342
+ // Capture ctx.emitWebhook into a local BEFORE scheduling — the handler returns
343
+ // immediately, but the closure outlives the request; ctx may be recycled.
344
+ const emit = ctx.emitWebhook!; // non-null: guaranteed populated when webhooks config is set
345
+
346
+ queueIoReview(req, async outcome => {
347
+ await emit({
348
+ url: (req as { push_notification_config?: { url: string } }).push_notification_config!.url,
349
+ payload: {
350
+ task: {
351
+ task_id: taskId,
352
+ status: outcome.approved ? 'completed' : 'rejected',
353
+ result: outcome.approved
354
+ ? { media_buy_id: outcome.media_buy_id, packages: outcome.packages }
355
+ : undefined,
356
+ },
357
+ },
358
+ operation_id: `create_media_buy.${taskId}`, // stable across retries — framework reuses same idempotency_key
359
+ });
360
+ });
361
+ return { status: 'submitted', task_id: taskId }; // synchronous response is the task envelope
362
+ },
363
+ updateMediaBuy: async (id, patch) => ({ media_buy_id: id, status: 'active' }),
364
+ getMediaBuys: async () => ({ media_buys: [] }),
365
+ getMediaBuyDelivery: async () => ({ deliveries: [] }),
366
+ syncCreatives: async () => [],
367
+ listCreativeFormats: async () => ({ formats: [] }),
368
+ };
369
+ }
370
+
297
371
  serve(() =>
298
- createAdcpServer({
372
+ createAdcpServerFromPlatform(new WebhookSeller(), {
299
373
  name: 'My Seller',
300
374
  version: '1.0.0',
301
375
  webhooks: {
302
376
  signerKey: { keyid: signerJwk.kid, alg: 'ed25519', privateKey: signerJwk },
303
377
  // Optional: retries, idempotencyKeyStore (swap memory → pg for multi-replica)
304
378
  },
305
- mediaBuy: {
306
- createMediaBuy: async (params, ctx) => {
307
- // sales-guaranteed: IO signing completes async. Emit the final result on completion.
308
- const taskId = `task_${randomUUID()}`;
309
-
310
- // Capture ctx.emitWebhook into a local BEFORE scheduling — the handler returns
311
- // immediately, but the closure outlives the request; ctx may be recycled.
312
- const emit = ctx.emitWebhook!; // non-null: guaranteed populated when webhooks config is set
313
-
314
- queueIoReview(params, async outcome => {
315
- await emit({
316
- url: (params as { push_notification_config?: { url: string } }).push_notification_config!.url,
317
- payload: {
318
- task: {
319
- task_id: taskId,
320
- status: outcome.approved ? 'completed' : 'rejected',
321
- result: outcome.approved
322
- ? { media_buy_id: outcome.media_buy_id, packages: outcome.packages }
323
- : undefined,
324
- },
325
- },
326
- operation_id: `create_media_buy.${taskId}`, // stable across retries — framework reuses same idempotency_key
327
- });
328
- });
329
- return { status: 'submitted', task_id: taskId }; // synchronous response is the task envelope
330
- },
331
- },
332
379
  })
333
380
  );
334
381
  ```
@@ -406,7 +453,7 @@ Non-guaranteed buys are always instant confirmation.
406
453
  >
407
454
  > **Cross-cutting pitfalls matrix runs keep catching:**
408
455
  >
409
- > - **Declare `capabilities: { specialisms: ['sales-guaranteed'] }` (or your actual specialism) on `createAdcpServer`.** Value is `string[]` of enum ids (not `[{id, version}]`). Agents that don't declare their specialism fail the grader with "No applicable tracks found" even if every tool works — tracks are gated on the specialism claim.
456
+ > - **Declare `capabilities.specialisms: ['sales-guaranteed']` (or your actual specialism) on the platform you pass to `createAdcpServerFromPlatform`.** Value is `string[]` of enum ids (not `[{id, version}]`). Agents that don't declare their specialism fail the grader with "No applicable tracks found" even if every tool works — tracks are gated on the specialism claim.
410
457
  > - `get_media_buy_delivery` response requires **top-level `currency: string`** (ISO 4217) — per-row `spend.currency` is NOT enough.
411
458
  > - `get_media_buy_delivery /media_buy_deliveries[i]/by_package[j]` rows are strict: each requires `package_id`, `spend` (number), `pricing_model`, `rate` (number), and `currency`. A mock that returns `{package_id, impressions, clicks}` fails validation — include the billing quintet on every package row.
412
459
  > - `get_media_buy_delivery /reporting_period/start` and `/end` are ISO 8601 **date-time** strings (`YYYY-MM-DDTHH:MM:SS.sssZ` via `new Date().toISOString()`), not date-only. A mock that returns `'2026-04-21'` fails the format check in GA.
@@ -622,7 +669,7 @@ Top-level `currency` is **required** per `get-media-buy-delivery-response.json`.
622
669
 
623
670
  ### Context and Ext Passthrough
624
671
 
625
- `createAdcpServer` auto-echoes the request's `context` into every response — **do not set `context` yourself in your handler return values.** The framework injects it post-handler only when the field isn't already present.
672
+ The framework auto-echoes the request's `context` into every response — **do not set `context` yourself in your handler return values.** The framework injects it post-handler only when the field isn't already present.
626
673
 
627
674
  **Crucial:** `context` is schema-typed as an object. If your handler hand-sets a string or narrative description (e.g., "E2E test run", a scenario label, `campaign_context` from the request body), validation fails with `/context: must be object` and the framework does not overwrite. Leave the field out entirely; the framework handles it.
628
675
 
@@ -697,13 +744,13 @@ productRepo.upsert(merged.product_id, merged);
697
744
  **2. `bridgeFromTestControllerStore`** — wires your seeded `Map` into `get_products` responses automatically. Sandbox requests see seeded + handler products merged (with seeded winning collisions); production traffic (no sandbox marker, or resolved non-sandbox account) skips the bridge entirely.
698
745
 
699
746
  ```ts
700
- import { createAdcpServer } from '@adcp/sdk';
701
- import { bridgeFromTestControllerStore } from '@adcp/sdk/server';
747
+ import { createAdcpServerFromPlatform, bridgeFromTestControllerStore, DEFAULT_REPORTING_CAPABILITIES } from '@adcp/sdk/server';
702
748
 
703
749
  const seedStore = new Map<string, unknown>();
704
750
 
705
- const server = createAdcpServer({
706
- mediaBuy: { getProducts: handleGetProducts },
751
+ const server = createAdcpServerFromPlatform(myPlatform, {
752
+ name: 'My Seller',
753
+ version: '1.0.0',
707
754
  testController: bridgeFromTestControllerStore(seedStore, {
708
755
  delivery_type: 'guaranteed',
709
756
  channels: ['display'],
@@ -785,8 +832,9 @@ Key SDK pieces you'll import from `@adcp/sdk`: `CONTROLLER_SCENARIOS`, `enforceM
785
832
 
786
833
  | SDK piece | Usage |
787
834
  | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
788
- | `createAdcpServer(config)` | Domain-grouped server — auto-wires schemas, response builders, capabilities |
789
- | `serve(() => createAdcpServer(config))` | Start HTTP server on `:3001/mcp` |
835
+ | `createAdcpServerFromPlatform(platform, opts)` | Build a server from a typed `DecisioningPlatform` compile-time specialism enforcement, ctx_metadata round-trip, idempotency-principal synthesis, status mappers, webhook auto-emit |
836
+ | `createAdcpServer(config)` *(legacy)* | v5 handler-bag entry. Mid-migration / escape-hatch only; reach via `@adcp/sdk/server/legacy/v5` |
837
+ | `serve(() => createAdcpServerFromPlatform(platform, opts))` | Start HTTP server on `:3001/mcp` |
790
838
  | `ctx.store` | State store in every handler — `get`, `put`, `patch`, `delete`, `list` |
791
839
  | `InMemoryStateStore` | Default state store (dev/testing) |
792
840
  | `PostgresStateStore` | Production state store (shared across instances) |
@@ -802,7 +850,7 @@ Key SDK pieces you'll import from `@adcp/sdk`: `CONTROLLER_SCENARIOS`, `enforceM
802
850
  | `enforceMapCap(map, key, label, cap?)` | Reject net-new keys once a session Map hits `SESSION_ENTRY_CAP` (1000) |
803
851
  | `expectControllerError(result, code)` / `expectControllerSuccess(result)` | Unit-test assertions — narrow responses to error or success arms |
804
852
 
805
- Response builders (`productsResponse`, `mediaBuyResponse`, `deliveryResponse`, etc.) are auto-applied by `createAdcpServer` — you return the data, the framework wraps it. You only need to call them directly for tools without a dedicated builder.
853
+ Response builders (`productsResponse`, `mediaBuyResponse`, `deliveryResponse`, etc.) are auto-applied by the framework — you return the data, the framework wraps it. You only need to call them directly for tools without a dedicated builder.
806
854
 
807
855
  Import everything from `@adcp/sdk`. Types from `@adcp/sdk` with `import type`.
808
856
 
@@ -833,48 +881,56 @@ Minimal `tsconfig.json`:
833
881
 
834
882
  ## Implementation
835
883
 
836
- Use `createAdcpServer` — it auto-wires schemas, response builders, and `get_adcp_capabilities` from the handlers you provide. Handlers receive `(params, ctx)` where `ctx.store` persists state and `ctx.account` is the resolved account.
884
+ Use `createAdcpServerFromPlatform` — it auto-wires schemas, response builders, and `get_adcp_capabilities` from the typed `DecisioningPlatform` you provide. Handlers receive `(params, ctx)` where `ctx.store` persists state, `ctx.account` is the resolved account, and `ctx.ctxMetadata` is the resource-keyed cache (when wired).
837
885
 
838
886
  **Imports**: most things live at `@adcp/sdk`. The idempotency store helpers (`createIdempotencyStore`, `memoryBackend`, `pgBackend`) live at the narrower `@adcp/sdk/server` subpath. Both are re-exported from the root — either works — but splitting them makes intent obvious.
839
887
 
840
888
  ```typescript
841
889
  import { randomUUID } from 'node:crypto';
842
890
  import {
843
- createAdcpServer,
891
+ createAdcpServerFromPlatform,
844
892
  serve,
845
893
  adcpError,
846
894
  InMemoryStateStore,
847
895
  checkGovernance,
848
896
  governanceDeniedError,
849
- } from '@adcp/sdk';
850
- import { createIdempotencyStore, memoryBackend } from '@adcp/sdk/server';
897
+ createIdempotencyStore,
898
+ memoryBackend,
899
+ type DecisioningPlatform,
900
+ type SalesPlatform,
901
+ type AccountStore,
902
+ } from '@adcp/sdk/server';
851
903
  import type { ServeContext } from '@adcp/sdk';
852
904
 
905
+ // Publisher-typed metadata blob round-tripped via Account.ctx_metadata.
906
+ // Whatever shape your adapter wants — the SDK doesn't inspect it.
907
+ interface MySellerMeta {
908
+ governanceUrl?: string;
909
+ brand?: string;
910
+ operator?: string;
911
+ [key: string]: unknown;
912
+ }
913
+
853
914
  const stateStore = new InMemoryStateStore(); // shared across requests
854
915
 
855
- // Idempotency — required for any v3-compliant seller that accepts mutating
856
- // requests. `createIdempotencyStore` throws if `ttlSeconds` is outside the
857
- // spec bounds (3600–604800).
916
+ // Idempotency — required for any AdCP-3-compliant seller that accepts
917
+ // mutating requests. `createIdempotencyStore` throws if `ttlSeconds` is
918
+ // outside the spec bounds (3600–604800).
858
919
  const idempotency = createIdempotencyStore({
859
920
  backend: memoryBackend(), // pgBackend(pool) for production
860
921
  ttlSeconds: 86400, // 24 hours
861
922
  });
862
923
 
863
- function createAgent({ taskStore }: ServeContext) {
864
- return createAdcpServer({
865
- name: 'My Seller Agent',
866
- version: '1.0.0',
867
- taskStore,
868
- stateStore,
869
- idempotency,
924
+ class MySeller implements DecisioningPlatform<{}, MySellerMeta> {
925
+ capabilities = {
926
+ specialisms: ['sales-non-guaranteed'] as const,
927
+ pricingModels: ['cpm'] as const,
928
+ channels: ['display'] as const,
929
+ config: {},
930
+ };
870
931
 
871
- // Principal scoping for idempotency. MUST never return undefined — or
872
- // every mutating request rejects as SERVICE_UNAVAILABLE. A constant is
873
- // fine for a demo; for multi-tenant production use ctx.account typed
874
- // via `createAdcpServer<MyAccount>({...})`.
875
- resolveSessionKey: () => 'default-principal',
876
-
877
- // resolveAccount runs BEFORE idempotency / handler dispatch. If it
932
+ accounts: AccountStore<MySellerMeta> = {
933
+ // accounts.resolve runs BEFORE idempotency / handler dispatch. If it
878
934
  // returns null for a valid-shape reference, every mutating request
879
935
  // short-circuits as ACCOUNT_NOT_FOUND — which masks idempotency
880
936
  // conformance (missing-key / replay tests fail with the wrong code).
@@ -883,118 +939,157 @@ function createAgent({ taskStore }: ServeContext) {
883
939
  // { brand: { domain }, operator } — the canonical spec shape.
884
940
  // Conformance storyboards use this by default (e.g. brand.domain
885
941
  // "acmeoutdoor.example", operator "pinnacle-agency.example").
886
- resolveAccount: async ref => {
887
- if ('account_id' in ref) return stateStore.get('accounts', ref.account_id);
942
+ resolve: async (ref, ctx) => {
943
+ if ('account_id' in ref) {
944
+ const acc = await stateStore.get('accounts', ref.account_id);
945
+ return acc ?? null;
946
+ }
888
947
  if ('brand' in ref && ref.brand?.domain && ref.operator) {
889
- // In dev/compliance mode, auto-materialize an account for any
890
- // valid brand+operator so conformance tests reach the handler.
891
- // In production, replace with a real lookup against your tenant
892
- // registry — returning null here for unknown tenants is correct
893
- // and will (correctly) surface ACCOUNT_NOT_FOUND to the buyer.
894
- return { brand: ref.brand.domain, operator: ref.operator };
948
+ // Dev/compliance mode: auto-materialize for any valid brand+operator
949
+ // so conformance tests reach the handler. Production replaces this
950
+ // with a real lookup against your tenant registry; returning null
951
+ // for unknown tenants surfaces ACCOUNT_NOT_FOUND correctly.
952
+ return {
953
+ id: `${ref.operator}:${ref.brand.domain}`,
954
+ operator: ref.operator,
955
+ ctx_metadata: { brand: ref.brand.domain, operator: ref.operator },
956
+ };
895
957
  }
896
958
  return null;
897
959
  },
960
+ upsert: async (params, ctx) => {
961
+ /* sync_accounts impl */
962
+ return { ok: true, items: [] };
963
+ },
964
+ list: async (params, ctx) => ({ items: [], nextCursor: null }),
965
+ };
898
966
 
899
- accounts: {
900
- syncAccounts: async (params, ctx) => {
901
- /* ... */
902
- },
967
+ sales: SalesPlatform<MySellerMeta> = {
968
+ getProducts: async (req, ctx) => {
969
+ return { products: PRODUCTS, sandbox: true };
970
+ // productsResponse() auto-applied by framework
903
971
  },
904
- mediaBuy: {
905
- getProducts: async (params, ctx) => {
906
- return { products: PRODUCTS, sandbox: true };
907
- // productsResponse() auto-applied by framework
908
- },
909
- createMediaBuy: async (params, ctx) => {
910
- // Governance check for financial commitment
911
- if (ctx.account?.governanceUrl) {
912
- const gov = await checkGovernance({
913
- agentUrl: ctx.account.governanceUrl,
914
- planId: params.plan_id ?? 'default',
915
- caller: 'https://my-agent.com/mcp',
916
- tool: 'create_media_buy',
917
- payload: params,
918
- });
919
- if (!gov.approved) return governanceDeniedError(gov);
920
- }
921
- // Use randomUUID (not Date.now) so ids are unguessable — a guessable
922
- // media_buy_id lets another buyer probe or cancel. Same applies to
923
- // any seller-issued id (package_id, creative_id, etc.).
924
- // `currency` + `total_budget` are REQUIRED on get_media_buys response rows.
925
- // The request carries them under `total_budget: { amount, currency }` (object).
926
- // Flatten to top-level fields at create time — storing only `packages[].budget`
927
- // and reconstructing later fails schema validation in get_media_buys/update_media_buy.
928
- const currency = params.total_budget?.currency ?? 'USD';
929
- const totalBudget =
930
- params.total_budget?.amount ?? (params.packages ?? []).reduce((a, p) => a + (p.budget ?? 0), 0);
931
- const buy = {
932
- media_buy_id: `mb_${randomUUID()}`,
933
- status: 'pending_creatives' as const,
934
- currency,
935
- total_budget: totalBudget,
936
- packages:
937
- params.packages?.map(pkg => ({
938
- package_id: `pkg_${randomUUID()}`,
939
- product_id: pkg.product_id,
940
- pricing_option_id: pkg.pricing_option_id,
941
- budget: pkg.budget,
942
- })) ?? [],
943
- };
944
- await ctx.store.put('media_buys', buy.media_buy_id, buy);
945
- return buy; // mediaBuyResponse() auto-applied (sets revision, confirmed_at, valid_actions)
946
- },
947
- updateMediaBuy: async (params, ctx) => {
948
- const existing = await ctx.store.get('media_buys', params.media_buy_id);
949
- if (!existing) {
950
- return adcpError('MEDIA_BUY_NOT_FOUND', {
951
- message: `No media buy with id ${params.media_buy_id}`,
952
- field: 'media_buy_id',
953
- });
954
- }
955
- // Only merge the fields you want to persist — do NOT spread `params`
956
- // wholesale. `params` carries envelope fields (idempotency_key,
957
- // context) that have no business in your domain state. Spreading
958
- // them pollutes `get_media_buys` responses and breaks dedup.
959
- const updated = { ...existing, status: params.active === false ? 'paused' : 'active' };
960
- await ctx.store.put('media_buys', params.media_buy_id, updated);
961
- return {
962
- media_buy_id: params.media_buy_id,
963
- status: updated.status as 'paused' | 'active',
964
- // `affected_packages` is `Package[]` (per `/schemas/latest/core/package.json`)
965
- // — objects with at minimum `package_id`. Don't return bare strings;
966
- // the update-media-buy-response oneOf discriminates against them and
967
- // the error looks like `/affected_packages/0: must be object`.
968
- affected_packages: (existing.packages ?? []).map((p: { package_id: string }) => ({
969
- package_id: p.package_id,
970
- })),
971
- };
972
- },
973
- getMediaBuys: async (params, ctx) => {
974
- const result = await ctx.store.list('media_buys');
975
- return { media_buys: result.items };
976
- },
977
- getMediaBuyDelivery: async (params, ctx) => {
978
- /* ... */
979
- },
980
- listCreativeFormats: async (params, ctx) => {
981
- /* ... */
982
- },
983
- syncCreatives: async (params, ctx) => {
984
- return {
985
- // Response shape is `creatives: [{ creative_id, action }]` per the
986
- // sync_creatives response schema — NOT `synced_creatives`.
987
- creatives:
988
- params.creatives?.map(c => ({
989
- creative_id: c.creative_id ?? `cr_${randomUUID()}`,
990
- action: 'created' as const,
991
- })) ?? [],
992
- };
993
- },
972
+
973
+ createMediaBuy: async (req, ctx) => {
974
+ // Governance check for financial commitment. The publisher's
975
+ // governance URL rides on Account.ctx_metadata so any per-tenant
976
+ // override is read at request time.
977
+ const govUrl = ctx.account?.ctx_metadata?.governanceUrl;
978
+ if (typeof govUrl === 'string') {
979
+ const gov = await checkGovernance({
980
+ agentUrl: govUrl,
981
+ planId: (req as { plan_id?: string }).plan_id ?? 'default',
982
+ caller: 'https://my-agent.com/mcp',
983
+ tool: 'create_media_buy',
984
+ payload: req,
985
+ });
986
+ if (!gov.approved) return governanceDeniedError(gov);
987
+ }
988
+
989
+ // Use randomUUID (not Date.now) so ids are unguessable — a guessable
990
+ // media_buy_id lets another buyer probe or cancel. Same applies to
991
+ // any seller-issued id (package_id, creative_id, etc.).
992
+ // `currency` + `total_budget` are REQUIRED on get_media_buys response
993
+ // rows. The request carries them under `total_budget: { amount, currency }`.
994
+ // Flatten to top-level fields at create time — storing only
995
+ // `packages[].budget` and reconstructing later fails schema validation
996
+ // in get_media_buys/update_media_buy.
997
+ const totalBudget = req.total_budget;
998
+ const currency = typeof totalBudget === 'object' && totalBudget ? (totalBudget.currency ?? 'USD') : 'USD';
999
+ const amount =
1000
+ typeof totalBudget === 'object' && totalBudget
1001
+ ? (totalBudget.amount ?? 0)
1002
+ : typeof totalBudget === 'number'
1003
+ ? totalBudget
1004
+ : 0;
1005
+
1006
+ const buy = {
1007
+ media_buy_id: `mb_${randomUUID()}`,
1008
+ status: 'pending_creatives' as const,
1009
+ currency,
1010
+ total_budget: amount,
1011
+ packages:
1012
+ req.packages?.map(pkg => ({
1013
+ package_id: `pkg_${randomUUID()}`,
1014
+ product_id: pkg.product_id,
1015
+ pricing_option_id: pkg.pricing_option_id,
1016
+ budget: pkg.budget,
1017
+ })) ?? [],
1018
+ };
1019
+ await ctx.store.put('media_buys', buy.media_buy_id, buy);
1020
+ return buy; // mediaBuyResponse() auto-applied (sets revision, confirmed_at, valid_actions)
994
1021
  },
995
- capabilities: {
996
- features: { inlineCreativeManagement: false },
1022
+
1023
+ updateMediaBuy: async (mediaBuyId, patch, ctx) => {
1024
+ const existing = await ctx.store.get('media_buys', mediaBuyId);
1025
+ if (!existing) {
1026
+ return adcpError('MEDIA_BUY_NOT_FOUND', {
1027
+ message: `No media buy with id ${mediaBuyId}`,
1028
+ field: 'media_buy_id',
1029
+ });
1030
+ }
1031
+ // Only merge the fields you want to persist — do NOT spread `patch`
1032
+ // wholesale. The patch carries envelope fields (idempotency_key,
1033
+ // context) that have no business in your domain state. Spreading
1034
+ // them pollutes `get_media_buys` responses and breaks dedup.
1035
+ const updated = { ...existing, status: patch.paused === true ? 'paused' : 'active' };
1036
+ await ctx.store.put('media_buys', mediaBuyId, updated);
1037
+ return {
1038
+ media_buy_id: mediaBuyId,
1039
+ status: updated.status as 'paused' | 'active',
1040
+ // `affected_packages` is `Package[]` (per `/schemas/latest/core/package.json`)
1041
+ // — objects with at minimum `package_id`. Don't return bare strings;
1042
+ // the update-media-buy-response oneOf discriminates against them and
1043
+ // the error looks like `/affected_packages/0: must be object`.
1044
+ affected_packages: (existing.packages ?? []).map((p: { package_id: string }) => ({
1045
+ package_id: p.package_id,
1046
+ })),
1047
+ };
997
1048
  },
1049
+
1050
+ getMediaBuys: async (params, ctx) => {
1051
+ const result = await ctx.store.list('media_buys');
1052
+ return { media_buys: result.items };
1053
+ },
1054
+
1055
+ getMediaBuyDelivery: async (filter, ctx) => {
1056
+ /* ... */
1057
+ return {
1058
+ currency: 'USD',
1059
+ reporting_period: {
1060
+ start: filter.start_date ?? '2026-01-01',
1061
+ end: filter.end_date ?? '2026-01-31',
1062
+ },
1063
+ media_buy_deliveries: [],
1064
+ };
1065
+ },
1066
+
1067
+ listCreativeFormats: async (params, ctx) => ({ formats: [] }),
1068
+
1069
+ // Response is `creatives: [{ creative_id, action }]` per the spec response
1070
+ // schema — NOT `synced_creatives`. v6 takes the creatives array directly;
1071
+ // the framework unpacks the request envelope.
1072
+ syncCreatives: async (creatives, ctx) =>
1073
+ creatives.map(c => ({
1074
+ creative_id: (c as { creative_id?: string }).creative_id ?? `cr_${randomUUID()}`,
1075
+ action: 'created' as const,
1076
+ })),
1077
+ };
1078
+ }
1079
+
1080
+ const platform = new MySeller();
1081
+
1082
+ function createAgent({ taskStore }: ServeContext) {
1083
+ return createAdcpServerFromPlatform(platform, {
1084
+ name: 'My Seller Agent',
1085
+ version: '1.0.0',
1086
+ taskStore,
1087
+ stateStore,
1088
+ idempotency,
1089
+ // Principal scoping for idempotency. MUST never return undefined — or
1090
+ // every mutating request rejects as SERVICE_UNAVAILABLE. A constant is
1091
+ // fine for a demo; for multi-tenant production use `ctx.account.id`.
1092
+ resolveSessionKey: () => 'default-principal',
998
1093
  });
999
1094
  }
1000
1095
 
@@ -1003,7 +1098,7 @@ serve(createAgent);
1003
1098
 
1004
1099
  Key points:
1005
1100
 
1006
- 1. Single `.ts` file — all domain handlers in one `createAdcpServer` call
1101
+ 1. Single `.ts` file — one `DecisioningPlatform` class passed to `createAdcpServerFromPlatform`
1007
1102
  2. `get_adcp_capabilities` is auto-generated from your handlers — don't register it manually (idempotency capability is auto-declared too)
1008
1103
  3. Response builders are auto-applied — just return the data
1009
1104
  4. Use `ctx.store` for state — persists across stateless HTTP requests
@@ -1032,72 +1127,105 @@ The buyer signals this by setting `plan.human_review_required: true` on the gove
1032
1127
 
1033
1128
  ```typescript
1034
1129
  import {
1035
- createAdcpServer,
1130
+ createAdcpServerFromPlatform,
1036
1131
  serve,
1037
1132
  adcpError,
1038
1133
  buildHumanOverride,
1039
1134
  checkGovernance,
1040
1135
  governanceDeniedError,
1041
- } from '@adcp/sdk';
1042
- import { taskToolResponse, type AdcpStateStore } from '@adcp/sdk/server';
1136
+ taskToolResponse,
1137
+ type DecisioningPlatform,
1138
+ type SalesPlatform,
1139
+ type AccountStore,
1140
+ type AdcpStateStore,
1141
+ } from '@adcp/sdk/server';
1043
1142
  import { randomUUID } from 'node:crypto';
1044
1143
 
1144
+ interface RegulatedMeta {
1145
+ governanceUrl?: string;
1146
+ [key: string]: unknown;
1147
+ }
1148
+
1149
+ class RegulatedPublisher implements DecisioningPlatform<{}, RegulatedMeta> {
1150
+ capabilities = {
1151
+ specialisms: ['sales-guaranteed'] as const,
1152
+ pricingModels: ['cpm'] as const,
1153
+ channels: ['display'] as const,
1154
+ config: {},
1155
+ };
1156
+
1157
+ accounts: AccountStore<RegulatedMeta> = {
1158
+ resolve: async ref => db.findAccount(ref),
1159
+ upsert: async () => ({ ok: true, items: [] }),
1160
+ list: async () => ({ items: [], nextCursor: null }),
1161
+ };
1162
+
1163
+ sales: SalesPlatform<RegulatedMeta> = {
1164
+ getProducts: async () => ({ products: [] }),
1165
+
1166
+ createMediaBuy: async (req, ctx) => {
1167
+ if (!ctx.account) {
1168
+ return adcpError('ACCOUNT_NOT_FOUND', { field: 'account' });
1169
+ }
1170
+ const plan = await ctx.store.get('governance_plans', (req as { plan_id?: string }).plan_id ?? '');
1171
+ if (!plan) return adcpError('PLAN_NOT_FOUND', { field: 'plan_id' });
1172
+
1173
+ // Human-review gate — GDPR Art 22 / EU AI Act Annex III.
1174
+ if (plan.human_review_required === true) {
1175
+ const taskId = `task_${randomUUID()}`;
1176
+ await ctx.store.put('pending_reviews', taskId, {
1177
+ plan_id: (req as { plan_id?: string }).plan_id,
1178
+ params: req,
1179
+ enqueued_at: new Date().toISOString(),
1180
+ account_id: ctx.account.id,
1181
+ // Buyer's webhook target for async completion, if they supplied one.
1182
+ webhook_url: (req as { push_notification_config?: { url: string } }).push_notification_config?.url,
1183
+ });
1184
+ // Route this task_id to your human-review queue (Slack approval,
1185
+ // ops ticket, internal UI — whatever your reviewers use).
1186
+ await humanReviewQueue.enqueue(taskId);
1187
+ // Submitted envelope per CreateMediaBuySubmitted. Do NOT return a
1188
+ // populated MediaBuy here — media_buy_id and packages land on the
1189
+ // completion artifact once a human approves. taskToolResponse bypasses
1190
+ // the default mediaBuyResponse wrap, which would stamp revision /
1191
+ // confirmed_at / valid_actions — fields that don't belong on a task
1192
+ // envelope.
1193
+ return taskToolResponse({ status: 'submitted', task_id: taskId });
1194
+ }
1195
+
1196
+ // Non-regulated path — normal governance check, commit synchronously.
1197
+ const govUrl = ctx.account.ctx_metadata?.governanceUrl;
1198
+ if (typeof govUrl === 'string') {
1199
+ const gov = await checkGovernance({
1200
+ agentUrl: govUrl,
1201
+ planId: (req as { plan_id?: string }).plan_id ?? 'default',
1202
+ caller: 'https://my-publisher.com/mcp',
1203
+ tool: 'create_media_buy',
1204
+ payload: req,
1205
+ });
1206
+ if (!gov.approved) return governanceDeniedError(gov);
1207
+ }
1208
+ return executeBuy(req, ctx.store);
1209
+ },
1210
+
1211
+ updateMediaBuy: async (id, patch) => ({ media_buy_id: id, status: 'active' }),
1212
+ getMediaBuys: async () => ({ media_buys: [] }),
1213
+ getMediaBuyDelivery: async () => ({ deliveries: [] }),
1214
+ syncCreatives: async () => [],
1215
+ listCreativeFormats: async () => ({ formats: [] }),
1216
+ };
1217
+ }
1218
+
1045
1219
  serve(() =>
1046
- createAdcpServer({
1220
+ createAdcpServerFromPlatform(new RegulatedPublisher(), {
1047
1221
  name: 'Regulated Publisher',
1048
1222
  version: '1.0.0',
1049
- resolveAccount: async ref => db.findAccount(ref),
1050
- mediaBuy: {
1051
- createMediaBuy: async (params, ctx) => {
1052
- if (!ctx.account) {
1053
- return adcpError('ACCOUNT_NOT_FOUND', { field: 'account' });
1054
- }
1055
- const plan = await ctx.store.get('governance_plans', params.plan_id ?? '');
1056
- if (!plan) return adcpError('PLAN_NOT_FOUND', { field: 'plan_id' });
1057
-
1058
- // Human-review gate — GDPR Art 22 / EU AI Act Annex III.
1059
- if (plan.human_review_required === true) {
1060
- const taskId = `task_${randomUUID()}`;
1061
- await ctx.store.put('pending_reviews', taskId, {
1062
- plan_id: params.plan_id,
1063
- params,
1064
- enqueued_at: new Date().toISOString(),
1065
- account_id: ctx.account.id,
1066
- // Buyer's webhook target for async completion, if they supplied one.
1067
- webhook_url: params.push_notification_config?.url,
1068
- });
1069
- // Route this task_id to your human-review queue (Slack approval,
1070
- // ops ticket, internal UI — whatever your reviewers use).
1071
- await humanReviewQueue.enqueue(taskId);
1072
- // Submitted envelope per CreateMediaBuySubmitted. Do NOT return a
1073
- // populated MediaBuy here — media_buy_id and packages land on the
1074
- // completion artifact once a human approves. taskToolResponse bypasses
1075
- // the default mediaBuyResponse wrap, which would stamp revision /
1076
- // confirmed_at / valid_actions — fields that don't belong on a task
1077
- // envelope.
1078
- return taskToolResponse({ status: 'submitted', task_id: taskId });
1079
- }
1080
-
1081
- // Non-regulated path — normal governance check, commit synchronously.
1082
- if (ctx.account.governanceUrl) {
1083
- const gov = await checkGovernance({
1084
- agentUrl: ctx.account.governanceUrl,
1085
- planId: params.plan_id ?? 'default',
1086
- caller: 'https://my-publisher.com/mcp',
1087
- tool: 'create_media_buy',
1088
- payload: params,
1089
- });
1090
- if (!gov.approved) return governanceDeniedError(gov);
1091
- }
1092
- return executeBuy(params, ctx.store);
1093
- },
1094
- },
1095
1223
  })
1096
1224
  );
1097
1225
 
1098
1226
  // Called by the human-review UI when a reviewer signs off. Lives outside any
1099
1227
  // request handler, so it takes its own AdcpStateStore — the same instance you
1100
- // passed to createAdcpServer via `stateStore`. No ctx in scope here.
1228
+ // passed to the framework via `stateStore` option. No ctx in scope here.
1101
1229
  async function onHumanApproval(store: AdcpStateStore, taskId: string, approver: string, reason: string): Promise<void> {
1102
1230
  const pending = await store.get('pending_reviews', taskId);
1103
1231
  if (!pending) throw new Error(`No pending review with id ${taskId}`);
@@ -1120,9 +1248,9 @@ async function onHumanApproval(store: AdcpStateStore, taskId: string, approver:
1120
1248
  await store.delete('pending_reviews', taskId);
1121
1249
 
1122
1250
  // Notify the buyer. Two options, pick based on what your server wires up:
1123
- // 1. If you configured `webhooks` on createAdcpServer and the buyer sent
1251
+ // 1. If you configured `webhooks` on the framework server and the buyer sent
1124
1252
  // push_notification_config.url, POST the completion event from the
1125
- // emitter built at boot (hoisted outside createAdcpServer so it's
1253
+ // emitter built at boot (hoisted outside the framework constructor so it's
1126
1254
  // reachable here). See § Guaranteed delivery / IO signing for the
1127
1255
  // emitter construction.
1128
1256
  // 2. Otherwise the buyer polls — they already have the task_id and will
@@ -1154,7 +1282,7 @@ async function onHumanApproval(store: AdcpStateStore, taskId: string, approver:
1154
1282
 
1155
1283
  AdCP v3 requires an `idempotency_key` on every mutating request. For sellers, that's `create_media_buy`, `update_media_buy`, `sync_creatives`, and any `sync_*` tools you implement. Idempotency is wired in the Implementation example above — this section explains what the framework does for you and the subtleties to know.
1156
1284
 
1157
- **What the framework handles when you pass `idempotency` to `createAdcpServer`:**
1285
+ **What the framework handles when you pass `idempotency` to `createAdcpServerFromPlatform`:**
1158
1286
 
1159
1287
  - Rejects missing or malformed `idempotency_key` with `INVALID_REQUEST`. The spec pattern is `^[A-Za-z0-9_.:-]{16,255}$` — a test key like `"key1"` will be rejected for length, not idempotency logic. **Ordering gotcha**: idempotency runs AFTER `resolveAccount`. If your `resolveAccount` returns null for a valid-shape reference, the buyer gets `ACCOUNT_NOT_FOUND` — NOT the missing-key error they expected — and conformance tests fail with the wrong code. Either handle both AccountReference branches (see Implementation above) or accept dev-mode brand+operator wildcards so compliance graders reach the idempotency layer.
1160
1288
  - Hashes the request payload with RFC 8785 JCS. The emitted error codes and their semantics are in the table at [§ Composing OAuth, signing, and idempotency](#composing-oauth-signing-and-idempotency).
@@ -1197,7 +1325,7 @@ The quick-start uses `memoryBackend()` + `InMemoryStateStore` — both reset on
1197
1325
 
1198
1326
  ```ts
1199
1327
  const store = createIdempotencyStore({ backend: pgBackend(pool), ttlSeconds: 86400 });
1200
- pool.on('error', (err) => console.error('pg pool error', err)); // prevent crash on idle-client errors
1328
+ pool.on('error', err => console.error('pg pool error', err)); // prevent crash on idle-client errors
1201
1329
  serve(createAgent, {
1202
1330
  readinessCheck: () => store.probe(), // throws with a descriptive error if pool/table is broken
1203
1331
  });
@@ -1351,9 +1479,10 @@ Common failure decoder:
1351
1479
 
1352
1480
  | Mistake | Fix |
1353
1481
  | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1354
- | Using `createTaskCapableServer` + `server.tool()` | Use `createAdcpServer` — handles schemas, response builders, capabilities |
1482
+ | Using `createTaskCapableServer` + `server.tool()` | Use `createAdcpServerFromPlatform(platform, opts)` — handles schemas, response builders, capabilities, ctx_metadata round-trip, idempotency-principal synthesis |
1483
+ | Calling `createAdcpServer` directly in new code | Reach for `createAdcpServerFromPlatform` first; `createAdcpServer` lives at `@adcp/sdk/server/legacy/v5` for mid-migration / escape-hatch use only |
1355
1484
  | Using module-level Maps for state | Use `ctx.store` — persists across HTTP requests, swappable for postgres |
1356
- | Return raw JSON without response builders | `createAdcpServer` auto-applies response builders — just return the data |
1485
+ | Return raw JSON without response builders | The framework auto-applies response builders — just return the data |
1357
1486
  | Missing `brand`/`operator` in sync_accounts response | Echo them back from the request — they're required |
1358
1487
  | sync_governance returns wrong shape | Must include `status: 'synced'` and `governance_agents` array |
1359
1488
  | `sandbox: false` on mock data | Buyers may treat mock data as real |
@@ -1399,7 +1528,7 @@ Claim exactly the specialisms your agent actually implements in `capabilities.sp
1399
1528
 
1400
1529
  ## Reference
1401
1530
 
1402
- - `docs/guides/BUILD-AN-AGENT.md` — createAdcpServer patterns, async tools, state persistence
1531
+ - `docs/guides/BUILD-AN-AGENT.md` — `createAdcpServerFromPlatform` patterns, async tools, state persistence
1403
1532
  - `docs/llms.txt` — full protocol reference
1404
1533
  - `docs/TYPE-SUMMARY.md` — curated type signatures
1405
1534
  - `storyboards/media_buy_seller.yaml` — full buyer interaction sequence