@adcp/sdk 14.0.0-rc.38 → 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 (600) 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/evidence.d.mts +1 -0
  79. package/dist/lib/reporting/evidence.d.ts +1 -0
  80. package/dist/lib/reporting/evidence.js +32 -3
  81. package/dist/lib/reporting/evidence.mjs +31 -3
  82. package/dist/lib/reporting/ledger/handler.d.mts +51 -1
  83. package/dist/lib/reporting/ledger/handler.d.ts +51 -1
  84. package/dist/lib/reporting/ledger/handler.js +306 -38
  85. package/dist/lib/reporting/ledger/handler.mjs +304 -37
  86. package/dist/lib/reporting/ledger/health.d.mts +12 -0
  87. package/dist/lib/reporting/ledger/health.d.ts +12 -0
  88. package/dist/lib/reporting/ledger/health.js +15 -2
  89. package/dist/lib/reporting/ledger/health.mjs +13 -1
  90. package/dist/lib/reporting/ledger/index.d.mts +3 -0
  91. package/dist/lib/reporting/ledger/index.d.ts +3 -0
  92. package/dist/lib/reporting/ledger/index.js +7 -1
  93. package/dist/lib/reporting/ledger/index.mjs +3 -0
  94. package/dist/lib/reporting/ledger/lifecycle.d.mts +31 -4
  95. package/dist/lib/reporting/ledger/lifecycle.d.ts +31 -4
  96. package/dist/lib/reporting/ledger/lifecycle.js +244 -37
  97. package/dist/lib/reporting/ledger/lifecycle.mjs +244 -37
  98. package/dist/lib/reporting/ledger/managed-postgres.d.mts +231 -0
  99. package/dist/lib/reporting/ledger/managed-postgres.d.ts +231 -0
  100. package/dist/lib/reporting/ledger/managed-postgres.js +1797 -0
  101. package/dist/lib/reporting/ledger/managed-postgres.mjs +1776 -0
  102. package/dist/lib/reporting/ledger/managed.d.mts +355 -0
  103. package/dist/lib/reporting/ledger/managed.d.ts +355 -0
  104. package/dist/lib/reporting/ledger/managed.js +661 -0
  105. package/dist/lib/reporting/ledger/managed.mjs +633 -0
  106. package/dist/lib/reporting/ledger/notification-activity.d.mts +222 -0
  107. package/dist/lib/reporting/ledger/notification-activity.d.ts +222 -0
  108. package/dist/lib/reporting/ledger/notification-activity.js +1139 -0
  109. package/dist/lib/reporting/ledger/notification-activity.mjs +1110 -0
  110. package/dist/lib/reporting/ledger/postgres.d.mts +161 -1
  111. package/dist/lib/reporting/ledger/postgres.d.ts +161 -1
  112. package/dist/lib/reporting/ledger/postgres.js +1153 -21
  113. package/dist/lib/reporting/ledger/postgres.mjs +1152 -21
  114. package/dist/lib/reporting/ledger/producer.d.mts +8 -0
  115. package/dist/lib/reporting/ledger/producer.d.ts +8 -0
  116. package/dist/lib/reporting/ledger/producer.js +146 -52
  117. package/dist/lib/reporting/ledger/producer.mjs +146 -51
  118. package/dist/lib/reporting/ledger/types.d.mts +358 -1
  119. package/dist/lib/reporting/ledger/types.d.ts +358 -1
  120. package/dist/lib/reporting/ledger/types.js +3 -0
  121. package/dist/lib/reporting/ledger/types.mjs +2 -0
  122. package/dist/lib/reporting/service/conformance.d.mts +35 -0
  123. package/dist/lib/reporting/service/conformance.d.ts +35 -0
  124. package/dist/lib/reporting/service/conformance.js +89 -0
  125. package/dist/lib/reporting/service/conformance.mjs +68 -0
  126. package/dist/lib/reporting/service/index.d.mts +182 -0
  127. package/dist/lib/reporting/service/index.d.ts +182 -0
  128. package/dist/lib/reporting/service/index.js +793 -0
  129. package/dist/lib/reporting/service/index.mjs +787 -0
  130. package/dist/lib/reporting/source/index.d.mts +2 -1
  131. package/dist/lib/reporting/source/index.d.ts +2 -1
  132. package/dist/lib/reporting/source/index.js +16 -3
  133. package/dist/lib/reporting/source/index.mjs +12 -1
  134. package/dist/lib/reporting/source/inline.d.mts +88 -1
  135. package/dist/lib/reporting/source/inline.d.ts +88 -1
  136. package/dist/lib/reporting/source/inline.js +1161 -163
  137. package/dist/lib/reporting/source/inline.mjs +1166 -163
  138. package/dist/lib/reporting/source/manifest.js +12 -7
  139. package/dist/lib/reporting/source/manifest.mjs +12 -7
  140. package/dist/lib/reporting/source/source.d.mts +26 -0
  141. package/dist/lib/reporting/source/source.d.ts +26 -0
  142. package/dist/lib/reporting/source/source.js +71 -2
  143. package/dist/lib/reporting/source/source.mjs +65 -1
  144. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  145. package/dist/lib/server/create-adcp-server.d.mts +18 -0
  146. package/dist/lib/server/create-adcp-server.d.ts +18 -0
  147. package/dist/lib/server/create-adcp-server.js +52 -2
  148. package/dist/lib/server/create-adcp-server.mjs +52 -2
  149. package/dist/lib/server/decisioning/account.d.mts +1 -1
  150. package/dist/lib/server/decisioning/account.d.ts +1 -1
  151. package/dist/lib/server/decisioning/index.d.mts +1 -0
  152. package/dist/lib/server/decisioning/index.d.ts +1 -0
  153. package/dist/lib/server/decisioning/platform.d.mts +3 -0
  154. package/dist/lib/server/decisioning/platform.d.ts +3 -0
  155. package/dist/lib/server/decisioning/runtime/from-platform.js +88 -10
  156. package/dist/lib/server/decisioning/runtime/from-platform.mjs +88 -10
  157. package/dist/lib/server/decisioning/specialisms/reporting.d.mts +38 -0
  158. package/dist/lib/server/decisioning/specialisms/reporting.d.ts +38 -0
  159. package/dist/lib/{types/v3-1-beta/index.js → server/decisioning/specialisms/reporting.js} +2 -8
  160. package/dist/lib/server/decisioning/specialisms/reporting.mjs +0 -0
  161. package/dist/lib/server/index.d.mts +4 -2
  162. package/dist/lib/server/index.d.ts +4 -2
  163. package/dist/lib/server/index.js +5 -0
  164. package/dist/lib/server/index.mjs +4 -0
  165. package/dist/lib/server/media-buy-action-resolver.d.mts +63 -0
  166. package/dist/lib/server/media-buy-action-resolver.d.ts +63 -0
  167. package/dist/lib/server/media-buy-action-resolver.js +297 -0
  168. package/dist/lib/server/media-buy-action-resolver.mjs +285 -0
  169. package/dist/lib/server/media-buy-actions.d.mts +7 -8
  170. package/dist/lib/server/media-buy-actions.d.ts +7 -8
  171. package/dist/lib/server/media-buy-actions.js +57 -14
  172. package/dist/lib/server/media-buy-actions.mjs +63 -14
  173. package/dist/lib/server/notification-subscriptions/index.d.mts +2 -2
  174. package/dist/lib/server/notification-subscriptions/index.d.ts +2 -2
  175. package/dist/lib/server/notification-subscriptions/index.js +2 -0
  176. package/dist/lib/server/notification-subscriptions/index.mjs +2 -0
  177. package/dist/lib/server/notification-subscriptions/postgres-runtime.d.mts +3 -1
  178. package/dist/lib/server/notification-subscriptions/postgres-runtime.d.ts +3 -1
  179. package/dist/lib/server/notification-subscriptions/postgres-runtime.js +1 -0
  180. package/dist/lib/server/notification-subscriptions/postgres-runtime.mjs +1 -0
  181. package/dist/lib/server/notification-subscriptions/runtime.d.mts +12 -1
  182. package/dist/lib/server/notification-subscriptions/runtime.d.ts +12 -1
  183. package/dist/lib/server/notification-subscriptions/runtime.js +102 -16
  184. package/dist/lib/server/notification-subscriptions/runtime.mjs +101 -16
  185. package/dist/lib/server/notification-subscriptions/types.d.mts +104 -1
  186. package/dist/lib/server/notification-subscriptions/types.d.ts +104 -1
  187. package/dist/lib/server/webhook-emitter.d.mts +40 -5
  188. package/dist/lib/server/webhook-emitter.d.ts +40 -5
  189. package/dist/lib/server/webhook-emitter.js +23 -12
  190. package/dist/lib/server/webhook-emitter.mjs +23 -12
  191. package/dist/lib/storage/interfaces.d.mts +14 -2
  192. package/dist/lib/storage/interfaces.d.ts +14 -2
  193. package/dist/lib/testing/compliance/comply.d.mts +9 -3
  194. package/dist/lib/testing/compliance/comply.d.ts +9 -3
  195. package/dist/lib/testing/compliance/comply.js +158 -3
  196. package/dist/lib/testing/compliance/comply.mjs +159 -4
  197. package/dist/lib/testing/compliance/storyboard-tracks.js +7 -5
  198. package/dist/lib/testing/compliance/storyboard-tracks.mjs +7 -5
  199. package/dist/lib/testing/compliance/summary.js +10 -1
  200. package/dist/lib/testing/compliance/summary.mjs +10 -1
  201. package/dist/lib/testing/index.d.mts +1 -1
  202. package/dist/lib/testing/index.d.ts +1 -1
  203. package/dist/lib/testing/index.js +2 -0
  204. package/dist/lib/testing/index.mjs +3 -1
  205. package/dist/lib/testing/storyboard/agent-routing.d.mts +12 -43
  206. package/dist/lib/testing/storyboard/agent-routing.d.ts +12 -43
  207. package/dist/lib/testing/storyboard/agent-routing.js +75 -13
  208. package/dist/lib/testing/storyboard/agent-routing.mjs +70 -12
  209. package/dist/lib/testing/storyboard/index.d.mts +1 -0
  210. package/dist/lib/testing/storyboard/index.d.ts +1 -0
  211. package/dist/lib/testing/storyboard/index.js +3 -0
  212. package/dist/lib/testing/storyboard/index.mjs +2 -0
  213. package/dist/lib/testing/storyboard/junit.d.mts +1 -1
  214. package/dist/lib/testing/storyboard/junit.d.ts +1 -1
  215. package/dist/lib/testing/storyboard/junit.js +21 -2
  216. package/dist/lib/testing/storyboard/junit.mjs +21 -2
  217. package/dist/lib/testing/storyboard/probes.d.mts +130 -0
  218. package/dist/lib/testing/storyboard/probes.d.ts +130 -0
  219. package/dist/lib/testing/storyboard/probes.js +682 -10
  220. package/dist/lib/testing/storyboard/probes.mjs +683 -3
  221. package/dist/lib/testing/storyboard/request-signing/builder.d.mts +16 -5
  222. package/dist/lib/testing/storyboard/request-signing/builder.d.ts +16 -5
  223. package/dist/lib/testing/storyboard/request-signing/grader.d.mts +44 -1
  224. package/dist/lib/testing/storyboard/request-signing/grader.d.ts +44 -1
  225. package/dist/lib/testing/storyboard/request-signing/grader.js +46 -22
  226. package/dist/lib/testing/storyboard/request-signing/grader.mjs +43 -21
  227. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.mts +86 -11
  228. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.d.ts +86 -11
  229. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.js +119 -25
  230. package/dist/lib/testing/storyboard/request-signing/probe-dispatch.mjs +117 -26
  231. package/dist/lib/testing/storyboard/request-signing/synthesize.js +22 -2
  232. package/dist/lib/testing/storyboard/request-signing/synthesize.mjs +22 -2
  233. package/dist/lib/testing/storyboard/runner.d.mts +103 -0
  234. package/dist/lib/testing/storyboard/runner.d.ts +103 -0
  235. package/dist/lib/testing/storyboard/runner.js +1090 -268
  236. package/dist/lib/testing/storyboard/runner.mjs +1096 -261
  237. package/dist/lib/testing/storyboard/seeding.d.mts +4 -1
  238. package/dist/lib/testing/storyboard/seeding.d.ts +4 -1
  239. package/dist/lib/testing/storyboard/seeding.js +55 -24
  240. package/dist/lib/testing/storyboard/seeding.mjs +55 -24
  241. package/dist/lib/testing/storyboard/test-kit.d.mts +37 -3
  242. package/dist/lib/testing/storyboard/test-kit.d.ts +37 -3
  243. package/dist/lib/testing/storyboard/test-kit.js +8 -2
  244. package/dist/lib/testing/storyboard/test-kit.mjs +7 -2
  245. package/dist/lib/testing/storyboard/types.d.mts +132 -10
  246. package/dist/lib/testing/storyboard/types.d.ts +132 -10
  247. package/dist/lib/testing/storyboard/types.js +17 -0
  248. package/dist/lib/testing/storyboard/types.mjs +16 -0
  249. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  250. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  251. package/dist/lib/testing/storyboard/validations.js +16 -0
  252. package/dist/lib/testing/storyboard/validations.mjs +16 -0
  253. package/dist/lib/testing/test-controller.d.mts +1 -1
  254. package/dist/lib/testing/test-controller.d.ts +1 -1
  255. package/dist/lib/types/buy-products.d.ts +71 -6
  256. package/dist/lib/types/control-media-buy.d.ts +25 -11
  257. package/dist/lib/types/core.generated.d.mts +69 -10
  258. package/dist/lib/types/core.generated.d.ts +69 -10
  259. package/dist/lib/types/create-media-buy.d.ts +71 -6
  260. package/dist/lib/types/forward-compat-error-codes.d.mts +7 -6
  261. package/dist/lib/types/forward-compat-error-codes.d.ts +7 -6
  262. package/dist/lib/types/index.d.mts +2 -2
  263. package/dist/lib/types/index.d.ts +2 -2
  264. package/dist/lib/types/schemas.generated.d.ts +8040 -8116
  265. package/dist/lib/types/schemas.generated.js +1074 -309
  266. package/dist/lib/types/schemas.generated.mjs +1068 -309
  267. package/dist/lib/types/tools.generated.d.mts +65 -6
  268. package/dist/lib/types/tools.generated.d.ts +65 -6
  269. package/dist/lib/types/update-media-buy.d.ts +71 -6
  270. package/dist/lib/utils/redact-secrets.d.mts +1 -1
  271. package/dist/lib/utils/redact-secrets.d.ts +1 -1
  272. package/dist/lib/utils/redact-secrets.js +1 -1
  273. package/dist/lib/utils/redact-secrets.mjs +1 -1
  274. package/dist/lib/v2/projection/cache-versions.d.mts +0 -1
  275. package/dist/lib/v2/projection/cache-versions.d.ts +0 -1
  276. package/dist/lib/v2/projection/cache-versions.js +1 -15
  277. package/dist/lib/v2/projection/cache-versions.mjs +1 -14
  278. package/dist/lib/v2/projection/canonical-properties.js +2 -2
  279. package/dist/lib/v2/projection/canonical-properties.mjs +3 -3
  280. package/dist/lib/v2/projection/registry.d.mts +1 -1
  281. package/dist/lib/v2/projection/registry.d.ts +1 -1
  282. package/dist/lib/v2/projection/registry.js +2 -2
  283. package/dist/lib/v2/projection/registry.mjs +3 -3
  284. package/dist/lib/version.d.mts +5 -5
  285. package/dist/lib/version.d.ts +5 -5
  286. package/dist/lib/version.js +3 -8
  287. package/dist/lib/version.mjs +3 -8
  288. package/dist/lib/wholesale-feed-sync/index.d.mts +1 -0
  289. package/dist/lib/wholesale-feed-sync/index.d.ts +1 -0
  290. package/dist/lib/wholesale-feed-sync/protocol-types.d.mts +42 -0
  291. package/dist/lib/wholesale-feed-sync/protocol-types.d.ts +42 -0
  292. package/dist/lib/wholesale-feed-sync/protocol-types.js +16 -0
  293. package/dist/lib/wholesale-feed-sync/protocol-types.mjs +0 -0
  294. package/dist/lib/wholesale-feed-sync/sync.d.mts +6 -6
  295. package/dist/lib/wholesale-feed-sync/sync.d.ts +6 -6
  296. package/dist/lib/wholesale-feed-sync/sync.js +3 -1
  297. package/dist/lib/wholesale-feed-sync/sync.mjs +3 -1
  298. package/dist/lib/wholesale-feed-sync/types.d.mts +15 -14
  299. package/dist/lib/wholesale-feed-sync/types.d.ts +15 -14
  300. package/dist/lib/wholesale-feed-sync/webhook-notification.d.mts +7 -5
  301. package/dist/lib/wholesale-feed-sync/webhook-notification.d.ts +7 -5
  302. package/dist/lib/wholesale-feed-sync/webhook-notification.js +45 -0
  303. package/dist/lib/wholesale-feed-sync/webhook-notification.mjs +44 -0
  304. package/docs/CLI.md +1 -1
  305. package/docs/TYPE-SUMMARY.md +118 -4
  306. package/docs/guides/ASYNC-API-REFERENCE.md +62 -1
  307. package/docs/guides/ASYNC-DEVELOPER-GUIDE.md +25 -0
  308. package/docs/guides/ASYNC-DOCUMENTATION-INDEX.md +3 -0
  309. package/docs/guides/BUYER-QUICKSTART-3.2.md +5 -0
  310. package/docs/guides/DURABLE-BUYER-WRITES.md +337 -0
  311. package/docs/guides/EXISTING-PLATFORM.md +35 -0
  312. package/docs/guides/MEDIA-BUY-3.2-COMPATIBILITY.md +7 -3
  313. package/docs/guides/MEDIA-BUY-ACTION-ASSESSMENT.md +300 -0
  314. package/docs/guides/REPORTING-LEDGER.md +654 -3
  315. package/docs/guides/REPORTING-SOURCE-EXECUTOR.md +63 -1
  316. package/docs/guides/SELLER-QUICKSTART-3.2.md +2 -0
  317. package/docs/guides/VALIDATE-YOUR-AGENT.md +35 -0
  318. package/docs/guides/idempotency-crash-recovery.md +4 -0
  319. package/docs/llms.txt +4 -2
  320. package/docs/migration-14.x-rc-worksheet.md +5 -5
  321. package/docs/migration-7.9-to-7.10.md +7 -5
  322. package/examples/README.md +17 -0
  323. package/examples/durable-buyer-writes/caller.ts +745 -0
  324. package/examples/durable-buyer-writes/worker.ts +240 -0
  325. package/examples/reliable-reporting-service/README.md +100 -0
  326. package/examples/seller-3.2-starter.ts +7 -8
  327. package/examples/targeting-input-existing-platform.ts +153 -0
  328. package/package.json +41 -29
  329. package/skills/build-seller-agent/specialisms/signed-requests.md +11 -1
  330. package/skills/cross-cutting.md +28 -11
  331. package/compliance/cache/3.1.0-beta.7/domains/brand/index.yaml +0 -160
  332. package/compliance/cache/3.1.0-beta.7/domains/brand/scenarios/distributed_brand_resolution.yaml +0 -415
  333. package/compliance/cache/3.1.0-beta.7/domains/brand/scenarios/single_side_trust_extension.yaml +0 -454
  334. package/compliance/cache/3.1.0-beta.7/domains/creative/index.yaml +0 -339
  335. package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/billing_out_of_band.yaml +0 -153
  336. package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/creative_lifecycle_webhooks.yaml +0 -389
  337. package/compliance/cache/3.1.0-beta.7/domains/creative/scenarios/native_in_feed.yaml +0 -543
  338. package/compliance/cache/3.1.0-beta.7/domains/governance/index.yaml +0 -682
  339. package/compliance/cache/3.1.0-beta.7/domains/media-buy/index.yaml +0 -781
  340. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/audience_buy_flow.yaml +0 -380
  341. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/available_actions.yaml +0 -565
  342. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/billing_finality_delivery.yaml +0 -354
  343. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/canonical_formats.yaml +0 -711
  344. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/clicks_buy_flow.yaml +0 -264
  345. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/completed_views_buy_flow.yaml +0 -344
  346. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/create_media_buy_async.yaml +0 -234
  347. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/creative_fate_after_cancellation.yaml +0 -419
  348. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/creative_reception.yaml +0 -247
  349. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/delivery_reporting.yaml +0 -357
  350. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/dependency_impairment.yaml +0 -633
  351. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/dependency_impairment_cardinality.yaml +0 -800
  352. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/event_dedup_flow.yaml +0 -399
  353. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/frequency_cap_enforcement.yaml +0 -309
  354. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_approved.yaml +0 -214
  355. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_conditions.yaml +0 -199
  356. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_denied.yaml +0 -204
  357. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/governance_denied_recovery.yaml +0 -252
  358. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/invalid_transitions.yaml +0 -289
  359. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/inventory_list_no_match.yaml +0 -148
  360. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/inventory_list_targeting.yaml +0 -276
  361. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/measurement_accountability.yaml +0 -244
  362. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/measurement_terms_rejected.yaml +0 -203
  363. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/pending_creatives_to_start.yaml +0 -274
  364. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/per_creative_conversion_attribution.yaml +0 -500
  365. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/performance_buy_flow.yaml +0 -428
  366. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/performance_buy_flow_roas.yaml +0 -470
  367. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/product_signal_targeting.yaml +0 -373
  368. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_finalize.yaml +0 -399
  369. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_finalize_asap_timing.yaml +0 -264
  370. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/proposal_not_found_errors.yaml +0 -257
  371. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_audit_observation.yaml +0 -333
  372. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_enforcement.yaml +0 -517
  373. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/provenance_truth_of_claim.yaml +0 -294
  374. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/reach_buy_flow.yaml +0 -823
  375. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/refine_finalize_exclusivity.yaml +0 -360
  376. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/refine_products.yaml +0 -148
  377. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_accountability.yaml +0 -293
  378. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +0 -307
  379. package/compliance/cache/3.1.0-beta.7/domains/media-buy/scenarios/vendor_metric_optimization_flow.yaml +0 -576
  380. package/compliance/cache/3.1.0-beta.7/domains/media-buy/state-machine.yaml +0 -442
  381. package/compliance/cache/3.1.0-beta.7/domains/signals/index.yaml +0 -266
  382. package/compliance/cache/3.1.0-beta.7/domains/sponsored-intelligence/index.yaml +0 -256
  383. package/compliance/cache/3.1.0-beta.7/index.json +0 -356
  384. package/compliance/cache/3.1.0-beta.7/protocols/brand/index.yaml +0 -160
  385. package/compliance/cache/3.1.0-beta.7/protocols/brand/scenarios/distributed_brand_resolution.yaml +0 -415
  386. package/compliance/cache/3.1.0-beta.7/protocols/brand/scenarios/single_side_trust_extension.yaml +0 -454
  387. package/compliance/cache/3.1.0-beta.7/protocols/creative/index.yaml +0 -339
  388. package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/billing_out_of_band.yaml +0 -153
  389. package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/creative_lifecycle_webhooks.yaml +0 -389
  390. package/compliance/cache/3.1.0-beta.7/protocols/creative/scenarios/native_in_feed.yaml +0 -543
  391. package/compliance/cache/3.1.0-beta.7/protocols/governance/index.yaml +0 -682
  392. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/index.yaml +0 -781
  393. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/audience_buy_flow.yaml +0 -380
  394. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/available_actions.yaml +0 -565
  395. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/billing_finality_delivery.yaml +0 -354
  396. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/canonical_formats.yaml +0 -711
  397. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/clicks_buy_flow.yaml +0 -264
  398. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/completed_views_buy_flow.yaml +0 -344
  399. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/create_media_buy_async.yaml +0 -234
  400. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/creative_fate_after_cancellation.yaml +0 -419
  401. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/creative_reception.yaml +0 -247
  402. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/delivery_reporting.yaml +0 -357
  403. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/dependency_impairment.yaml +0 -633
  404. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/dependency_impairment_cardinality.yaml +0 -800
  405. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/event_dedup_flow.yaml +0 -399
  406. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/frequency_cap_enforcement.yaml +0 -309
  407. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_approved.yaml +0 -214
  408. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_conditions.yaml +0 -199
  409. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_denied.yaml +0 -204
  410. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/governance_denied_recovery.yaml +0 -252
  411. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/invalid_transitions.yaml +0 -289
  412. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/inventory_list_no_match.yaml +0 -148
  413. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/inventory_list_targeting.yaml +0 -276
  414. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/measurement_accountability.yaml +0 -244
  415. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/measurement_terms_rejected.yaml +0 -203
  416. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/pending_creatives_to_start.yaml +0 -274
  417. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/per_creative_conversion_attribution.yaml +0 -500
  418. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/performance_buy_flow.yaml +0 -428
  419. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/performance_buy_flow_roas.yaml +0 -470
  420. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/product_signal_targeting.yaml +0 -373
  421. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_finalize.yaml +0 -399
  422. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_finalize_asap_timing.yaml +0 -264
  423. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/proposal_not_found_errors.yaml +0 -257
  424. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_audit_observation.yaml +0 -333
  425. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_enforcement.yaml +0 -517
  426. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/provenance_truth_of_claim.yaml +0 -294
  427. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/reach_buy_flow.yaml +0 -823
  428. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/refine_finalize_exclusivity.yaml +0 -360
  429. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/refine_products.yaml +0 -148
  430. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_accountability.yaml +0 -293
  431. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_catalog_precondition.yaml +0 -307
  432. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/scenarios/vendor_metric_optimization_flow.yaml +0 -576
  433. package/compliance/cache/3.1.0-beta.7/protocols/media-buy/state-machine.yaml +0 -442
  434. package/compliance/cache/3.1.0-beta.7/protocols/signals/index.yaml +0 -266
  435. package/compliance/cache/3.1.0-beta.7/protocols/sponsored-intelligence/index.yaml +0 -256
  436. package/compliance/cache/3.1.0-beta.7/specialisms/audience-sync/index.yaml +0 -313
  437. package/compliance/cache/3.1.0-beta.7/specialisms/brand-rights/index.yaml +0 -350
  438. package/compliance/cache/3.1.0-beta.7/specialisms/brand-rights/scenarios/governance_denied.yaml +0 -226
  439. package/compliance/cache/3.1.0-beta.7/specialisms/collection-lists/index.yaml +0 -359
  440. package/compliance/cache/3.1.0-beta.7/specialisms/content-standards/index.yaml +0 -572
  441. package/compliance/cache/3.1.0-beta.7/specialisms/creative-ad-server/index.yaml +0 -409
  442. package/compliance/cache/3.1.0-beta.7/specialisms/creative-generative/generative-seller.yaml +0 -807
  443. package/compliance/cache/3.1.0-beta.7/specialisms/creative-generative/index.yaml +0 -758
  444. package/compliance/cache/3.1.0-beta.7/specialisms/creative-template/index.yaml +0 -510
  445. package/compliance/cache/3.1.0-beta.7/specialisms/governance-aware-seller/index.yaml +0 -143
  446. package/compliance/cache/3.1.0-beta.7/specialisms/governance-aware-seller/scenarios/governance_multi_agent_rejected.yaml +0 -117
  447. package/compliance/cache/3.1.0-beta.7/specialisms/governance-delivery-monitor/index.yaml +0 -441
  448. package/compliance/cache/3.1.0-beta.7/specialisms/governance-spend-authority/denied.yaml +0 -221
  449. package/compliance/cache/3.1.0-beta.7/specialisms/governance-spend-authority/index.yaml +0 -330
  450. package/compliance/cache/3.1.0-beta.7/specialisms/property-lists/index.yaml +0 -482
  451. package/compliance/cache/3.1.0-beta.7/specialisms/sales-broadcast-tv/index.yaml +0 -738
  452. package/compliance/cache/3.1.0-beta.7/specialisms/sales-catalog-driven/index.yaml +0 -840
  453. package/compliance/cache/3.1.0-beta.7/specialisms/sales-guaranteed/index.yaml +0 -601
  454. package/compliance/cache/3.1.0-beta.7/specialisms/sales-non-guaranteed/index.yaml +0 -546
  455. package/compliance/cache/3.1.0-beta.7/specialisms/sales-proposal-mode/index.yaml +0 -586
  456. package/compliance/cache/3.1.0-beta.7/specialisms/sales-social/index.yaml +0 -919
  457. package/compliance/cache/3.1.0-beta.7/specialisms/signal-marketplace/index.yaml +0 -424
  458. package/compliance/cache/3.1.0-beta.7/specialisms/signal-marketplace/scenarios/governance_denied.yaml +0 -211
  459. package/compliance/cache/3.1.0-beta.7/specialisms/signal-owned/index.yaml +0 -317
  460. package/compliance/cache/3.1.0-beta.7/specialisms/sponsored-intelligence/index.yaml +0 -59
  461. package/compliance/cache/3.1.0-beta.7/test-kits/acme-outdoor-live.yaml +0 -78
  462. package/compliance/cache/3.1.0-beta.7/test-kits/acme-outdoor.yaml +0 -223
  463. package/compliance/cache/3.1.0-beta.7/test-kits/billing-gate-runner.yaml +0 -115
  464. package/compliance/cache/3.1.0-beta.7/test-kits/bistro-oranje.yaml +0 -126
  465. package/compliance/cache/3.1.0-beta.7/test-kits/distributed-brand-runner.yaml +0 -281
  466. package/compliance/cache/3.1.0-beta.7/test-kits/nova-motors.yaml +0 -262
  467. package/compliance/cache/3.1.0-beta.7/test-kits/osei-natural.yaml +0 -126
  468. package/compliance/cache/3.1.0-beta.7/test-kits/parallel-dispatch-runner.yaml +0 -196
  469. package/compliance/cache/3.1.0-beta.7/test-kits/rate-limit-trip-runner.yaml +0 -172
  470. package/compliance/cache/3.1.0-beta.7/test-kits/signed-requests-runner.yaml +0 -155
  471. package/compliance/cache/3.1.0-beta.7/test-kits/single-side-trust-runner.yaml +0 -294
  472. package/compliance/cache/3.1.0-beta.7/test-kits/substitution-observer-runner.yaml +0 -688
  473. package/compliance/cache/3.1.0-beta.7/test-kits/summit-foods.yaml +0 -125
  474. package/compliance/cache/3.1.0-beta.7/test-kits/webhook-receiver-runner.yaml +0 -265
  475. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/001-minimal-plan.json +0 -43
  476. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/002-full-plan.json +0 -217
  477. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/003-bookkeeping-stripped.json +0 -60
  478. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/004a-human-review-omitted.json +0 -43
  479. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/004b-human-review-explicit-null.json +0 -49
  480. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/005a-policy-categories-order-1.json +0 -53
  481. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/005b-policy-categories-order-2.json +0 -57
  482. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/006a-ext-trace-v1.json +0 -49
  483. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/006b-ext-trace-v2.json +0 -53
  484. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/007-unicode-objectives.json +0 -43
  485. package/compliance/cache/3.1.0-beta.7/test-vectors/plan-hash/008-numeric-canonicalization.json +0 -65
  486. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/README.md +0 -220
  487. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/canonicalization.json +0 -241
  488. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/keys.json +0 -60
  489. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/001-no-signature-header.json +0 -24
  490. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/002-wrong-tag.json +0 -26
  491. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/003-expired-signature.json +0 -26
  492. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/004-window-too-long.json +0 -26
  493. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/005-alg-not-allowed.json +0 -26
  494. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/006-missing-covered-component.json +0 -26
  495. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/007-missing-content-digest.json +0 -26
  496. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/008-unknown-keyid.json +0 -26
  497. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/009-key-ops-missing-verify.json +0 -27
  498. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/010-content-digest-mismatch.json +0 -33
  499. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/011-malformed-header.json +0 -27
  500. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/012-missing-expires-param.json +0 -26
  501. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/013-expires-le-created.json +0 -27
  502. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/014-missing-nonce-param.json +0 -27
  503. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/015-signature-invalid.json +0 -28
  504. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/016-replayed-nonce.json +0 -35
  505. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/017-key-revoked.json +0 -38
  506. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/018-digest-covered-when-forbidden.json +0 -28
  507. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/019-signature-without-signature-input.json +0 -26
  508. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/020-rate-abuse.json +0 -34
  509. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/021-duplicate-signature-input-label.json +0 -31
  510. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/022-multi-valued-content-type.json +0 -31
  511. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/023-multi-valued-content-digest.json +0 -32
  512. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/024-unquoted-string-param.json +0 -31
  513. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/025-jwk-alg-crv-mismatch.json +0 -43
  514. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/026-non-ascii-host.json +0 -31
  515. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/027-webhook-registration-authentication-unsigned.json +0 -25
  516. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/negative/028-unsigned-protocol-method-required.json +0 -26
  517. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/001-basic-post.json +0 -30
  518. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/002-post-with-content-digest.json +0 -31
  519. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/003-es256-post.json +0 -30
  520. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/004-multiple-signature-labels.json +0 -26
  521. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/005-default-port-stripped.json +0 -30
  522. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/006-dot-segment-path.json +0 -30
  523. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/007-query-byte-preserved.json +0 -30
  524. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/008-percent-encoded-path.json +0 -30
  525. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/009-percent-encoded-unreserved-decoded.json +0 -30
  526. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/010-percent-encoded-slash-preserved.json +0 -30
  527. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/011-ipv6-authority.json +0 -30
  528. package/compliance/cache/3.1.0-beta.7/test-vectors/request-signing/positive/012-ipv6-authority-default-port-stripped.json +0 -30
  529. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/README.md +0 -211
  530. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/keys.json +0 -61
  531. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/001-wrong-tag.json +0 -26
  532. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/002-expired-signature.json +0 -26
  533. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/003-window-too-long.json +0 -26
  534. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/004-alg-not-allowed.json +0 -26
  535. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/005-missing-authority-component.json +0 -26
  536. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/006-missing-content-digest.json +0 -25
  537. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/007-unknown-keyid.json +0 -26
  538. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/008-wrong-adcp-use.json +0 -26
  539. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/009-content-digest-mismatch.json +0 -26
  540. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/010-malformed-signature-input.json +0 -26
  541. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/011-signature-without-input.json +0 -25
  542. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/012-missing-expires-param.json +0 -26
  543. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/013-expires-le-created.json +0 -26
  544. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/014-missing-nonce-param.json +0 -26
  545. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/015-signature-invalid.json +0 -26
  546. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/016-replayed-nonce.json +0 -37
  547. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/017-key-revoked.json +0 -32
  548. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/018-rate-abuse.json +0 -33
  549. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/019-revocation-stale.json +0 -32
  550. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/020-key-ops-missing-verify.json +0 -41
  551. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/negative/021-base64-alphabet-mixing.json +0 -26
  552. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/001-basic-post.json +0 -24
  553. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/002-es256-post.json +0 -24
  554. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/003-multiple-signature-labels.json +0 -24
  555. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/004-default-port-stripped.json +0 -24
  556. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/005-percent-encoded-path.json +0 -24
  557. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/006-query-byte-preserved.json +0 -24
  558. package/compliance/cache/3.1.0-beta.7/test-vectors/webhook-signing/positive/007-body-without-idempotency-key.json +0 -25
  559. package/compliance/cache/3.1.0-beta.7/universal/billing-gate-dispatch.yaml +0 -450
  560. package/compliance/cache/3.1.0-beta.7/universal/canonical-format-validate-input.yaml +0 -640
  561. package/compliance/cache/3.1.0-beta.7/universal/capability-discovery.yaml +0 -125
  562. package/compliance/cache/3.1.0-beta.7/universal/collection-lists-pagination-integrity.yaml +0 -306
  563. package/compliance/cache/3.1.0-beta.7/universal/comply-controller-mode-gate.yaml +0 -141
  564. package/compliance/cache/3.1.0-beta.7/universal/content-standards-pagination-integrity.yaml +0 -326
  565. package/compliance/cache/3.1.0-beta.7/universal/deterministic-testing.yaml +0 -1430
  566. package/compliance/cache/3.1.0-beta.7/universal/error-compliance-signals.yaml +0 -377
  567. package/compliance/cache/3.1.0-beta.7/universal/error-compliance.yaml +0 -528
  568. package/compliance/cache/3.1.0-beta.7/universal/fictional-entities.yaml +0 -307
  569. package/compliance/cache/3.1.0-beta.7/universal/get-media-buys-pagination-integrity.yaml +0 -160
  570. package/compliance/cache/3.1.0-beta.7/universal/get-signals-pagination-integrity.yaml +0 -211
  571. package/compliance/cache/3.1.0-beta.7/universal/idempotency.yaml +0 -861
  572. package/compliance/cache/3.1.0-beta.7/universal/notification-config-event-scope.yaml +0 -119
  573. package/compliance/cache/3.1.0-beta.7/universal/notification-config-lifecycle.yaml +0 -337
  574. package/compliance/cache/3.1.0-beta.7/universal/notification-config-rejections.yaml +0 -107
  575. package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity-creative-formats.yaml +0 -265
  576. package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity-list-accounts.yaml +0 -245
  577. package/compliance/cache/3.1.0-beta.7/universal/pagination-integrity.yaml +0 -263
  578. package/compliance/cache/3.1.0-beta.7/universal/property-lists-pagination-integrity.yaml +0 -307
  579. package/compliance/cache/3.1.0-beta.7/universal/read-tool-idempotency.yaml +0 -405
  580. package/compliance/cache/3.1.0-beta.7/universal/runner-output-contract.yaml +0 -1266
  581. package/compliance/cache/3.1.0-beta.7/universal/schema-validation-signals.yaml +0 -181
  582. package/compliance/cache/3.1.0-beta.7/universal/schema-validation.yaml +0 -548
  583. package/compliance/cache/3.1.0-beta.7/universal/security.yaml +0 -539
  584. package/compliance/cache/3.1.0-beta.7/universal/signed-requests.yaml +0 -217
  585. package/compliance/cache/3.1.0-beta.7/universal/stale-response-advisory.yaml +0 -295
  586. package/compliance/cache/3.1.0-beta.7/universal/storyboard-schema.yaml +0 -2136
  587. package/compliance/cache/3.1.0-beta.7/universal/v3-envelope-integrity.yaml +0 -117
  588. package/compliance/cache/3.1.0-beta.7/universal/version-negotiation.yaml +0 -130
  589. package/compliance/cache/3.1.0-beta.7/universal/webhook-emission.yaml +0 -411
  590. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-bulk-webhooks.yaml +0 -82
  591. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-product-webhooks.yaml +0 -83
  592. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-products.yaml +0 -151
  593. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-signal-webhooks.yaml +0 -83
  594. package/compliance/cache/3.1.0-beta.7/universal/wholesale-feed-signals.yaml +0 -149
  595. package/dist/lib/types/v3-1-beta/index.d.mts +0 -1
  596. package/dist/lib/types/v3-1-beta/index.d.ts +0 -1
  597. package/dist/lib/types/v3-1-beta/index.mjs +0 -1
  598. package/dist/lib/types/v3-1-beta/tools.generated.d.mts +0 -26788
  599. package/dist/lib/types/v3-1-beta/tools.generated.d.ts +0 -26788
  600. /package/dist/lib/{types/v3-1-beta/tools.generated.mjs → media-buy/action-types.mjs} +0 -0
