@adcp/sdk 14.0.0-rc.37 → 14.0.0-rc.39

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 (622) hide show
  1. package/bin/adcp-storyboard-summary.js +286 -0
  2. package/bin/adcp.js +221 -10
  3. package/dist/lib/conformance/schemaLoader.d.mts +1 -1
  4. package/dist/lib/conformance/schemaLoader.d.ts +1 -1
  5. package/dist/lib/core/AgentClient.d.mts +6 -1
  6. package/dist/lib/core/AgentClient.d.ts +6 -1
  7. package/dist/lib/core/AgentClient.js +9 -0
  8. package/dist/lib/core/AgentClient.mjs +9 -0
  9. package/dist/lib/core/ConversationTypes.d.mts +29 -0
  10. package/dist/lib/core/ConversationTypes.d.ts +29 -0
  11. package/dist/lib/core/SingleAgentClient.d.mts +3 -1
  12. package/dist/lib/core/SingleAgentClient.d.ts +3 -1
  13. package/dist/lib/core/SingleAgentClient.js +9 -1
  14. package/dist/lib/core/SingleAgentClient.mjs +9 -1
  15. package/dist/lib/core/TaskExecutor.d.mts +13 -1
  16. package/dist/lib/core/TaskExecutor.d.ts +13 -1
  17. package/dist/lib/core/TaskExecutor.js +216 -18
  18. package/dist/lib/core/TaskExecutor.mjs +216 -19
  19. package/dist/lib/index.d.mts +5 -2
  20. package/dist/lib/index.d.ts +5 -2
  21. package/dist/lib/index.js +10 -1
  22. package/dist/lib/index.mjs +10 -1
  23. package/dist/lib/media-buy/action-assessment.d.mts +77 -0
  24. package/dist/lib/media-buy/action-assessment.d.ts +77 -0
  25. package/dist/lib/media-buy/action-assessment.js +300 -0
  26. package/dist/lib/media-buy/action-assessment.mjs +284 -0
  27. package/dist/lib/media-buy/action-constraints.d.mts +9 -0
  28. package/dist/lib/media-buy/action-constraints.d.ts +9 -0
  29. package/dist/lib/media-buy/action-constraints.js +170 -0
  30. package/dist/lib/media-buy/action-constraints.mjs +146 -0
  31. package/dist/lib/media-buy/action-contracts.d.mts +40 -0
  32. package/dist/lib/media-buy/action-contracts.d.ts +40 -0
  33. package/dist/lib/media-buy/action-contracts.js +304 -0
  34. package/dist/lib/media-buy/action-contracts.mjs +267 -0
  35. package/dist/lib/media-buy/action-metadata.generated.d.mts +37 -0
  36. package/dist/lib/media-buy/action-metadata.generated.d.ts +37 -0
  37. package/dist/lib/media-buy/action-metadata.generated.js +128 -0
  38. package/dist/lib/media-buy/action-metadata.generated.mjs +101 -0
  39. package/dist/lib/media-buy/action-types.d.mts +106 -0
  40. package/dist/lib/media-buy/action-types.d.ts +106 -0
  41. package/dist/lib/{types/v3-1-beta/tools.generated.js → media-buy/action-types.js} +2 -2
  42. package/dist/lib/media-buy/actions.d.mts +6 -0
  43. package/dist/lib/media-buy/actions.d.ts +6 -0
  44. package/dist/lib/media-buy/actions.js +38 -0
  45. package/dist/lib/media-buy/actions.mjs +9 -0
  46. package/dist/lib/media-buy/available-actions.d.mts +2 -1
  47. package/dist/lib/media-buy/available-actions.d.ts +2 -1
  48. package/dist/lib/media-buy/available-actions.js +4 -3
  49. package/dist/lib/media-buy/available-actions.mjs +4 -3
  50. package/dist/lib/media-buy/compatibility.js +3 -0
  51. package/dist/lib/media-buy/compatibility.mjs +3 -0
  52. package/dist/lib/media-buy/index.d.mts +4 -0
  53. package/dist/lib/media-buy/index.d.ts +4 -0
  54. package/dist/lib/media-buy/index.js +3 -1
  55. package/dist/lib/media-buy/index.mjs +1 -0
  56. package/dist/lib/media-buy/legacy-action-ids.d.mts +1 -0
  57. package/dist/lib/media-buy/legacy-action-ids.d.ts +1 -0
  58. package/dist/lib/media-buy/legacy-action-ids.js +50 -0
  59. package/dist/lib/media-buy/legacy-action-ids.mjs +26 -0
  60. package/dist/lib/media-buy/mutations.d.mts +70 -0
  61. package/dist/lib/media-buy/mutations.d.ts +70 -0
  62. package/dist/lib/media-buy/mutations.js +525 -0
  63. package/dist/lib/media-buy/mutations.mjs +498 -0
  64. package/dist/lib/media-buy/preflight.d.mts +20 -69
  65. package/dist/lib/media-buy/preflight.d.ts +20 -69
  66. package/dist/lib/media-buy/preflight.js +100 -336
  67. package/dist/lib/media-buy/preflight.mjs +111 -333
  68. package/dist/lib/media-buy/targeting-input.d.mts +7 -4
  69. package/dist/lib/media-buy/targeting-input.d.ts +7 -4
  70. package/dist/lib/media-buy/types.d.mts +55 -7
  71. package/dist/lib/media-buy/types.d.ts +55 -7
  72. package/dist/lib/protocols/rawResponseCapture.d.mts +12 -0
  73. package/dist/lib/protocols/rawResponseCapture.d.ts +12 -0
  74. package/dist/lib/protocols/rawResponseCapture.js +33 -3
  75. package/dist/lib/protocols/rawResponseCapture.mjs +32 -3
  76. package/dist/lib/registry/types.generated.d.mts +377 -9
  77. package/dist/lib/registry/types.generated.d.ts +377 -9
  78. package/dist/lib/reporting/content-mismatch.d.mts +126 -0
  79. package/dist/lib/reporting/content-mismatch.d.ts +126 -0
  80. package/dist/lib/reporting/content-mismatch.js +90 -0
  81. package/dist/lib/reporting/content-mismatch.mjs +66 -0
  82. package/dist/lib/reporting/evidence.d.mts +1 -0
  83. package/dist/lib/reporting/evidence.d.ts +1 -0
  84. package/dist/lib/reporting/evidence.js +32 -3
  85. package/dist/lib/reporting/evidence.mjs +31 -3
  86. package/dist/lib/reporting/index.d.mts +3 -1
  87. package/dist/lib/reporting/index.d.ts +3 -1
  88. package/dist/lib/reporting/index.js +3 -0
  89. package/dist/lib/reporting/index.mjs +2 -0
  90. package/dist/lib/reporting/ledger/handler.d.mts +51 -1
  91. package/dist/lib/reporting/ledger/handler.d.ts +51 -1
  92. package/dist/lib/reporting/ledger/handler.js +306 -38
  93. package/dist/lib/reporting/ledger/handler.mjs +304 -37
  94. package/dist/lib/reporting/ledger/health.d.mts +12 -0
  95. package/dist/lib/reporting/ledger/health.d.ts +12 -0
  96. package/dist/lib/reporting/ledger/health.js +15 -2
  97. package/dist/lib/reporting/ledger/health.mjs +13 -1
  98. package/dist/lib/reporting/ledger/index.d.mts +3 -0
  99. package/dist/lib/reporting/ledger/index.d.ts +3 -0
  100. package/dist/lib/reporting/ledger/index.js +7 -1
  101. package/dist/lib/reporting/ledger/index.mjs +3 -0
  102. package/dist/lib/reporting/ledger/lifecycle.d.mts +31 -4
  103. package/dist/lib/reporting/ledger/lifecycle.d.ts +31 -4
  104. package/dist/lib/reporting/ledger/lifecycle.js +244 -37
  105. package/dist/lib/reporting/ledger/lifecycle.mjs +244 -37
  106. package/dist/lib/reporting/ledger/managed-postgres.d.mts +231 -0
  107. package/dist/lib/reporting/ledger/managed-postgres.d.ts +231 -0
  108. package/dist/lib/reporting/ledger/managed-postgres.js +1797 -0
  109. package/dist/lib/reporting/ledger/managed-postgres.mjs +1776 -0
  110. package/dist/lib/reporting/ledger/managed.d.mts +355 -0
  111. package/dist/lib/reporting/ledger/managed.d.ts +355 -0
  112. package/dist/lib/reporting/ledger/managed.js +661 -0
  113. package/dist/lib/reporting/ledger/managed.mjs +633 -0
  114. package/dist/lib/reporting/ledger/notification-activity.d.mts +222 -0
  115. package/dist/lib/reporting/ledger/notification-activity.d.ts +222 -0
  116. package/dist/lib/reporting/ledger/notification-activity.js +1139 -0
  117. package/dist/lib/reporting/ledger/notification-activity.mjs +1110 -0
  118. package/dist/lib/reporting/ledger/postgres.d.mts +161 -1
  119. package/dist/lib/reporting/ledger/postgres.d.ts +161 -1
  120. package/dist/lib/reporting/ledger/postgres.js +1153 -21
  121. package/dist/lib/reporting/ledger/postgres.mjs +1152 -21
  122. package/dist/lib/reporting/ledger/producer.d.mts +8 -0
  123. package/dist/lib/reporting/ledger/producer.d.ts +8 -0
  124. package/dist/lib/reporting/ledger/producer.js +146 -52
  125. package/dist/lib/reporting/ledger/producer.mjs +146 -51
  126. package/dist/lib/reporting/ledger/types.d.mts +358 -1
  127. package/dist/lib/reporting/ledger/types.d.ts +358 -1
  128. package/dist/lib/reporting/ledger/types.js +3 -0
  129. package/dist/lib/reporting/ledger/types.mjs +2 -0
  130. package/dist/lib/reporting/reconciliation.d.mts +362 -2
  131. package/dist/lib/reporting/reconciliation.d.ts +362 -2
  132. package/dist/lib/reporting/reconciliation.js +1051 -7
  133. package/dist/lib/reporting/reconciliation.mjs +1053 -7
  134. package/dist/lib/reporting/service/conformance.d.mts +35 -0
  135. package/dist/lib/reporting/service/conformance.d.ts +35 -0
  136. package/dist/lib/reporting/service/conformance.js +89 -0
  137. package/dist/lib/reporting/service/conformance.mjs +68 -0
  138. package/dist/lib/reporting/service/index.d.mts +182 -0
  139. package/dist/lib/reporting/service/index.d.ts +182 -0
  140. package/dist/lib/reporting/service/index.js +793 -0
  141. package/dist/lib/reporting/service/index.mjs +787 -0
  142. package/dist/lib/reporting/source/index.d.mts +2 -1
  143. package/dist/lib/reporting/source/index.d.ts +2 -1
  144. package/dist/lib/reporting/source/index.js +16 -3
  145. package/dist/lib/reporting/source/index.mjs +12 -1
  146. package/dist/lib/reporting/source/inline.d.mts +88 -1
  147. package/dist/lib/reporting/source/inline.d.ts +88 -1
  148. package/dist/lib/reporting/source/inline.js +1161 -163
  149. package/dist/lib/reporting/source/inline.mjs +1166 -163
  150. package/dist/lib/reporting/source/manifest.js +12 -7
  151. package/dist/lib/reporting/source/manifest.mjs +12 -7
  152. package/dist/lib/reporting/source/source.d.mts +26 -0
  153. package/dist/lib/reporting/source/source.d.ts +26 -0
  154. package/dist/lib/reporting/source/source.js +71 -2
  155. package/dist/lib/reporting/source/source.mjs +65 -1
  156. package/dist/lib/schemas/index.d.mts +22 -1
  157. package/dist/lib/schemas/index.d.ts +22 -1
  158. package/dist/lib/schemas/index.js +7 -0
  159. package/dist/lib/schemas/index.mjs +7 -0
  160. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  161. package/dist/lib/server/create-adcp-server.d.mts +18 -0
  162. package/dist/lib/server/create-adcp-server.d.ts +18 -0
  163. package/dist/lib/server/create-adcp-server.js +52 -2
  164. package/dist/lib/server/create-adcp-server.mjs +52 -2
  165. package/dist/lib/server/decisioning/account.d.mts +1 -1
  166. package/dist/lib/server/decisioning/account.d.ts +1 -1
  167. package/dist/lib/server/decisioning/index.d.mts +1 -0
  168. package/dist/lib/server/decisioning/index.d.ts +1 -0
  169. package/dist/lib/server/decisioning/platform.d.mts +3 -0
  170. package/dist/lib/server/decisioning/platform.d.ts +3 -0
  171. package/dist/lib/server/decisioning/runtime/from-platform.js +88 -10
  172. package/dist/lib/server/decisioning/runtime/from-platform.mjs +88 -10
  173. package/dist/lib/server/decisioning/specialisms/reporting.d.mts +38 -0
  174. package/dist/lib/server/decisioning/specialisms/reporting.d.ts +38 -0
  175. package/dist/lib/{types/v3-1-beta/index.js → server/decisioning/specialisms/reporting.js} +2 -8
  176. package/dist/lib/server/decisioning/specialisms/reporting.mjs +0 -0
  177. package/dist/lib/server/index.d.mts +4 -2
  178. package/dist/lib/server/index.d.ts +4 -2
  179. package/dist/lib/server/index.js +5 -0
  180. package/dist/lib/server/index.mjs +4 -0
  181. package/dist/lib/server/media-buy-action-resolver.d.mts +63 -0
  182. package/dist/lib/server/media-buy-action-resolver.d.ts +63 -0
  183. package/dist/lib/server/media-buy-action-resolver.js +297 -0
  184. package/dist/lib/server/media-buy-action-resolver.mjs +285 -0
  185. package/dist/lib/server/media-buy-actions.d.mts +7 -8
  186. package/dist/lib/server/media-buy-actions.d.ts +7 -8
  187. package/dist/lib/server/media-buy-actions.js +57 -14
  188. package/dist/lib/server/media-buy-actions.mjs +63 -14
  189. package/dist/lib/server/notification-subscriptions/index.d.mts +2 -2
  190. package/dist/lib/server/notification-subscriptions/index.d.ts +2 -2
  191. package/dist/lib/server/notification-subscriptions/index.js +2 -0
  192. package/dist/lib/server/notification-subscriptions/index.mjs +2 -0
  193. package/dist/lib/server/notification-subscriptions/postgres-runtime.d.mts +3 -1
  194. package/dist/lib/server/notification-subscriptions/postgres-runtime.d.ts +3 -1
  195. package/dist/lib/server/notification-subscriptions/postgres-runtime.js +1 -0
  196. package/dist/lib/server/notification-subscriptions/postgres-runtime.mjs +1 -0
  197. package/dist/lib/server/notification-subscriptions/runtime.d.mts +12 -1
  198. package/dist/lib/server/notification-subscriptions/runtime.d.ts +12 -1
  199. package/dist/lib/server/notification-subscriptions/runtime.js +102 -16
  200. package/dist/lib/server/notification-subscriptions/runtime.mjs +101 -16
  201. package/dist/lib/server/notification-subscriptions/types.d.mts +104 -1
  202. package/dist/lib/server/notification-subscriptions/types.d.ts +104 -1
  203. package/dist/lib/server/webhook-emitter.d.mts +40 -5
  204. package/dist/lib/server/webhook-emitter.d.ts +40 -5
  205. package/dist/lib/server/webhook-emitter.js +23 -12
  206. package/dist/lib/server/webhook-emitter.mjs +23 -12
  207. package/dist/lib/storage/interfaces.d.mts +14 -2
  208. package/dist/lib/storage/interfaces.d.ts +14 -2
  209. package/dist/lib/testing/compliance/comply.d.mts +9 -3
  210. package/dist/lib/testing/compliance/comply.d.ts +9 -3
  211. package/dist/lib/testing/compliance/comply.js +158 -3
  212. package/dist/lib/testing/compliance/comply.mjs +159 -4
  213. package/dist/lib/testing/compliance/storyboard-tracks.js +7 -5
  214. package/dist/lib/testing/compliance/storyboard-tracks.mjs +7 -5
  215. package/dist/lib/testing/compliance/summary.js +10 -1
  216. package/dist/lib/testing/compliance/summary.mjs +10 -1
  217. package/dist/lib/testing/index.d.mts +1 -1
  218. package/dist/lib/testing/index.d.ts +1 -1
  219. package/dist/lib/testing/index.js +2 -0
  220. package/dist/lib/testing/index.mjs +3 -1
  221. package/dist/lib/testing/storyboard/agent-routing.d.mts +12 -43
  222. package/dist/lib/testing/storyboard/agent-routing.d.ts +12 -43
  223. package/dist/lib/testing/storyboard/agent-routing.js +75 -13
  224. package/dist/lib/testing/storyboard/agent-routing.mjs +70 -12
  225. package/dist/lib/testing/storyboard/index.d.mts +1 -0
  226. package/dist/lib/testing/storyboard/index.d.ts +1 -0
  227. package/dist/lib/testing/storyboard/index.js +3 -0
  228. package/dist/lib/testing/storyboard/index.mjs +2 -0
  229. package/dist/lib/testing/storyboard/junit.d.mts +1 -1
  230. package/dist/lib/testing/storyboard/junit.d.ts +1 -1
  231. package/dist/lib/testing/storyboard/junit.js +21 -2
  232. package/dist/lib/testing/storyboard/junit.mjs +21 -2
  233. package/dist/lib/testing/storyboard/probes.d.mts +130 -0
  234. package/dist/lib/testing/storyboard/probes.d.ts +130 -0
  235. package/dist/lib/testing/storyboard/probes.js +682 -10
  236. package/dist/lib/testing/storyboard/probes.mjs +683 -3
  237. package/dist/lib/testing/storyboard/request-signing/builder.d.mts +16 -5
  238. package/dist/lib/testing/storyboard/request-signing/builder.d.ts +16 -5
  239. package/dist/lib/testing/storyboard/request-signing/grader.d.mts +44 -1
  240. package/dist/lib/testing/storyboard/request-signing/grader.d.ts +44 -1
  241. package/dist/lib/testing/storyboard/request-signing/grader.js +46 -22
  242. package/dist/lib/testing/storyboard/request-signing/grader.mjs +43 -21
  243. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.mts +86 -11
  244. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.ts +86 -11
  245. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.js +119 -25
  246. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +117 -26
  247. package/dist/lib/testing/storyboard/request-signing/synthesize.js +22 -2
  248. package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +22 -2
  249. package/dist/lib/testing/storyboard/runner.d.mts +103 -0
  250. package/dist/lib/testing/storyboard/runner.d.ts +103 -0
  251. package/dist/lib/testing/storyboard/runner.js +1090 -268
  252. package/dist/lib/testing/storyboard/runner.mjs +1096 -261
  253. package/dist/lib/testing/storyboard/seeding.d.mts +4 -1
  254. package/dist/lib/testing/storyboard/seeding.d.ts +4 -1
  255. package/dist/lib/testing/storyboard/seeding.js +55 -24
  256. package/dist/lib/testing/storyboard/seeding.mjs +55 -24
  257. package/dist/lib/testing/storyboard/test-kit.d.mts +37 -3
  258. package/dist/lib/testing/storyboard/test-kit.d.ts +37 -3
  259. package/dist/lib/testing/storyboard/test-kit.js +8 -2
  260. package/dist/lib/testing/storyboard/test-kit.mjs +7 -2
  261. package/dist/lib/testing/storyboard/types.d.mts +132 -10
  262. package/dist/lib/testing/storyboard/types.d.ts +132 -10
  263. package/dist/lib/testing/storyboard/types.js +17 -0
  264. package/dist/lib/testing/storyboard/types.mjs +16 -0
  265. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  266. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  267. package/dist/lib/testing/storyboard/validations.js +16 -0
  268. package/dist/lib/testing/storyboard/validations.mjs +16 -0
  269. package/dist/lib/testing/test-controller.d.mts +1 -1
  270. package/dist/lib/testing/test-controller.d.ts +1 -1
  271. package/dist/lib/types/buy-products.d.ts +71 -6
  272. package/dist/lib/types/control-media-buy.d.ts +25 -11
  273. package/dist/lib/types/core.generated.d.mts +69 -10
  274. package/dist/lib/types/core.generated.d.ts +69 -10
  275. package/dist/lib/types/create-media-buy.d.ts +71 -6
  276. package/dist/lib/types/forward-compat-error-codes.d.mts +7 -6
  277. package/dist/lib/types/forward-compat-error-codes.d.ts +7 -6
  278. package/dist/lib/types/index.d.mts +2 -2
  279. package/dist/lib/types/index.d.ts +2 -2
  280. package/dist/lib/types/schemas.generated.d.ts +8040 -8116
  281. package/dist/lib/types/schemas.generated.js +1074 -309
  282. package/dist/lib/types/schemas.generated.mjs +1068 -309
  283. package/dist/lib/types/tools.generated.d.mts +65 -6
  284. package/dist/lib/types/tools.generated.d.ts +65 -6
  285. package/dist/lib/types/update-media-buy.d.ts +71 -6
  286. package/dist/lib/utils/redact-secrets.d.mts +1 -1
  287. package/dist/lib/utils/redact-secrets.d.ts +1 -1
  288. package/dist/lib/utils/redact-secrets.js +1 -1
  289. package/dist/lib/utils/redact-secrets.mjs +1 -1
  290. package/dist/lib/v2/projection/cache-versions.d.mts +0 -1
  291. package/dist/lib/v2/projection/cache-versions.d.ts +0 -1
  292. package/dist/lib/v2/projection/cache-versions.js +1 -15
  293. package/dist/lib/v2/projection/cache-versions.mjs +1 -14
  294. package/dist/lib/v2/projection/canonical-properties.js +2 -2
  295. package/dist/lib/v2/projection/canonical-properties.mjs +3 -3
  296. package/dist/lib/v2/projection/registry.d.mts +1 -1
  297. package/dist/lib/v2/projection/registry.d.ts +1 -1
  298. package/dist/lib/v2/projection/registry.js +2 -2
  299. package/dist/lib/v2/projection/registry.mjs +3 -3
  300. package/dist/lib/validation/schema-loader.d.mts +9 -1
  301. package/dist/lib/validation/schema-loader.d.ts +9 -1
  302. package/dist/lib/validation/schema-loader.js +65 -8
  303. package/dist/lib/validation/schema-loader.mjs +64 -8
  304. package/dist/lib/version.d.mts +5 -5
  305. package/dist/lib/version.d.ts +5 -5
  306. package/dist/lib/version.js +3 -8
  307. package/dist/lib/version.mjs +3 -8
  308. package/dist/lib/wholesale-feed-sync/index.d.mts +1 -0
  309. package/dist/lib/wholesale-feed-sync/index.d.ts +1 -0
  310. package/dist/lib/wholesale-feed-sync/protocol-types.d.mts +42 -0
  311. package/dist/lib/wholesale-feed-sync/protocol-types.d.ts +42 -0
  312. package/dist/lib/wholesale-feed-sync/protocol-types.js +16 -0
  313. package/dist/lib/wholesale-feed-sync/protocol-types.mjs +0 -0
  314. package/dist/lib/wholesale-feed-sync/sync.d.mts +6 -6
  315. package/dist/lib/wholesale-feed-sync/sync.d.ts +6 -6
  316. package/dist/lib/wholesale-feed-sync/sync.js +3 -1
  317. package/dist/lib/wholesale-feed-sync/sync.mjs +3 -1
  318. package/dist/lib/wholesale-feed-sync/types.d.mts +15 -14
  319. package/dist/lib/wholesale-feed-sync/types.d.ts +15 -14
  320. package/dist/lib/wholesale-feed-sync/webhook-notification.d.mts +7 -5
  321. package/dist/lib/wholesale-feed-sync/webhook-notification.d.ts +7 -5
  322. package/dist/lib/wholesale-feed-sync/webhook-notification.js +45 -0
  323. package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +44 -0
  324. package/docs/CLI.md +1 -1
  325. package/docs/TYPE-SUMMARY.md +118 -4
  326. package/docs/guides/ASYNC-API-REFERENCE.md +62 -1
  327. package/docs/guides/ASYNC-DEVELOPER-GUIDE.md +25 -0
  328. package/docs/guides/ASYNC-DOCUMENTATION-INDEX.md +3 -0
  329. package/docs/guides/BUYER-QUICKSTART-3.2.md +5 -0
  330. package/docs/guides/DURABLE-BUYER-WRITES.md +337 -0
  331. package/docs/guides/EXISTING-PLATFORM.md +80 -1
  332. package/docs/guides/MEDIA-BUY-3.2-COMPATIBILITY.md +57 -0
  333. package/docs/guides/MEDIA-BUY-ACTION-ASSESSMENT.md +300 -0
  334. package/docs/guides/REPORTING-LEDGER.md +654 -3
  335. package/docs/guides/REPORTING-RECONCILIATION.md +50 -0
  336. package/docs/guides/REPORTING-SOURCE-EXECUTOR.md +63 -1
  337. package/docs/guides/SELLER-QUICKSTART-3.2.md +2 -0
  338. package/docs/guides/VALIDATE-YOUR-AGENT.md +35 -0
  339. package/docs/guides/idempotency-crash-recovery.md +4 -0
  340. package/docs/llms.txt +4 -2
  341. package/docs/migration-14.x-rc-worksheet.md +60 -5
  342. package/docs/migration-7.9-to-7.10.md +7 -5
  343. package/examples/README.md +17 -0
  344. package/examples/durable-buyer-writes/caller.ts +745 -0
  345. package/examples/durable-buyer-writes/worker.ts +240 -0
  346. package/examples/reliable-reporting-service/README.md +100 -0
  347. package/examples/seller-3.2-starter.ts +7 -8
  348. package/examples/targeting-input-existing-platform.ts +153 -0
  349. package/package.json +41 -29
  350. package/skills/build-seller-agent/SKILL.md +2 -0
  351. package/skills/build-seller-agent/specialisms/signed-requests.md +11 -1
  352. package/skills/cross-cutting.md +28 -11
  353. package/compliance/cache/3.1.0-beta.7/domains/brand/index.yaml +0 -160
  354. package/compliance/cache/3.1.0-beta.7/domains/brand/scenarios/distributed_brand_resolution.yaml +0 -415
  355. package/compliance/cache/3.1.0-beta.7/domains/brand/scenarios/single_side_trust_extension.yaml +0 -454
  356. package/compliance/cache/3.1.0-beta.7/domains/creative/index.yaml +0 -339
  357. package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/billing_out_of_band.yaml +0 -153
  358. package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/creative_lifecycle_webhooks.yaml +0 -389
  359. package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/native_in_feed.yaml +0 -543
  360. package/compliance/cache/3.1.0-beta.7/domains/governance/index.yaml +0 -682
  361. package/compliance/cache/3.1.0-beta.7/domains/media-buy/index.yaml +0 -781
  362. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/audience_buy_flow.yaml +0 -380
  363. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/available_actions.yaml +0 -565
  364. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/billing_finality_delivery.yaml +0 -354
  365. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/canonical_formats.yaml +0 -711
  366. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/clicks_buy_flow.yaml +0 -264
  367. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/completed_views_buy_flow.yaml +0 -344
  368. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/create_media_buy_async.yaml +0 -234
  369. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml +0 -419
  370. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/creative_reception.yaml +0 -247
  371. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/delivery_reporting.yaml +0 -357
  372. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/dependency_impairment.yaml +0 -633
  373. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/dependency_impairment_cardinality.yaml +0 -800
  374. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/event_dedup_flow.yaml +0 -399
  375. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/frequency_cap_enforcement.yaml +0 -309
  376. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_approved.yaml +0 -214
  377. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_conditions.yaml +0 -199
  378. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_denied.yaml +0 -204
  379. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_denied_recovery.yaml +0 -252
  380. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/invalid_transitions.yaml +0 -289
  381. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/inventory_list_no_match.yaml +0 -148
  382. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/inventory_list_targeting.yaml +0 -276
  383. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/measurement_accountability.yaml +0 -244
  384. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/measurement_terms_rejected.yaml +0 -203
  385. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/pending_creatives_to_start.yaml +0 -274
  386. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/per_creative_conversion_attribution.yaml +0 -500
  387. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/performance_buy_flow.yaml +0 -428
  388. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/performance_buy_flow_roas.yaml +0 -470
  389. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/product_signal_targeting.yaml +0 -373
  390. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_finalize.yaml +0 -399
  391. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_finalize_asap_timing.yaml +0 -264
  392. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_not_found_errors.yaml +0 -257
  393. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_audit_observation.yaml +0 -333
  394. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_enforcement.yaml +0 -517
  395. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_truth_of_claim.yaml +0 -294
  396. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/reach_buy_flow.yaml +0 -823
  397. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/refine_finalize_exclusivity.yaml +0 -360
  398. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/refine_products.yaml +0 -148
  399. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_accountability.yaml +0 -293
  400. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +0 -307
  401. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_optimization_flow.yaml +0 -576
  402. package/compliance/cache/3.1.0-beta.7/domains/media-buy/state-machine.yaml +0 -442
  403. package/compliance/cache/3.1.0-beta.7/domains/signals/index.yaml +0 -266
  404. package/compliance/cache/3.1.0-beta.7/domains/sponsored-intelligence/index.yaml +0 -256
  405. package/compliance/cache/3.1.0-beta.7/index.json +0 -356
  406. package/compliance/cache/3.1.0-beta.7/protocols/brand/index.yaml +0 -160
  407. package/compliance/cache/3.1.0-beta.7/protocols/brand/scenarios/distributed_brand_resolution.yaml +0 -415
  408. package/compliance/cache/3.1.0-beta.7/protocols/brand/scenarios/single_side_trust_extension.yaml +0 -454
  409. package/compliance/cache/3.1.0-beta.7/protocols/creative/index.yaml +0 -339
  410. package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/billing_out_of_band.yaml +0 -153
  411. package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/creative_lifecycle_webhooks.yaml +0 -389
  412. package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/native_in_feed.yaml +0 -543
  413. package/compliance/cache/3.1.0-beta.7/protocols/governance/index.yaml +0 -682
  414. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/index.yaml +0 -781
  415. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/audience_buy_flow.yaml +0 -380
  416. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/available_actions.yaml +0 -565
  417. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/billing_finality_delivery.yaml +0 -354
  418. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/canonical_formats.yaml +0 -711
  419. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/clicks_buy_flow.yaml +0 -264
  420. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/completed_views_buy_flow.yaml +0 -344
  421. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/create_media_buy_async.yaml +0 -234
  422. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml +0 -419
  423. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/creative_reception.yaml +0 -247
  424. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/delivery_reporting.yaml +0 -357
  425. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/dependency_impairment.yaml +0 -633
  426. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/dependency_impairment_cardinality.yaml +0 -800
  427. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/event_dedup_flow.yaml +0 -399
  428. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/frequency_cap_enforcement.yaml +0 -309
  429. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_approved.yaml +0 -214
  430. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_conditions.yaml +0 -199
  431. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_denied.yaml +0 -204
  432. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_denied_recovery.yaml +0 -252
  433. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/invalid_transitions.yaml +0 -289
  434. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/inventory_list_no_match.yaml +0 -148
  435. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/inventory_list_targeting.yaml +0 -276
  436. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/measurement_accountability.yaml +0 -244
  437. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/measurement_terms_rejected.yaml +0 -203
  438. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/pending_creatives_to_start.yaml +0 -274
  439. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/per_creative_conversion_attribution.yaml +0 -500
  440. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/performance_buy_flow.yaml +0 -428
  441. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/performance_buy_flow_roas.yaml +0 -470
  442. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/product_signal_targeting.yaml +0 -373
  443. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_finalize.yaml +0 -399
  444. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_finalize_asap_timing.yaml +0 -264
  445. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_not_found_errors.yaml +0 -257
  446. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_audit_observation.yaml +0 -333
  447. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_enforcement.yaml +0 -517
  448. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_truth_of_claim.yaml +0 -294
  449. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/reach_buy_flow.yaml +0 -823
  450. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/refine_finalize_exclusivity.yaml +0 -360
  451. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/refine_products.yaml +0 -148
  452. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_accountability.yaml +0 -293
  453. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +0 -307
  454. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml +0 -576
  455. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/state-machine.yaml +0 -442
  456. package/compliance/cache/3.1.0-beta.7/protocols/signals/index.yaml +0 -266
  457. package/compliance/cache/3.1.0-beta.7/protocols/sponsored-intelligence/index.yaml +0 -256
  458. package/compliance/cache/3.1.0-beta.7/specialisms/audience-sync/index.yaml +0 -313
  459. package/compliance/cache/3.1.0-beta.7/specialisms/brand-rights/index.yaml +0 -350
  460. package/compliance/cache/3.1.0-beta.7/specialisms/brand-rights/scenarios/governance_denied.yaml +0 -226
  461. package/compliance/cache/3.1.0-beta.7/specialisms/collection-lists/index.yaml +0 -359
  462. package/compliance/cache/3.1.0-beta.7/specialisms/content-standards/index.yaml +0 -572
  463. package/compliance/cache/3.1.0-beta.7/specialisms/creative-ad-server/index.yaml +0 -409
  464. package/compliance/cache/3.1.0-beta.7/specialisms/creative-generative/generative-seller.yaml +0 -807
  465. package/compliance/cache/3.1.0-beta.7/specialisms/creative-generative/index.yaml +0 -758
  466. package/compliance/cache/3.1.0-beta.7/specialisms/creative-template/index.yaml +0 -510
  467. package/compliance/cache/3.1.0-beta.7/specialisms/governance-aware-seller/index.yaml +0 -143
  468. package/compliance/cache/3.1.0-beta.7/specialisms/governance-aware-seller/scenarios/governance_multi_agent_rejected.yaml +0 -117
  469. package/compliance/cache/3.1.0-beta.7/specialisms/governance-delivery-monitor/index.yaml +0 -441
  470. package/compliance/cache/3.1.0-beta.7/specialisms/governance-spend-authority/denied.yaml +0 -221
  471. package/compliance/cache/3.1.0-beta.7/specialisms/governance-spend-authority/index.yaml +0 -330
  472. package/compliance/cache/3.1.0-beta.7/specialisms/property-lists/index.yaml +0 -482
  473. package/compliance/cache/3.1.0-beta.7/specialisms/sales-broadcast-tv/index.yaml +0 -738
  474. package/compliance/cache/3.1.0-beta.7/specialisms/sales-catalog-driven/index.yaml +0 -840
  475. package/compliance/cache/3.1.0-beta.7/specialisms/sales-guaranteed/index.yaml +0 -601
  476. package/compliance/cache/3.1.0-beta.7/specialisms/sales-non-guaranteed/index.yaml +0 -546
  477. package/compliance/cache/3.1.0-beta.7/specialisms/sales-proposal-mode/index.yaml +0 -586
  478. package/compliance/cache/3.1.0-beta.7/specialisms/sales-social/index.yaml +0 -919
  479. package/compliance/cache/3.1.0-beta.7/specialisms/signal-marketplace/index.yaml +0 -424
  480. package/compliance/cache/3.1.0-beta.7/specialisms/signal-marketplace/scenarios/governance_denied.yaml +0 -211
  481. package/compliance/cache/3.1.0-beta.7/specialisms/signal-owned/index.yaml +0 -317
  482. package/compliance/cache/3.1.0-beta.7/specialisms/sponsored-intelligence/index.yaml +0 -59
  483. package/compliance/cache/3.1.0-beta.7/test-kits/acme-outdoor-live.yaml +0 -78
  484. package/compliance/cache/3.1.0-beta.7/test-kits/acme-outdoor.yaml +0 -223
  485. package/compliance/cache/3.1.0-beta.7/test-kits/billing-gate-runner.yaml +0 -115
  486. package/compliance/cache/3.1.0-beta.7/test-kits/bistro-oranje.yaml +0 -126
  487. package/compliance/cache/3.1.0-beta.7/test-kits/distributed-brand-runner.yaml +0 -281
  488. package/compliance/cache/3.1.0-beta.7/test-kits/nova-motors.yaml +0 -262
  489. package/compliance/cache/3.1.0-beta.7/test-kits/osei-natural.yaml +0 -126
  490. package/compliance/cache/3.1.0-beta.7/test-kits/parallel-dispatch-runner.yaml +0 -196
  491. package/compliance/cache/3.1.0-beta.7/test-kits/rate-limit-trip-runner.yaml +0 -172
  492. package/compliance/cache/3.1.0-beta.7/test-kits/signed-requests-runner.yaml +0 -155
  493. package/compliance/cache/3.1.0-beta.7/test-kits/single-side-trust-runner.yaml +0 -294
  494. package/compliance/cache/3.1.0-beta.7/test-kits/substitution-observer-runner.yaml +0 -688
  495. package/compliance/cache/3.1.0-beta.7/test-kits/summit-foods.yaml +0 -125
  496. package/compliance/cache/3.1.0-beta.7/test-kits/webhook-receiver-runner.yaml +0 -265
  497. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/001-minimal-plan.json +0 -43
  498. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/002-full-plan.json +0 -217
  499. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/003-bookkeeping-stripped.json +0 -60
  500. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/004a-human-review-omitted.json +0 -43
  501. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/004b-human-review-explicit-null.json +0 -49
  502. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/005a-policy-categories-order-1.json +0 -53
  503. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/005b-policy-categories-order-2.json +0 -57
  504. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/006a-ext-trace-v1.json +0 -49
  505. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/006b-ext-trace-v2.json +0 -53
  506. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/007-unicode-objectives.json +0 -43
  507. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/008-numeric-canonicalization.json +0 -65
  508. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/README.md +0 -220
  509. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/canonicalization.json +0 -241
  510. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/keys.json +0 -60
  511. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/001-no-signature-header.json +0 -24
  512. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/002-wrong-tag.json +0 -26
  513. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/003-expired-signature.json +0 -26
  514. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/004-window-too-long.json +0 -26
  515. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/005-alg-not-allowed.json +0 -26
  516. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/006-missing-covered-component.json +0 -26
  517. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/007-missing-content-digest.json +0 -26
  518. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/008-unknown-keyid.json +0 -26
  519. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/009-key-ops-missing-verify.json +0 -27
  520. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/010-content-digest-mismatch.json +0 -33
  521. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/011-malformed-header.json +0 -27
  522. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/012-missing-expires-param.json +0 -26
  523. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/013-expires-le-created.json +0 -27
  524. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/014-missing-nonce-param.json +0 -27
  525. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/015-signature-invalid.json +0 -28
  526. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/016-replayed-nonce.json +0 -35
  527. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/017-key-revoked.json +0 -38
  528. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/018-digest-covered-when-forbidden.json +0 -28
  529. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/019-signature-without-signature-input.json +0 -26
  530. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/020-rate-abuse.json +0 -34
  531. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/021-duplicate-signature-input-label.json +0 -31
  532. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/022-multi-valued-content-type.json +0 -31
  533. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/023-multi-valued-content-digest.json +0 -32
  534. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/024-unquoted-string-param.json +0 -31
  535. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/025-jwk-alg-crv-mismatch.json +0 -43
  536. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/026-non-ascii-host.json +0 -31
  537. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/027-webhook-registration-authentication-unsigned.json +0 -25
  538. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/028-unsigned-protocol-method-required.json +0 -26
  539. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/001-basic-post.json +0 -30
  540. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/002-post-with-content-digest.json +0 -31
  541. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/003-es256-post.json +0 -30
  542. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/004-multiple-signature-labels.json +0 -26
  543. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/005-default-port-stripped.json +0 -30
  544. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/006-dot-segment-path.json +0 -30
  545. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/007-query-byte-preserved.json +0 -30
  546. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/008-percent-encoded-path.json +0 -30
  547. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/009-percent-encoded-unreserved-decoded.json +0 -30
  548. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/010-percent-encoded-slash-preserved.json +0 -30
  549. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/011-ipv6-authority.json +0 -30
  550. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/012-ipv6-authority-default-port-stripped.json +0 -30
  551. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/README.md +0 -211
  552. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/keys.json +0 -61
  553. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/001-wrong-tag.json +0 -26
  554. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/002-expired-signature.json +0 -26
  555. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/003-window-too-long.json +0 -26
  556. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/004-alg-not-allowed.json +0 -26
  557. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/005-missing-authority-component.json +0 -26
  558. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/006-missing-content-digest.json +0 -25
  559. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/007-unknown-keyid.json +0 -26
  560. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/008-wrong-adcp-use.json +0 -26
  561. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/009-content-digest-mismatch.json +0 -26
  562. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/010-malformed-signature-input.json +0 -26
  563. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/011-signature-without-input.json +0 -25
  564. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/012-missing-expires-param.json +0 -26
  565. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/013-expires-le-created.json +0 -26
  566. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/014-missing-nonce-param.json +0 -26
  567. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/015-signature-invalid.json +0 -26
  568. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/016-replayed-nonce.json +0 -37
  569. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/017-key-revoked.json +0 -32
  570. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/018-rate-abuse.json +0 -33
  571. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/019-revocation-stale.json +0 -32
  572. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/020-key-ops-missing-verify.json +0 -41
  573. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/021-base64-alphabet-mixing.json +0 -26
  574. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/001-basic-post.json +0 -24
  575. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/002-es256-post.json +0 -24
  576. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/003-multiple-signature-labels.json +0 -24
  577. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/004-default-port-stripped.json +0 -24
  578. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/005-percent-encoded-path.json +0 -24
  579. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/006-query-byte-preserved.json +0 -24
  580. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/007-body-without-idempotency-key.json +0 -25
  581. package/compliance/cache/3.1.0-beta.7/universal/billing-gate-dispatch.yaml +0 -450
  582. package/compliance/cache/3.1.0-beta.7/universal/canonical-format-validate-input.yaml +0 -640
  583. package/compliance/cache/3.1.0-beta.7/universal/capability-discovery.yaml +0 -125
  584. package/compliance/cache/3.1.0-beta.7/universal/collection-lists-pagination-integrity.yaml +0 -306
  585. package/compliance/cache/3.1.0-beta.7/universal/comply-controller-mode-gate.yaml +0 -141
  586. package/compliance/cache/3.1.0-beta.7/universal/content-standards-pagination-integrity.yaml +0 -326
  587. package/compliance/cache/3.1.0-beta.7/universal/deterministic-testing.yaml +0 -1430
  588. package/compliance/cache/3.1.0-beta.7/universal/error-compliance-signals.yaml +0 -377
  589. package/compliance/cache/3.1.0-beta.7/universal/error-compliance.yaml +0 -528
  590. package/compliance/cache/3.1.0-beta.7/universal/fictional-entities.yaml +0 -307
  591. package/compliance/cache/3.1.0-beta.7/universal/get-media-buys-pagination-integrity.yaml +0 -160
  592. package/compliance/cache/3.1.0-beta.7/universal/get-signals-pagination-integrity.yaml +0 -211
  593. package/compliance/cache/3.1.0-beta.7/universal/idempotency.yaml +0 -861
  594. package/compliance/cache/3.1.0-beta.7/universal/notification-config-event-scope.yaml +0 -119
  595. package/compliance/cache/3.1.0-beta.7/universal/notification-config-lifecycle.yaml +0 -337
  596. package/compliance/cache/3.1.0-beta.7/universal/notification-config-rejections.yaml +0 -107
  597. package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity-creative-formats.yaml +0 -265
  598. package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity-list-accounts.yaml +0 -245
  599. package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity.yaml +0 -263
  600. package/compliance/cache/3.1.0-beta.7/universal/property-lists-pagination-integrity.yaml +0 -307
  601. package/compliance/cache/3.1.0-beta.7/universal/read-tool-idempotency.yaml +0 -405
  602. package/compliance/cache/3.1.0-beta.7/universal/runner-output-contract.yaml +0 -1266
  603. package/compliance/cache/3.1.0-beta.7/universal/schema-validation-signals.yaml +0 -181
  604. package/compliance/cache/3.1.0-beta.7/universal/schema-validation.yaml +0 -548
  605. package/compliance/cache/3.1.0-beta.7/universal/security.yaml +0 -539
  606. package/compliance/cache/3.1.0-beta.7/universal/signed-requests.yaml +0 -217
  607. package/compliance/cache/3.1.0-beta.7/universal/stale-response-advisory.yaml +0 -295
  608. package/compliance/cache/3.1.0-beta.7/universal/storyboard-schema.yaml +0 -2136
  609. package/compliance/cache/3.1.0-beta.7/universal/v3-envelope-integrity.yaml +0 -117
  610. package/compliance/cache/3.1.0-beta.7/universal/version-negotiation.yaml +0 -130
  611. package/compliance/cache/3.1.0-beta.7/universal/webhook-emission.yaml +0 -411
  612. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-bulk-webhooks.yaml +0 -82
  613. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-product-webhooks.yaml +0 -83
  614. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-products.yaml +0 -151
  615. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-signal-webhooks.yaml +0 -83
  616. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-signals.yaml +0 -149
  617. package/dist/lib/types/v3-1-beta/index.d.mts +0 -1
  618. package/dist/lib/types/v3-1-beta/index.d.ts +0 -1
  619. package/dist/lib/types/v3-1-beta/index.mjs +0 -1
  620. package/dist/lib/types/v3-1-beta/tools.generated.d.mts +0 -26788
  621. package/dist/lib/types/v3-1-beta/tools.generated.d.ts +0 -26788
  622. /package/dist/lib/{types/v3-1-beta/tools.generated.mjs → media-buy/action-types.mjs} +0 -0
@@ -2,6 +2,110 @@
2
2
 
3
3
  `@adcp/sdk/reporting/ledger` turns a conforming reporting source into durable seller-side Reliable Reporting Core. It is separate from the buyer-side `reconcileReporting` API.
4
4
 
5
+ ## Recommended: install the lifecycle service
6
+
7
+ `createReliableReportingService` is the adapter-first production path. A
8
+ provider adapter supplies one bounded slice fetch and two immutable offering
9
+ descriptions; the service reuses the PostgreSQL ledger, source executor,
10
+ producer, handlers, and decisioning-platform account resolver.
11
+
12
+ ```ts
13
+ import { Pool } from 'pg';
14
+ import { PostgresReportingLedgerStore } from '@adcp/sdk/reporting/ledger';
15
+ import { createReliableReportingService } from '@adcp/sdk/reporting/service';
16
+ import { createAdcpServerFromPlatform } from '@adcp/sdk/server';
17
+
18
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
19
+ const store = new PostgresReportingLedgerStore(pool, {
20
+ acknowledgeIsolatedDatabase: true,
21
+ });
22
+
23
+ const reporting = createReliableReportingService({
24
+ store,
25
+ adapters: {
26
+ // These descriptions are immutable declarations owned by your adapter.
27
+ gam: { sourceOffering: gamSourceOffering, deliveryOffering: gamDeliveryOffering,
28
+ fetchSlice: (slice, ctx) => gam.fetchDeliverySlice(slice, ctx) },
29
+ },
30
+ contact: { name: 'Reporting operations', email: 'reporting@example.com' },
31
+ automatedRecoveryWindowSeconds: 86_400,
32
+ statusRetentionDays: 90,
33
+
34
+ // `account` is the framework-resolved account, not request.account.
35
+ resolveSource: account => ({
36
+ adapterId: 'gam',
37
+ sourceScope: { network_id: account.ctx_metadata.gam.networkId },
38
+ sourceTimezone: account.ctx_metadata.gam.reportingTimezone,
39
+ }),
40
+ // Resolve from trusted commercial/account state. The result is frozen into
41
+ // the generation and every obligation; no request-body fallback exists.
42
+ resolveCurrency: account => account.ctx_metadata.gam.currency,
43
+ // The authorization boundary for shared upstream networks: return only the
44
+ // constituents this account may report on. Never echo the declaration.
45
+ resolveCoverage: async account => ({
46
+ constituents: await bookings.authorizedReportingConstituents(account.id),
47
+ }),
48
+ resolveConsumerId: ctx => {
49
+ if (!ctx.agent) throw new Error('Authenticated buyer-agent registry required');
50
+ return ctx.agent.agent_url;
51
+ },
52
+ });
53
+
54
+ await pool.query(reporting.setup.migrations[0]);
55
+ const installedPlatform = reporting.install(platform);
56
+ const server = createAdcpServerFromPlatform(installedPlatform, serverOptions);
57
+
58
+ reporting.start({ intervalMilliseconds: 60_000, deploymentWide: true });
59
+ process.once('SIGTERM', () => void reporting.stop());
60
+ ```
61
+
62
+ The service advertises Reliable Reporting Core only. An inline adapter cannot
63
+ turn on Managed Delivery, Reconciled Billing, receipts, webhook activity, or
64
+ reporting notifications. `sync_reporting_status` is advertised only when
65
+ `resolveConsumerId` is installed and the supplied ledger implements its
66
+ atomic consumer-status methods. Follow-up work adds those higher tiers; do not
67
+ place them in a manual capability override.
68
+
69
+ Install a buyer declaration after the account and its media-buy scope have
70
+ been authorized and resolved. `installConfiguration` intentionally accepts no
71
+ account, `sourceScope`, contract, timezone, currency, `constituents`, or
72
+ `mediaBuyIds` fields from the declaration. Pass the framework-resolved
73
+ `ctx.account`; trusted callbacks derive the remaining lineage. `resolveCoverage`
74
+ is the media-buy/package authorization boundary and must derive the denominator
75
+ from the resolved account: `sourceScope` may legitimately name a shared upstream
76
+ network, in which case the constituent list is the only thing keeping one
77
+ buyer's orders out of another buyer's report. `mediaBuyIds` is always derived
78
+ from the returned constituents, so a buyer-named order ID can never reach
79
+ `fetchSlice`. `expectedCurrency`, `expectedSourceTimezone`, and
80
+ `expectedMediaBuyIds` are optional assertions and fail closed on conflict.
81
+ The service rejects credential-shaped keys and `ctx_metadata` anywhere in the
82
+ returned `sourceScope`, then applies the source contract and existing ledger
83
+ immutability checks. Return the resulting secret-free configuration state from
84
+ your `sync_accounts` implementation.
85
+
86
+ For tenant-partitioned jobs, call `runCycle({ accountId })`, or configure
87
+ `start({ accountIds: [...] })`. Every planner and worker call receives that
88
+ same account boundary. A deployment-owned worker must explicitly pass
89
+ `deploymentWide: true`; use that form only when one trusted service instance is
90
+ authorized for every account in the store. Widening is reachable only through
91
+ that opt-in: a cycle with a missing, empty, or overlong `accountId` is refused
92
+ rather than silently promoted to a deployment-wide scan. Under `accountIds`,
93
+ one account's failed cycle is reported to `onError` and the remaining accounts
94
+ still run, so a persistently failing tenant cannot starve the tenants behind
95
+ it. Planning is resumable and bounded;
96
+ set `maxObligationsPerAccount` and `maxWorkerIterationsPerAccount` for tighter
97
+ operational limits. `stop()` aborts current source work, waits for settlement,
98
+ and wakes a sleeping scheduler immediately. Planning is a bounded ledger
99
+ operation rather than abortable source I/O, so shutdown waits for an in-flight
100
+ planning pass to settle and does not begin its worker afterward.
101
+
102
+ Run `runReliableReportingServiceConformanceV1` against an isolated test ledger
103
+ before deployment. It covers source replay, two-account isolation,
104
+ configuration replay/frozen currency, lifecycle start/stop, and Core
105
+ capability truthfulness.
106
+
107
+ ## Advanced: assemble the primitives directly
108
+
5
109
  ```ts
6
110
  import { Pool } from 'pg';
7
111
  import {
@@ -39,15 +143,562 @@ Pass `getReportingStatus` and `getMediaBuyDelivery` directly to the matching `cr
39
143
 