@@ -19,6 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var postgres_exports = {};
20
20
  __export(postgres_exports, {
21
21
  PostgresReportingLedgerStore: () => PostgresReportingLedgerStore,
22
+ REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT: () => REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT,
23
+ REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION: () => REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
22
24
  REPORTING_LEDGER_MIGRATION: () => REPORTING_LEDGER_MIGRATION,
23
25
  ReportingLedgerContinuityError: () => ReportingLedgerContinuityError,
24
26
  ReportingLedgerLeaseLostError: () => ReportingLedgerLeaseLostError,
@@ -35,6 +37,8 @@ var import_health = require('./health.js');
35
37
  var import_instant = require('./instant.js');
36
38
  var import_types = require('./types.js');
37
39
  var import_types2 = require('./types.js');
40
+ var import_handler = require('./handler.js');
41
+ var import_producer = require('./producer.js');
38
42
  var import_consumer_status_identity = require('./consumer-status-identity.js');
39
43
  const REPORTING_LEDGER_MIGRATION = `
40
44
  CREATE TABLE IF NOT EXISTS adcp_reporting_configurations (
@@ -182,6 +186,12 @@ CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_scope
182
186
  ON adcp_reporting_consumer_statuses (account_id, consumer_id, created_at, consumer_status_id);
183
187
  CREATE UNIQUE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_current
184
188
  ON adcp_reporting_consumer_statuses (account_id, consumer_id, chain_key) WHERE is_current;
189
+ -- The managed state digest is read inside the apply transaction, under the
190
+ -- account lock. Filtering this table by obligation alone matched no index, so
191
+ -- every apply scanned every consumer status in the deployment while holding
192
+ -- that lock and pushed concurrent Core writes past their lock timeout.
193
+ CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_obligation
194
+ ON adcp_reporting_consumer_statuses (obligation_id, created_at, consumer_status_id);
185
195
 
186
196
  CREATE TABLE IF NOT EXISTS adcp_reporting_consumer_status_batches (
187
197
  account_id TEXT NOT NULL,
@@ -206,6 +216,29 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_issues (
206
216
  CREATE INDEX IF NOT EXISTS adcp_reporting_issues_obligation
207
217
  ON adcp_reporting_issues (obligation_id, observed_at, issue_id);
208
218
 
219
+ -- Per-obligation reconciliation watermark. A reconcile that changes nothing
220
+ -- still has to record that it looked, or a change with no health effect stays
221
+ -- a candidate forever and starves newer work behind it. Core, not managed:
222
+ -- the lifecycle sweep is a Core concern and must not depend on the add-on.
223
+ CREATE TABLE IF NOT EXISTS adcp_reporting_lifecycle_state (
224
+ obligation_id TEXT PRIMARY KEY REFERENCES adcp_reporting_obligations(obligation_id),
225
+ processed_state_version TEXT,
226
+ processed_roster_version TEXT,
227
+ processed_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
228
+ );
229
+ CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_processed
230
+ ON adcp_reporting_lifecycle_state (processed_at, obligation_id);
231
+ ALTER TABLE adcp_reporting_lifecycle_state
232
+ ADD COLUMN IF NOT EXISTS current_roster_version TEXT;
233
+ ALTER TABLE adcp_reporting_lifecycle_state
234
+ ADD COLUMN IF NOT EXISTS failure_count INTEGER NOT NULL DEFAULT 0;
235
+ ALTER TABLE adcp_reporting_lifecycle_state
236
+ ADD COLUMN IF NOT EXISTS next_attempt_at TIMESTAMPTZ;
237
+ ALTER TABLE adcp_reporting_lifecycle_state
238
+ ADD COLUMN IF NOT EXISTS roster_refreshed_at TIMESTAMPTZ;
239
+ CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_roster_refreshed
240
+ ON adcp_reporting_lifecycle_state (roster_refreshed_at, obligation_id);
241
+
209
242
  CREATE TABLE IF NOT EXISTS adcp_reporting_transitions (
210
243
  transition_id TEXT PRIMARY KEY,
211
244
  transition_sequence BIGSERIAL NOT NULL UNIQUE,
@@ -245,7 +278,23 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_checkpoints (
245
278
  CREATE INDEX IF NOT EXISTS adcp_reporting_checkpoints_expiry
246
279
  ON adcp_reporting_checkpoints (expires_at);
247
280
  `.trim();
281
+ const REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT = "adcp_reporting_transitions_finality_recorded";
282
+ const REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION = `
283
+ DO $$
284
+ BEGIN
285
+ IF NOT EXISTS (
286
+ SELECT 1 FROM pg_constraint
287
+ WHERE conrelid = 'adcp_reporting_transitions'::regclass
288
+ AND conname = '${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}'
289
+ ) THEN
290
+ ALTER TABLE adcp_reporting_transitions
291
+ ADD CONSTRAINT ${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}
292
+ CHECK (data ? 'finality') NOT VALID;
293
+ END IF;
294
+ END $$;
295
+ `.trim();
248
296
  const MAX_SNAPSHOT_ITEMS = 1e4;
297
+ const MAX_LIFECYCLE_RECEIPT_LEAVES = 1e5;
249
298
  const MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
250
299
  const MAX_ACTIVE_SNAPSHOTS_PER_ACCOUNT = 32;
251
300
  const MAX_ACTIVE_SNAPSHOT_BYTES_PER_ACCOUNT = 128 * 1024 * 1024;
@@ -300,6 +349,9 @@ async function sweepExpiredReportingLedgerState(pool, limit = 1e3) {
300
349
  class PostgresReportingLedgerStore {
301
350
  constructor(pool, options = {}) {
302
351
  this.pool = pool;
352
+ this.managedDelivery = options.managedDelivery === true;
353
+ this.obligatedConsumers = options.obligatedConsumers;
354
+ this[import_types.REPORTING_LEDGER_AUTHORITY] = { substrate: pool, managedDelivery: this.managedDelivery };
303
355
  const development = process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development";
304
356
  if (!development && !options.acknowledgeIsolatedDatabase) {
305
357
  throw new Error(
@@ -307,6 +359,8 @@ class PostgresReportingLedgerStore {
307
359
  );
308
360
  }
309
361
  this.consumerMismatchEscalation = (0, import_health.assertReportingConsumerMismatchEscalation)(options.consumerMismatchEscalation);
362
+ this.notificationActivityPort = options.notificationActivityPort;
363
+ this.transactionalNotificationActivity = options.notificationActivityPort !== void 0;
310
364
  }
311
365
  pool;
312
366
  /**
@@ -317,6 +371,11 @@ class PostgresReportingLedgerStore {
317
371
  * contradict the summary.
318
372
  */
319
373
  consumerMismatchEscalation;
374
+ [import_types.REPORTING_LEDGER_AUTHORITY];
375
+ managedDelivery;
376
+ obligatedConsumers;
377
+ transactionalNotificationActivity;
378
+ notificationActivityPort;
320
379
  async putConfiguration(configuration) {
321
380
  return this.transaction(
322
381
  async (client) => {
@@ -417,14 +476,26 @@ class PostgresReportingLedgerStore {
417
476
  positiveInteger(input.limit, "limit");
418
477
  if (input.limit > 1e3) throw new RangeError("limit must not exceed 1000");
419
478
  if (!Number.isFinite(Date.parse(input.ledgerAsOf))) throw new TypeError("ledgerAsOf must be an RFC 3339 instant");
479
+ const managedDueArm = await this.managedDueTablesReady() ? MANAGED_DUE_ARM : "";
420
480
  const result = await this.query(
421
481
  `SELECT obligation.data FROM adcp_reporting_obligations obligation
422
482
  LEFT JOIN LATERAL (
423
- SELECT transition.data->>'health' AS health
483
+ SELECT transition.data->>'health' AS health, transition.occurred_at
424
484
  FROM adcp_reporting_transitions transition
425
485
  WHERE transition.obligation_id = obligation.obligation_id
426
486
  ORDER BY transition.transition_sequence DESC LIMIT 1
427
487
  ) latest ON TRUE
488
+ LEFT JOIN adcp_reporting_lifecycle_state state
489
+ ON state.obligation_id = obligation.obligation_id
490
+ CROSS JOIN LATERAL (
491
+ -- A reconcile that changed no health still has to count as work done,
492
+ -- or a managed change with no health effect keeps the obligation due
493
+ -- forever and, at small page sizes, starves everything behind it.
494
+ SELECT GREATEST(
495
+ COALESCE(state.processed_at, obligation.created_at),
496
+ COALESCE(latest.occurred_at, obligation.created_at)
497
+ ) AS since
498
+ ) watermark
428
499
  WHERE ($1::text IS NULL OR obligation.account_id = $1)
429
500
  AND ((COALESCE(latest.health, 'waiting') = 'waiting'
430
501
  AND (obligation.data->>'expectedAt')::timestamptz <= $2)
@@ -435,8 +506,14 @@ class PostgresReportingLedgerStore {
435
506
  AND EXISTS (
436
507
  SELECT 1 FROM adcp_reporting_revisions revision
437
508
  WHERE revision.obligation_id = obligation.obligation_id
438
- )))
439
- ORDER BY LEAST(
509
+ ))
510
+ ${managedDueArm} )
511
+ -- A tenant that keeps failing must not monopolise every sweep. Its
512
+ -- backoff cursor holds it out of selection until it is due again,
513
+ -- while processed_at ordering still favours whoever has waited
514
+ -- longest among the eligible.
515
+ AND (state.next_attempt_at IS NULL OR state.next_attempt_at <= $2)
516
+ ORDER BY watermark.since, LEAST(
440
517
  (obligation.data->>'expectedAt')::timestamptz,
441
518
  (obligation.data->>'recoveryDeadlineAt')::timestamptz
442
519
  ), obligation.obligation_id
@@ -583,7 +660,8 @@ class PostgresReportingLedgerStore {
583
660
  obligationId: revision.reporting_obligation_id,
584
661
  owner: lease.owner,
585
662
  generation: lease.generation
586
- }
663
+ },
664
+ revisionLegacyCanonicalDigestReplay
587
665
  );
588
666
  }
589
667
  async getRevision(id, accountId) {
@@ -657,7 +735,8 @@ class PostgresReportingLedgerStore {
657
735
  obligationId: adjustment.reporting_obligation_id,
658
736
  owner: lease.owner,
659
737
  generation: lease.generation
660
- }
738
+ },
739
+ adjustmentLegacyCanonicalDigestReplay
661
740
  );
662
741
  }
663
742
  async listAdjustments(obligationId) {
@@ -1048,27 +1127,59 @@ class PostgresReportingLedgerStore {
1048
1127
  );
1049
1128
  return result.rows.map((row) => clone(row.data));
1050
1129
  }
1130
+ /**
1131
+ * Resolves — and for pre-SDK-14 rows persists — the observed finality baseline
1132
+ * of the obligation's latest transition, so the lifecycle decision and this
1133
+ * store's compare-and-set read one committed value.
1134
+ */
1135
+ async resolveTransitionFinalityBaseline(obligationId) {
1136
+ const lock = await this.accountLockForObligation(obligationId);
1137
+ return this.transaction((client) => resolveStoredFinalityBaseline(client, obligationId), {
1138
+ preBeginAdvisoryLock: lock
1139
+ });
1140
+ }
1051
1141
  async appendTransition(transition) {
1052
1142
  const lock = await this.accountLockForObligation(transition.reporting_obligation_id);
1143
+ return assertFinalityWriterFence(() => this.appendTransitionWithinLock(transition, lock));
1144
+ }
1145
+ async appendTransitionWithinLock(transition, lock) {
1053
1146
  return this.transaction(
1054
1147
  async (client) => {
1148
+ if (this.notificationActivityPort) {
1149
+ await assertNoLegacyPendingTransitions(client, transition.reporting_obligation_id);
1150
+ }
1151
+ const previousFinality = await resolveStoredFinalityBaseline(client, transition.reporting_obligation_id);
1152
+ const latest = await client.query(
1153
+ `SELECT data->>'health' AS health FROM adcp_reporting_transitions
1154
+ WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1`,
1155
+ [transition.reporting_obligation_id]
1156
+ );
1157
+ if ((latest.rows[0]?.health ?? "waiting") !== transition.previousHealth || transition.previousFinality !== void 0 && previousFinality !== transition.previousFinality) {
1158
+ return { inserted: false };
1159
+ }
1160
+ const storedTransition = this.notificationActivityPort ? { ...transition, notifiedAt: transition.occurredAt } : transition;
1055
1161
  const result = await client.query(
1056
1162
  `INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
1057
- SELECT $1, $2, $3::jsonb, $4
1058
- WHERE COALESCE((
1059
- SELECT previous.data->>'health' FROM adcp_reporting_transitions previous
1060
- WHERE previous.obligation_id = $2
1061
- ORDER BY previous.transition_sequence DESC LIMIT 1
1062
- ), 'waiting') = $5
1063
- ON CONFLICT DO NOTHING RETURNING transition_id`,
1163
+ VALUES ($1, $2, $3::jsonb, $4)
1164
+ ON CONFLICT DO NOTHING RETURNING transition_id`,
1064
1165
  [
1065
1166
  transition.transitionId,
1066
1167
  transition.reporting_obligation_id,
1067
- JSON.stringify(transition),
1068
- transition.occurredAt,
1069
- transition.previousHealth
1168
+ JSON.stringify(storedTransition),
1169
+ transition.occurredAt
1070
1170
  ]
1071
1171
  );
1172
+ if (result.rowCount === 1 && this.notificationActivityPort) {
1173
+ const obligation = await client.query(
1174
+ "SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1",
1175
+ [transition.reporting_obligation_id]
1176
+ );
1177
+ if (!obligation.rows[0]) throw new Error("Reporting obligation is unavailable");
1178
+ await this.notificationActivityPort.recordTransition(
1179
+ { transition, obligation: obligation.rows[0].data },
1180
+ client
1181
+ );
1182
+ }
1072
1183
  return { inserted: result.rowCount === 1 };
1073
1184
  },
1074
1185
  { preBeginAdvisoryLock: lock }
@@ -1076,12 +1187,19 @@ class PostgresReportingLedgerStore {
1076
1187
  }
1077
1188
  async applyLifecycleProjection(input) {
1078
1189
  const lock = await this.accountLockForObligation(input.reporting_obligation_id);
1190
+ return assertFinalityWriterFence(() => this.applyLifecycleProjectionWithinLock(input, lock));
1191
+ }
1192
+ async applyLifecycleProjectionWithinLock(input, lock) {
1079
1193
  return this.transaction(
1080
1194
  async (client) => {
1081
1195
  const obligations = await client.query(
1082
1196
  `SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1 FOR UPDATE`,
1083
1197
  [input.reporting_obligation_id]
1084
1198
  );
1199
+ if (this.notificationActivityPort) {
1200
+ await assertNoLegacyPendingTransitions(client, input.reporting_obligation_id);
1201
+ }
1202
+ const previousFinality = await resolveStoredFinalityBaseline(client, input.reporting_obligation_id);
1085
1203
  const revisions = await client.query(
1086
1204
  `SELECT revision_id FROM adcp_reporting_revisions
1087
1205
  WHERE obligation_id = $1 ORDER BY revision_number, revision_id`,
@@ -1095,11 +1213,26 @@ class PostgresReportingLedgerStore {
1095
1213
  const revisionIds = revisions.rows.map((value) => value.revision_id);
1096
1214
  const previousHealth = latest.rows[0]?.health ?? "waiting";
1097
1215
  const obligation = obligations.rows[0]?.data;
1098
- if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || (0, import_source.canonicalJsonV1)(revisionIds) !== (0, import_source.canonicalJsonV1)(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth) {
1216
+ if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || (0, import_source.canonicalJsonV1)(revisionIds) !== (0, import_source.canonicalJsonV1)(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth || input.expectedPreviousFinality !== void 0 && previousFinality !== input.expectedPreviousFinality) {
1099
1217
  return { applied: false, transitionInserted: false };
1100
1218
  }
1219
+ if (input.expectedManagedStateVersion !== void 0 && await this.readManagedStateVersion(client, input.reporting_obligation_id) !== input.expectedManagedStateVersion) {
1220
+ return { applied: false, transitionInserted: false };
1221
+ }
1222
+ if (input.expectedObligatedConsumerRosterVersion !== void 0) {
1223
+ const state = await client.query(
1224
+ `SELECT current_roster_version FROM adcp_reporting_lifecycle_state
1225
+ WHERE obligation_id = $1 FOR UPDATE`,
1226
+ [input.reporting_obligation_id]
1227
+ );
1228
+ const observed = state.rows[0]?.current_roster_version;
1229
+ if (observed !== void 0 && observed !== input.expectedObligatedConsumerRosterVersion) {
1230
+ return { applied: false, transitionInserted: false };
1231
+ }
1232
+ }
1101
1233
  let transitionInserted = false;
1102
1234
  if (input.transition) {
1235
+ const storedTransition = this.notificationActivityPort ? { ...input.transition, notifiedAt: input.ledgerAsOf } : input.transition;
1103
1236
  const inserted = await client.query(
1104
1237
  `INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
1105
1238
  VALUES ($1, $2, $3::jsonb, $4)
@@ -1107,12 +1240,60 @@ class PostgresReportingLedgerStore {
1107
1240
  [
1108
1241
  input.transition.transitionId,
1109
1242
  input.reporting_obligation_id,
1110
- JSON.stringify(input.transition),
1243
+ JSON.stringify(storedTransition),
1111
1244
  input.transition.occurredAt
1112
1245
  ]
1113
1246
  );
1114
1247
  if (inserted.rowCount !== 1) return { applied: false, transitionInserted: false };
1115
1248
  transitionInserted = true;
1249
+ if (this.notificationActivityPort) {
1250
+ await this.notificationActivityPort.recordTransition({ transition: input.transition, obligation }, client);
1251
+ }
1252
+ }
1253
+ {
1254
+ await client.query(
1255
+ `INSERT INTO adcp_reporting_lifecycle_state
1256
+ (obligation_id, processed_state_version, processed_roster_version,
1257
+ current_roster_version, processed_at)
1258
+ VALUES ($1, $2, $3, $3, LEAST($4::timestamptz, clock_timestamp()))
1259
+ ON CONFLICT (obligation_id) DO UPDATE SET
1260
+ processed_state_version = EXCLUDED.processed_state_version,
1261
+ processed_roster_version = EXCLUDED.processed_roster_version,
1262
+ -- Reconciling also observes the roster, so the current version
1263
+ -- moves with the processed one. A later external change
1264
+ -- publishes a different current version and re-arms; without
1265
+ -- this the two never matched and the obligation stayed due.
1266
+ -- Never over a version this reconcile did not observe: if the
1267
+ -- row was created or advanced after the check above, that
1268
+ -- newer observation is what has to survive, or its re-arm is
1269
+ -- silently dropped.
1270
+ current_roster_version = CASE
1271
+ WHEN $5::text IS NOT NULL
1272
+ AND adcp_reporting_lifecycle_state.current_roster_version IS DISTINCT FROM $5::text
1273
+ THEN adcp_reporting_lifecycle_state.current_roster_version
1274
+ ELSE EXCLUDED.processed_roster_version END,
1275
+ processed_at = EXCLUDED.processed_at,
1276
+ failure_count = 0,
1277
+ next_attempt_at = NULL`,
1278
+ [
1279
+ input.reporting_obligation_id,
1280
+ input.processedManagedStateVersion ?? null,
1281
+ // Empty string, not null: null means "never reconciled", which
1282
+ // is a due condition. A deployment with no roster source would
1283
+ // otherwise stay due forever.
1284
+ input.processedObligatedConsumerRosterVersion ?? "",
1285
+ // The cutoff the projection actually read at, never the commit
1286
+ // instant. Watermarking at commit time swallowed everything
1287
+ // that landed between the two: excluded from the projection,
1288
+ // and then behind the watermark forever, so it never became
1289
+ // due again. It is clamped to this database's clock in SQL
1290
+ // below, because a cutoff ahead of the ledger buries
1291
+ // database-timestamped work the same way for the opposite
1292
+ // reason.
1293
+ input.ledgerAsOf,
1294
+ input.expectedObligatedConsumerRosterVersion ?? null
1295
+ ]
1296
+ );
1116
1297
  }
1117
1298
  const projectedIds = input.projectedIssues.map((issue) => issue.issueId);
1118
1299
  for (const issue of input.projectedIssues) {
@@ -1135,7 +1316,17 @@ class PostgresReportingLedgerStore {
1135
1316
  data = data || jsonb_build_object('resolvedAt', $2::text),
1136
1317
  changed_at = clock_timestamp()
1137
1318
  WHERE obligation_id = $1 AND resolved_at IS NULL
1138
- AND data->>'code' IN ('REPORT_OVERDUE', 'REPORTING_COVERAGE_INCOMPLETE')
1319
+ -- Every code this projection can emit, not only the Core two.
1320
+ -- A managed DELIVERY_FAILED persisted by an earlier reconcile
1321
+ -- was never cleared, so after a successful redelivery the
1322
+ -- status kept publishing a stale delivery failure for as long
1323
+ -- as Core stayed degraded for some unrelated reason.
1324
+ AND data->>'code' IN (
1325
+ 'REPORT_OVERDUE', 'REPORTING_COVERAGE_INCOMPLETE',
1326
+ 'DELIVERY_FAILED', 'RESOURCE_EXPIRED',
1327
+ 'RECEIPT_REQUIRED', 'RECEIPT_REJECTED',
1328
+ 'ADJUSTMENT_RECEIPT_REQUIRED', 'ADJUSTMENT_RECEIPT_REJECTED'
1329
+ )
1139
1330
  AND NOT (issue_id = ANY($3::text[]))`,
1140
1331
  [input.reporting_obligation_id, input.ledgerAsOf, projectedIds]
1141
1332
  );
@@ -1152,6 +1343,7 @@ class PostgresReportingLedgerStore {
1152
1343
  await client.query(
1153
1344
  `UPDATE adcp_reporting_transitions
1154
1345
  SET data = data || jsonb_build_object('notifiedAt', $2::text)
1346
+ || CASE WHEN data ? 'finality' THEN '{}'::jsonb ELSE jsonb_build_object('finality', 'none') END
1155
1347
  WHERE transition_id = $1 AND NOT (data ? 'notifiedAt')`,
1156
1348
  [transitionId, notifiedAt]
1157
1349
  );
@@ -1209,11 +1401,31 @@ class PostgresReportingLedgerStore {
1209
1401
  }
1210
1402
  const activeSnapshotBytes = Number(active.rows[0]?.bytes ?? 0);
1211
1403
  const changesAfter = query.changes_after ? await this.resolveChangesCheckpoint(client, query, query.changes_after) : void 0;
1404
+ const managedInstalled = this.managedDelivery && await this.managedTablesInstalled(client);
1212
1405
  const configurations = await this.listSnapshotConfigurations(client, query, ledgerAsOf);
1213
1406
  if (configurations.length > MAX_SNAPSHOT_ITEMS) {
1214
1407
  throw new Error("Reporting ledger snapshot exceeds the configuration limit");
1215
1408
  }
1216
1409
  let obligations = await this.listSnapshotObligations(client, query, ledgerAsOf, changesAfter);
1410
+ if (managedInstalled && changesAfter) {
1411
+ const managedChangedIds = await this.listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter);
1412
+ if (managedChangedIds.size) {
1413
+ const fullScope = await this.listSnapshotObligations(
1414
+ client,
1415
+ { ...query, changes_after: void 0, health: void 0 },
1416
+ ledgerAsOf
1417
+ );
1418
+ const byId = new Map(obligations.map((value) => [value.reporting_obligation_id, value]));
1419
+ for (const obligation of fullScope) {
1420
+ if (managedChangedIds.has(obligation.reporting_obligation_id)) {
1421
+ byId.set(obligation.reporting_obligation_id, obligation);
1422
+ }
1423
+ }
1424
+ obligations = [...byId.values()].sort(
1425
+ (left, right) => left.period.start.localeCompare(right.period.start) || left.reporting_obligation_id.localeCompare(right.reporting_obligation_id)
1426
+ );
1427
+ }
1428
+ }
1217
1429
  if (obligations.length > MAX_SNAPSHOT_ITEMS) {
1218
1430
  throw new Error("Reporting ledger snapshot exceeds the item limit");
1219
1431
  }
@@ -1265,6 +1477,30 @@ class PostgresReportingLedgerStore {
1265
1477
  throw new Error("Reporting ledger snapshot exceeds the item limit");
1266
1478
  }
1267
1479
  let issues = await this.listSnapshotIssues(client, obligationIds, MAX_SNAPSHOT_ITEMS + 1);
1480
+ const managedBindings = managedInstalled ? await this.listSnapshotManagedBindings(
1481
+ client,
1482
+ configurations.map((value) => value.configurationId)
1483
+ ) : [];
1484
+ let materializations = managedInstalled ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) : [];
1485
+ let receipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "revision") : [];
1486
+ let adjustmentReceipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "adjustment") : [];
1487
+ const tombstonedAcceptedSubjects = managedInstalled ? (await this.listTombstonedAcceptedSubjects(client, query, obligationIds, false, ledgerAsOf)).subjects : [];
1488
+ const tombstonedDeliveredRevisionIds = managedInstalled ? (await client.query(
1489
+ `SELECT revision_id FROM adcp_reporting_materialization_tombstones
1490
+ WHERE obligation_id = ANY($1::text[]) AND reached_success
1491
+ AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
1492
+ [obligationIds, MAX_SNAPSHOT_ITEMS + 1, ledgerAsOf]
1493
+ )).rows.map((row) => row.revision_id) : [];
1494
+ const materializationProjection = managedInstalled ? await this.listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) : [];
1495
+ const materializationHistoryProjection = managedInstalled && changesAfter ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf) : materializations;
1496
+ const receiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "revision") : receipts;
1497
+ const adjustmentReceiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "adjustment") : adjustmentReceipts;
1498
+ if (materializationProjection.length > MAX_SNAPSHOT_ITEMS || materializationHistoryProjection.length > MAX_SNAPSHOT_ITEMS || receiptProjection.length > MAX_SNAPSHOT_ITEMS || adjustmentReceiptProjection.length > MAX_SNAPSHOT_ITEMS) {
1499
+ throw new Error("Reporting ledger snapshot exceeds the managed projection limit");
1500
+ }
1501
+ if (obligations.length + revisions.length + adjustments.length + consumerStatuses.length + materializations.length + receipts.length + adjustmentReceipts.length > MAX_SNAPSHOT_ITEMS) {
1502
+ throw new Error("Reporting ledger snapshot exceeds the item limit");
1503
+ }
1268
1504
  if (issues.length > MAX_SNAPSHOT_ITEMS) throw new Error("Reporting ledger snapshot exceeds the issue limit");
1269
1505
  if (query.view === "periods" && query.health) {
1270
1506
  const scopedObligations = obligations;
@@ -1289,7 +1525,34 @@ class PostgresReportingLedgerStore {
1289
1525
  ledgerAsOf,
1290
1526
  this.consumerMismatchEscalation
1291
1527
  );
1292
- return query.health.includes(mismatch?.health ?? sellerHealth);
1528
+ const baseProjection = (0, import_health.projectReportingObligationHealthV1)(
1529
+ value,
1530
+ obligationRevisions,
1531
+ ledgerAsOf,
1532
+ (0, import_coverage.reportingLedgerScopeClosed)(query, ledgerAsOf, ledgerCoverage.complete)
1533
+ );
1534
+ const managed = (0, import_handler.projectManagedDelivery)({
1535
+ obligation: value,
1536
+ binding: managedBindings.find((binding) => binding.configurationId === value.configurationId),
1537
+ revisions: obligationRevisions,
1538
+ adjustments: adjustments.filter(
1539
+ (item) => item.reporting_obligation_id === value.reporting_obligation_id
1540
+ ),
1541
+ materializations: materializationProjection.filter(
1542
+ (item) => item.reporting_obligation_id === value.reporting_obligation_id
1543
+ ),
1544
+ materializationHistory: materializationHistoryProjection.filter(
1545
+ (item) => item.reporting_obligation_id === value.reporting_obligation_id
1546
+ ),
1547
+ receipts: receiptProjection,
1548
+ adjustmentReceipts: adjustmentReceiptProjection,
1549
+ base: baseProjection,
1550
+ ledgerAsOf,
1551
+ tombstonedAcceptedSubjects,
1552
+ tombstonedDeliveredRevisionIds
1553
+ });
1554
+ const health = mismatch ? (0, import_handler.moreSevereReportingHealthV1)(managed?.projection.health ?? sellerHealth, mismatch.health) : managed?.projection.health ?? sellerHealth;
1555
+ return query.health.includes(health);
1293
1556
  }).map((value) => value.reporting_obligation_id)
1294
1557
  );
1295
1558
  const obligationsByConsumerStatusChain = new Map(
@@ -1305,6 +1568,13 @@ class PostgresReportingLedgerStore {
1305
1568
  obligations = obligations.filter((value) => accepted.has(value.reporting_obligation_id));
1306
1569
  revisions = revisions.filter((value) => accepted.has(value.reporting_obligation_id));
1307
1570
  adjustments = adjustments.filter((value) => accepted.has(value.reporting_obligation_id));
1571
+ materializations = materializations.filter((value) => accepted.has(value.reporting_obligation_id));
1572
+ const acceptedRevisionIds = new Set(revisions.map((value) => value.reporting_revision_id));
1573
+ const acceptedAdjustmentIds = new Set(adjustments.map((value) => value.reporting_adjustment_id));
1574
+ receipts = receipts.filter((value) => acceptedRevisionIds.has(value.reporting_revision_id));
1575
+ adjustmentReceipts = adjustmentReceipts.filter(
1576
+ (value) => acceptedAdjustmentIds.has(value.reporting_adjustment_id)
1577
+ );
1308
1578
  consumerStatuses = consumerStatuses.filter((value) => {
1309
1579
  const key = (0, import_consumer_status_identity.reportingConsumerStatusChainKeyV1)(value);
1310
1580
  const matchingObligation = obligationsByConsumerStatusChain.get(key);
@@ -1329,6 +1599,18 @@ class PostgresReportingLedgerStore {
1329
1599
  obligations,
1330
1600
  revisions,
1331
1601
  adjustments,
1602
+ ...managedInstalled ? {
1603
+ tombstonedAcceptedSubjects,
1604
+ tombstonedDeliveredRevisionIds,
1605
+ managedBindings,
1606
+ materializations,
1607
+ materializationHistoryProjection,
1608
+ materializationProjection,
1609
+ receipts,
1610
+ receiptProjection,
1611
+ adjustmentReceipts,
1612
+ adjustmentReceiptProjection
1613
+ } : {},
1332
1614
  consumerStatuses,
1333
1615
  consumerStatusProjection,
1334
1616
  issues
@@ -1398,6 +1680,13 @@ class PostgresReportingLedgerStore {
1398
1680
  const consumerStatuses = selected.filter(
1399
1681
  (value) => value.kind === "consumer_status"
1400
1682
  ).map((value) => value.value);
1683
+ const materializations = selected.filter(
1684
+ (value) => value.kind === "materialization"
1685
+ ).map((value) => value.value);
1686
+ const receipts = selected.filter((value) => value.kind === "receipt").map((value) => value.value);
1687
+ const adjustmentReceipts = selected.filter(
1688
+ (value) => value.kind === "adjustment_receipt"
1689
+ ).map((value) => value.value);
1401
1690
  const nextOffset = offset + selected.length;
1402
1691
  const hasMore = nextOffset < items.length;
1403
1692
  return {
@@ -1405,6 +1694,9 @@ class PostgresReportingLedgerStore {
1405
1694
  obligations,
1406
1695
  revisions,
1407
1696
  adjustments,
1697
+ materializations,
1698
+ receipts,
1699
+ adjustmentReceipts,
1408
1700
  consumerStatuses,
1409
1701
  totalCount: items.length,
1410
1702
  offset,
@@ -1584,11 +1876,614 @@ class PostgresReportingLedgerStore {
1584
1876
  );
1585
1877
  return result.rows.map((row) => row.data);
1586
1878
  }
1879
+ /**
1880
+ * Managed Delivery projection inputs for one obligation, grouped per consumer.
1881
+ *
1882
+ * The lifecycle reconciler folds these through the same
1883
+ * `projectManagedDelivery` the read path uses, so a persisted transition and
1884
+ * its webhook report the health a read of the same obligation would return.
1885
+ * Returns `null` when Managed Delivery is not installed or the obligation's
1886
+ * configuration has no binding, which keeps a Core-only ledger unchanged.
1887
+ */
1888
+ async getManagedLifecycleProjection(input) {
1889
+ if (!this.managedDelivery) return null;
1890
+ const base = await this.transaction(
1891
+ async (client) => {
1892
+ if (!await this.managedTablesInstalled(client)) return null;
1893
+ const obligation = await client.query(
1894
+ "SELECT configuration_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
1895
+ [input.reporting_obligation_id]
1896
+ );
1897
+ const configurationId = obligation.rows[0]?.configuration_id;
1898
+ if (!configurationId) return null;
1899
+ const [binding] = await this.listSnapshotManagedBindings(client, [configurationId]);
1900
+ if (!binding) return null;
1901
+ const resolvedLedgerAsOf = (await client.query(
1902
+ // RFC 3339 with microseconds. PostgreSQL's own ::text rendering
1903
+ // uses a space separator and a two-digit offset, which the SDK's
1904
+ // instant parser rejects, and round-tripping through a JS Date
1905
+ // would truncate the microseconds this exists to preserve.
1906
+ `SELECT ${rfc3339Microseconds("COALESCE($1::timestamptz, clock_timestamp())")} AS instant`,
1907
+ [input.ledgerAsOf ?? null]
1908
+ )).rows[0].instant;
1909
+ const rows = await client.query(
1910
+ `SELECT materialization.data,
1911
+ materialization.changed_at > $2::timestamptz AS settled_after_cutoff,
1912
+ CASE WHEN authz.revoked_at <= $2::timestamptz THEN authz.revoked_at END AS revoked_at
1913
+ FROM adcp_reporting_materializations materialization
1914
+ JOIN adcp_reporting_destination_authorizations authz
1915
+ ON authz.account_id = materialization.account_id
1916
+ AND authz.destination_ref = materialization.destination_ref
1917
+ AND authz.generation = materialization.authorization_generation
1918
+ WHERE materialization.obligation_id = $1
1919
+ AND materialization.recorded_at <= $2::timestamptz
1920
+ ORDER BY materialization.attempt, materialization.materialization_id
1921
+ LIMIT $3`,
1922
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
1923
+ );
1924
+ if (rows.rows.length > MAX_SNAPSHOT_ITEMS) {
1925
+ throw new Error("Reporting lifecycle projection exceeds the managed materialization limit");
1926
+ }
1927
+ const placed = rows.rows.map((row) => ({
1928
+ atAsOf: row.settled_after_cutoff ? pendingAtCutoff(row.data) : clone(row.data),
1929
+ revokedAt: row.revoked_at
1930
+ }));
1931
+ const materializationHistory = placed.map((value) => value.atAsOf);
1932
+ const materializations = placed.map(
1933
+ ({ atAsOf, revokedAt }) => revokedAt && (atAsOf.status === "available" || atAsOf.status === "delivered") ? {
1934
+ ...atAsOf,
1935
+ status: "failed",
1936
+ failed_at: revokedAt.toISOString(),
1937
+ failure_code: "AUTHORIZATION_REVOKED"
1938
+ } : atAsOf
1939
+ );
1940
+ const receipts = await client.query(
1941
+ `WITH subject AS (
1942
+ -- Drive from this obligation's own subjects. Asking the global
1943
+ -- receipt table "which of your rows belong to this obligation"
1944
+ -- matched no index \u2014 they lead with account and consumer, and
1945
+ -- this question supplies neither \u2014 so every due obligation
1946
+ -- re-scanned the whole receipt history.
1947
+ SELECT revision_id AS subject_id, 'revision' AS receipt_kind
1948
+ FROM adcp_reporting_revisions WHERE obligation_id = $1
1949
+ UNION ALL
1950
+ SELECT adjustment_id, 'adjustment'
1951
+ FROM adcp_reporting_adjustments WHERE obligation_id = $1
1952
+ )
1953
+ SELECT receipt.consumer_id, receipt.receipt_kind, receipt.data
1954
+ FROM subject
1955
+ JOIN adcp_reporting_receipts receipt
1956
+ ON receipt.subject_id = subject.subject_id
1957
+ AND receipt.receipt_kind = subject.receipt_kind
1958
+ -- Ordered and cut off by the database clock, exactly as every other
1959
+ -- receipt read path is, so a skewed host cannot make the lifecycle
1960
+ -- projection see a different receipt set than get_reporting_status.
1961
+ --
1962
+ -- One leaf per chain, resolved AS OF the cutoff. The fold reads
1963
+ -- exactly one thing from each chain, and walking the whole history
1964
+ -- to find it pushed this read past its bound for a subject repaired
1965
+ -- often enough. But is_current is today's answer: at a cutoff
1966
+ -- before an acceptance superseded a rejection, the rejection was no
1967
+ -- longer current and the acceptance was not yet in scope, so the
1968
+ -- projection saw neither and persisted RECEIPT_REQUIRED over a
1969
+ -- rejection the buyer had already filed. The leaf at the cutoff is
1970
+ -- the receipt that nothing recorded by then supersedes. Wire
1971
+ -- counters are computed on the read path, which still sees every
1972
+ -- row.
1973
+ WHERE receipt.recorded_at <= $2
1974
+ AND NOT EXISTS (
1975
+ SELECT 1 FROM adcp_reporting_receipts successor
1976
+ WHERE successor.account_id = receipt.account_id
1977
+ AND successor.consumer_id = receipt.consumer_id
1978
+ AND successor.supersedes_receipt_id = receipt.reporting_receipt_id
1979
+ AND successor.recorded_at <= $2
1980
+ )
1981
+ -- A successor whose body has been pruned still superseded this
1982
+ -- row. Counting only live successors let retention resurrect the
1983
+ -- rejected predecessor of a pruned acceptance as the live leaf,
1984
+ -- which overrode that acceptance's own tombstone and reopened a
1985
+ -- settled subject the write path then refused to repair.
1986
+ AND NOT EXISTS (
1987
+ SELECT 1 FROM adcp_reporting_receipt_tombstones gone
1988
+ WHERE gone.account_id = receipt.account_id
1989
+ AND gone.consumer_id = receipt.consumer_id
1990
+ AND gone.supersedes_receipt_id = receipt.reporting_receipt_id
1991
+ AND COALESCE(gone.subject_recorded_at, gone.pruned_at) <= $2
1992
+ )
1993
+ ORDER BY receipt.consumer_id, receipt.subject_id, receipt.recorded_at, receipt.reporting_receipt_id
1994
+ LIMIT $3`,
1995
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_LIFECYCLE_RECEIPT_LEAVES + 1]
1996
+ );
1997
+ let receiptEvidenceComplete = true;
1998
+ let leaves = receipts.rows;
1999
+ if (leaves.length > MAX_LIFECYCLE_RECEIPT_LEAVES) {
2000
+ receiptEvidenceComplete = false;
2001
+ const partial = leaves[MAX_LIFECYCLE_RECEIPT_LEAVES].consumer_id;
2002
+ leaves = leaves.slice(0, MAX_LIFECYCLE_RECEIPT_LEAVES).filter((row) => row.consumer_id !== partial);
2003
+ }
2004
+ const byConsumer = /* @__PURE__ */ new Map();
2005
+ for (const row of leaves) {
2006
+ const consumer = byConsumer.get(row.consumer_id) ?? {
2007
+ consumer_id: row.consumer_id,
2008
+ receipts: [],
2009
+ adjustmentReceipts: []
2010
+ };
2011
+ if (row.receipt_kind === "revision") consumer.receipts.push(clone(row.data));
2012
+ else consumer.adjustmentReceipts.push(clone(row.data));
2013
+ byConsumer.set(row.consumer_id, consumer);
2014
+ }
2015
+ const engaged = await client.query(
2016
+ `SELECT DISTINCT consumer_id FROM adcp_reporting_consumer_statuses
2017
+ WHERE obligation_id = $1 AND consumer_id <> '__legacy_unscoped_consumer__'
2018
+ ORDER BY consumer_id LIMIT $2`,
2019
+ [input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1]
2020
+ );
2021
+ if (engaged.rows.length > MAX_SNAPSHOT_ITEMS) {
2022
+ receiptEvidenceComplete = false;
2023
+ engaged.rows = engaged.rows.slice(0, MAX_SNAPSHOT_ITEMS);
2024
+ }
2025
+ const observed = [.../* @__PURE__ */ new Set([...byConsumer.keys(), ...engaged.rows.map((row) => row.consumer_id)])].sort();
2026
+ const tombstoned = await this.listTombstonedAcceptedSubjects(
2027
+ client,
2028
+ { account_id: binding.account_id, consumer_id: void 0, view: "periods" },
2029
+ [input.reporting_obligation_id],
2030
+ true,
2031
+ resolvedLedgerAsOf
2032
+ );
2033
+ if (!tombstoned.complete) receiptEvidenceComplete = false;
2034
+ const visible = await client.query(
2035
+ `SELECT adjustment_id FROM adcp_reporting_adjustments
2036
+ WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
2037
+ ORDER BY adjustment_number, adjustment_id LIMIT $3`,
2038
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
2039
+ );
2040
+ if (visible.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
2041
+ const visibleAdjustmentIds = visible.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.adjustment_id);
2042
+ const visibleRevisions = await client.query(
2043
+ `SELECT revision_id FROM adcp_reporting_revisions
2044
+ WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
2045
+ ORDER BY revision_number, revision_id LIMIT $3`,
2046
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
2047
+ );
2048
+ if (visibleRevisions.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
2049
+ const visibleRevisionIds = visibleRevisions.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
2050
+ const tombstonedAcceptedSubjects = tombstoned.subjects;
2051
+ const delivered = await client.query(
2052
+ `SELECT revision_id FROM adcp_reporting_materialization_tombstones
2053
+ WHERE obligation_id = $1 AND reached_success
2054
+ AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
2055
+ [input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1, resolvedLedgerAsOf]
2056
+ );
2057
+ if (delivered.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
2058
+ const tombstonedDeliveredRevisionIds = delivered.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
2059
+ const managedStateVersion = await this.readManagedStateVersion(client, input.reporting_obligation_id);
2060
+ return {
2061
+ binding,
2062
+ materializations,
2063
+ materializationHistory,
2064
+ consumers: [...byConsumer.values()],
2065
+ obligatedConsumerIds: observed,
2066
+ obligatedConsumerRosterComplete: false,
2067
+ receiptEvidenceComplete,
2068
+ visibleAdjustmentIds,
2069
+ visibleRevisionIds,
2070
+ managedStateVersion,
2071
+ resolvedLedgerAsOf,
2072
+ tombstonedAcceptedSubjects,
2073
+ tombstonedDeliveredRevisionIds
2074
+ };
2075
+ },
2076
+ { isolation: "REPEATABLE READ" }
2077
+ );
2078
+ if (!base || !this.obligatedConsumers) return base;
2079
+ const supplied = await withReportingCallbackDeadline(
2080
+ this.obligatedConsumers({
2081
+ reporting_obligation_id: input.reporting_obligation_id,
2082
+ account_id: base.binding.account_id
2083
+ }),
2084
+ OBLIGATED_CONSUMERS_DEADLINE_MS,
2085
+ "Reporting obligated-consumer lookup deadline elapsed"
2086
+ );
2087
+ const all = obligatedConsumerIdsFor(supplied, base.obligatedConsumerIds ?? []);
2088
+ const identityIds = supplied.version === void 0 && supplied.complete !== true ? obligatedConsumerIdsFor(supplied, await this.observedConsumerIds(input.reporting_obligation_id)) : all;
2089
+ const ids = all.slice(0, MAX_SNAPSHOT_ITEMS);
2090
+ const rosterComplete = supplied.complete === true && all.length === ids.length;
2091
+ return {
2092
+ ...base,
2093
+ obligatedConsumerIds: ids,
2094
+ obligatedConsumerRosterComplete: rosterComplete,
2095
+ ...all.length === ids.length ? {} : { receiptEvidenceComplete: false },
2096
+ obligatedConsumerRosterVersion: obligatedConsumerRosterVersionFor(supplied, identityIds)
2097
+ };
2098
+ }
2099
+ /**
2100
+ * Digest over every managed row that can change this obligation's projected
2101
+ * health: materialization status, receipt existence and current-leaf flag,
2102
+ * adjustments, and the destination authorization's revocation/cleanup state.
2103
+ *
2104
+ * `is_current` is included because superseding a receipt flips it with no
2105
+ * timestamp of its own, and the authorization's `cleanup_completed_at` is
2106
+ * included because `completeRevocation` does not touch `changed_at`.
2107
+ */
2108
+ async readManagedStateVersion(client, obligationId) {
2109
+ const result = await client.query(
2110
+ `SELECT md5(COALESCE(string_agg(marker, '|' ORDER BY marker), '')) AS version FROM (
2111
+ SELECT 'm:' || materialization_id || ':' || status || ':'
2112
+ || EXTRACT(EPOCH FROM changed_at)::text AS marker
2113
+ FROM adcp_reporting_materializations WHERE obligation_id = $1
2114
+ UNION ALL
2115
+ -- Driven from this obligation's subjects into the receipt subject
2116
+ -- index. This digest is read while the apply holds the account
2117
+ -- lock, so asking the global receipt table which of its rows belong
2118
+ -- to an obligation -- a question supplying neither account nor
2119
+ -- consumer -- turned every apply into a full scan with that lock
2120
+ -- held, and concurrent Core writes failed with 55P03.
2121
+ SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
2122
+ || EXTRACT(EPOCH FROM receipt.recorded_at)::text
2123
+ FROM adcp_reporting_revisions revision
2124
+ JOIN adcp_reporting_receipts receipt
2125
+ ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
2126
+ WHERE revision.obligation_id = $1
2127
+ UNION ALL
2128
+ SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
2129
+ || EXTRACT(EPOCH FROM receipt.recorded_at)::text
2130
+ FROM adcp_reporting_adjustments adjustment
2131
+ JOIN adcp_reporting_receipts receipt
2132
+ ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
2133
+ WHERE adjustment.obligation_id = $1
2134
+ UNION ALL
2135
+ SELECT 'a:' || adjustment_id || ':' || EXTRACT(EPOCH FROM recorded_at)::text
2136
+ FROM adcp_reporting_adjustments WHERE obligation_id = $1
2137
+ UNION ALL
2138
+ -- Consumer statuses are a health input through the mismatch
2139
+ -- projection and they widen the observed roster, so a status landing
2140
+ -- between projection and apply has to move the token too.
2141
+ SELECT 'c:' || status.consumer_id || ':' || status.consumer_status_id || ':'
2142
+ || status.is_current::text || ':' || EXTRACT(EPOCH FROM status.created_at)::text
2143
+ FROM adcp_reporting_consumer_statuses status
2144
+ WHERE status.obligation_id = $1
2145
+ UNION ALL
2146
+ SELECT 'z:' || authz.generation::text
2147
+ || ':' || COALESCE(EXTRACT(EPOCH FROM authz.revoked_at)::text, '')
2148
+ || ':' || COALESCE(EXTRACT(EPOCH FROM authz.cleanup_completed_at)::text, '')
2149
+ || ':' || EXTRACT(EPOCH FROM authz.changed_at)::text
2150
+ FROM adcp_reporting_destination_authorizations authz
2151
+ JOIN adcp_reporting_managed_bindings binding
2152
+ ON binding.account_id = authz.account_id AND binding.destination_ref = authz.destination_ref
2153
+ JOIN adcp_reporting_obligations obligation
2154
+ ON obligation.configuration_id = binding.configuration_id
2155
+ WHERE obligation.obligation_id = $1
2156
+ ) markers`,
2157
+ [obligationId]
2158
+ );
2159
+ return result.rows[0]?.version ?? "";
2160
+ }
2161
+ /**
2162
+ * Current external roster version, read outside any transaction so the
2163
+ * reconciler can re-check it immediately before an apply.
2164
+ */
2165
+ /**
2166
+ * Publishes the roster version currently observed for an obligation.
2167
+ *
2168
+ * The roster is external, so nothing in this database changes when it does.
2169
+ * Recording what a reader saw is what lets the due query notice drift from
2170
+ * the version last reconciled. Never lowers a watermark; it only reports.
2171
+ */
2172
+ async recordObligatedConsumerRosterVersion(input) {
2173
+ await this.query(
2174
+ `INSERT INTO adcp_reporting_lifecycle_state
2175
+ (obligation_id, current_roster_version, roster_refreshed_at, processed_at)
2176
+ VALUES ($1, $2, clock_timestamp(), clock_timestamp())
2177
+ ON CONFLICT (obligation_id) DO UPDATE SET
2178
+ current_roster_version = EXCLUDED.current_roster_version,
2179
+ -- Advances the refresh cursor, never the reconciliation watermark:
2180
+ -- observing a roster is not reconciling an obligation.
2181
+ roster_refreshed_at = clock_timestamp()`,
2182
+ [input.reporting_obligation_id, input.version]
2183
+ );
2184
+ }
2185
+ /**
2186
+ * Re-reads the external roster for managed obligations and publishes what
2187
+ * it saw, so a change made while nothing was scheduled can re-arm.
2188
+ *
2189
+ * Ordered by least-recently-refreshed and bounded, so a large account is
2190
+ * swept over successive passes rather than in one burst of callbacks.
2191
+ */
2192
+ async refreshObligatedConsumerRosterVersions(input) {
2193
+ if (!this.managedDelivery || !this.obligatedConsumers) return 0;
2194
+ positiveInteger(input.limit, "limit");
2195
+ if (!await this.managedDueTablesReady()) return 0;
2196
+ const budget = Math.min(input.limit, MAX_ROSTER_REFRESH_PER_SWEEP);
2197
+ const candidates = await this.query(
2198
+ `SELECT obligation.obligation_id
2199
+ FROM adcp_reporting_obligations obligation
2200
+ JOIN adcp_reporting_managed_bindings binding
2201
+ ON binding.configuration_id = obligation.configuration_id
2202
+ LEFT JOIN adcp_reporting_lifecycle_state state
2203
+ ON state.obligation_id = obligation.obligation_id
2204
+ WHERE ($1::text IS NULL OR obligation.account_id = $1)
2205
+ -- Its own cursor, advanced by the refresh itself. Ordering by the
2206
+ -- reconciliation watermark, which a refresh never moves, meant every
2207
+ -- sweep re-read the same first page and nothing beyond one page of quiet
2208
+ -- obligations was ever refreshed, so their roster changes could never
2209
+ -- re-arm.
2210
+ ORDER BY state.roster_refreshed_at NULLS FIRST, obligation.obligation_id
2211
+ LIMIT $2`,
2212
+ [input.account_id ?? null, budget]
2213
+ );
2214
+ let refreshed = 0;
2215
+ for (const row of candidates.rows) {
2216
+ try {
2217
+ await this.readObligatedConsumerRosterVersion({ reporting_obligation_id: row.obligation_id });
2218
+ refreshed += 1;
2219
+ } catch {
2220
+ await this.query(
2221
+ `INSERT INTO adcp_reporting_lifecycle_state (obligation_id, roster_refreshed_at, processed_at)
2222
+ VALUES ($1, clock_timestamp(), clock_timestamp())
2223
+ ON CONFLICT (obligation_id) DO UPDATE SET roster_refreshed_at = clock_timestamp()`,
2224
+ [row.obligation_id]
2225
+ ).catch(() => void 0);
2226
+ }
2227
+ }
2228
+ return refreshed;
2229
+ }
2230
+ /**
2231
+ * Records a failed reconcile so the obligation backs off instead of
2232
+ * re-occupying the head of every sweep, without ever hiding it: the
2233
+ * watermark is untouched, so it stays unresolved work.
2234
+ */
2235
+ async recordLifecycleFailure(input) {
2236
+ await this.query(
2237
+ `INSERT INTO adcp_reporting_lifecycle_state (obligation_id, failure_count, next_attempt_at, processed_at)
2238
+ VALUES ($1, 1, clock_timestamp() + INTERVAL '30 seconds', clock_timestamp())
2239
+ ON CONFLICT (obligation_id) DO UPDATE SET
2240
+ failure_count = adcp_reporting_lifecycle_state.failure_count + 1,
2241
+ next_attempt_at = clock_timestamp() + (LEAST(
2242
+ ${MAX_LIFECYCLE_BACKOFF_SECONDS}, 30 * POWER(2, LEAST(6, adcp_reporting_lifecycle_state.failure_count))
2243
+ ) * INTERVAL '1 second')`,
2244
+ [input.reporting_obligation_id]
2245
+ );
2246
+ }
2247
+ /** Authoritative instant from the database clock, at microsecond precision. */
2248
+ async readLedgerInstant() {
2249
+ const result = await this.query(
2250
+ `SELECT ${rfc3339Microseconds("clock_timestamp()")} AS instant`
2251
+ );
2252
+ return result.rows[0].instant;
2253
+ }
2254
+ async readObligatedConsumerRosterVersion(input) {
2255
+ if (!this.managedDelivery || !this.obligatedConsumers) return void 0;
2256
+ const accountId = await this.query(
2257
+ "SELECT account_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
2258
+ [input.reporting_obligation_id]
2259
+ );
2260
+ const account_id = accountId.rows[0]?.account_id;
2261
+ if (!account_id) return void 0;
2262
+ const supplied = await withReportingCallbackDeadline(
2263
+ this.obligatedConsumers({ reporting_obligation_id: input.reporting_obligation_id, account_id }),
2264
+ OBLIGATED_CONSUMERS_DEADLINE_MS,
2265
+ "Reporting obligated-consumer lookup deadline elapsed"
2266
+ );
2267
+ const observed = await this.observedConsumerIds(input.reporting_obligation_id);
2268
+ const version = obligatedConsumerRosterVersionFor(supplied, obligatedConsumerIdsFor(supplied, observed));
2269
+ await this.recordObligatedConsumerRosterVersion({
2270
+ reporting_obligation_id: input.reporting_obligation_id,
2271
+ version
2272
+ });
2273
+ return version;
2274
+ }
2275
+ /** Principals that have engaged with this obligation, as the projection sees them. */
2276
+ async observedConsumerIds(obligationId) {
2277
+ const result = await this.query(
2278
+ `SELECT DISTINCT consumer_id FROM (
2279
+ -- Driven from this obligation's subjects into the receipt subject
2280
+ -- index, like every other obligation-scoped receipt read. Asking the
2281
+ -- global receipt table which of its rows belong to an obligation
2282
+ -- supplies neither account nor consumer, so the roster read scanned
2283
+ -- the whole receipt history once per reconcile and once per
2284
+ -- pre-apply re-check.
2285
+ SELECT receipt.consumer_id
2286
+ FROM adcp_reporting_revisions revision
2287
+ JOIN adcp_reporting_receipts receipt
2288
+ ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
2289
+ WHERE revision.obligation_id = $1
2290
+ UNION
2291
+ SELECT receipt.consumer_id
2292
+ FROM adcp_reporting_adjustments adjustment
2293
+ JOIN adcp_reporting_receipts receipt
2294
+ ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
2295
+ WHERE adjustment.obligation_id = $1
2296
+ UNION
2297
+ SELECT status.consumer_id FROM adcp_reporting_consumer_statuses status
2298
+ WHERE status.obligation_id = $1 AND status.consumer_id <> '__legacy_unscoped_consumer__'
2299
+ ) engaged ORDER BY consumer_id`,
2300
+ [obligationId]
2301
+ );
2302
+ return result.rows.map((row) => row.consumer_id);
2303
+ }
2304
+ managedDueTablesReadyCache;
2305
+ /** Whether the managed schema exists, resolved once per store instance. */
2306
+ managedDueTablesReady() {
2307
+ if (!this.managedDelivery) return Promise.resolve(false);
2308
+ this.managedDueTablesReadyCache ??= this.query(
2309
+ `SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
2310
+ AND to_regclass('adcp_reporting_materializations') IS NOT NULL
2311
+ AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
2312
+ ).then((result) => result.rows[0]?.ready === true).catch((cause) => {
2313
+ this.managedDueTablesReadyCache = void 0;
2314
+ throw cause;
2315
+ });
2316
+ return this.managedDueTablesReadyCache;
2317
+ }
2318
+ /**
2319
+ * Terminal receipt subjects whose bodies have been pruned.
2320
+ *
2321
+ * The projection decides reconciliation from the receipt rows it can see,
2322
+ * so once an accepted leaf ages out the subject looks unreconciled again
2323
+ * and a settled billing period reopens. The tombstone is the only remaining
2324
+ * proof, so the projection has to consult it.
2325
+ */
2326
+ async listTombstonedAcceptedSubjects(client, query, obligationIds, allConsumers = false, ledgerAsOf) {
2327
+ if (!obligationIds.length || !query.consumer_id && !allConsumers) return { subjects: [], complete: true };
2328
+ const result = await client.query(
2329
+ `SELECT tombstone.receipt_kind, tombstone.subject_id, tombstone.consumer_id
2330
+ FROM adcp_reporting_receipt_tombstones tombstone
2331
+ WHERE tombstone.account_id = $1 AND ($2::text IS NULL OR tombstone.consumer_id = $2)
2332
+ AND tombstone.status = 'accepted' AND tombstone.was_current
2333
+ -- A conclusion still has a place in time. Applying every accepted
2334
+ -- tombstone regardless of when its receipt was filed let a
2335
+ -- historical projection settle a subject on an acceptance that had
2336
+ -- not happened yet at its cutoff. Rows written before the column
2337
+ -- existed fall back to pruned_at, which is never earlier, so the
2338
+ -- fallback can only withhold a conclusion, never invent one.
2339
+ AND ($5::timestamptz IS NULL
2340
+ OR COALESCE(tombstone.subject_recorded_at, tombstone.pruned_at) <= $5::timestamptz)
2341
+ AND ((tombstone.receipt_kind = 'revision' AND EXISTS (
2342
+ SELECT 1 FROM adcp_reporting_revisions revision
2343
+ WHERE revision.revision_id = tombstone.subject_id
2344
+ AND revision.obligation_id = ANY($3::text[])))
2345
+ OR (tombstone.receipt_kind = 'adjustment' AND EXISTS (
2346
+ SELECT 1 FROM adcp_reporting_adjustments adjustment
2347
+ WHERE adjustment.adjustment_id = tombstone.subject_id
2348
+ AND adjustment.obligation_id = ANY($3::text[]))))
2349
+ LIMIT $4`,
2350
+ [
2351
+ query.account_id,
2352
+ allConsumers ? null : query.consumer_id,
2353
+ obligationIds,
2354
+ MAX_SNAPSHOT_ITEMS + 1,
2355
+ ledgerAsOf ?? null
2356
+ ]
2357
+ );
2358
+ const complete = result.rows.length <= MAX_SNAPSHOT_ITEMS;
2359
+ return {
2360
+ complete,
2361
+ subjects: result.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => ({
2362
+ kind: row.receipt_kind === "adjustment" ? "adjustment" : "revision",
2363
+ subjectId: row.subject_id,
2364
+ // Carried through deliberately. An acceptance belongs to the consumer
2365
+ // that gave it, so a lifecycle fold that runs per consumer must not
2366
+ // let A's pruned acceptance settle the obligation on B's behalf.
2367
+ consumerId: row.consumer_id
2368
+ }))
2369
+ };
2370
+ }
2371
+ async managedTablesInstalled(client) {
2372
+ const result = await client.query(
2373
+ `SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
2374
+ AND to_regclass('adcp_reporting_materializations') IS NOT NULL
2375
+ AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
2376
+ );
2377
+ return result.rows[0]?.ready === true;
2378
+ }
2379
+ async listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter) {
2380
+ const result = await client.query(
2381
+ `SELECT DISTINCT changed.obligation_id FROM (
2382
+ SELECT materialization.obligation_id
2383
+ FROM adcp_reporting_materializations materialization
2384
+ WHERE materialization.account_id = $1
2385
+ AND GREATEST(materialization.recorded_at, materialization.changed_at) > $3
2386
+ AND GREATEST(materialization.recorded_at, materialization.changed_at) <= $2
2387
+ UNION ALL
2388
+ SELECT COALESCE(revision.obligation_id, adjustment.obligation_id) AS obligation_id
2389
+ FROM adcp_reporting_receipts receipt
2390
+ LEFT JOIN adcp_reporting_revisions revision
2391
+ ON receipt.receipt_kind = 'revision' AND revision.revision_id = receipt.subject_id
2392
+ LEFT JOIN adcp_reporting_adjustments adjustment
2393
+ ON receipt.receipt_kind = 'adjustment' AND adjustment.adjustment_id = receipt.subject_id
2394
+ WHERE receipt.account_id = $1 AND receipt.consumer_id = $4
2395
+ AND receipt.recorded_at > $3 AND receipt.recorded_at <= $2
2396
+ UNION ALL
2397
+ SELECT obligation.obligation_id
2398
+ FROM adcp_reporting_destination_authorizations authz
2399
+ JOIN adcp_reporting_managed_bindings binding
2400
+ ON binding.account_id = authz.account_id
2401
+ AND binding.destination_ref = authz.destination_ref
2402
+ AND binding.authorization_generation = authz.generation
2403
+ JOIN adcp_reporting_obligations obligation ON obligation.configuration_id = binding.configuration_id
2404
+ WHERE authz.account_id = $1 AND authz.revoked_at IS NOT NULL
2405
+ AND authz.changed_at > $3 AND authz.changed_at <= $2
2406
+ ) changed WHERE changed.obligation_id IS NOT NULL`,
2407
+ [query.account_id, ledgerAsOf, changesAfter, query.consumer_id ?? null]
2408
+ );
2409
+ return new Set(result.rows.map((row) => row.obligation_id));
2410
+ }
2411
+ async listSnapshotManagedBindings(client, configurationIds) {
2412
+ if (!configurationIds.length) return [];
2413
+ const result = await client.query(
2414
+ `SELECT data FROM adcp_reporting_managed_bindings
2415
+ WHERE configuration_id = ANY($1::text[]) ORDER BY configuration_id`,
2416
+ [configurationIds]
2417
+ );
2418
+ return result.rows.map((row) => row.data);
2419
+ }
2420
+ async listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) {
2421
+ if (!obligationIds.length) return [];
2422
+ const result = await client.query(
2423
+ `SELECT data FROM adcp_reporting_materializations
2424
+ WHERE obligation_id = ANY($1::text[])
2425
+ AND recorded_at <= $2
2426
+ AND ($3::timestamptz IS NULL OR GREATEST(recorded_at, changed_at) > $3)
2427
+ ORDER BY obligation_id, attempt, materialization_id
2428
+ LIMIT $4`,
2429
+ [obligationIds, ledgerAsOf, changesAfter ?? null, MAX_SNAPSHOT_ITEMS + 1]
2430
+ );
2431
+ return result.rows.map((row) => row.data);
2432
+ }
2433
+ async listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) {
2434
+ if (!obligationIds.length) return [];
2435
+ const result = await client.query(
2436
+ `SELECT materialization.data, authz.revoked_at
2437
+ FROM adcp_reporting_materializations materialization
2438
+ JOIN adcp_reporting_destination_authorizations authz
2439
+ ON authz.account_id = materialization.account_id
2440
+ AND authz.destination_ref = materialization.destination_ref
2441
+ AND authz.generation = materialization.authorization_generation
2442
+ WHERE materialization.obligation_id = ANY($1::text[])
2443
+ AND materialization.recorded_at <= $2
2444
+ ORDER BY materialization.obligation_id, materialization.attempt, materialization.materialization_id
2445
+ LIMIT $3`,
2446
+ [obligationIds, ledgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
2447
+ );
2448
+ return result.rows.map((row) => {
2449
+ if (!row.revoked_at || row.data.status !== "available" && row.data.status !== "delivered") return row.data;
2450
+ return {
2451
+ ...row.data,
2452
+ status: "failed",
2453
+ failed_at: row.revoked_at.toISOString(),
2454
+ failure_code: "AUTHORIZATION_REVOKED"
2455
+ };
2456
+ });
2457
+ }
2458
+ async listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, kind) {
2459
+ if (!obligationIds.length || !query.consumer_id) return [];
2460
+ const join = kind === "revision" ? "JOIN adcp_reporting_revisions subject ON subject.revision_id = receipt.subject_id" : "JOIN adcp_reporting_adjustments subject ON subject.adjustment_id = receipt.subject_id";
2461
+ const result = await client.query(
2462
+ `SELECT receipt.data FROM adcp_reporting_receipts receipt
2463
+ ${join}
2464
+ WHERE receipt.account_id = $1 AND receipt.consumer_id = $2
2465
+ AND receipt.receipt_kind = $3 AND subject.obligation_id = ANY($4::text[])
2466
+ AND receipt.recorded_at <= $5
2467
+ AND ($6::timestamptz IS NULL OR receipt.recorded_at > $6)
2468
+ ORDER BY subject.obligation_id, receipt.recorded_at, receipt.reporting_receipt_id
2469
+ LIMIT $7`,
2470
+ [
2471
+ query.account_id,
2472
+ query.consumer_id,
2473
+ kind,
2474
+ obligationIds,
2475
+ ledgerAsOf,
2476
+ changesAfter ?? null,
2477
+ MAX_SNAPSHOT_ITEMS + 1
2478
+ ]
2479
+ );
2480
+ return result.rows.map((row) => row.data);
2481
+ }
1587
2482
  async one(sql, params) {
1588
2483
  const result = await this.query(sql, params);
1589
2484
  return result.rows[0] ? clone(result.rows[0].data) : null;
1590
2485
  }
1591
- async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence) {
2486
+ async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence, legacyReplayEquivalent) {
1592
2487
  return this.transaction(
1593
2488
  async (client) => {
1594
2489
  const inserted = await client.query(insertSql, insertParams);
@@ -1606,7 +2501,7 @@ class PostgresReportingLedgerStore {
1606
2501
  }
1607
2502
  throw new Error("Immutable reporting ledger replay could not be resolved");
1608
2503
  }
1609
- if (fingerprint(value) !== fingerprint(proposed)) {
2504
+ if (fingerprint(value) !== fingerprint(proposed) && !legacyReplayEquivalent?.(value, proposed)) {
1610
2505
  throw new Error("Immutable reporting ledger identity names different content");
1611
2506
  }
1612
2507
  return { inserted: inserted.rowCount === 1, value: clone(value) };
@@ -1718,6 +2613,38 @@ function snapshotItems(snapshot) {
1718
2613
  values.push(adjustment);
1719
2614
  adjustments.set(adjustment.reporting_obligation_id, values);
1720
2615
  }
2616
+ const obligationByRevision = new Map(
2617
+ snapshot.revisions.map((value) => [value.reporting_revision_id, value.reporting_obligation_id])
2618
+ );
2619
+ const obligationByAdjustment = new Map(
2620
+ snapshot.adjustments.map((value) => [value.reporting_adjustment_id, value.reporting_obligation_id])
2621
+ );
2622
+ const materializations = /* @__PURE__ */ new Map();
2623
+ for (const materialization of snapshot.materializations ?? []) {
2624
+ if (snapshot.query.finality && !visibleRevisionIds.has(materialization.reporting_revision_id)) continue;
2625
+ const values = materializations.get(materialization.reporting_obligation_id) ?? [];
2626
+ values.push(materialization);
2627
+ materializations.set(materialization.reporting_obligation_id, values);
2628
+ }
2629
+ const receipts = /* @__PURE__ */ new Map();
2630
+ for (const receipt of snapshot.receipts ?? []) {
2631
+ if (snapshot.query.finality && !visibleRevisionIds.has(receipt.reporting_revision_id)) continue;
2632
+ const obligationId = obligationByRevision.get(receipt.reporting_revision_id);
2633
+ if (!obligationId) continue;
2634
+ const values = receipts.get(obligationId) ?? [];
2635
+ values.push(receipt);
2636
+ receipts.set(obligationId, values);
2637
+ }
2638
+ const adjustmentReceipts = /* @__PURE__ */ new Map();
2639
+ const visibleAdjustmentIds = new Set([...adjustments.values()].flat().map((value) => value.reporting_adjustment_id));
2640
+ for (const receipt of snapshot.adjustmentReceipts ?? []) {
2641
+ if (snapshot.query.finality && !visibleAdjustmentIds.has(receipt.reporting_adjustment_id)) continue;
2642
+ const obligationId = obligationByAdjustment.get(receipt.reporting_adjustment_id);
2643
+ if (!obligationId) continue;
2644
+ const values = adjustmentReceipts.get(obligationId) ?? [];
2645
+ values.push(receipt);
2646
+ adjustmentReceipts.set(obligationId, values);
2647
+ }
1721
2648
  return [
1722
2649
  ...snapshot.obligations.flatMap((obligation) => [
1723
2650
  ...snapshot.query.view === "revision" ? [] : [{ kind: "obligation", value: obligation }],
@@ -1728,6 +2655,18 @@ function snapshotItems(snapshot) {
1728
2655
  ...(adjustments.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
1729
2656
  kind: "adjustment",
1730
2657
  value
2658
+ })),
2659
+ ...(materializations.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
2660
+ kind: "materialization",
2661
+ value
2662
+ })),
2663
+ ...(receipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
2664
+ kind: "receipt",
2665
+ value
2666
+ })),
2667
+ ...(adjustmentReceipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
2668
+ kind: "adjustment_receipt",
2669
+ value
1731
2670
  }))
1732
2671
  ]),
1733
2672
  ...snapshot.query.view === "periods" || snapshot.query.view === "revision" ? (snapshot.consumerStatuses ?? []).map((value) => ({ kind: "consumer_status", value })) : []
@@ -1808,9 +2747,200 @@ function adjustmentIdentityFingerprint(value) {
1808
2747
  const { created_at: _wireCreatedAt, ...stableWireAdjustment } = wireAdjustment;
1809
2748
  return digest(JSON.parse(JSON.stringify({ ...identity, wireAdjustment: stableWireAdjustment })));
1810
2749
  }
2750
+ function revisionLegacyCanonicalDigestReplay(stored, proposed) {
2751
+ if (stored.wireRevision.canonical_content_digest !== void 0) return false;
2752
+ if (proposed.wireRevision.canonical_content_digest === void 0) return false;
2753
+ const { canonical_content_digest: _digest, ...withoutDigest } = proposed.wireRevision;
2754
+ return revisionIdentityFingerprint({
2755
+ ...proposed,
2756
+ wireRevision: withoutDigest
2757
+ }) === revisionIdentityFingerprint(stored);
2758
+ }
2759
+ function adjustmentLegacyCanonicalDigestReplay(stored, proposed) {
2760
+ if (stored.wireAdjustment.canonical_adjustment_sha256 !== void 0) return false;
2761
+ const proposedDigest = proposed.wireAdjustment.canonical_adjustment_sha256;
2762
+ if (proposedDigest === void 0) return false;
2763
+ const { canonical_adjustment_sha256: _digest, ...withoutDigest } = proposed.wireAdjustment;
2764
+ if ((0, import_producer.reportingCanonicalAdjustmentSha256V1)(withoutDigest) !== proposedDigest) return false;
2765
+ return adjustmentIdentityFingerprint({
2766
+ ...proposed,
2767
+ wireAdjustment: withoutDigest
2768
+ }) === adjustmentIdentityFingerprint(stored);
2769
+ }
2770
+ const OBLIGATED_CONSUMERS_DEADLINE_MS = 1e4;
2771
+ async function withReportingCallbackDeadline(value, milliseconds, message) {
2772
+ let timer;
2773
+ try {
2774
+ return await Promise.race([
2775
+ value,
2776
+ new Promise((_resolve, reject) => {
2777
+ timer = setTimeout(() => reject(new Error(message)), milliseconds);
2778
+ })
2779
+ ]);
2780
+ } finally {
2781
+ if (timer) clearTimeout(timer);
2782
+ }
2783
+ }
2784
+ function pendingAtCutoff(value) {
2785
+ const {
2786
+ ready_at: _readyAt,
2787
+ resource: _resource,
2788
+ verification: _verification,
2789
+ failed_at: _failedAt,
2790
+ failure_code: _failureCode,
2791
+ ...pending
2792
+ } = clone(value);
2793
+ return { ...pending, status: "pending" };
2794
+ }
2795
+ const MANAGED_DUE_ARM = ` OR (EXISTS (
2796
+ SELECT 1 FROM adcp_reporting_managed_bindings binding
2797
+ WHERE binding.configuration_id = obligation.configuration_id
2798
+ AND (
2799
+ -- A managed change after the last transition. Health is
2800
+ -- composed from managed state, but only Core evidence
2801
+ -- and the clock used to schedule a reconcile \u2014 so a
2802
+ -- settlement, revocation, receipt, consumer status or
2803
+ -- roster-visible change arriving after a complete left
2804
+ -- that complete persisted and webhooked forever while
2805
+ -- a live read of the same obligation degraded.
2806
+ EXISTS (
2807
+ SELECT 1 FROM adcp_reporting_materializations m
2808
+ WHERE m.obligation_id = obligation.obligation_id
2809
+ AND m.changed_at > watermark.since
2810
+ AND m.changed_at <= $2)
2811
+ OR EXISTS (
2812
+ -- Adjustments are managed health inputs too: after an
2813
+ -- accepted billing receipt, a correction makes the live
2814
+ -- read action_required while the persisted health and
2815
+ -- its webhook stayed complete.
2816
+ SELECT 1 FROM adcp_reporting_adjustments adj
2817
+ WHERE adj.obligation_id = obligation.obligation_id
2818
+ AND adj.recorded_at > watermark.since
2819
+ AND adj.recorded_at <= $2)
2820
+ OR EXISTS (
2821
+ SELECT 1 FROM adcp_reporting_destination_authorizations authz
2822
+ WHERE authz.account_id = binding.account_id
2823
+ AND authz.destination_ref = binding.destination_ref
2824
+ AND GREATEST(authz.changed_at, COALESCE(authz.cleanup_completed_at, authz.changed_at))
2825
+ > watermark.since
2826
+ AND authz.changed_at <= $2)
2827
+ -- Driven from this obligation's subjects into the
2828
+ -- receipt table's subject index. Asking the global
2829
+ -- receipt table which of its rows belong to this
2830
+ -- obligation supplied neither account nor consumer, so
2831
+ -- every candidate obligation in every sweep re-scanned
2832
+ -- the whole receipt history.
2833
+ OR EXISTS (
2834
+ SELECT 1 FROM adcp_reporting_revisions rv
2835
+ JOIN adcp_reporting_receipts receipt
2836
+ ON receipt.receipt_kind = 'revision'
2837
+ AND receipt.subject_id = rv.revision_id
2838
+ WHERE rv.obligation_id = obligation.obligation_id
2839
+ AND receipt.recorded_at > watermark.since
2840
+ AND receipt.recorded_at <= $2)
2841
+ OR EXISTS (
2842
+ SELECT 1 FROM adcp_reporting_adjustments aj
2843
+ JOIN adcp_reporting_receipts receipt
2844
+ ON receipt.receipt_kind = 'adjustment'
2845
+ AND receipt.subject_id = aj.adjustment_id
2846
+ WHERE aj.obligation_id = obligation.obligation_id
2847
+ AND receipt.recorded_at > watermark.since
2848
+ AND receipt.recorded_at <= $2)
2849
+ OR EXISTS (
2850
+ SELECT 1 FROM adcp_reporting_consumer_statuses status
2851
+ WHERE status.obligation_id = obligation.obligation_id
2852
+ AND status.created_at > watermark.since
2853
+ AND status.created_at <= $2)
2854
+ -- An external roster change writes nothing here at all,
2855
+ -- so the only durable trace is the version recorded at
2856
+ -- the last reconcile. A null one means never reconciled.
2857
+ OR state.processed_roster_version IS NULL
2858
+ -- A roster lives outside this database, so the only way
2859
+ -- a change can re-arm anything is if someone records
2860
+ -- it. Every read of the roster publishes the version it
2861
+ -- saw; a difference from what was last reconciled is a
2862
+ -- due condition. Without this the first reconcile made
2863
+ -- processed_roster_version non-null and no later roster
2864
+ -- change could ever schedule work again.
2865
+ OR state.current_roster_version IS DISTINCT FROM state.processed_roster_version
2866
+ -- A retained resource expiring is a health change with
2867
+ -- no row written anywhere, so nothing else can notice it.
2868
+ OR EXISTS (
2869
+ SELECT 1 FROM adcp_reporting_materializations m
2870
+ WHERE m.obligation_id = obligation.obligation_id
2871
+ AND m.status IN ('available', 'delivered')
2872
+ AND (m.data -> 'resource' ->> 'expires_at')::timestamptz <= $2
2873
+ AND (m.data -> 'resource' ->> 'expires_at')::timestamptz
2874
+ > watermark.since)
2875
+ )
2876
+ ))
2877
+ `;
2878
+ function obligatedConsumerIdsFor(supplied, observed) {
2879
+ return supplied.complete === true ? [...new Set(supplied.ids)].sort() : [.../* @__PURE__ */ new Set([...supplied.ids, ...observed])].sort();
2880
+ }
2881
+ function obligatedConsumerRosterVersionFor(supplied, ids) {
2882
+ return supplied.version ?? digest({ ids, complete: supplied.complete === true });
2883
+ }
2884
+ const MAX_ROSTER_REFRESH_PER_SWEEP = 25;
2885
+ const MAX_LIFECYCLE_BACKOFF_SECONDS = 900;
2886
+ function rfc3339Microseconds(expression) {
2887
+ return `to_char(${expression} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`;
2888
+ }
1811
2889
  function accountLock(accountId) {
1812
2890
  return `adcp-reporting-account:${accountId}`;
1813
2891
  }
2892
+ async function resolveStoredFinalityBaseline(transaction, obligationId) {
2893
+ const latest = await transaction.query(
2894
+ `SELECT transition_id, data->>'finality' AS finality
2895
+ FROM adcp_reporting_transitions
2896
+ WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1
2897
+ FOR UPDATE`,
2898
+ [obligationId]
2899
+ );
2900
+ const previous = latest.rows[0];
2901
+ if (!previous) return "none";
2902
+ if (previous.finality) return previous.finality;
2903
+ await transaction.query(
2904
+ `UPDATE adcp_reporting_transitions
2905
+ SET data = data || jsonb_build_object('finality', 'none')
2906
+ WHERE transition_id = $1 AND NOT (data ? 'finality')`,
2907
+ [previous.transition_id]
2908
+ );
2909
+ return "none";
2910
+ }
2911
+ async function assertNoLegacyPendingTransitions(transaction, obligationId) {
2912
+ const pending = await transaction.query(
2913
+ `SELECT transition_id FROM adcp_reporting_transitions
2914
+ WHERE obligation_id = $1 AND data->>'notifiedAt' IS NULL
2915
+ LIMIT 1`,
2916
+ [obligationId]
2917
+ );
2918
+ if (pending.rowCount !== 0) {
2919
+ throw new Error(
2920
+ "Drain or explicitly resolve legacy pending reporting transitions before enabling transactional notification activity"
2921
+ );
2922
+ }
2923
+ }
2924
+ async function assertFinalityWriterFence(work) {
2925
+ try {
2926
+ return await work();
2927
+ } catch (error) {
2928
+ if (isFinalityWriterFenceViolation(error)) {
2929
+ throw new Error(
2930
+ "Reporting transition rejected by the finality writer fence: every transition written after REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION must record an observed finality. Drain pre-SDK-14 writers before installing the fence.",
2931
+ { cause: error }
2932
+ );
2933
+ }
2934
+ throw error;
2935
+ }
2936
+ }
2937
+ function isFinalityWriterFenceViolation(error) {
2938
+ for (let current = error; current instanceof Error; current = current.cause) {
2939
+ const candidate = current;
2940
+ if (candidate.code === "23514" && candidate.constraint === REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT) return true;
2941
+ }
2942
+ return false;
2943
+ }
1814
2944
  function consumerStatusChainKeyForObligation(obligation) {
1815
2945
  return (0, import_consumer_status_identity.reportingConsumerStatusChainKeyFromIdentityV1)({
1816
2946
  delivery_config_id: obligation.delivery_config_id,
@@ -1891,6 +3021,8 @@ function positiveInteger(value, name) {
1891
3021
  // Annotate the CommonJS export names for ESM import in node:
1892
3022
  0 && (module.exports = {
1893
3023
  PostgresReportingLedgerStore,
3024
+ REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT,
3025
+ REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
1894
3026
  REPORTING_LEDGER_MIGRATION,
1895
3027
  ReportingLedgerContinuityError,
1896
3028
  ReportingLedgerLeaseLostError,