40
144
  The planner uses fixed millisecond periods and an explicitly frozen IANA source timezone. Calendar or billing-cycle schedules should be expanded by the seller into immutable period boundaries before installation; the SDK intentionally has no Temporal dependency. At period end, the obligation freezes the constituent denominator and coverage. A zero-row source object commits like any other revision. Absence remains an empty revision association. A deployment with per-tenant workers should pass the resolved `account_id` to both `planObligations()` and `runWorker()`; omitting it intentionally runs a deployment-wide worker.
41
145
 
42
- Official configurations also pin a `finalityPolicy` (`policyId` plus `source_final` or `contractual_cutoff`). For `source_final`, set `sourceSignal` to the exact opaque signal identifier the adapter places in the manifest finality evidence's `evidenceRef`; the worker requires an exact match before irreversible official publication. `expected_at` and the wire delivery SLA use the same official deadline.
146
+ ### Migrating an existing manual lifecycle
147
+
148
+ Keep the same `PostgresReportingLedgerStore` and run the same
149
+ `REPORTING_LEDGER_MIGRATION`; there is no second store and no data migration.
150
+ Move each inline fetch plus its source/delivery offering into an entry in
151
+ `adapters`, move account routing and currency lookup into the two trusted
152
+ resolvers, and replace manual producer/handler/capability assembly with
153
+ `reporting.install(platform)`. Replace cron calls to `planObligations` and
154
+ `runWorker` with `runCycle` or `start`. Remove manual reporting capability
155
+ overrides so discovery has one owner. Existing configuration IDs and semantic
156
+ fingerprints remain compatible because the service delegates installation to
157
+ the existing producer: replaying a generation that predates the reserved
158
+ adapter route key reuses its stored `sourceScope` verbatim, so the fingerprint
159
+ still matches and the replay does not trip generation immutability. Only new
160
+ generations carry the reserved key. With one installed adapter, pre-service
161
+ obligations without the reserved adapter route continue through that sole
162
+ adapter. A
163
+ multi-adapter migration must create a new immutable configuration generation
164
+ with an explicit route. An adapter supplies exactly one of `fetchSlice` or
165
+ `executor`: `fetchSlice` is the inline boundary, and `executor` accepts any
166
+ `ReportingSourceWithReaderV1` — including a paginated, asynchronous, or
167
+ externally staged one — so a custom executor that needs those capabilities
168
+ runs under the service today rather than waiting for a future extension. The
169
+ inline-only knob `inlineReplayRetention` applies to `fetchSlice` adapters and
170
+ is ignored by an adapter that brings its own executor.
171
+
172
+ `reporting.install(platform)` mutates that platform object in place and
173
+ requires it to be extensible; this preserves class instances and private-field
174
+ methods that a shallow wrapper would break. It also requires the platform's
175
+ native `accounts.upsert` seam. The service cannot truthfully advertise `configuration_task:
176
+ sync_accounts` without it. That account method remains responsible for mapping
177
+ an authorized wire reporting configuration to the service's resolved input and
178
+ calling `installConfiguration`; the service does not claim a generic mapping
179
+ that the current protocol does not define.
180
+
181
+ Official configurations also pin a `finalityPolicy` (`policyId` plus `source_final` or `contractual_cutoff`). For `source_final`, set `sourceSignal` to the exact opaque signal identifier the adapter places in the manifest finality evidence's `evidenceRef`; the worker requires an exact match before irreversible official publication. `expected_at` and the wire delivery SLA use the same official deadline: the service refuses a configuration whose `officialAfterMilliseconds` disagrees with the `schedule.delivery_sla` its offering advertises, and omitting the field derives that same advertised value.
182
+
183
+ Every revision stores its rows together with an RFC 8785 JCS SHA-256 binding and exact decimal control totals for requested numeric metrics. A revision number and obligation are immutable. Official revisions are terminal; later source corrections are immutable adjustments bound to the official revision, never superseding revisions. Status snapshots omit row payloads, are capped at 8 MiB, expire after 15 minutes, and keep cursor pages stable over the flat obligation/revision/adjustment union. A periods response returns an opaque `changes_checkpoint`; echo that value verbatim as `changes_after` rather than supplying a timestamp. Account-scoped write/snapshot locks make those checkpoints gap-free for SDK store writes. The default table set is deployment-wide; use a dedicated database/schema and acknowledge that boundary explicitly. `sourceScope` must contain opaque routing identities only—never credentials or bearer tokens—because it is retained with the obligation. Retained resource locations are held to the same rule, and at the seam that persists them rather than only in the worker's pre-flight, because a caller driving `settleMaterialization` directly would otherwise store a presigned location that `get_reporting_status` then publishes. Query and fragment are refused on the raw string rather than on a successful parse, because a relative path carrying a presigning query never parsed as a URL at all; userinfo is refused as a colon-separated pair before the `@`, which is the credential shape, plus any `http(s)` userinfo at all. A blanket `@` rule would refuse `abfss://container@account.dfs.core.windows.net/...` and a Snowflake stage reference, neither of which carries a secret.
184
+
185
+ ## Managed Delivery and Reconciled Billing
186
+
187
+ Core remains the default and has no destination, external-resource, or receipt dependency. To opt into the higher tiers, apply `REPORTING_MANAGED_DELIVERY_MIGRATION` **after** `REPORTING_LEDGER_MIGRATION`, explicitly construct the Core store with `managedDelivery: true`, create a `PostgresReportingManagedDeliveryStore`, and pass both stores with a destination adapter to `createReportingManagedDeliveryRuntime`. The async factory proves the stores share one authority and validates the RC3 tier wiring before returning it. It advertises `managed_delivery` only when an immutable binding, delivery, bounded resource reading, generation-fenced revocation, and at least one verification profile are installed. The advertised automated recovery window must be at least the widest installed managed Core configuration recovery window. It advertises `reconciled_billing` and `receipt_task` only when an authenticated consumer resolver and canonical-digest verification are also installed.
188
+
189
+ ```ts
190
+ import {
191
+ PostgresReportingLedgerStore,
192
+ PostgresReportingManagedDeliveryStore,
193
+ REPORTING_MANAGED_DELIVERY_MIGRATION,
194
+ createReportingManagedDeliveryRuntime,
195
+ reportingManagedDeliveryBindingV1,
196
+ } from '@adcp/sdk/reporting/ledger';
197
+
198
+ await pool.query(REPORTING_MANAGED_DELIVERY_MIGRATION);
199
+ const managedStore = new PostgresReportingManagedDeliveryStore(pool);
200
+ const managedCoreStore = new PostgresReportingLedgerStore(pool, {
201
+ acknowledgeIsolatedDatabase: true,
202
+ managedDelivery: true,
203
+ });
204
+ await managedStore.authorizeDestination({
205
+ account_id: internalAccountId,
206
+ destination_ref: destinationRef,
207
+ generation: 1,
208
+ authorized_at: new Date().toISOString(),
209
+ });
210
+ await managedStore.installBinding(
211
+ reportingManagedDeliveryBindingV1({
212
+ // Binds the Core configuration generation installed above. That
213
+ // configuration's `schedule.recoveryWindowMilliseconds` is 3_600_000, which
214
+ // is what `automatedRecoveryWindowSeconds: 3600` below must meet or exceed.
215
+ ...bindingForInstalledCoreConfiguration,
216
+ account_id: internalAccountId,
217
+ destination_ref: destinationRef,
218
+ authorization_generation: 1,
219
+ })
220
+ );
221
+
222
+ type SellerContext = { account?: unknown; agent: { agent_url: string } };
223
+ const managed = await createReportingManagedDeliveryRuntime<SellerContext>({
224
+ coreStore: managedCoreStore,
225
+ store: managedStore,
226
+ adapter: destinationAdapter,
227
+ offerings: reportingDeliveryOfferings,
228
+ resolveConsumerId: context => context.agent.agent_url,
229
+ // MUST be at least the widest installed managed binding's Core
230
+ // `schedule.recoveryWindowMilliseconds` / 1000 — see the precondition note below.
231
+ automatedRecoveryWindowSeconds: 3600,
232
+ statusRetentionDays: 90,
233
+ resourceRetentionDays: 30,
234
+ authorizationRevocationSeconds: 60,
235
+ });
236
+
237
+ // Supply these to the matching server slots/capability document.
238
+ const { getReportingStatus, getMediaBuyDelivery, syncReportingReceipts } = managed;
239
+ const reportingDelivery = managed.reportingDeliveryCapabilities;
240
+
241
+ // Run from a durable scheduler; replicas safely share SKIP LOCKED leases.
242
+ await managed.runWorker({ maxIterations: 100 });
243
+ ```
244
+
245
+ `automated_recovery_window_seconds` is published once per agent, in one capability document, while Core `schedule.recoveryWindowMilliseconds` is per configuration. The advertised value is a **maximum** — the longest a due obligation may stay `delayed` while automated recovery continues before it becomes `action_required` — so one agent-wide number is truthful exactly when it is at least every installed window. `createReportingManagedDeliveryRuntime` enforces that bound and nothing more: advertising less than the widest installed window is refused with the offending value named, advertising more is conservative and allowed, sub-second Core windows are rounded up to the whole second the capability is expressed in, and a deployment with no managed binding — a fresh install, or one that has just offboarded its last managed tenant — starts normally. Heterogeneous tenants behind one agent therefore need no separate endpoint per cohort: advertise the widest window they run. The bound is enforced on the write path as well as at startup — `adoptAdvertisedPolicies` validates existing bindings while exclusively locking the same durable policy sentinel as `installBinding`, and `installBinding` refuses a later Core configuration whose recovery window exceeds the durable bound. `listInstalledRecoveryWindowSeconds` is optional direct-store introspection, not an authoritative publication check: an install can otherwise land between a list and a later adoption.
246
+
247
+ All four capability promises are durable and database-wide. The atomic hook adopts recovery and authorization-revocation maximums in the stronger, decreasing direction, and status and resource-retention minimums in the stronger, increasing direction. It returns those effective values so a weaker replica still runs its worker to the strongest policy already registered; PostgreSQL also enforces resource retention at settlement and authorization revocation at claim time for direct callers. A rejected binding check or policy write leaves all four columns unchanged, and a later replica can never weaken an adopted promise. Custom stores used by `createReportingManagedDeliveryRuntime` must provide the same atomic, binding-fenced contract; the optional separate recovery/status hooks remain only for compatible direct-store use and are not sufficient for capability publication. Apply `REPORTING_MANAGED_DELIVERY_MIGRATION` on upgrade as well as first install: it adds the resource-retention and revocation columns to an existing two-column registry without replacing prior promises.
248
+
249
+ Authorize a destination generation and install its immutable binding before creating any obligation for that Core configuration. Once an obligation is observable, only an exact idempotent replay of the existing binding is accepted, so the managed tier cannot appear outside the Core changes checkpoint. Build the binding with `reportingManagedDeliveryBindingV1`, which binds the exact internal account, Core configuration generation, destination authorization generation, feed purpose, method, verification profile, reconciliation mode, and resource-retention promise. That promise is a floor rather than a default at settlement: an explicit `minimum_resource_retention_days` may tighten it, never waive it, and a negative value is refused. Only a failed outcome is exempt from it — a successful one must name an expiry the database can check, or a direct store caller could persist a permanently successful materialization whose bytes no reader can fetch and which nothing revisits. It is judged only by the clock that stores the row — `assertMaterializationOutcome` deliberately does not re-check the horizon against the worker's clock, because a worker running ahead of the database then refused a resource the database considers well inside the window and reported it as `DELIVERY_FAILED`. Every claim of a pending materialization counts toward the delivery-attempt cap, and the worker calls `failExhaustedMaterializations` before planning so a row that used them all is failed rather than left pending — that sweep takes the same account lock the lifecycle apply holds, in the canonical order, because it mutates exactly the state the lifecycle compare-and-set fences and could otherwise commit `pending` -> `failed` between a matching state-version check and that apply's commit; it mutates only the accounts it locked, so a lease that expires while it waits on another account's lock is left for the next sweep rather than failed unfenced — which the planner treats as work in flight, making the revision neither claimable nor replannable. That sweep re-checks eligibility against the row it finally locked rather than only the set it selected, so a settlement that commits while the sweep waits on the row is not overwritten with `DELIVERY_ATTEMPTS_EXHAUSTED`. Revocation commits the durable deny first, fails queued work, and makes resource reads and new receipt evidence fail closed; provider-side grant cleanup is a separately leased worker action. Reauthorization uses a strictly greater generation and a new Core configuration generation. It never re-enables an old binding.
250
+
251
+ The worker claims and commits through short PostgreSQL transactions but performs destination I/O outside them. Pass `authorizationRevocationSeconds` to `runWorker()` when using a direct store; `createReportingManagedDeliveryRuntime` uses the strongest value returned by atomic adoption. PostgreSQL's claim boundary independently applies the durable minimum, so a different replica cannot widen or omit the promise: a cleanup attempt is clipped so it cannot run past `revoked_at` plus the window, a failed attempt's retry lease never outlasts the window, and a grant that has already outlived it is returned as `revocationsOverdue` for alarming. Every SLA fact — `revoked_at`, the remaining window, and whether a grant is overdue — comes from the database that committed the revocation, never the worker host, and the exact boundary counts as overdue. A crashed worker's lease is reclaimable at that boundary even before it expires, with lease generation fencing the old holder — but only once the holder has had a full attempt's worth of time, so live workers cannot steal from each other on an already-late grant and leave cleanup permanently uncommitted. `revoked_at` and `authorized_at` are both written by the committing database, never by the caller, and the strongest of the binding, caller and durable resource-retention floors is judged entirely in SQL against that clock; a settle the database refuses as under-retained is terminalized rather than left pending forever. A failed attempt gives up its lease under a short retry backoff rather than holding it for a full lease or clearing it outright — clearing it let the next iteration of the same worker tick reclaim the same grant, so one broken provider consumed every iteration — so the grant stays reclaimable and its elapsed SLA is visible rather than masked by a lease that has not expired; `claimRevocation` orders by cleanup lease generation, which the failed attempt already incremented, so releasing cannot let one broken grant starve the queue. Delivery, revocation, and resource reads have hard SDK deadlines even when an adapter ignores cancellation; resource descriptors are limited to 1 MiB and resource bodies to 64 MiB by default. Adapters must advertise `revocationFencesDeliveryGenerations: true`, make the logical `(configuration generation, revision, destination generation)` write idempotent, and install a provider-side generation tombstone before `revoke()` returns. Every delivery must be keyed by that generation and refuse a tombstoned generation, including a late provider write that completes after the SDK timed out. A successful commit requires the lease generation and unexpired lease, current authorization, exact Core row count and control totals, all evidence required by the selected verification profile, the official canonical digest when required, an immutable native version reference when applicable, and an `expires_at` satisfying the configured retention window.
252
+
253
+ `sync_reporting_receipts` derives both account and consumer from authenticated context, and `received_at` is the server's to assign: a request carrying it is refused with a typed `VALIDATION_ERROR` rather than quietly stripped, so the handler and the request schema agree on what a valid request is. RC3 caps `receipts` and `adjustment_receipts` at 100 each in JSON Schema, and bounds the batch as a whole in the request's `x-adcp-validation.batch_identity`: receipt IDs must be unique across both arrays, "whose combined length MUST NOT exceed 100". That annotation is normative prose rather than machine-checked on this path — `x-adcp-validation` is registered as an AJV keyword for commercial terms only — so the handler enforces the combined bound itself. A request that satisfies both per-array caps but exceeds 100 combined is therefore spec-invalid — and unanswerable regardless, since `results` is capped at 100 while one result per submitted receipt is required — so it is refused with a `VALIDATION_ERROR` envelope rather than an over-long `results` array. A finality filter hides managed evidence with the revision it names: returning a materialization or receipt for a revision `finality: official` omitted left the response carrying public references to a revision it does not contain. An adjustment receipt and the adjustment it names are separate pagination items, so a small `max_results` puts them on different pages; the named adjustment therefore travels with the receipt as context on whichever page carries it, and the RC3 rule that a receipt may not appear without the correction it names holds per page. Only items are counted by the cursor, so paging itself is unchanged. Its PostgreSQL implementation serializes each caller namespace, records a compact per-entry verdict for idempotency replay — receipt bodies are rehydrated from the append-only receipt table rather than duplicated — and retains those replay rows for 30 days so a consumer at the per-consumer batch cap is throttled instead of permanently locked out. An exact same-key replay is side-effect free and returns the caller its own recorded verdict even after the destination authorization is revoked. A byte-identical re-presentation of an already-recorded receipt under a fresh idempotency key likewise returns `unchanged`: both paths resolve immutable, caller-owned state and write no new evidence. Revocation governs what may be newly *accepted*, not what the caller's own stored identity answers, so no other consumer's state is read; genuinely new evidence while revoked is still refused. It exposes append-only histories only to that consumer. Revision receipts must name the exact obligation, successful materialization, current authorization, and verification evidence, and a revision whose finality satisfies the obligation's own `required_finality`, so a snapshot-finality contract reconciles on the same terms an official one does. `billing` feeds are not such a contract: RC3 requires `required_finality: official` for them unconditionally, and both `installConfiguration` and `installBinding` refuse the combination for a new generation — an immutable generation that predates the rule reinstalls unchanged, because the replay is resolved before either the offering or the validation — the binding revalidates the referenced Core configuration atomically, because a generation created before that rule existed still sits in the database — so a terminal accepted billing receipt can never land against a provisional revision. Accepted leaves are terminal; a rejected leaf may be repaired only by an exact `supersedes_reporting_receipt_id` — and a leaf whose body has been pruned still owns its subject, so the successor named by its tombstone is admitted while a fresh root carrying the same content is not. Entries that resolve to an already-stored receipt are resolved before the batch's duplicate-subject rule is applied, because such an entry writes nothing and is not competing for the subject: a batch carrying an existing rejected receipt together with its own correction records the correction rather than failing both. Duplicate receipt IDs remain a property of the whole submitted batch. A request with no receipts at all is refused with a typed `VALIDATION_ERROR` before any per-entry refusal, including a wrong-account one, because `results` has `minItems: 1` and a per-entry answer to an empty batch is an empty — schema-invalid — body. Later source corrections remain Core adjustments and receive separate append-only adjustment receipts—neither official revisions nor earlier receipts are rewritten. The per-consumer receipt cap is admission control on new rows, applied at the point one would be inserted: an exact resubmission of a receipt that already exists stores nothing, so it replays as `unchanged` at the cap rather than flipping to `failed` on the row count alone, and a refusal for capacity leaves the current leaf undemoted. Lookup failures intentionally share one generic error so the task cannot probe another account's retained objects.
254
+
255
+ The lifecycle compare-and-set covers managed state as well as Core evidence. The projection reads managed rows in their own transaction, so `getManagedLifecycleProjection` returns a `managedStateVersion` token over the obligation's materializations, receipts (including current-leaf flips), adjustments and destination-authorization state; `applyLifecycleProjection` re-reads it inside the apply transaction and refuses a stale apply. Without it a revocation, receipt or settled materialization arriving between projection and apply would be overwritten by a health computed before it existed — most visibly as a `complete` persisted and webhooked over a receipt that had just arrived. Omit `ledgerAsOf` outside a deadline sweep and the store resolves its own cutoff: a host `toISOString()` is millisecond-truncated while these columns are microsecond, so a caller-taken "now" sorts before a row written in the same millisecond and silently drops it. A pinned cutoff is also clamped to the ledger's own clock — only where there is one to clamp against; a store with no `readLedgerInstant` takes a first-attempt pin exactly as given, because `now` is precisely what pinning overrides — at the reconciler and again when the watermark is written, and a store with its own clock uses nothing but that clock on a compare-and-set retry. When a store resolves a different instant than the one it was asked for, that resolved instant is what the transition and the watermark use — the projection was computed at it, so stamping the caller's value instead watermarked a moment later than the read and buried everything in between — taking the later of the two put the caller's future pin back the moment the first attempt lost its race. A host running fast pinned an instant the database had not reached, the watermark took that instant, and every database-timestamped change inside the skew was then permanently behind it — excluded from the projection that wrote it and never due again, which left a `complete` transition and its webhook standing over a revocation that had already contradicted it. The producer's own reconciles pass their host instant as a fallback clock rather than as a pin for the same reason. Every comparison against the cutoff runs in SQL for the same reason. The projection reads and the token are taken in one `REPEATABLE READ` snapshot, so a settle committing between them cannot pair a pre-settle health with a post-settle token — the one combination the CAS would otherwise accept. A refused apply is recomputed against a **fresh** authoritative cutoff and retried immediately, bounded; the cutoff never moves backwards. The token covers materializations, receipts including current-leaf flips, adjustments, consumer statuses and destination-authorization state. The externally supplied obligated-consumer roster cannot be re-read inside the apply transaction, so it is versioned separately and re-checked immediately before the apply — every sweep refreshes a bounded slice of its managed obligations' roster versions before selecting, paging on a cursor the refresh itself advances — on failure as well as success, so a tenant whose authorization service is down yields its slot instead of occupying it every sweep, and a difference from the version last reconciled is itself a due condition. The apply fences on that observation too: it locks the obligation's lifecycle row, refuses when the published version has moved since the re-check, and never writes its own version over a newer one — otherwise a refresh landing in that window was overwritten by the version the projection had used, and since due-ness is exactly "observed differs from processed", the roster change had nothing left to re-arm from. Publishing only inside a reconcile was circular — the reconcile needs the obligation to already be due, which is what the roster change was meant to cause. Supply `version` from `obligatedConsumers`, or its resolved content is hashed for you — the projection and the re-check hash the same set, so an unversioned roster converges instead of burning its retry budget. A roster returned with `complete: true` is authoritative and excludes principals it does not list — in the lifecycle fold as well as the live projection and the digest — which is what stops a same-account principal inserting itself into the obligated set by posting a receipt. An incomplete roster is only a hint, so observed principals are still unioned in. Mutable managed state is placed at that cutoff rather than at now: `changed_at` after the cutoff means the materialization was still `pending` then, and a revocation counts only once `revoked_at` is at or before it, so a later settlement is never backdated into an earlier transition.
256
+
257
+ Managed-only changes are lifecycle candidates in their own right. A settlement, revocation, receipt, adjustment, consumer status, external-roster change or resource expiry after the last reconcile makes the obligation due, so a persisted `complete` cannot outlive a live status that has since degraded — Core deadlines alone would never reschedule it. Candidacy is measured against a per-obligation watermark in `adcp_reporting_lifecycle_state`, written on every reconcile including one that changes no health, and stamped with the cutoff the projection read at rather than the commit instant. Sweeps take that cutoff from the ledger's own clock rather than the worker host, so a fast host cannot stamp a future watermark and bury database-timestamped work committed inside the skew — anything recorded after that cutoff stays due instead of being buried by the write that follows it; without it a change with no health effect keeps the obligation due forever and, in a fair-ordered page, starves everything behind it. Sweeps reconcile each obligation in isolation, so one tenant's failing roster callback cannot abort the obligations queued after it, and a failure records an exponential backoff cursor so a page of failing tenants yields its slots instead of monopolising every sweep — including a reconcile that exhausts its compare-and-set retry budget, which is a failure rather than a completion. The backoff never advances the watermark, so the work stays visible as unresolved.
258
+
259
+ `MAX_MATERIALIZATIONS_PER_ACCOUNT` and `MAX_RECEIPTS_PER_CONSUMER` are lifetime counts by default. Because managed evidence is immutable, a long-lived account eventually reaches them and then stops planning materializations and refuses every receipt with no way back. Construct the store with `new PostgresReportingManagedDeliveryStore(pool, { evidenceRetentionDays })` to make the caps active-scope: only evidence recorded inside that window counts against them, and `pruneExpiredEvidence({ account_id, limit })` deletes what has fallen outside it — never anything still inside the window, never a `pending` or leased materialization, and never a receipt batch inside its own replay retention. Retention has a floor: at least the 30-day receipt replay retention, and at least the strongest durable status or resource-retention horizon registered for the database. Adoption and binding installation exclusively lock the migration-created policy sentinel before account/binding locks; settlement, revocation claim, and pruning hold shared locks on it through their writes. A stronger promise therefore cannot land between a weaker check and its write, while unrelated hot-path readers remain concurrent. Pruning genuinely does its selection before taking the account lock, and asks which receipts a live replay row still names by expanding those rows once rather than posing a containment question per candidate — a shape no index could serve, which held the lock long enough to fail concurrent Core writes with 55P03. Because that selection is unlocked it is a proposal rather than a verdict: every victim is revalidated under the account lock against current live replay rows, surviving receipts and unexpired resources before it is deleted, so a replay that commits in the gap keeps the receipt it promised to reproduce. The revalidation is narrowed to the candidates' own consumers and keys, so the lock still holds no per-candidate scan. The registry lock is held to commit inside the transaction that acts on it, and a precondition such as the installed-window check runs inside that same transaction so a refusal rolls the write back rather than leaving a durable promise nobody validated, so an install cannot read an empty registry while a narrower promise is being registered, and a prune cannot approve a horizon another replica is about to widen. Pruning deletes and tombstones in one statement against one frozen cutoff, so a moving boundary can never leave a deleted row without its permanent identity. Successive prunes demote the tombstones they supersede, so a subject has exactly one tombstone still claiming to be its leaf and a chain pruned twice resolves the same way every time. Re-presenting a pruned receipt byte-identically answers `unchanged` with the instant the tombstone kept, resolves that receipt's own tombstone before any rule about its subject — so an exact re-presentation is not refused as new content for a settled subject — replays the same answer under the same idempotency key by rehydrating from the tombstone when the body is gone, counts as already-resolved for the batch's duplicate-subject rule, and never recreates the row: recreating it restarted the retention clock, undid the storage the prune reclaimed, and moved the published `received_at` to a moment the consumer never filed anything at. A pruned receipt ID can never bind different content, a subject whose accepted leaf expired can never reopen — the read projection consults tombstones, not just live rows — and materialization attempt history survives as a compact terminal record, so a revision that exhausted its attempts or already succeeded does not restart at attempt 1 once its rows age out. Both conclusions — the acceptance and the fact a delivery succeeded — are folded into the live, filtered and lifecycle projections alike. Counters describe exactly the records the response emits, never more and never less, so a buyer recomputing the association cannot see `ASSOCIATED_HISTORY_INCOMPLETE`; retention therefore refuses to prune an acceptance while the resource it accepts is still readable, which is what keeps a `complete` period from having nothing to show for itself. An acceptance carries the consumer that gave it, so one consumer's pruned acceptance never settles the obligation on another's behalf — including the anonymous fail-safe consumer, which owns no acceptances at all. Pruning also removes the expired replay row before the receipts it names, keeps any receipt a surviving replay row still references — a later batch can name an earlier receipt — and keeps any materialization whose resource is still readable or that a retained receipt names as its evidence. An adjustment receipt names no materialization — it names the revision it corrects — so the readable-resource hold matches on that revision as well, or a period whose revision resource was still readable lost the adjustment acceptance behind its own `complete`. Run it from the same scheduler that runs the worker. Lifecycle receipt reads are driven from the obligation's own subjects into a `(subject_id, receipt_kind, recorded_at)` index rather than asking the global receipt table which of its rows belong to an obligation — a question that supplies neither account nor consumer, so every due obligation re-scanned the whole receipt history. The managed state digest, the obligated-consumer roster read and the permanent delivery tombstones do the same — consumer statuses carry an `obligation_id` index and materialization tombstones an `(obligation_id, reached_success, reached_success_at)` one — because that digest is read inside the apply transaction while it holds the account lock: a scan there is a scan with the lock held, which pushed concurrent Core writes past their lock timeout and failed them with 55P03.
260
+
261
+ The managed tables are additive and do not alter the Core tables. This is the schema boundary coordinated with #2943: that work owns transactional reporting notification/activity intent and the existing webhook delivery/credential plane. Managed Delivery does not create a second webhook sender, outbox, credential store, or subscriber model. Apply both feature migrations after the Core migration in either order; each owns separate tables and both reuse the Core authority.
262
+
263
+ ## Transactional status notifications and account activity
264
+
265
+ Production deployments can join every health or observed-finality transition to
266
+ a compact account-operator activity record. Health transitions additionally
267
+ create one durable, schema-conformant `reporting.status_changed` intent;
268
+ finality-only changes remain internal activity because the AdCP event is defined
269
+ only for health changes:
270
+
271
+ ```ts
272
+ import { createPostgresPersistentNotificationRuntime } from '@adcp/sdk/server';
273
+ import {
274
+ createPostgresReportingNotificationActivityRuntime,
275
+ createPostgresReportingNotificationAttemptCheckpoint,
276
+ PostgresReportingLedgerStore,
277
+ REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
278
+ REPORTING_LEDGER_MIGRATION,
279
+ } from '@adcp/sdk/reporting/ledger';
280
+
281
+ // Build the durable pre-POST checkpoint first. The notification runtime needs
282
+ // it, and the activity runtime verifies it targets the same durable store —
283
+ // a mismatched pair checkpoints nothing and loses notifications silently, so
284
+ // both are refused at construction.
285
+ const attemptCheckpoint = createPostgresReportingNotificationAttemptCheckpoint({
286
+ db: pool,
287
+ namespace: 'seller-production',
288
+ });
289
+ const notifications = createPostgresPersistentNotificationRuntime({
290
+ db: pool,
291
+ publisherScope: 'seller-production',
292
+ checkpointDeliveryAttempt: attemptCheckpoint,
293
+ subscriptions: { acknowledgeIsolatedDatabase: true },
294
+ ...notificationOptions, // proof, protected credentials, webhooks, authorization
295
+ });
296
+ const reportingActivity = createPostgresReportingNotificationActivityRuntime({
297
+ db: pool,
298
+ notifications,
299
+ // Use a deployment-unique value whenever a PostgreSQL schema is shared.
300
+ namespace: 'seller-production',
301
+ // The same checkpoint, so its store can be verified against this runtime's.
302
+ attemptCheckpoint,
303
+ // Pure host-owned mapping from an internal ledger account. Never derive
304
+ // this from transition data, an incoming request body, or ctx_metadata.
305
+ tenantScopeForAccount: accountId => durableAccountDirectory.tenantFor(accountId),
306
+ });
307
+
308
+ // Rolling-deployment order: ledger and notification tables, activity table,
309
+ // drain legacy pending transitions, then application code configured with the port.
310
+ await pool.query(REPORTING_LEDGER_MIGRATION);
311
+ for (const sql of notifications.migrations.all) await pool.query(sql);
312
+ for (const sql of reportingActivity.migrations.all) await pool.query(sql);
313
+
314
+ // Before enabling the port, keep legacy subscribers configured and run
315
+ // retryReportingStatusNotificationsV1() until listPendingTransitions() is empty.
316
+ // The transactional store fails closed if legacy pending rows remain.
317
+
318
+ // Last cutover step, only once no pre-SDK-14 writer is still serving: fence
319
+ // finality-less transitions out of the log. A surviving legacy writer now fails
320
+ // closed on append instead of silently adding another redundant finality event.
321
+ await pool.query(REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION);
322
+
323
+ const store = new PostgresReportingLedgerStore(pool, {
324
+ acknowledgeIsolatedDatabase: true,
325
+ notificationActivityPort: reportingActivity.port,
326
+ });
327
+
328
+ // Do not also pass legacy ReportingLedgerSubscriberV1 callbacks to lifecycle
329
+ // reconciliation. The transactional port is the sole notification handoff.
330
+
331
+ await reportingActivity.probe();
332
+ await notifications.probe();
333
+
334
+ // Run both bounded calls repeatedly from the deployment's durable scheduler.
335
+ await reportingActivity.recoverOnce({
336
+ ownerToken: process.env.INSTANCE_ID!,
337
+ onError: (error, claim) => operationalLogger.error({ error, claim }),
338
+ });
339
+ await notifications.recoverOnce({ ownerToken: process.env.INSTANCE_ID! });
340
+ ```
341
+
342
+ `applyLifecycleProjection()` owns the transaction. After locking and rechecking
343
+ the obligation and revision evidence, the PostgreSQL store inserts the
344
+ transition, updates its issues, and calls `notificationActivityPort` with that
345
+ same transaction client. A port error rolls the whole unit back. The port does
346
+ no network I/O and stores no destination or authentication material. Only after
347
+ commit does `recoverOnce()` call the existing persistent-notification runtime,
348
+ which reads current subscriptions, enforces tenant/account matching and live
349
+ authorization, resolves opaque credential bindings, and checkpoints the normal
350
+ encrypted webhook outbox before POSTing. The activity queue is not a second
351
+ sender, credential authority, retry engine, or reporting ledger.
352
+ The store atomically stamps the transition's `notifiedAt` field as a durable
353
+ handoff marker in that same transaction. In transactional mode this field means
354
+ the activity intent is durable, not that a recipient matched or network delivery
355
+ occurred. A legacy deployment with unnotified transitions must drain or
356
+ explicitly resolve them before enabling the port; the lifecycle rejects the
357
+ cutover rather than silently abandoning those rows.
358
+
359
+ The logical notification identity is derived from the immutable transition and
360
+ is reused through ambiguous crashes. Intent insertion is exactly once;
361
+ delivery remains at least once. A crash before commit exposes neither the
362
+ transition nor its activity. A crash after commit leaves pending work. A crash
363
+ after webhook checkpointing may replay projection, but the existing webhook
364
+ delivery identity prevents rebinding. The bridge intentionally binds recipients
365
+ when the existing notification runtime checkpoints each per-subscriber webhook
366
+ delivery, not while the ledger transaction is open: that keeps subscription
367
+ credentials and destination authority out of the ledger transaction and ensures
368
+ a replacement or revocation that wins before checkpointing is honored. From
369
+ that checkpoint onward the subscriber and destination generation are stable;
370
+ the runtime rechecks live authorization on every attempt and suppresses stale
371
+ or revoked generations. Already-authorized in-flight POSTs cannot be retracted.
372
+ This explicit drain-time rule is what the replacement and revocation crash tests
373
+ assert.
374
+
375
+ Account operators can read a bounded keyset page without loading report rows:
376
+
377
+ ```ts
378
+ const page = await reportingActivity.listActivity({
379
+ tenantId: authenticatedTenant.id,
380
+ accountId: resolvedInternalAccount.id,
381
+ limit: 100, // 1..200
382
+ cursor: previousPage.nextCursor,
383
+ });
384
+ ```
385
+
386
+ Both scope values must come from authenticated server context. Cursors are
387
+ scope-bound and cannot be moved between accounts or tenants; the runtime also
388
+ revalidates the account-to-tenant mapping on every read. Pagination is a
389
+ newest-first operator view, not a gap-free change feed: a transaction that
390
+ commits after a page was read may have an earlier PostgreSQL sequence, so
391
+ refresh from the first page to discover concurrent late commits. Records include
392
+ the transition identity, health and observed-finality change, occurrence and
393
+ projection timestamps, issue IDs, period, and non-secret configuration/report
394
+ correlation references. They never embed revision rows, `sourceScope`,
395
+ subscriber destinations, credential handles, credentials, or `ctx_metadata`.
396
+ Each compact activity intent is capped at 64 KiB.
397
+ The runtime also applies atomic per-tenant backpressure at 100,000 pending
398
+ health notifications by default; tune `maxPendingPerTenant` to deployment
399
+ capacity and alert on the operational error instead of dropping durable intent.
400
+ This is an SDK/adopter API only: AdCP defines the complete health-notification
401
+ wire payload but no public account-activity read task, so do not expose `listActivity()`
402
+ as an invented wire extension.
403
+
404
+ Projected activity defaults to 90-day retention measured from projection (or
405
+ from commit for finality-only records that require no wire projection).
406
+ Abandoned activity is retained on the same schedule, measured from
407
+ `abandoned_at`. Override `retentionMs` only to match an explicit operator
408
+ policy, schedule bounded `pruneProjected()` calls — which reclaims projected and
409
+ abandoned rows alike — and retain pending rows until they are settled. The
410
+ reporting worker retries a failed projection up to `maxAttempts` (default 100)
411
+ and then abandons the claim; once the notification runtime has durably accepted
412
+ every matched subscriber, its own webhook outbox owns delivery retry and
413
+ retention. Supply `recoverOnce({ onError })` to report a
414
+ failed projection attempt without changing lease or retry semantics.
415
+ `matched` reports how many active subscribers were checkpointed; a projected
416
+ row with `matched: 0` is expected after revocation and does not claim network
417
+ delivery. Each recovery poll claims one row at a time so work waiting behind a
418
+ slow fanout is never left under an expiring pre-claimed lease.
419
+ The host remains responsible for a database-level retained-row/byte quota and
420
+ storage alerting per tenant or isolated deployment; the runtime's pending cap
421
+ protects delivery backlog but is not a general PostgreSQL storage quota.
422
+
423
+ ### Recipient intent is frozen before any send, and revisable per recipient
424
+
425
+ The recovery worker commits its resolved recipients before anything leaves the
426
+ process, via `NotificationEvent.freezeRecipients`. The runtime then delivers
427
+ only the intersection of what is resolvable now and what was committed, so each
428
+ subscriber's `delivery_id` — and therefore the `idempotency_key` it dedupes on —
429
+ is stable across an ambiguous retry.
430
+
431
+ What makes a frozen set safely revisable is a second durable barrier:
432
+ `PersistentNotificationRuntimeOptions.checkpointDeliveryAttempt`, awaited on the
433
+ allow path of live delivery authority immediately before every external POST.
434
+ Wire `createPostgresReportingNotificationAttemptCheckpoint()` into it. It is a
435
+ runtime-level option keyed on the durable attempt context rather than a
436
+ per-emission closure because an emission snapshot cannot carry a function, so a
437
+ per-emission barrier would be skipped by the recovered outbox path — the path
438
+ where an ambiguous send is most likely.
439
+
440
+ The hook is runtime-wide, so the reporting checkpoint passes any event type it
441
+ does not own straight through. Failing closed on another subsystem's
442
+ notification would suppress every one of its attempts until the retry horizon
443
+ expired. `eventTypes` **extends** the owned set and can never shrink it —
444
+ `reporting.status_changed` is always owned, because a configuration that
445
+ silently stopped checkpointing reporting deliveries while the runtime still
446
+ advertised checkpoint support is the precise bug the checkpoint exists to
447
+ prevent.
448
+
449
+ The checkpoint is bound to the exact `(queryable, namespace, table)` it writes
450
+ to, and the activity runtime requires that same object as `attemptCheckpoint`.
451
+ It verifies in two tiers: when the port exposes `deliveryAttemptCheckpoint` —
452
+ the checkpoint it actually invokes — that must be the identical object, because
453
+ two correctly-built checkpoints can each look valid while targeting different
454
+ stores. When it does not, the declared store binding becomes mandatory and is
455
+ compared instead. Either way a mismatch is refused at construction. Configuring the two independently was
456
+ undetectable at runtime: the checkpoint's update matched no row, every attempt
457
+ was suppressed as retryable, the delivery binding eventually retired, the
458
+ recipient settled terminal and the activity projected — losing the notification
459
+ with no error anywhere.
460
+
461
+ Construction and `probe()` both fail closed unless the notification port proves
462
+ it runs the checkpoint. A custom `{ emit }` port must set
463
+ `hasDeliveryAttemptCheckpoint: true`, asserting that it forwards the event it is
464
+ handed to a runtime that does; otherwise a crash plus a destination replacement
465
+ re-addresses the notification under a second generation and idempotency key.
466
+ `acknowledgeMissingAttemptCheckpoint` exists only for tests that deliberately
467
+ demonstrate that hazard.
468
+
469
+ Declaring the capability is not sufficient, and is not trusted. Freeze and
470
+ checkpoint are one contract: a port that declares support but never calls
471
+ `freezeRecipients` leaves nothing addressable, so the checkpoint has no row to
472
+ mark and settlement would see zero outstanding recipients and record the
473
+ notification as delivered although nothing was sent. The runtime verifies that
474
+ the freeze actually ran and refuses to project the emission otherwise.
475
+
476
+ The checkpoint and a concurrent recipient replacement run as separate statements
477
+ against a pool, so neither sees the other's uncommitted work: a freeze can
478
+ propose a replacement generation while the original is being checkpointed, and
479
+ PostgreSQL keeps both rows. A partial unique index on
480
+ `(namespace, transition_id, subscriber_key) WHERE attempt_at IS NOT NULL` is the
481
+ arbiter — the second generation's checkpoint fails, so it is never POSTed, and
482
+ the next freeze drops it because its subscriber is already claimed.
483
+
484
+ Revisability is tracked **per recipient**, in `<activity_table>_recipients`:
485
+
486
+ - A recipient with no `attempt_at` provably never received a POST, because
487
+ suppression fails closed before the checkpoint. It is replaced in place when
488
+ it goes stale, which closes the window where a destination is replaced between
489
+ candidate enumeration and the first POST.
490
+ - A recipient with `attempt_at` is pinned. Pinning is keyed on the **subscriber**,
491
+ not the destination generation: once a subscriber has been addressed, a later
492
+ generation of that subscriber is never addressed for this notification,
493
+ because that would be one logical delivery under two idempotency keys.
494
+ - One recipient's attempt never pins a sibling. In a fanout, a subscriber
495
+ suppressed stale before its own first POST is still re-resolved while an
496
+ already-addressed sibling stays pinned.
497
+ - Unattempted rows are replaced rather than superseded, so a claim that retries
498
+ many times before any send cannot accumulate rows. A settled recipient is left
499
+ out of later emissions — it still gates projection, but re-addressing it would
500
+ be redundant traffic.
501
+ - Settled history is compacted to one row per subscriber, and
502
+ `maxRetainedRecipients` bounds **every retained row**. Counting only the
503
+ addressable recipients let
504
+ terminal rows grow for the lifetime of a claim that kept retrying while fresh
505
+ subscribers settled.
506
+ - Every mutation — the replacement delete, the insert, compaction and
507
+ settlement — takes a `FOR UPDATE` lock on the parent activity row before it
508
+ touches a recipient, in a `MATERIALIZED` CTE so that lock is the statement's
509
+ first act. Reading the lease without locking it only proved the lease was
510
+ live when the snapshot was taken: a statement that then blocked on a
511
+ recipient lock could resume after a successor had claimed, still see its own
512
+ lease in the cached snapshot, and mutate the successor's rows. Holding the
513
+ parent means a takeover cannot complete while a leaseholder's statement is in
514
+ flight, and a statement starting after one matches nothing. Lock order is
515
+ always parent then recipient, so the paths cannot deadlock.
516
+ - Every mutation is also gated on a live, matching lease, and so is the gate
517
+ that authorises them. A stale worker sees an empty lease source, which makes every
518
+ other source empty and the budget zero; gating only the row sources let that
519
+ empty budget satisfy the check and reap the rows a successor had already
520
+ frozen. After a takeover a stale worker is a strict no-op that refuses.
521
+ - `maxRecipients` bounds one emission's fanout and must be **at least** the
522
+ notification runtime's `maxFanoutCandidates`. `maxRetainedRecipients` bounds
523
+ every stored row — that fanout plus the pinned identities of subscribers
524
+ addressed and then replaced — and defaults to twice `maxRecipients`. They are
525
+ separate because a maximum 10,000-recipient fanout with one former subscriber
526
+ pinned needs 10,001 rows, which a single bound capped at the fanout ceiling
527
+ could never express. Setting either below what a notification legitimately
528
+ needs is a misconfiguration: the claim retries until `maxAttempts` (default
529
+ 100) abandons it. An abandoned claim leaves the pending set — so it cannot
530
+ exhaust `maxPendingPerTenant` and start refusing writes for the whole tenant —
531
+ while staying visible as `notificationAbandonedAt` (the real abandonment
532
+ instant) in account activity. It is never recorded as delivered.
533
+ - The bound and the replacement are one statement, and the rows it measures are
534
+ taken `FOR UPDATE`. Measuring separately let a concurrent checkpoint turn a
535
+ revisable row into a pinned one after the budget approved the write: the
536
+ `DELETE` then re-checked the locked row, skipped it, and the retained set
537
+ landed above the bound. Compaction runs before the budget, so a bound that
538
+ compaction can satisfy never refuses, and a refusal mutates nothing — raise
539
+ `maxRecipients` and the claim self-heals on its next pass.
540
+
541
+ | Replacement lands | Outcome |
542
+ | --- | --- |
543
+ | Before candidate enumeration | New generation enumerated and delivered |
544
+ | Between enumeration and the first POST | Suppressed `subscription_stale`, claim released, next pass replaces that recipient with the new generation; the superseded one gets nothing |
545
+ | After that recipient was checkpointed | Never re-addressed; the pinned recipient settles terminally |
546
+ | Revoked entirely | Empty recipient set is committed and the activity settles undelivered |
547
+
548
+ ### Suppression is not delivery, and delivery is not settlement
549
+
550
+ Live delivery authority fails closed before every POST. Use
551
+ `notificationSuppressionDisposition(reason)` to tell the two kinds apart:
552
+
553
+ - **terminal** — `subscription_missing`, `subscription_inactive`,
554
+ `event_not_allowed`, `authorization_denied`. The subscriber must not receive
555
+ this event.
556
+ - **retryable** — `authorization_error`, `credential_unavailable`,
557
+ `subscription_stale`, `attempt_checkpoint_unavailable`. Authority could not be
558
+ established: a store read failed, an authorization or credential callback threw
559
+ or timed out, the generation moved mid-flight, or the durable checkpoint could
560
+ not be written. Nothing was sent (`attempts: 0`).
561
+
562
+ `subscription_stale` is the one reason whose disposition depends on the caller.
563
+ A live emission can re-resolve the new generation, so it stays retryable. A
564
+ **recovered** attempt (`WebhookEmitAttempt.recovered`) is pinned to the snapshot
565
+ it was taken from and can never become valid for a replaced generation, so it is
566
+ terminal — otherwise the outbox reclaims a dead delivery until its horizon
567
+ expires.
568
+
569
+ A delivery that throws is classified too: a retired binding or an exhausted
570
+ retry horizon surfaces as `failure.reason: 'delivery_binding_retired'` with
571
+ `terminal: true` and settles under terminal policy. Flattening it into a
572
+ retryable failure left the activity pending forever and eventually exhausted the
573
+ tenant's pending capacity.
574
+
575
+ A retryable suppression no longer terminalizes the delivery in the webhook
576
+ outbox either — it releases it, exactly as a retryable exhausted HTTP result
577
+ does, so the only durable record of the send survives for the outbox worker.
578
+
579
+ Projection requires **every** stored recipient to have reached a terminal
580
+ disposition: delivered, or deliberately not delivered. An outcome that never
581
+ reached a subscriber — a retryable suppression, a transport error, an exhausted
582
+ but retryable HTTP result — leaves the claim unsettled and the activity is not
583
+ recorded as delivered. The bundled runtime raises
584
+ `ReportingNotificationRetryableSuppressionError` for a retryable suppression.
585
+ Custom notification runtimes must implement the same barriers and the same
586
+ classification.
587
+
588
+ Intent is stored relationally, one row per recipient, keyed by a bounded 64-hex
589
+ fingerprint; only that fingerprint is indexed, so an individual recipient
590
+ reference has no length limit. `maxRecipients` defaults to 10,000 — the ceiling
591
+ the notification runtime enforces on `maxFanoutCandidates`.
592
+
593
+ Custom ledger stores implement
594
+ `ReportingLedgerNotificationActivityPortV1<TTransaction>` over their existing
595
+ authority transaction. Their `applyLifecycleProjection` equivalent must call
596
+ `recordTransition({ transition, obligation }, tx)` after its compare/lock and
597
+ before commit, and must roll back the authoritative transition if the port
598
+ fails. In the same transaction they must stamp `notifiedAt` as the durable
599
+ handoff marker. Stores must also compare `expectedPreviousFinality` with the
600
+ latest stored transition before applying a finality-only projection; this field
601
+ is optional only so pre-v14 implementations continue to compile during
602
+ migration.
603
+
604
+ That comparison must read **one** committed baseline, never a freshly
605
+ recomputed one. Transitions written from SDK 14 onward carry their own
606
+ `finality`, so the baseline is read straight back off the row. Pre-v14 rows
607
+ carry none, and their baseline is **not** reconstructed — it resolves to `none`,
608
+ which the store persists via
609
+ `resolveTransitionFinalityBaseline(reporting_obligation_id)` under its account
610
+ lock. `reconcileReportingStatusLifecycleV1` calls that port before deciding the
611
+ transition.
612
+
613
+ Stores that omit the port must also ignore `expectedPreviousFinality`, and
614
+ omitting it is **not** the same as returning `none`. The lifecycle cannot
615
+ persist a baseline on such a store's behalf, so it uses the currently observed
616
+ finality instead and the comparison becomes a no-op: finality is unobservable
617
+ there, health transitions still fire, and finality-only ones never do — exactly
618
+ the behaviour from before finality existed. Assuming `none` instead would make
619
+ every reconciliation tick observe `none -> official` and append another
620
+ finality-only transition, forever. Implement the port if you want finality-only
621
+ activity at all.
622
+
623
+ Do not try to reconstruct a historical baseline. Nothing already stored proves
624
+ which revisions had committed when a pre-v14 transition was recorded:
625
+
626
+ - **Payload timestamps** (a revision's `createdAt` against the transition's
627
+ `occurredAt`) rank creation instants, not commits. A revision constructed
628
+ before the transition but committed after it counts as already observed.
629
+ - **Insert wall clocks** (`recorded_at`, `created_at`, anything derived from
630
+ `clock_timestamp()`) can repeat within a microsecond and can step backward, so
631
+ a revision that committed after the transition can still compare equal or
632
+ earlier. Comparing one against the transition's application-clock `occurredAt`
633
+ additionally mixes clocks, so the store and the lifecycle decision disagree
634
+ under skew and the compare-and-set wedges permanently.
635
+
636
+ Either rule can conclude `official`, which makes `previousFinality` equal
637
+ `finality` and silently suppresses the real snapshot→official transition and its
638
+ activity record forever. Resolving to `none` instead records at most one
639
+ redundant finality-only transition per obligation at upgrade, which stays
640
+ internal activity because the AdCP status webhook is health-only.
641
+
642
+ That bound holds only while no pre-v14 writer is still appending. During a
643
+ rolling deploy an old pod keeps writing finality-less transitions; each becomes
644
+ the latest row, gets its baseline committed as `none`, and produces another
645
+ redundant finality-only transition. Deploy ordering and wall clocks cannot rule
646
+ that out, so make it enforceable in the database.
647
+ `REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION` adds
648
+
649
+ ```sql
650
+ CHECK (data ? 'finality') NOT VALID
651
+ ```
43
652
 
44
- Every revision stores its rows together with an RFC 8785 JCS SHA-256 binding and exact decimal control totals for requested numeric metrics. A revision number and obligation are immutable. Official revisions are terminal; later source corrections are immutable adjustments bound to the official revision, never superseding revisions. Status snapshots omit row payloads, are capped at 8 MiB, expire after 15 minutes, and keep cursor pages stable over the flat obligation/revision/adjustment union. A periods response returns an opaque `changes_checkpoint`; echo that value verbatim as `changes_after` rather than supplying a timestamp. Account-scoped write/snapshot locks make those checkpoints gap-free for SDK store writes. The default table set is deployment-wide; use a dedicated database/schema and acknowledge that boundary explicitly. `sourceScope` must contain opaque routing identities only—never credentials or bearer tokens—because it is retained with the obligation.
653
+ to `adcp_reporting_transitions`. `NOT VALID` is the whole point: PostgreSQL
654
+ enforces the constraint for every INSERT and UPDATE while leaving historical
655
+ rows unvalidated, so existing finality-less rows keep working and new
656
+ legacy-shaped writes are rejected. Two consequences to plan for:
657
+
658
+ - **Run it last**, after legacy pending transitions are drained and no old pod
659
+ remains. A surviving legacy writer will fail its appends with
660
+ `rejected by the finality writer fence`. That is deliberate — a loud rejection
661
+ beats a quietly unbounded event stream.
662
+ - **Any UPDATE must leave the row fence-clean.** The baseline resolver and
663
+ `markTransitionNotified` both write `finality` as part of their update, so a
664
+ historical row is repaired by the same statement that touches it.
665
+
666
+ A store that implements neither the baseline port nor the optional `finality`
667
+ fields is treated as unable to observe finality at all: the baseline becomes the
668
+ currently observed finality, so the comparison is a no-op and no finality-only
669
+ transition is ever written. Without that, every reconciliation tick would see
670
+ `none -> official` and append another one forever. Such a store behaves exactly
671
+ as it did before finality existed — health transitions still fire.
672
+
673
+ Custom stores carry the same obligation: after cutover, reject any transition
674
+ write that does not record an observed finality, and enforce it in the storage
675
+ engine rather than in application code — an application-level check does not
676
+ bind a pod running last release's binary. Repair a historical row in the same
677
+ statement that mutates it.
678
+
679
+ The transaction argument must be one BEGIN/COMMIT-bound connection, never a
680
+ pool or autocommit queryable; the per-tenant advisory transaction lock provides
681
+ capacity serialization under READ COMMITTED. Never call the port in a
682
+ post-commit subscriber callback. The bundled
683
+ PostgreSQL activity runtime accepts only the active queryable transaction and
684
+ can therefore be reused by a custom PostgreSQL ledger without adopting the SDK
685
+ ledger tables.
686
+
687
+ The transactional port and legacy `ReportingLedgerSubscriberV1` callbacks are
688
+ mutually exclusive. The bundled store exposes that mode to lifecycle
689
+ reconciliation and fails closed if both are supplied, preventing double fire;
690
+ the port's pending rows, rather than `listPendingTransitions()`, own retry.
691
+
692
+ The activity table is deliberately separate from Core revision and obligation
693
+ rows. Managed Delivery/Reconciled Billing work in #2944 can add immutable
694
+ materialization and receipt tables without changing this transition identity,
695
+ queue schema, or migration ordering.
45
696
 
46
697
  `planObligations()` creates at most 1,000 obligations per call by default. Use its `account_id` and `maxObligations` options from a resumable scheduler when catching up dense or old schedules. Source executions are bounded to 10,000 objects, 1,000,000 rows, and 64 MiB per revision.
47
698
 
48
699
  When `get_reporting_status` omits a period, the operational default horizon is the 24 hours ending at `ledger_as_of`. The `health` and `finality` arrays filter periods-view output only; they do not rewrite summary health or the underlying obligation projection.
49
700
 
50
- `projectReportingObligationHealthV1` is the pure five-state projection. Before `expectedAt`, missing evidence is `waiting`; during recovery it is `delayed`; after the recovery deadline it is `action_required`; readable qualifying evidence is `healthy` for an open scope and `complete` for a closed scope. An unfiltered closed scope with no caller-owned configurations or no due periods is vacuously `complete`; an explicitly unknown configuration returns `lookup_unavailable`, and a snapshot with missing elapsed obligations fails closed. The simplified lifecycle persists deterministic issues and `reporting.status_changed` transitions, then calls only subscribers already authorized and supplied by the host.
701
+ `projectReportingObligationHealthV1` is the pure five-state projection. Before `expectedAt`, missing evidence is `waiting`; during recovery it is `delayed`; after the recovery deadline it is `action_required`; readable qualifying evidence is `healthy` for an open scope and `complete` for a closed scope. An unfiltered closed scope with no caller-owned configurations or no due periods is vacuously `complete`; an explicitly unknown configuration returns `lookup_unavailable`, and a snapshot with missing elapsed obligations fails closed. The simplified lifecycle persists deterministic issues and `reporting.status_changed` transitions. In legacy mode it then calls only subscribers already authorized and supplied by the host; with the transactional port, finality-only changes stay in internal activity and health changes flow through the durable AdCP notification runtime. When the store implements the optional `getManagedLifecycleProjection`, the reconciler folds Managed Delivery through the same projection the read path uses, so a persisted transition and its webhook report the health a read of that obligation would return instead of Core health alone. Every managed instant is written at database precision: a JS `Date` holds milliseconds, so taking the batch instant through one truncated `recorded_at` below the microsecond watermark written from the same clock and the reconcile it should have triggered could never become due. The lifecycle projection reads each chain's leaf **as of the cutoff** — the receipt nothing recorded by then supersedes — rather than its whole history, because the leaf is the only thing the verdict uses. Asking which row is current *now* and only then applying the cutoff answered "neither" for a rejection an acceptance had since repaired, and persisted `RECEIPT_REQUIRED` over a rejection the buyer had already filed. Reading the history instead made an obligation whose subject was repaired more times than a snapshot page may carry — a state the receipt store admits — permanently unreconcilable. A leaf also stops being a leaf when its successor is pruned — the tombstone records what it superseded — or retention handed a settled subject back to the rejection its acceptance had replaced. The read path applies the same rule at the verdict: a tombstoned acceptance outranks a rejection it superseded, so `reconciliation_status` cannot report `rejected` for a subject the lifecycle considers settled. Evidence recorded after the cutoff is out of scope at that cutoff: revisions and adjustments alike are filtered by it exactly as receipts are — a revision committed after the cutoff arrives with no materialization and no receipt in scope and would read as an unmet consumer obligation, while the compare-and-set still fences on the full revision set, which is a concurrency check rather than a statement about an instant — by the store's own ordering column, not by the producer-authored `createdAt` on the body, because a producer clock running ahead otherwise hid a committed correction from the lifecycle while the public read, which orders by that column, kept demanding a receipt for it — and pruned conclusions carry the instant they concluded, so a historical reconcile cannot settle on an acceptance or a delivery that had not happened yet. The bound is now one leaf per (consumer, subject), which is the product of two dimensions the store admits independently, and crossing it — like an oversized roster or conclusion set — truncates at a deterministic boundary and reports `receiptEvidenceComplete: false` rather than failing: a bound the write path can legitimately cross must never be a hard error, and an incomplete projection is treated exactly as an unproven roster is, so it can never report an obligation reconciled. Wire counters are computed on the read path, which still sees every row. A projection that cannot be published is reported by `runWorker` as `reconcilesDeferred` rather than aborting the sweep — the durable write already committed and the obligation stays due, so the deadline sweep, which isolates and backs off per obligation, owns the retry. Reads are scoped to one authenticated consumer while a transition is account-level, so the reconciler keeps the most severe consumer: the seller's obligation is not reconciled until every consumer that owes a receipt has accepted. Consumer-specific issue codes — `RECEIPT_REQUIRED`, `RECEIPT_REJECTED`, `ADJUSTMENT_RECEIPT_REQUIRED`, `ADJUSTMENT_RECEIPT_REJECTED` — are deliberately excluded from that persisted set and from transition `issueIds`, because the issue store is keyed by obligation with no consumer dimension and reads republish persisted issues to whichever consumer is asking; publishing them would hand one consumer another's rejection state and exact receipt ingest timing. Their severity still reaches the account-level `health`, and each caller's own issues are recomputed per read. Aggregation runs over `obligatedConsumerIds`, not over whoever happens to have submitted, so a silent authorized consumer cannot vanish when another accepts. The managed tables carry no consumer dimension on destination authorizations or bindings, so the built-in PostgreSQL store cannot prove the roster is complete and reports `obligatedConsumerRosterComplete: false`; while that is false the reconciler keeps one zero-receipt consumer in the fold and never reports a `consumer_receipt` obligation reconciled. Supply the roster from your own authorization layer through the store's `obligatedConsumers` option — `(input: { reporting_obligation_id, account_id }) => Promise<{ ids, complete }>` — and return `complete: true` to get accurate reconciled transitions. Because the conservative default holds a `consumer_receipt` obligation at `action_required` from first delivery, and the per-consumer receipt issues are deliberately not persisted, the reconciler restates that state once per obligation as a `RECEIPT_REQUIRED` issue anchored to the obligation's own `expected_at`. It names no principal and carries no receipt timing, so a degraded persisted health is never unexplained and the leak stays closed.
51
702
 
52
703
  ## Consumer status ingest
53
704