@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,9 +19,12 @@ import {
19
19
  REPORTING_CONSUMER_STATUS_ERROR_FIELD_MAX_BYTES,
20
20
  REPORTING_CONSUMER_STATUS_ERROR_MESSAGE_MAX_BYTES,
21
21
  REPORTING_CONSUMER_STATUS_MAX_BYTES,
22
+ REPORTING_LEDGER_AUTHORITY,
22
23
  ReportingLedgerSnapshotUnavailableError
23
24
  } from "./types.mjs";
24
25
  import { ReportingConsumerStatusConflictError } from "./types.mjs";
26
+ import { moreSevereReportingHealthV1, projectManagedDelivery } from "./handler.mjs";
27
+ import { reportingCanonicalAdjustmentSha256V1 } from "./producer.mjs";
25
28
  import {
26
29
  normalizeReportingConsumerStatusIdsV1,
27
30
  reportingConsumerStatusChainKeyFromIdentityV1,
@@ -174,6 +177,12 @@ CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_scope
174
177
  ON adcp_reporting_consumer_statuses (account_id, consumer_id, created_at, consumer_status_id);
175
178
  CREATE UNIQUE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_current
176
179
  ON adcp_reporting_consumer_statuses (account_id, consumer_id, chain_key) WHERE is_current;
180
+ -- The managed state digest is read inside the apply transaction, under the
181
+ -- account lock. Filtering this table by obligation alone matched no index, so
182
+ -- every apply scanned every consumer status in the deployment while holding
183
+ -- that lock and pushed concurrent Core writes past their lock timeout.
184
+ CREATE INDEX IF NOT EXISTS adcp_reporting_consumer_statuses_obligation
185
+ ON adcp_reporting_consumer_statuses (obligation_id, created_at, consumer_status_id);
177
186
 
178
187
  CREATE TABLE IF NOT EXISTS adcp_reporting_consumer_status_batches (
179
188
  account_id TEXT NOT NULL,
@@ -198,6 +207,29 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_issues (
198
207
  CREATE INDEX IF NOT EXISTS adcp_reporting_issues_obligation
199
208
  ON adcp_reporting_issues (obligation_id, observed_at, issue_id);
200
209
 
210
+ -- Per-obligation reconciliation watermark. A reconcile that changes nothing
211
+ -- still has to record that it looked, or a change with no health effect stays
212
+ -- a candidate forever and starves newer work behind it. Core, not managed:
213
+ -- the lifecycle sweep is a Core concern and must not depend on the add-on.
214
+ CREATE TABLE IF NOT EXISTS adcp_reporting_lifecycle_state (
215
+ obligation_id TEXT PRIMARY KEY REFERENCES adcp_reporting_obligations(obligation_id),
216
+ processed_state_version TEXT,
217
+ processed_roster_version TEXT,
218
+ processed_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
219
+ );
220
+ CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_processed
221
+ ON adcp_reporting_lifecycle_state (processed_at, obligation_id);
222
+ ALTER TABLE adcp_reporting_lifecycle_state
223
+ ADD COLUMN IF NOT EXISTS current_roster_version TEXT;
224
+ ALTER TABLE adcp_reporting_lifecycle_state
225
+ ADD COLUMN IF NOT EXISTS failure_count INTEGER NOT NULL DEFAULT 0;
226
+ ALTER TABLE adcp_reporting_lifecycle_state
227
+ ADD COLUMN IF NOT EXISTS next_attempt_at TIMESTAMPTZ;
228
+ ALTER TABLE adcp_reporting_lifecycle_state
229
+ ADD COLUMN IF NOT EXISTS roster_refreshed_at TIMESTAMPTZ;
230
+ CREATE INDEX IF NOT EXISTS adcp_reporting_lifecycle_state_roster_refreshed
231
+ ON adcp_reporting_lifecycle_state (roster_refreshed_at, obligation_id);
232
+
201
233
  CREATE TABLE IF NOT EXISTS adcp_reporting_transitions (
202
234
  transition_id TEXT PRIMARY KEY,
203
235
  transition_sequence BIGSERIAL NOT NULL UNIQUE,
@@ -237,7 +269,23 @@ CREATE TABLE IF NOT EXISTS adcp_reporting_checkpoints (
237
269
  CREATE INDEX IF NOT EXISTS adcp_reporting_checkpoints_expiry
238
270
  ON adcp_reporting_checkpoints (expires_at);
239
271
  `.trim();
272
+ const REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT = "adcp_reporting_transitions_finality_recorded";
273
+ const REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION = `
274
+ DO $$
275
+ BEGIN
276
+ IF NOT EXISTS (
277
+ SELECT 1 FROM pg_constraint
278
+ WHERE conrelid = 'adcp_reporting_transitions'::regclass
279
+ AND conname = '${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}'
280
+ ) THEN
281
+ ALTER TABLE adcp_reporting_transitions
282
+ ADD CONSTRAINT ${REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT}
283
+ CHECK (data ? 'finality') NOT VALID;
284
+ END IF;
285
+ END $$;
286
+ `.trim();
240
287
  const MAX_SNAPSHOT_ITEMS = 1e4;
288
+ const MAX_LIFECYCLE_RECEIPT_LEAVES = 1e5;
241
289
  const MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
242
290
  const MAX_ACTIVE_SNAPSHOTS_PER_ACCOUNT = 32;
243
291
  const MAX_ACTIVE_SNAPSHOT_BYTES_PER_ACCOUNT = 128 * 1024 * 1024;
@@ -292,6 +340,9 @@ async function sweepExpiredReportingLedgerState(pool, limit = 1e3) {
292
340
  class PostgresReportingLedgerStore {
293
341
  constructor(pool, options = {}) {
294
342
  this.pool = pool;
343
+ this.managedDelivery = options.managedDelivery === true;
344
+ this.obligatedConsumers = options.obligatedConsumers;
345
+ this[REPORTING_LEDGER_AUTHORITY] = { substrate: pool, managedDelivery: this.managedDelivery };
295
346
  const development = process.env.NODE_ENV === "test" || process.env.NODE_ENV === "development";
296
347
  if (!development && !options.acknowledgeIsolatedDatabase) {
297
348
  throw new Error(
@@ -299,6 +350,8 @@ class PostgresReportingLedgerStore {
299
350
  );
300
351
  }
301
352
  this.consumerMismatchEscalation = assertReportingConsumerMismatchEscalation(options.consumerMismatchEscalation);
353
+ this.notificationActivityPort = options.notificationActivityPort;
354
+ this.transactionalNotificationActivity = options.notificationActivityPort !== void 0;
302
355
  }
303
356
  pool;
304
357
  /**
@@ -309,6 +362,11 @@ class PostgresReportingLedgerStore {
309
362
  * contradict the summary.
310
363
  */
311
364
  consumerMismatchEscalation;
365
+ [REPORTING_LEDGER_AUTHORITY];
366
+ managedDelivery;
367
+ obligatedConsumers;
368
+ transactionalNotificationActivity;
369
+ notificationActivityPort;
312
370
  async putConfiguration(configuration) {
313
371
  return this.transaction(
314
372
  async (client) => {
@@ -409,14 +467,26 @@ class PostgresReportingLedgerStore {
409
467
  positiveInteger(input.limit, "limit");
410
468
  if (input.limit > 1e3) throw new RangeError("limit must not exceed 1000");
411
469
  if (!Number.isFinite(Date.parse(input.ledgerAsOf))) throw new TypeError("ledgerAsOf must be an RFC 3339 instant");
470
+ const managedDueArm = await this.managedDueTablesReady() ? MANAGED_DUE_ARM : "";
412
471
  const result = await this.query(
413
472
  `SELECT obligation.data FROM adcp_reporting_obligations obligation
414
473
  LEFT JOIN LATERAL (
415
- SELECT transition.data->>'health' AS health
474
+ SELECT transition.data->>'health' AS health, transition.occurred_at
416
475
  FROM adcp_reporting_transitions transition
417
476
  WHERE transition.obligation_id = obligation.obligation_id
418
477
  ORDER BY transition.transition_sequence DESC LIMIT 1
419
478
  ) latest ON TRUE
479
+ LEFT JOIN adcp_reporting_lifecycle_state state
480
+ ON state.obligation_id = obligation.obligation_id
481
+ CROSS JOIN LATERAL (
482
+ -- A reconcile that changed no health still has to count as work done,
483
+ -- or a managed change with no health effect keeps the obligation due
484
+ -- forever and, at small page sizes, starves everything behind it.
485
+ SELECT GREATEST(
486
+ COALESCE(state.processed_at, obligation.created_at),
487
+ COALESCE(latest.occurred_at, obligation.created_at)
488
+ ) AS since
489
+ ) watermark
420
490
  WHERE ($1::text IS NULL OR obligation.account_id = $1)
421
491
  AND ((COALESCE(latest.health, 'waiting') = 'waiting'
422
492
  AND (obligation.data->>'expectedAt')::timestamptz <= $2)
@@ -427,8 +497,14 @@ class PostgresReportingLedgerStore {
427
497
  AND EXISTS (
428
498
  SELECT 1 FROM adcp_reporting_revisions revision
429
499
  WHERE revision.obligation_id = obligation.obligation_id
430
- )))
431
- ORDER BY LEAST(
500
+ ))
501
+ ${managedDueArm} )
502
+ -- A tenant that keeps failing must not monopolise every sweep. Its
503
+ -- backoff cursor holds it out of selection until it is due again,
504
+ -- while processed_at ordering still favours whoever has waited
505
+ -- longest among the eligible.
506
+ AND (state.next_attempt_at IS NULL OR state.next_attempt_at <= $2)
507
+ ORDER BY watermark.since, LEAST(
432
508
  (obligation.data->>'expectedAt')::timestamptz,
433
509
  (obligation.data->>'recoveryDeadlineAt')::timestamptz
434
510
  ), obligation.obligation_id
@@ -575,7 +651,8 @@ class PostgresReportingLedgerStore {
575
651
  obligationId: revision.reporting_obligation_id,
576
652
  owner: lease.owner,
577
653
  generation: lease.generation
578
- }
654
+ },
655
+ revisionLegacyCanonicalDigestReplay
579
656
  );
580
657
  }
581
658
  async getRevision(id, accountId) {
@@ -649,7 +726,8 @@ class PostgresReportingLedgerStore {
649
726
  obligationId: adjustment.reporting_obligation_id,
650
727
  owner: lease.owner,
651
728
  generation: lease.generation
652
- }
729
+ },
730
+ adjustmentLegacyCanonicalDigestReplay
653
731
  );
654
732
  }
655
733
  async listAdjustments(obligationId) {
@@ -1040,27 +1118,59 @@ class PostgresReportingLedgerStore {
1040
1118
  );
1041
1119
  return result.rows.map((row) => clone(row.data));
1042
1120
  }
1121
+ /**
1122
+ * Resolves — and for pre-SDK-14 rows persists — the observed finality baseline
1123
+ * of the obligation's latest transition, so the lifecycle decision and this
1124
+ * store's compare-and-set read one committed value.
1125
+ */
1126
+ async resolveTransitionFinalityBaseline(obligationId) {
1127
+ const lock = await this.accountLockForObligation(obligationId);
1128
+ return this.transaction((client) => resolveStoredFinalityBaseline(client, obligationId), {
1129
+ preBeginAdvisoryLock: lock
1130
+ });
1131
+ }
1043
1132
  async appendTransition(transition) {
1044
1133
  const lock = await this.accountLockForObligation(transition.reporting_obligation_id);
1134
+ return assertFinalityWriterFence(() => this.appendTransitionWithinLock(transition, lock));
1135
+ }
1136
+ async appendTransitionWithinLock(transition, lock) {
1045
1137
  return this.transaction(
1046
1138
  async (client) => {
1139
+ if (this.notificationActivityPort) {
1140
+ await assertNoLegacyPendingTransitions(client, transition.reporting_obligation_id);
1141
+ }
1142
+ const previousFinality = await resolveStoredFinalityBaseline(client, transition.reporting_obligation_id);
1143
+ const latest = await client.query(
1144
+ `SELECT data->>'health' AS health FROM adcp_reporting_transitions
1145
+ WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1`,
1146
+ [transition.reporting_obligation_id]
1147
+ );
1148
+ if ((latest.rows[0]?.health ?? "waiting") !== transition.previousHealth || transition.previousFinality !== void 0 && previousFinality !== transition.previousFinality) {
1149
+ return { inserted: false };
1150
+ }
1151
+ const storedTransition = this.notificationActivityPort ? { ...transition, notifiedAt: transition.occurredAt } : transition;
1047
1152
  const result = await client.query(
1048
1153
  `INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
1049
- SELECT $1, $2, $3::jsonb, $4
1050
- WHERE COALESCE((
1051
- SELECT previous.data->>'health' FROM adcp_reporting_transitions previous
1052
- WHERE previous.obligation_id = $2
1053
- ORDER BY previous.transition_sequence DESC LIMIT 1
1054
- ), 'waiting') = $5
1055
- ON CONFLICT DO NOTHING RETURNING transition_id`,
1154
+ VALUES ($1, $2, $3::jsonb, $4)
1155
+ ON CONFLICT DO NOTHING RETURNING transition_id`,
1056
1156
  [
1057
1157
  transition.transitionId,
1058
1158
  transition.reporting_obligation_id,
1059
- JSON.stringify(transition),
1060
- transition.occurredAt,
1061
- transition.previousHealth
1159
+ JSON.stringify(storedTransition),
1160
+ transition.occurredAt
1062
1161
  ]
1063
1162
  );
1163
+ if (result.rowCount === 1 && this.notificationActivityPort) {
1164
+ const obligation = await client.query(
1165
+ "SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1",
1166
+ [transition.reporting_obligation_id]
1167
+ );
1168
+ if (!obligation.rows[0]) throw new Error("Reporting obligation is unavailable");
1169
+ await this.notificationActivityPort.recordTransition(
1170
+ { transition, obligation: obligation.rows[0].data },
1171
+ client
1172
+ );
1173
+ }
1064
1174
  return { inserted: result.rowCount === 1 };
1065
1175
  },
1066
1176
  { preBeginAdvisoryLock: lock }
@@ -1068,12 +1178,19 @@ class PostgresReportingLedgerStore {
1068
1178
  }
1069
1179
  async applyLifecycleProjection(input) {
1070
1180
  const lock = await this.accountLockForObligation(input.reporting_obligation_id);
1181
+ return assertFinalityWriterFence(() => this.applyLifecycleProjectionWithinLock(input, lock));
1182
+ }
1183
+ async applyLifecycleProjectionWithinLock(input, lock) {
1071
1184
  return this.transaction(
1072
1185
  async (client) => {
1073
1186
  const obligations = await client.query(
1074
1187
  `SELECT data FROM adcp_reporting_obligations WHERE obligation_id = $1 FOR UPDATE`,
1075
1188
  [input.reporting_obligation_id]
1076
1189
  );
1190
+ if (this.notificationActivityPort) {
1191
+ await assertNoLegacyPendingTransitions(client, input.reporting_obligation_id);
1192
+ }
1193
+ const previousFinality = await resolveStoredFinalityBaseline(client, input.reporting_obligation_id);
1077
1194
  const revisions = await client.query(
1078
1195
  `SELECT revision_id FROM adcp_reporting_revisions
1079
1196
  WHERE obligation_id = $1 ORDER BY revision_number, revision_id`,
@@ -1087,11 +1204,26 @@ class PostgresReportingLedgerStore {
1087
1204
  const revisionIds = revisions.rows.map((value) => value.revision_id);
1088
1205
  const previousHealth = latest.rows[0]?.health ?? "waiting";
1089
1206
  const obligation = obligations.rows[0]?.data;
1090
- if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || canonicalJsonV1(revisionIds) !== canonicalJsonV1(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth) {
1207
+ if (!obligation || obligation.state !== input.expectedObligationState || obligation.attemptCount !== input.expectedAttemptCount || canonicalJsonV1(revisionIds) !== canonicalJsonV1(input.expectedRevisionIds) || previousHealth !== input.expectedPreviousHealth || input.expectedPreviousFinality !== void 0 && previousFinality !== input.expectedPreviousFinality) {
1091
1208
  return { applied: false, transitionInserted: false };
1092
1209
  }
1210
+ if (input.expectedManagedStateVersion !== void 0 && await this.readManagedStateVersion(client, input.reporting_obligation_id) !== input.expectedManagedStateVersion) {
1211
+ return { applied: false, transitionInserted: false };
1212
+ }
1213
+ if (input.expectedObligatedConsumerRosterVersion !== void 0) {
1214
+ const state = await client.query(
1215
+ `SELECT current_roster_version FROM adcp_reporting_lifecycle_state
1216
+ WHERE obligation_id = $1 FOR UPDATE`,
1217
+ [input.reporting_obligation_id]
1218
+ );
1219
+ const observed = state.rows[0]?.current_roster_version;
1220
+ if (observed !== void 0 && observed !== input.expectedObligatedConsumerRosterVersion) {
1221
+ return { applied: false, transitionInserted: false };
1222
+ }
1223
+ }
1093
1224
  let transitionInserted = false;
1094
1225
  if (input.transition) {
1226
+ const storedTransition = this.notificationActivityPort ? { ...input.transition, notifiedAt: input.ledgerAsOf } : input.transition;
1095
1227
  const inserted = await client.query(
1096
1228
  `INSERT INTO adcp_reporting_transitions (transition_id, obligation_id, data, occurred_at)
1097
1229
  VALUES ($1, $2, $3::jsonb, $4)
@@ -1099,12 +1231,60 @@ class PostgresReportingLedgerStore {
1099
1231
  [
1100
1232
  input.transition.transitionId,
1101
1233
  input.reporting_obligation_id,
1102
- JSON.stringify(input.transition),
1234
+ JSON.stringify(storedTransition),
1103
1235
  input.transition.occurredAt
1104
1236
  ]
1105
1237
  );
1106
1238
  if (inserted.rowCount !== 1) return { applied: false, transitionInserted: false };
1107
1239
  transitionInserted = true;
1240
+ if (this.notificationActivityPort) {
1241
+ await this.notificationActivityPort.recordTransition({ transition: input.transition, obligation }, client);
1242
+ }
1243
+ }
1244
+ {
1245
+ await client.query(
1246
+ `INSERT INTO adcp_reporting_lifecycle_state
1247
+ (obligation_id, processed_state_version, processed_roster_version,
1248
+ current_roster_version, processed_at)
1249
+ VALUES ($1, $2, $3, $3, LEAST($4::timestamptz, clock_timestamp()))
1250
+ ON CONFLICT (obligation_id) DO UPDATE SET
1251
+ processed_state_version = EXCLUDED.processed_state_version,
1252
+ processed_roster_version = EXCLUDED.processed_roster_version,
1253
+ -- Reconciling also observes the roster, so the current version
1254
+ -- moves with the processed one. A later external change
1255
+ -- publishes a different current version and re-arms; without
1256
+ -- this the two never matched and the obligation stayed due.
1257
+ -- Never over a version this reconcile did not observe: if the
1258
+ -- row was created or advanced after the check above, that
1259
+ -- newer observation is what has to survive, or its re-arm is
1260
+ -- silently dropped.
1261
+ current_roster_version = CASE
1262
+ WHEN $5::text IS NOT NULL
1263
+ AND adcp_reporting_lifecycle_state.current_roster_version IS DISTINCT FROM $5::text
1264
+ THEN adcp_reporting_lifecycle_state.current_roster_version
1265
+ ELSE EXCLUDED.processed_roster_version END,
1266
+ processed_at = EXCLUDED.processed_at,
1267
+ failure_count = 0,
1268
+ next_attempt_at = NULL`,
1269
+ [
1270
+ input.reporting_obligation_id,
1271
+ input.processedManagedStateVersion ?? null,
1272
+ // Empty string, not null: null means "never reconciled", which
1273
+ // is a due condition. A deployment with no roster source would
1274
+ // otherwise stay due forever.
1275
+ input.processedObligatedConsumerRosterVersion ?? "",
1276
+ // The cutoff the projection actually read at, never the commit
1277
+ // instant. Watermarking at commit time swallowed everything
1278
+ // that landed between the two: excluded from the projection,
1279
+ // and then behind the watermark forever, so it never became
1280
+ // due again. It is clamped to this database's clock in SQL
1281
+ // below, because a cutoff ahead of the ledger buries
1282
+ // database-timestamped work the same way for the opposite
1283
+ // reason.
1284
+ input.ledgerAsOf,
1285
+ input.expectedObligatedConsumerRosterVersion ?? null
1286
+ ]
1287
+ );
1108
1288
  }
1109
1289
  const projectedIds = input.projectedIssues.map((issue) => issue.issueId);
1110
1290
  for (const issue of input.projectedIssues) {
@@ -1127,7 +1307,17 @@ class PostgresReportingLedgerStore {
1127
1307
  data = data || jsonb_build_object('resolvedAt', $2::text),
1128
1308
  changed_at = clock_timestamp()
1129
1309
  WHERE obligation_id = $1 AND resolved_at IS NULL
1130
- AND data->>'code' IN ('REPORT_OVERDUE', 'REPORTING_COVERAGE_INCOMPLETE')
1310
+ -- Every code this projection can emit, not only the Core two.
1311
+ -- A managed DELIVERY_FAILED persisted by an earlier reconcile
1312
+ -- was never cleared, so after a successful redelivery the
1313
+ -- status kept publishing a stale delivery failure for as long
1314
+ -- as Core stayed degraded for some unrelated reason.
1315
+ AND data->>'code' IN (
1316
+ 'REPORT_OVERDUE', 'REPORTING_COVERAGE_INCOMPLETE',
1317
+ 'DELIVERY_FAILED', 'RESOURCE_EXPIRED',
1318
+ 'RECEIPT_REQUIRED', 'RECEIPT_REJECTED',
1319
+ 'ADJUSTMENT_RECEIPT_REQUIRED', 'ADJUSTMENT_RECEIPT_REJECTED'
1320
+ )
1131
1321
  AND NOT (issue_id = ANY($3::text[]))`,
1132
1322
  [input.reporting_obligation_id, input.ledgerAsOf, projectedIds]
1133
1323
  );
@@ -1144,6 +1334,7 @@ class PostgresReportingLedgerStore {
1144
1334
  await client.query(
1145
1335
  `UPDATE adcp_reporting_transitions
1146
1336
  SET data = data || jsonb_build_object('notifiedAt', $2::text)
1337
+ || CASE WHEN data ? 'finality' THEN '{}'::jsonb ELSE jsonb_build_object('finality', 'none') END
1147
1338
  WHERE transition_id = $1 AND NOT (data ? 'notifiedAt')`,
1148
1339
  [transitionId, notifiedAt]
1149
1340
  );
@@ -1201,11 +1392,31 @@ class PostgresReportingLedgerStore {
1201
1392
  }
1202
1393
  const activeSnapshotBytes = Number(active.rows[0]?.bytes ?? 0);
1203
1394
  const changesAfter = query.changes_after ? await this.resolveChangesCheckpoint(client, query, query.changes_after) : void 0;
1395
+ const managedInstalled = this.managedDelivery && await this.managedTablesInstalled(client);
1204
1396
  const configurations = await this.listSnapshotConfigurations(client, query, ledgerAsOf);
1205
1397
  if (configurations.length > MAX_SNAPSHOT_ITEMS) {
1206
1398
  throw new Error("Reporting ledger snapshot exceeds the configuration limit");
1207
1399
  }
1208
1400
  let obligations = await this.listSnapshotObligations(client, query, ledgerAsOf, changesAfter);
1401
+ if (managedInstalled && changesAfter) {
1402
+ const managedChangedIds = await this.listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter);
1403
+ if (managedChangedIds.size) {
1404
+ const fullScope = await this.listSnapshotObligations(
1405
+ client,
1406
+ { ...query, changes_after: void 0, health: void 0 },
1407
+ ledgerAsOf
1408
+ );
1409
+ const byId = new Map(obligations.map((value) => [value.reporting_obligation_id, value]));
1410
+ for (const obligation of fullScope) {
1411
+ if (managedChangedIds.has(obligation.reporting_obligation_id)) {
1412
+ byId.set(obligation.reporting_obligation_id, obligation);
1413
+ }
1414
+ }
1415
+ obligations = [...byId.values()].sort(
1416
+ (left, right) => left.period.start.localeCompare(right.period.start) || left.reporting_obligation_id.localeCompare(right.reporting_obligation_id)
1417
+ );
1418
+ }
1419
+ }
1209
1420
  if (obligations.length > MAX_SNAPSHOT_ITEMS) {
1210
1421
  throw new Error("Reporting ledger snapshot exceeds the item limit");
1211
1422
  }
@@ -1257,6 +1468,30 @@ class PostgresReportingLedgerStore {
1257
1468
  throw new Error("Reporting ledger snapshot exceeds the item limit");
1258
1469
  }
1259
1470
  let issues = await this.listSnapshotIssues(client, obligationIds, MAX_SNAPSHOT_ITEMS + 1);
1471
+ const managedBindings = managedInstalled ? await this.listSnapshotManagedBindings(
1472
+ client,
1473
+ configurations.map((value) => value.configurationId)
1474
+ ) : [];
1475
+ let materializations = managedInstalled ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) : [];
1476
+ let receipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "revision") : [];
1477
+ let adjustmentReceipts = managedInstalled && query.consumer_id ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, "adjustment") : [];
1478
+ const tombstonedAcceptedSubjects = managedInstalled ? (await this.listTombstonedAcceptedSubjects(client, query, obligationIds, false, ledgerAsOf)).subjects : [];
1479
+ const tombstonedDeliveredRevisionIds = managedInstalled ? (await client.query(
1480
+ `SELECT revision_id FROM adcp_reporting_materialization_tombstones
1481
+ WHERE obligation_id = ANY($1::text[]) AND reached_success
1482
+ AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
1483
+ [obligationIds, MAX_SNAPSHOT_ITEMS + 1, ledgerAsOf]
1484
+ )).rows.map((row) => row.revision_id) : [];
1485
+ const materializationProjection = managedInstalled ? await this.listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) : [];
1486
+ const materializationHistoryProjection = managedInstalled && changesAfter ? await this.listSnapshotMaterializations(client, obligationIds, ledgerAsOf) : materializations;
1487
+ const receiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "revision") : receipts;
1488
+ const adjustmentReceiptProjection = managedInstalled && query.consumer_id && changesAfter ? await this.listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, void 0, "adjustment") : adjustmentReceipts;
1489
+ if (materializationProjection.length > MAX_SNAPSHOT_ITEMS || materializationHistoryProjection.length > MAX_SNAPSHOT_ITEMS || receiptProjection.length > MAX_SNAPSHOT_ITEMS || adjustmentReceiptProjection.length > MAX_SNAPSHOT_ITEMS) {
1490
+ throw new Error("Reporting ledger snapshot exceeds the managed projection limit");
1491
+ }
1492
+ if (obligations.length + revisions.length + adjustments.length + consumerStatuses.length + materializations.length + receipts.length + adjustmentReceipts.length > MAX_SNAPSHOT_ITEMS) {
1493
+ throw new Error("Reporting ledger snapshot exceeds the item limit");
1494
+ }
1260
1495
  if (issues.length > MAX_SNAPSHOT_ITEMS) throw new Error("Reporting ledger snapshot exceeds the issue limit");
1261
1496
  if (query.view === "periods" && query.health) {
1262
1497
  const scopedObligations = obligations;
@@ -1281,7 +1516,34 @@ class PostgresReportingLedgerStore {
1281
1516
  ledgerAsOf,
1282
1517
  this.consumerMismatchEscalation
1283
1518
  );
1284
- return query.health.includes(mismatch?.health ?? sellerHealth);
1519
+ const baseProjection = projectReportingObligationHealthV1(
1520
+ value,
1521
+ obligationRevisions,
1522
+ ledgerAsOf,
1523
+ reportingLedgerScopeClosed(query, ledgerAsOf, ledgerCoverage.complete)
1524
+ );
1525
+ const managed = projectManagedDelivery({
1526
+ obligation: value,
1527
+ binding: managedBindings.find((binding) => binding.configurationId === value.configurationId),
1528
+ revisions: obligationRevisions,
1529
+ adjustments: adjustments.filter(
1530
+ (item) => item.reporting_obligation_id === value.reporting_obligation_id
1531
+ ),
1532
+ materializations: materializationProjection.filter(
1533
+ (item) => item.reporting_obligation_id === value.reporting_obligation_id
1534
+ ),
1535
+ materializationHistory: materializationHistoryProjection.filter(
1536
+ (item) => item.reporting_obligation_id === value.reporting_obligation_id
1537
+ ),
1538
+ receipts: receiptProjection,
1539
+ adjustmentReceipts: adjustmentReceiptProjection,
1540
+ base: baseProjection,
1541
+ ledgerAsOf,
1542
+ tombstonedAcceptedSubjects,
1543
+ tombstonedDeliveredRevisionIds
1544
+ });
1545
+ const health = mismatch ? moreSevereReportingHealthV1(managed?.projection.health ?? sellerHealth, mismatch.health) : managed?.projection.health ?? sellerHealth;
1546
+ return query.health.includes(health);
1285
1547
  }).map((value) => value.reporting_obligation_id)
1286
1548
  );
1287
1549
  const obligationsByConsumerStatusChain = new Map(
@@ -1297,6 +1559,13 @@ class PostgresReportingLedgerStore {
1297
1559
  obligations = obligations.filter((value) => accepted.has(value.reporting_obligation_id));
1298
1560
  revisions = revisions.filter((value) => accepted.has(value.reporting_obligation_id));
1299
1561
  adjustments = adjustments.filter((value) => accepted.has(value.reporting_obligation_id));
1562
+ materializations = materializations.filter((value) => accepted.has(value.reporting_obligation_id));
1563
+ const acceptedRevisionIds = new Set(revisions.map((value) => value.reporting_revision_id));
1564
+ const acceptedAdjustmentIds = new Set(adjustments.map((value) => value.reporting_adjustment_id));
1565
+ receipts = receipts.filter((value) => acceptedRevisionIds.has(value.reporting_revision_id));
1566
+ adjustmentReceipts = adjustmentReceipts.filter(
1567
+ (value) => acceptedAdjustmentIds.has(value.reporting_adjustment_id)
1568
+ );
1300
1569
  consumerStatuses = consumerStatuses.filter((value) => {
1301
1570
  const key = reportingConsumerStatusChainKeyV1(value);
1302
1571
  const matchingObligation = obligationsByConsumerStatusChain.get(key);
@@ -1321,6 +1590,18 @@ class PostgresReportingLedgerStore {
1321
1590
  obligations,
1322
1591
  revisions,
1323
1592
  adjustments,
1593
+ ...managedInstalled ? {
1594
+ tombstonedAcceptedSubjects,
1595
+ tombstonedDeliveredRevisionIds,
1596
+ managedBindings,
1597
+ materializations,
1598
+ materializationHistoryProjection,
1599
+ materializationProjection,
1600
+ receipts,
1601
+ receiptProjection,
1602
+ adjustmentReceipts,
1603
+ adjustmentReceiptProjection
1604
+ } : {},
1324
1605
  consumerStatuses,
1325
1606
  consumerStatusProjection,
1326
1607
  issues
@@ -1390,6 +1671,13 @@ class PostgresReportingLedgerStore {
1390
1671
  const consumerStatuses = selected.filter(
1391
1672
  (value) => value.kind === "consumer_status"
1392
1673
  ).map((value) => value.value);
1674
+ const materializations = selected.filter(
1675
+ (value) => value.kind === "materialization"
1676
+ ).map((value) => value.value);
1677
+ const receipts = selected.filter((value) => value.kind === "receipt").map((value) => value.value);
1678
+ const adjustmentReceipts = selected.filter(
1679
+ (value) => value.kind === "adjustment_receipt"
1680
+ ).map((value) => value.value);
1393
1681
  const nextOffset = offset + selected.length;
1394
1682
  const hasMore = nextOffset < items.length;
1395
1683
  return {
@@ -1397,6 +1685,9 @@ class PostgresReportingLedgerStore {
1397
1685
  obligations,
1398
1686
  revisions,
1399
1687
  adjustments,
1688
+ materializations,
1689
+ receipts,
1690
+ adjustmentReceipts,
1400
1691
  consumerStatuses,
1401
1692
  totalCount: items.length,
1402
1693
  offset,
@@ -1576,11 +1867,614 @@ class PostgresReportingLedgerStore {
1576
1867
  );
1577
1868
  return result.rows.map((row) => row.data);
1578
1869
  }
1870
+ /**
1871
+ * Managed Delivery projection inputs for one obligation, grouped per consumer.
1872
+ *
1873
+ * The lifecycle reconciler folds these through the same
1874
+ * `projectManagedDelivery` the read path uses, so a persisted transition and
1875
+ * its webhook report the health a read of the same obligation would return.
1876
+ * Returns `null` when Managed Delivery is not installed or the obligation's
1877
+ * configuration has no binding, which keeps a Core-only ledger unchanged.
1878
+ */
1879
+ async getManagedLifecycleProjection(input) {
1880
+ if (!this.managedDelivery) return null;
1881
+ const base = await this.transaction(
1882
+ async (client) => {
1883
+ if (!await this.managedTablesInstalled(client)) return null;
1884
+ const obligation = await client.query(
1885
+ "SELECT configuration_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
1886
+ [input.reporting_obligation_id]
1887
+ );
1888
+ const configurationId = obligation.rows[0]?.configuration_id;
1889
+ if (!configurationId) return null;
1890
+ const [binding] = await this.listSnapshotManagedBindings(client, [configurationId]);
1891
+ if (!binding) return null;
1892
+ const resolvedLedgerAsOf = (await client.query(
1893
+ // RFC 3339 with microseconds. PostgreSQL's own ::text rendering
1894
+ // uses a space separator and a two-digit offset, which the SDK's
1895
+ // instant parser rejects, and round-tripping through a JS Date
1896
+ // would truncate the microseconds this exists to preserve.
1897
+ `SELECT ${rfc3339Microseconds("COALESCE($1::timestamptz, clock_timestamp())")} AS instant`,
1898
+ [input.ledgerAsOf ?? null]
1899
+ )).rows[0].instant;
1900
+ const rows = await client.query(
1901
+ `SELECT materialization.data,
1902
+ materialization.changed_at > $2::timestamptz AS settled_after_cutoff,
1903
+ CASE WHEN authz.revoked_at <= $2::timestamptz THEN authz.revoked_at END AS revoked_at
1904
+ FROM adcp_reporting_materializations materialization
1905
+ JOIN adcp_reporting_destination_authorizations authz
1906
+ ON authz.account_id = materialization.account_id
1907
+ AND authz.destination_ref = materialization.destination_ref
1908
+ AND authz.generation = materialization.authorization_generation
1909
+ WHERE materialization.obligation_id = $1
1910
+ AND materialization.recorded_at <= $2::timestamptz
1911
+ ORDER BY materialization.attempt, materialization.materialization_id
1912
+ LIMIT $3`,
1913
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
1914
+ );
1915
+ if (rows.rows.length > MAX_SNAPSHOT_ITEMS) {
1916
+ throw new Error("Reporting lifecycle projection exceeds the managed materialization limit");
1917
+ }
1918
+ const placed = rows.rows.map((row) => ({
1919
+ atAsOf: row.settled_after_cutoff ? pendingAtCutoff(row.data) : clone(row.data),
1920
+ revokedAt: row.revoked_at
1921
+ }));
1922
+ const materializationHistory = placed.map((value) => value.atAsOf);
1923
+ const materializations = placed.map(
1924
+ ({ atAsOf, revokedAt }) => revokedAt && (atAsOf.status === "available" || atAsOf.status === "delivered") ? {
1925
+ ...atAsOf,
1926
+ status: "failed",
1927
+ failed_at: revokedAt.toISOString(),
1928
+ failure_code: "AUTHORIZATION_REVOKED"
1929
+ } : atAsOf
1930
+ );
1931
+ const receipts = await client.query(
1932
+ `WITH subject AS (
1933
+ -- Drive from this obligation's own subjects. Asking the global
1934
+ -- receipt table "which of your rows belong to this obligation"
1935
+ -- matched no index \u2014 they lead with account and consumer, and
1936
+ -- this question supplies neither \u2014 so every due obligation
1937
+ -- re-scanned the whole receipt history.
1938
+ SELECT revision_id AS subject_id, 'revision' AS receipt_kind
1939
+ FROM adcp_reporting_revisions WHERE obligation_id = $1
1940
+ UNION ALL
1941
+ SELECT adjustment_id, 'adjustment'
1942
+ FROM adcp_reporting_adjustments WHERE obligation_id = $1
1943
+ )
1944
+ SELECT receipt.consumer_id, receipt.receipt_kind, receipt.data
1945
+ FROM subject
1946
+ JOIN adcp_reporting_receipts receipt
1947
+ ON receipt.subject_id = subject.subject_id
1948
+ AND receipt.receipt_kind = subject.receipt_kind
1949
+ -- Ordered and cut off by the database clock, exactly as every other
1950
+ -- receipt read path is, so a skewed host cannot make the lifecycle
1951
+ -- projection see a different receipt set than get_reporting_status.
1952
+ --
1953
+ -- One leaf per chain, resolved AS OF the cutoff. The fold reads
1954
+ -- exactly one thing from each chain, and walking the whole history
1955
+ -- to find it pushed this read past its bound for a subject repaired
1956
+ -- often enough. But is_current is today's answer: at a cutoff
1957
+ -- before an acceptance superseded a rejection, the rejection was no
1958
+ -- longer current and the acceptance was not yet in scope, so the
1959
+ -- projection saw neither and persisted RECEIPT_REQUIRED over a
1960
+ -- rejection the buyer had already filed. The leaf at the cutoff is
1961
+ -- the receipt that nothing recorded by then supersedes. Wire
1962
+ -- counters are computed on the read path, which still sees every
1963
+ -- row.
1964
+ WHERE receipt.recorded_at <= $2
1965
+ AND NOT EXISTS (
1966
+ SELECT 1 FROM adcp_reporting_receipts successor
1967
+ WHERE successor.account_id = receipt.account_id
1968
+ AND successor.consumer_id = receipt.consumer_id
1969
+ AND successor.supersedes_receipt_id = receipt.reporting_receipt_id
1970
+ AND successor.recorded_at <= $2
1971
+ )
1972
+ -- A successor whose body has been pruned still superseded this
1973
+ -- row. Counting only live successors let retention resurrect the
1974
+ -- rejected predecessor of a pruned acceptance as the live leaf,
1975
+ -- which overrode that acceptance's own tombstone and reopened a
1976
+ -- settled subject the write path then refused to repair.
1977
+ AND NOT EXISTS (
1978
+ SELECT 1 FROM adcp_reporting_receipt_tombstones gone
1979
+ WHERE gone.account_id = receipt.account_id
1980
+ AND gone.consumer_id = receipt.consumer_id
1981
+ AND gone.supersedes_receipt_id = receipt.reporting_receipt_id
1982
+ AND COALESCE(gone.subject_recorded_at, gone.pruned_at) <= $2
1983
+ )
1984
+ ORDER BY receipt.consumer_id, receipt.subject_id, receipt.recorded_at, receipt.reporting_receipt_id
1985
+ LIMIT $3`,
1986
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_LIFECYCLE_RECEIPT_LEAVES + 1]
1987
+ );
1988
+ let receiptEvidenceComplete = true;
1989
+ let leaves = receipts.rows;
1990
+ if (leaves.length > MAX_LIFECYCLE_RECEIPT_LEAVES) {
1991
+ receiptEvidenceComplete = false;
1992
+ const partial = leaves[MAX_LIFECYCLE_RECEIPT_LEAVES].consumer_id;
1993
+ leaves = leaves.slice(0, MAX_LIFECYCLE_RECEIPT_LEAVES).filter((row) => row.consumer_id !== partial);
1994
+ }
1995
+ const byConsumer = /* @__PURE__ */ new Map();
1996
+ for (const row of leaves) {
1997
+ const consumer = byConsumer.get(row.consumer_id) ?? {
1998
+ consumer_id: row.consumer_id,
1999
+ receipts: [],
2000
+ adjustmentReceipts: []
2001
+ };
2002
+ if (row.receipt_kind === "revision") consumer.receipts.push(clone(row.data));
2003
+ else consumer.adjustmentReceipts.push(clone(row.data));
2004
+ byConsumer.set(row.consumer_id, consumer);
2005
+ }
2006
+ const engaged = await client.query(
2007
+ `SELECT DISTINCT consumer_id FROM adcp_reporting_consumer_statuses
2008
+ WHERE obligation_id = $1 AND consumer_id <> '__legacy_unscoped_consumer__'
2009
+ ORDER BY consumer_id LIMIT $2`,
2010
+ [input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1]
2011
+ );
2012
+ if (engaged.rows.length > MAX_SNAPSHOT_ITEMS) {
2013
+ receiptEvidenceComplete = false;
2014
+ engaged.rows = engaged.rows.slice(0, MAX_SNAPSHOT_ITEMS);
2015
+ }
2016
+ const observed = [.../* @__PURE__ */ new Set([...byConsumer.keys(), ...engaged.rows.map((row) => row.consumer_id)])].sort();
2017
+ const tombstoned = await this.listTombstonedAcceptedSubjects(
2018
+ client,
2019
+ { account_id: binding.account_id, consumer_id: void 0, view: "periods" },
2020
+ [input.reporting_obligation_id],
2021
+ true,
2022
+ resolvedLedgerAsOf
2023
+ );
2024
+ if (!tombstoned.complete) receiptEvidenceComplete = false;
2025
+ const visible = await client.query(
2026
+ `SELECT adjustment_id FROM adcp_reporting_adjustments
2027
+ WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
2028
+ ORDER BY adjustment_number, adjustment_id LIMIT $3`,
2029
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
2030
+ );
2031
+ if (visible.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
2032
+ const visibleAdjustmentIds = visible.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.adjustment_id);
2033
+ const visibleRevisions = await client.query(
2034
+ `SELECT revision_id FROM adcp_reporting_revisions
2035
+ WHERE obligation_id = $1 AND recorded_at <= $2::timestamptz
2036
+ ORDER BY revision_number, revision_id LIMIT $3`,
2037
+ [input.reporting_obligation_id, resolvedLedgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
2038
+ );
2039
+ if (visibleRevisions.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
2040
+ const visibleRevisionIds = visibleRevisions.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
2041
+ const tombstonedAcceptedSubjects = tombstoned.subjects;
2042
+ const delivered = await client.query(
2043
+ `SELECT revision_id FROM adcp_reporting_materialization_tombstones
2044
+ WHERE obligation_id = $1 AND reached_success
2045
+ AND COALESCE(reached_success_at, pruned_at) <= $3::timestamptz LIMIT $2`,
2046
+ [input.reporting_obligation_id, MAX_SNAPSHOT_ITEMS + 1, resolvedLedgerAsOf]
2047
+ );
2048
+ if (delivered.rows.length > MAX_SNAPSHOT_ITEMS) receiptEvidenceComplete = false;
2049
+ const tombstonedDeliveredRevisionIds = delivered.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => row.revision_id);
2050
+ const managedStateVersion = await this.readManagedStateVersion(client, input.reporting_obligation_id);
2051
+ return {
2052
+ binding,
2053
+ materializations,
2054
+ materializationHistory,
2055
+ consumers: [...byConsumer.values()],
2056
+ obligatedConsumerIds: observed,
2057
+ obligatedConsumerRosterComplete: false,
2058
+ receiptEvidenceComplete,
2059
+ visibleAdjustmentIds,
2060
+ visibleRevisionIds,
2061
+ managedStateVersion,
2062
+ resolvedLedgerAsOf,
2063
+ tombstonedAcceptedSubjects,
2064
+ tombstonedDeliveredRevisionIds
2065
+ };
2066
+ },
2067
+ { isolation: "REPEATABLE READ" }
2068
+ );
2069
+ if (!base || !this.obligatedConsumers) return base;
2070
+ const supplied = await withReportingCallbackDeadline(
2071
+ this.obligatedConsumers({
2072
+ reporting_obligation_id: input.reporting_obligation_id,
2073
+ account_id: base.binding.account_id
2074
+ }),
2075
+ OBLIGATED_CONSUMERS_DEADLINE_MS,
2076
+ "Reporting obligated-consumer lookup deadline elapsed"
2077
+ );
2078
+ const all = obligatedConsumerIdsFor(supplied, base.obligatedConsumerIds ?? []);
2079
+ const identityIds = supplied.version === void 0 && supplied.complete !== true ? obligatedConsumerIdsFor(supplied, await this.observedConsumerIds(input.reporting_obligation_id)) : all;
2080
+ const ids = all.slice(0, MAX_SNAPSHOT_ITEMS);
2081
+ const rosterComplete = supplied.complete === true && all.length === ids.length;
2082
+ return {
2083
+ ...base,
2084
+ obligatedConsumerIds: ids,
2085
+ obligatedConsumerRosterComplete: rosterComplete,
2086
+ ...all.length === ids.length ? {} : { receiptEvidenceComplete: false },
2087
+ obligatedConsumerRosterVersion: obligatedConsumerRosterVersionFor(supplied, identityIds)
2088
+ };
2089
+ }
2090
+ /**
2091
+ * Digest over every managed row that can change this obligation's projected
2092
+ * health: materialization status, receipt existence and current-leaf flag,
2093
+ * adjustments, and the destination authorization's revocation/cleanup state.
2094
+ *
2095
+ * `is_current` is included because superseding a receipt flips it with no
2096
+ * timestamp of its own, and the authorization's `cleanup_completed_at` is
2097
+ * included because `completeRevocation` does not touch `changed_at`.
2098
+ */
2099
+ async readManagedStateVersion(client, obligationId) {
2100
+ const result = await client.query(
2101
+ `SELECT md5(COALESCE(string_agg(marker, '|' ORDER BY marker), '')) AS version FROM (
2102
+ SELECT 'm:' || materialization_id || ':' || status || ':'
2103
+ || EXTRACT(EPOCH FROM changed_at)::text AS marker
2104
+ FROM adcp_reporting_materializations WHERE obligation_id = $1
2105
+ UNION ALL
2106
+ -- Driven from this obligation's subjects into the receipt subject
2107
+ -- index. This digest is read while the apply holds the account
2108
+ -- lock, so asking the global receipt table which of its rows belong
2109
+ -- to an obligation -- a question supplying neither account nor
2110
+ -- consumer -- turned every apply into a full scan with that lock
2111
+ -- held, and concurrent Core writes failed with 55P03.
2112
+ SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
2113
+ || EXTRACT(EPOCH FROM receipt.recorded_at)::text
2114
+ FROM adcp_reporting_revisions revision
2115
+ JOIN adcp_reporting_receipts receipt
2116
+ ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
2117
+ WHERE revision.obligation_id = $1
2118
+ UNION ALL
2119
+ SELECT 'r:' || receipt.reporting_receipt_id || ':' || receipt.is_current::text || ':'
2120
+ || EXTRACT(EPOCH FROM receipt.recorded_at)::text
2121
+ FROM adcp_reporting_adjustments adjustment
2122
+ JOIN adcp_reporting_receipts receipt
2123
+ ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
2124
+ WHERE adjustment.obligation_id = $1
2125
+ UNION ALL
2126
+ SELECT 'a:' || adjustment_id || ':' || EXTRACT(EPOCH FROM recorded_at)::text
2127
+ FROM adcp_reporting_adjustments WHERE obligation_id = $1
2128
+ UNION ALL
2129
+ -- Consumer statuses are a health input through the mismatch
2130
+ -- projection and they widen the observed roster, so a status landing
2131
+ -- between projection and apply has to move the token too.
2132
+ SELECT 'c:' || status.consumer_id || ':' || status.consumer_status_id || ':'
2133
+ || status.is_current::text || ':' || EXTRACT(EPOCH FROM status.created_at)::text
2134
+ FROM adcp_reporting_consumer_statuses status
2135
+ WHERE status.obligation_id = $1
2136
+ UNION ALL
2137
+ SELECT 'z:' || authz.generation::text
2138
+ || ':' || COALESCE(EXTRACT(EPOCH FROM authz.revoked_at)::text, '')
2139
+ || ':' || COALESCE(EXTRACT(EPOCH FROM authz.cleanup_completed_at)::text, '')
2140
+ || ':' || EXTRACT(EPOCH FROM authz.changed_at)::text
2141
+ FROM adcp_reporting_destination_authorizations authz
2142
+ JOIN adcp_reporting_managed_bindings binding
2143
+ ON binding.account_id = authz.account_id AND binding.destination_ref = authz.destination_ref
2144
+ JOIN adcp_reporting_obligations obligation
2145
+ ON obligation.configuration_id = binding.configuration_id
2146
+ WHERE obligation.obligation_id = $1
2147
+ ) markers`,
2148
+ [obligationId]
2149
+ );
2150
+ return result.rows[0]?.version ?? "";
2151
+ }
2152
+ /**
2153
+ * Current external roster version, read outside any transaction so the
2154
+ * reconciler can re-check it immediately before an apply.
2155
+ */
2156
+ /**
2157
+ * Publishes the roster version currently observed for an obligation.
2158
+ *
2159
+ * The roster is external, so nothing in this database changes when it does.
2160
+ * Recording what a reader saw is what lets the due query notice drift from
2161
+ * the version last reconciled. Never lowers a watermark; it only reports.
2162
+ */
2163
+ async recordObligatedConsumerRosterVersion(input) {
2164
+ await this.query(
2165
+ `INSERT INTO adcp_reporting_lifecycle_state
2166
+ (obligation_id, current_roster_version, roster_refreshed_at, processed_at)
2167
+ VALUES ($1, $2, clock_timestamp(), clock_timestamp())
2168
+ ON CONFLICT (obligation_id) DO UPDATE SET
2169
+ current_roster_version = EXCLUDED.current_roster_version,
2170
+ -- Advances the refresh cursor, never the reconciliation watermark:
2171
+ -- observing a roster is not reconciling an obligation.
2172
+ roster_refreshed_at = clock_timestamp()`,
2173
+ [input.reporting_obligation_id, input.version]
2174
+ );
2175
+ }
2176
+ /**
2177
+ * Re-reads the external roster for managed obligations and publishes what
2178
+ * it saw, so a change made while nothing was scheduled can re-arm.
2179
+ *
2180
+ * Ordered by least-recently-refreshed and bounded, so a large account is
2181
+ * swept over successive passes rather than in one burst of callbacks.
2182
+ */
2183
+ async refreshObligatedConsumerRosterVersions(input) {
2184
+ if (!this.managedDelivery || !this.obligatedConsumers) return 0;
2185
+ positiveInteger(input.limit, "limit");
2186
+ if (!await this.managedDueTablesReady()) return 0;
2187
+ const budget = Math.min(input.limit, MAX_ROSTER_REFRESH_PER_SWEEP);
2188
+ const candidates = await this.query(
2189
+ `SELECT obligation.obligation_id
2190
+ FROM adcp_reporting_obligations obligation
2191
+ JOIN adcp_reporting_managed_bindings binding
2192
+ ON binding.configuration_id = obligation.configuration_id
2193
+ LEFT JOIN adcp_reporting_lifecycle_state state
2194
+ ON state.obligation_id = obligation.obligation_id
2195
+ WHERE ($1::text IS NULL OR obligation.account_id = $1)
2196
+ -- Its own cursor, advanced by the refresh itself. Ordering by the
2197
+ -- reconciliation watermark, which a refresh never moves, meant every
2198
+ -- sweep re-read the same first page and nothing beyond one page of quiet
2199
+ -- obligations was ever refreshed, so their roster changes could never
2200
+ -- re-arm.
2201
+ ORDER BY state.roster_refreshed_at NULLS FIRST, obligation.obligation_id
2202
+ LIMIT $2`,
2203
+ [input.account_id ?? null, budget]
2204
+ );
2205
+ let refreshed = 0;
2206
+ for (const row of candidates.rows) {
2207
+ try {
2208
+ await this.readObligatedConsumerRosterVersion({ reporting_obligation_id: row.obligation_id });
2209
+ refreshed += 1;
2210
+ } catch {
2211
+ await this.query(
2212
+ `INSERT INTO adcp_reporting_lifecycle_state (obligation_id, roster_refreshed_at, processed_at)
2213
+ VALUES ($1, clock_timestamp(), clock_timestamp())
2214
+ ON CONFLICT (obligation_id) DO UPDATE SET roster_refreshed_at = clock_timestamp()`,
2215
+ [row.obligation_id]
2216
+ ).catch(() => void 0);
2217
+ }
2218
+ }
2219
+ return refreshed;
2220
+ }
2221
+ /**
2222
+ * Records a failed reconcile so the obligation backs off instead of
2223
+ * re-occupying the head of every sweep, without ever hiding it: the
2224
+ * watermark is untouched, so it stays unresolved work.
2225
+ */
2226
+ async recordLifecycleFailure(input) {
2227
+ await this.query(
2228
+ `INSERT INTO adcp_reporting_lifecycle_state (obligation_id, failure_count, next_attempt_at, processed_at)
2229
+ VALUES ($1, 1, clock_timestamp() + INTERVAL '30 seconds', clock_timestamp())
2230
+ ON CONFLICT (obligation_id) DO UPDATE SET
2231
+ failure_count = adcp_reporting_lifecycle_state.failure_count + 1,
2232
+ next_attempt_at = clock_timestamp() + (LEAST(
2233
+ ${MAX_LIFECYCLE_BACKOFF_SECONDS}, 30 * POWER(2, LEAST(6, adcp_reporting_lifecycle_state.failure_count))
2234
+ ) * INTERVAL '1 second')`,
2235
+ [input.reporting_obligation_id]
2236
+ );
2237
+ }
2238
+ /** Authoritative instant from the database clock, at microsecond precision. */
2239
+ async readLedgerInstant() {
2240
+ const result = await this.query(
2241
+ `SELECT ${rfc3339Microseconds("clock_timestamp()")} AS instant`
2242
+ );
2243
+ return result.rows[0].instant;
2244
+ }
2245
+ async readObligatedConsumerRosterVersion(input) {
2246
+ if (!this.managedDelivery || !this.obligatedConsumers) return void 0;
2247
+ const accountId = await this.query(
2248
+ "SELECT account_id FROM adcp_reporting_obligations WHERE obligation_id = $1",
2249
+ [input.reporting_obligation_id]
2250
+ );
2251
+ const account_id = accountId.rows[0]?.account_id;
2252
+ if (!account_id) return void 0;
2253
+ const supplied = await withReportingCallbackDeadline(
2254
+ this.obligatedConsumers({ reporting_obligation_id: input.reporting_obligation_id, account_id }),
2255
+ OBLIGATED_CONSUMERS_DEADLINE_MS,
2256
+ "Reporting obligated-consumer lookup deadline elapsed"
2257
+ );
2258
+ const observed = await this.observedConsumerIds(input.reporting_obligation_id);
2259
+ const version = obligatedConsumerRosterVersionFor(supplied, obligatedConsumerIdsFor(supplied, observed));
2260
+ await this.recordObligatedConsumerRosterVersion({
2261
+ reporting_obligation_id: input.reporting_obligation_id,
2262
+ version
2263
+ });
2264
+ return version;
2265
+ }
2266
+ /** Principals that have engaged with this obligation, as the projection sees them. */
2267
+ async observedConsumerIds(obligationId) {
2268
+ const result = await this.query(
2269
+ `SELECT DISTINCT consumer_id FROM (
2270
+ -- Driven from this obligation's subjects into the receipt subject
2271
+ -- index, like every other obligation-scoped receipt read. Asking the
2272
+ -- global receipt table which of its rows belong to an obligation
2273
+ -- supplies neither account nor consumer, so the roster read scanned
2274
+ -- the whole receipt history once per reconcile and once per
2275
+ -- pre-apply re-check.
2276
+ SELECT receipt.consumer_id
2277
+ FROM adcp_reporting_revisions revision
2278
+ JOIN adcp_reporting_receipts receipt
2279
+ ON receipt.receipt_kind = 'revision' AND receipt.subject_id = revision.revision_id
2280
+ WHERE revision.obligation_id = $1
2281
+ UNION
2282
+ SELECT receipt.consumer_id
2283
+ FROM adcp_reporting_adjustments adjustment
2284
+ JOIN adcp_reporting_receipts receipt
2285
+ ON receipt.receipt_kind = 'adjustment' AND receipt.subject_id = adjustment.adjustment_id
2286
+ WHERE adjustment.obligation_id = $1
2287
+ UNION
2288
+ SELECT status.consumer_id FROM adcp_reporting_consumer_statuses status
2289
+ WHERE status.obligation_id = $1 AND status.consumer_id <> '__legacy_unscoped_consumer__'
2290
+ ) engaged ORDER BY consumer_id`,
2291
+ [obligationId]
2292
+ );
2293
+ return result.rows.map((row) => row.consumer_id);
2294
+ }
2295
+ managedDueTablesReadyCache;
2296
+ /** Whether the managed schema exists, resolved once per store instance. */
2297
+ managedDueTablesReady() {
2298
+ if (!this.managedDelivery) return Promise.resolve(false);
2299
+ this.managedDueTablesReadyCache ??= this.query(
2300
+ `SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
2301
+ AND to_regclass('adcp_reporting_materializations') IS NOT NULL
2302
+ AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
2303
+ ).then((result) => result.rows[0]?.ready === true).catch((cause) => {
2304
+ this.managedDueTablesReadyCache = void 0;
2305
+ throw cause;
2306
+ });
2307
+ return this.managedDueTablesReadyCache;
2308
+ }
2309
+ /**
2310
+ * Terminal receipt subjects whose bodies have been pruned.
2311
+ *
2312
+ * The projection decides reconciliation from the receipt rows it can see,
2313
+ * so once an accepted leaf ages out the subject looks unreconciled again
2314
+ * and a settled billing period reopens. The tombstone is the only remaining
2315
+ * proof, so the projection has to consult it.
2316
+ */
2317
+ async listTombstonedAcceptedSubjects(client, query, obligationIds, allConsumers = false, ledgerAsOf) {
2318
+ if (!obligationIds.length || !query.consumer_id && !allConsumers) return { subjects: [], complete: true };
2319
+ const result = await client.query(
2320
+ `SELECT tombstone.receipt_kind, tombstone.subject_id, tombstone.consumer_id
2321
+ FROM adcp_reporting_receipt_tombstones tombstone
2322
+ WHERE tombstone.account_id = $1 AND ($2::text IS NULL OR tombstone.consumer_id = $2)
2323
+ AND tombstone.status = 'accepted' AND tombstone.was_current
2324
+ -- A conclusion still has a place in time. Applying every accepted
2325
+ -- tombstone regardless of when its receipt was filed let a
2326
+ -- historical projection settle a subject on an acceptance that had
2327
+ -- not happened yet at its cutoff. Rows written before the column
2328
+ -- existed fall back to pruned_at, which is never earlier, so the
2329
+ -- fallback can only withhold a conclusion, never invent one.
2330
+ AND ($5::timestamptz IS NULL
2331
+ OR COALESCE(tombstone.subject_recorded_at, tombstone.pruned_at) <= $5::timestamptz)
2332
+ AND ((tombstone.receipt_kind = 'revision' AND EXISTS (
2333
+ SELECT 1 FROM adcp_reporting_revisions revision
2334
+ WHERE revision.revision_id = tombstone.subject_id
2335
+ AND revision.obligation_id = ANY($3::text[])))
2336
+ OR (tombstone.receipt_kind = 'adjustment' AND EXISTS (
2337
+ SELECT 1 FROM adcp_reporting_adjustments adjustment
2338
+ WHERE adjustment.adjustment_id = tombstone.subject_id
2339
+ AND adjustment.obligation_id = ANY($3::text[]))))
2340
+ LIMIT $4`,
2341
+ [
2342
+ query.account_id,
2343
+ allConsumers ? null : query.consumer_id,
2344
+ obligationIds,
2345
+ MAX_SNAPSHOT_ITEMS + 1,
2346
+ ledgerAsOf ?? null
2347
+ ]
2348
+ );
2349
+ const complete = result.rows.length <= MAX_SNAPSHOT_ITEMS;
2350
+ return {
2351
+ complete,
2352
+ subjects: result.rows.slice(0, MAX_SNAPSHOT_ITEMS).map((row) => ({
2353
+ kind: row.receipt_kind === "adjustment" ? "adjustment" : "revision",
2354
+ subjectId: row.subject_id,
2355
+ // Carried through deliberately. An acceptance belongs to the consumer
2356
+ // that gave it, so a lifecycle fold that runs per consumer must not
2357
+ // let A's pruned acceptance settle the obligation on B's behalf.
2358
+ consumerId: row.consumer_id
2359
+ }))
2360
+ };
2361
+ }
2362
+ async managedTablesInstalled(client) {
2363
+ const result = await client.query(
2364
+ `SELECT to_regclass('adcp_reporting_managed_bindings') IS NOT NULL
2365
+ AND to_regclass('adcp_reporting_materializations') IS NOT NULL
2366
+ AND to_regclass('adcp_reporting_receipts') IS NOT NULL AS ready`
2367
+ );
2368
+ return result.rows[0]?.ready === true;
2369
+ }
2370
+ async listManagedChangedObligationIds(client, query, ledgerAsOf, changesAfter) {
2371
+ const result = await client.query(
2372
+ `SELECT DISTINCT changed.obligation_id FROM (
2373
+ SELECT materialization.obligation_id
2374
+ FROM adcp_reporting_materializations materialization
2375
+ WHERE materialization.account_id = $1
2376
+ AND GREATEST(materialization.recorded_at, materialization.changed_at) > $3
2377
+ AND GREATEST(materialization.recorded_at, materialization.changed_at) <= $2
2378
+ UNION ALL
2379
+ SELECT COALESCE(revision.obligation_id, adjustment.obligation_id) AS obligation_id
2380
+ FROM adcp_reporting_receipts receipt
2381
+ LEFT JOIN adcp_reporting_revisions revision
2382
+ ON receipt.receipt_kind = 'revision' AND revision.revision_id = receipt.subject_id
2383
+ LEFT JOIN adcp_reporting_adjustments adjustment
2384
+ ON receipt.receipt_kind = 'adjustment' AND adjustment.adjustment_id = receipt.subject_id
2385
+ WHERE receipt.account_id = $1 AND receipt.consumer_id = $4
2386
+ AND receipt.recorded_at > $3 AND receipt.recorded_at <= $2
2387
+ UNION ALL
2388
+ SELECT obligation.obligation_id
2389
+ FROM adcp_reporting_destination_authorizations authz
2390
+ JOIN adcp_reporting_managed_bindings binding
2391
+ ON binding.account_id = authz.account_id
2392
+ AND binding.destination_ref = authz.destination_ref
2393
+ AND binding.authorization_generation = authz.generation
2394
+ JOIN adcp_reporting_obligations obligation ON obligation.configuration_id = binding.configuration_id
2395
+ WHERE authz.account_id = $1 AND authz.revoked_at IS NOT NULL
2396
+ AND authz.changed_at > $3 AND authz.changed_at <= $2
2397
+ ) changed WHERE changed.obligation_id IS NOT NULL`,
2398
+ [query.account_id, ledgerAsOf, changesAfter, query.consumer_id ?? null]
2399
+ );
2400
+ return new Set(result.rows.map((row) => row.obligation_id));
2401
+ }
2402
+ async listSnapshotManagedBindings(client, configurationIds) {
2403
+ if (!configurationIds.length) return [];
2404
+ const result = await client.query(
2405
+ `SELECT data FROM adcp_reporting_managed_bindings
2406
+ WHERE configuration_id = ANY($1::text[]) ORDER BY configuration_id`,
2407
+ [configurationIds]
2408
+ );
2409
+ return result.rows.map((row) => row.data);
2410
+ }
2411
+ async listSnapshotMaterializations(client, obligationIds, ledgerAsOf, changesAfter) {
2412
+ if (!obligationIds.length) return [];
2413
+ const result = await client.query(
2414
+ `SELECT data FROM adcp_reporting_materializations
2415
+ WHERE obligation_id = ANY($1::text[])
2416
+ AND recorded_at <= $2
2417
+ AND ($3::timestamptz IS NULL OR GREATEST(recorded_at, changed_at) > $3)
2418
+ ORDER BY obligation_id, attempt, materialization_id
2419
+ LIMIT $4`,
2420
+ [obligationIds, ledgerAsOf, changesAfter ?? null, MAX_SNAPSHOT_ITEMS + 1]
2421
+ );
2422
+ return result.rows.map((row) => row.data);
2423
+ }
2424
+ async listSnapshotMaterializationProjection(client, obligationIds, ledgerAsOf) {
2425
+ if (!obligationIds.length) return [];
2426
+ const result = await client.query(
2427
+ `SELECT materialization.data, authz.revoked_at
2428
+ FROM adcp_reporting_materializations materialization
2429
+ JOIN adcp_reporting_destination_authorizations authz
2430
+ ON authz.account_id = materialization.account_id
2431
+ AND authz.destination_ref = materialization.destination_ref
2432
+ AND authz.generation = materialization.authorization_generation
2433
+ WHERE materialization.obligation_id = ANY($1::text[])
2434
+ AND materialization.recorded_at <= $2
2435
+ ORDER BY materialization.obligation_id, materialization.attempt, materialization.materialization_id
2436
+ LIMIT $3`,
2437
+ [obligationIds, ledgerAsOf, MAX_SNAPSHOT_ITEMS + 1]
2438
+ );
2439
+ return result.rows.map((row) => {
2440
+ if (!row.revoked_at || row.data.status !== "available" && row.data.status !== "delivered") return row.data;
2441
+ return {
2442
+ ...row.data,
2443
+ status: "failed",
2444
+ failed_at: row.revoked_at.toISOString(),
2445
+ failure_code: "AUTHORIZATION_REVOKED"
2446
+ };
2447
+ });
2448
+ }
2449
+ async listSnapshotReceipts(client, query, obligationIds, ledgerAsOf, changesAfter, kind) {
2450
+ if (!obligationIds.length || !query.consumer_id) return [];
2451
+ 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";
2452
+ const result = await client.query(
2453
+ `SELECT receipt.data FROM adcp_reporting_receipts receipt
2454
+ ${join}
2455
+ WHERE receipt.account_id = $1 AND receipt.consumer_id = $2
2456
+ AND receipt.receipt_kind = $3 AND subject.obligation_id = ANY($4::text[])
2457
+ AND receipt.recorded_at <= $5
2458
+ AND ($6::timestamptz IS NULL OR receipt.recorded_at > $6)
2459
+ ORDER BY subject.obligation_id, receipt.recorded_at, receipt.reporting_receipt_id
2460
+ LIMIT $7`,
2461
+ [
2462
+ query.account_id,
2463
+ query.consumer_id,
2464
+ kind,
2465
+ obligationIds,
2466
+ ledgerAsOf,
2467
+ changesAfter ?? null,
2468
+ MAX_SNAPSHOT_ITEMS + 1
2469
+ ]
2470
+ );
2471
+ return result.rows.map((row) => row.data);
2472
+ }
1579
2473
  async one(sql, params) {
1580
2474
  const result = await this.query(sql, params);
1581
2475
  return result.rows[0] ? clone(result.rows[0].data) : null;
1582
2476
  }
1583
- async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence) {
2477
+ async putImmutable(insertSql, insertParams, readSql, readParams, proposed, fingerprint, advisoryLock, leaseFence, legacyReplayEquivalent) {
1584
2478
  return this.transaction(
1585
2479
  async (client) => {
1586
2480
  const inserted = await client.query(insertSql, insertParams);
@@ -1598,7 +2492,7 @@ class PostgresReportingLedgerStore {
1598
2492
  }
1599
2493
  throw new Error("Immutable reporting ledger replay could not be resolved");
1600
2494
  }
1601
- if (fingerprint(value) !== fingerprint(proposed)) {
2495
+ if (fingerprint(value) !== fingerprint(proposed) && !legacyReplayEquivalent?.(value, proposed)) {
1602
2496
  throw new Error("Immutable reporting ledger identity names different content");
1603
2497
  }
1604
2498
  return { inserted: inserted.rowCount === 1, value: clone(value) };
@@ -1710,6 +2604,38 @@ function snapshotItems(snapshot) {
1710
2604
  values.push(adjustment);
1711
2605
  adjustments.set(adjustment.reporting_obligation_id, values);
1712
2606
  }
2607
+ const obligationByRevision = new Map(
2608
+ snapshot.revisions.map((value) => [value.reporting_revision_id, value.reporting_obligation_id])
2609
+ );
2610
+ const obligationByAdjustment = new Map(
2611
+ snapshot.adjustments.map((value) => [value.reporting_adjustment_id, value.reporting_obligation_id])
2612
+ );
2613
+ const materializations = /* @__PURE__ */ new Map();
2614
+ for (const materialization of snapshot.materializations ?? []) {
2615
+ if (snapshot.query.finality && !visibleRevisionIds.has(materialization.reporting_revision_id)) continue;
2616
+ const values = materializations.get(materialization.reporting_obligation_id) ?? [];
2617
+ values.push(materialization);
2618
+ materializations.set(materialization.reporting_obligation_id, values);
2619
+ }
2620
+ const receipts = /* @__PURE__ */ new Map();
2621
+ for (const receipt of snapshot.receipts ?? []) {
2622
+ if (snapshot.query.finality && !visibleRevisionIds.has(receipt.reporting_revision_id)) continue;
2623
+ const obligationId = obligationByRevision.get(receipt.reporting_revision_id);
2624
+ if (!obligationId) continue;
2625
+ const values = receipts.get(obligationId) ?? [];
2626
+ values.push(receipt);
2627
+ receipts.set(obligationId, values);
2628
+ }
2629
+ const adjustmentReceipts = /* @__PURE__ */ new Map();
2630
+ const visibleAdjustmentIds = new Set([...adjustments.values()].flat().map((value) => value.reporting_adjustment_id));
2631
+ for (const receipt of snapshot.adjustmentReceipts ?? []) {
2632
+ if (snapshot.query.finality && !visibleAdjustmentIds.has(receipt.reporting_adjustment_id)) continue;
2633
+ const obligationId = obligationByAdjustment.get(receipt.reporting_adjustment_id);
2634
+ if (!obligationId) continue;
2635
+ const values = adjustmentReceipts.get(obligationId) ?? [];
2636
+ values.push(receipt);
2637
+ adjustmentReceipts.set(obligationId, values);
2638
+ }
1713
2639
  return [
1714
2640
  ...snapshot.obligations.flatMap((obligation) => [
1715
2641
  ...snapshot.query.view === "revision" ? [] : [{ kind: "obligation", value: obligation }],
@@ -1720,6 +2646,18 @@ function snapshotItems(snapshot) {
1720
2646
  ...(adjustments.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
1721
2647
  kind: "adjustment",
1722
2648
  value
2649
+ })),
2650
+ ...(materializations.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
2651
+ kind: "materialization",
2652
+ value
2653
+ })),
2654
+ ...(receipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
2655
+ kind: "receipt",
2656
+ value
2657
+ })),
2658
+ ...(adjustmentReceipts.get(obligation.reporting_obligation_id) ?? []).map((value) => ({
2659
+ kind: "adjustment_receipt",
2660
+ value
1723
2661
  }))
1724
2662
  ]),
1725
2663
  ...snapshot.query.view === "periods" || snapshot.query.view === "revision" ? (snapshot.consumerStatuses ?? []).map((value) => ({ kind: "consumer_status", value })) : []
@@ -1800,9 +2738,200 @@ function adjustmentIdentityFingerprint(value) {
1800
2738
  const { created_at: _wireCreatedAt, ...stableWireAdjustment } = wireAdjustment;
1801
2739
  return digest(JSON.parse(JSON.stringify({ ...identity, wireAdjustment: stableWireAdjustment })));
1802
2740
  }
2741
+ function revisionLegacyCanonicalDigestReplay(stored, proposed) {
2742
+ if (stored.wireRevision.canonical_content_digest !== void 0) return false;
2743
+ if (proposed.wireRevision.canonical_content_digest === void 0) return false;
2744
+ const { canonical_content_digest: _digest, ...withoutDigest } = proposed.wireRevision;
2745
+ return revisionIdentityFingerprint({
2746
+ ...proposed,
2747
+ wireRevision: withoutDigest
2748
+ }) === revisionIdentityFingerprint(stored);
2749
+ }
2750
+ function adjustmentLegacyCanonicalDigestReplay(stored, proposed) {
2751
+ if (stored.wireAdjustment.canonical_adjustment_sha256 !== void 0) return false;
2752
+ const proposedDigest = proposed.wireAdjustment.canonical_adjustment_sha256;
2753
+ if (proposedDigest === void 0) return false;
2754
+ const { canonical_adjustment_sha256: _digest, ...withoutDigest } = proposed.wireAdjustment;
2755
+ if (reportingCanonicalAdjustmentSha256V1(withoutDigest) !== proposedDigest) return false;
2756
+ return adjustmentIdentityFingerprint({
2757
+ ...proposed,
2758
+ wireAdjustment: withoutDigest
2759
+ }) === adjustmentIdentityFingerprint(stored);
2760
+ }
2761
+ const OBLIGATED_CONSUMERS_DEADLINE_MS = 1e4;
2762
+ async function withReportingCallbackDeadline(value, milliseconds, message) {
2763
+ let timer;
2764
+ try {
2765
+ return await Promise.race([
2766
+ value,
2767
+ new Promise((_resolve, reject) => {
2768
+ timer = setTimeout(() => reject(new Error(message)), milliseconds);
2769
+ })
2770
+ ]);
2771
+ } finally {
2772
+ if (timer) clearTimeout(timer);
2773
+ }
2774
+ }
2775
+ function pendingAtCutoff(value) {
2776
+ const {
2777
+ ready_at: _readyAt,
2778
+ resource: _resource,
2779
+ verification: _verification,
2780
+ failed_at: _failedAt,
2781
+ failure_code: _failureCode,
2782
+ ...pending
2783
+ } = clone(value);
2784
+ return { ...pending, status: "pending" };
2785
+ }
2786
+ const MANAGED_DUE_ARM = ` OR (EXISTS (
2787
+ SELECT 1 FROM adcp_reporting_managed_bindings binding
2788
+ WHERE binding.configuration_id = obligation.configuration_id
2789
+ AND (
2790
+ -- A managed change after the last transition. Health is
2791
+ -- composed from managed state, but only Core evidence
2792
+ -- and the clock used to schedule a reconcile \u2014 so a
2793
+ -- settlement, revocation, receipt, consumer status or
2794
+ -- roster-visible change arriving after a complete left
2795
+ -- that complete persisted and webhooked forever while
2796
+ -- a live read of the same obligation degraded.
2797
+ EXISTS (
2798
+ SELECT 1 FROM adcp_reporting_materializations m
2799
+ WHERE m.obligation_id = obligation.obligation_id
2800
+ AND m.changed_at > watermark.since
2801
+ AND m.changed_at <= $2)
2802
+ OR EXISTS (
2803
+ -- Adjustments are managed health inputs too: after an
2804
+ -- accepted billing receipt, a correction makes the live
2805
+ -- read action_required while the persisted health and
2806
+ -- its webhook stayed complete.
2807
+ SELECT 1 FROM adcp_reporting_adjustments adj
2808
+ WHERE adj.obligation_id = obligation.obligation_id
2809
+ AND adj.recorded_at > watermark.since
2810
+ AND adj.recorded_at <= $2)
2811
+ OR EXISTS (
2812
+ SELECT 1 FROM adcp_reporting_destination_authorizations authz
2813
+ WHERE authz.account_id = binding.account_id
2814
+ AND authz.destination_ref = binding.destination_ref
2815
+ AND GREATEST(authz.changed_at, COALESCE(authz.cleanup_completed_at, authz.changed_at))
2816
+ > watermark.since
2817
+ AND authz.changed_at <= $2)
2818
+ -- Driven from this obligation's subjects into the
2819
+ -- receipt table's subject index. Asking the global
2820
+ -- receipt table which of its rows belong to this
2821
+ -- obligation supplied neither account nor consumer, so
2822
+ -- every candidate obligation in every sweep re-scanned
2823
+ -- the whole receipt history.
2824
+ OR EXISTS (
2825
+ SELECT 1 FROM adcp_reporting_revisions rv
2826
+ JOIN adcp_reporting_receipts receipt
2827
+ ON receipt.receipt_kind = 'revision'
2828
+ AND receipt.subject_id = rv.revision_id
2829
+ WHERE rv.obligation_id = obligation.obligation_id
2830
+ AND receipt.recorded_at > watermark.since
2831
+ AND receipt.recorded_at <= $2)
2832
+ OR EXISTS (
2833
+ SELECT 1 FROM adcp_reporting_adjustments aj
2834
+ JOIN adcp_reporting_receipts receipt
2835
+ ON receipt.receipt_kind = 'adjustment'
2836
+ AND receipt.subject_id = aj.adjustment_id
2837
+ WHERE aj.obligation_id = obligation.obligation_id
2838
+ AND receipt.recorded_at > watermark.since
2839
+ AND receipt.recorded_at <= $2)
2840
+ OR EXISTS (
2841
+ SELECT 1 FROM adcp_reporting_consumer_statuses status
2842
+ WHERE status.obligation_id = obligation.obligation_id
2843
+ AND status.created_at > watermark.since
2844
+ AND status.created_at <= $2)
2845
+ -- An external roster change writes nothing here at all,
2846
+ -- so the only durable trace is the version recorded at
2847
+ -- the last reconcile. A null one means never reconciled.
2848
+ OR state.processed_roster_version IS NULL
2849
+ -- A roster lives outside this database, so the only way
2850
+ -- a change can re-arm anything is if someone records
2851
+ -- it. Every read of the roster publishes the version it
2852
+ -- saw; a difference from what was last reconciled is a
2853
+ -- due condition. Without this the first reconcile made
2854
+ -- processed_roster_version non-null and no later roster
2855
+ -- change could ever schedule work again.
2856
+ OR state.current_roster_version IS DISTINCT FROM state.processed_roster_version
2857
+ -- A retained resource expiring is a health change with
2858
+ -- no row written anywhere, so nothing else can notice it.
2859
+ OR EXISTS (
2860
+ SELECT 1 FROM adcp_reporting_materializations m
2861
+ WHERE m.obligation_id = obligation.obligation_id
2862
+ AND m.status IN ('available', 'delivered')
2863
+ AND (m.data -> 'resource' ->> 'expires_at')::timestamptz <= $2
2864
+ AND (m.data -> 'resource' ->> 'expires_at')::timestamptz
2865
+ > watermark.since)
2866
+ )
2867
+ ))
2868
+ `;
2869
+ function obligatedConsumerIdsFor(supplied, observed) {
2870
+ return supplied.complete === true ? [...new Set(supplied.ids)].sort() : [.../* @__PURE__ */ new Set([...supplied.ids, ...observed])].sort();
2871
+ }
2872
+ function obligatedConsumerRosterVersionFor(supplied, ids) {
2873
+ return supplied.version ?? digest({ ids, complete: supplied.complete === true });
2874
+ }
2875
+ const MAX_ROSTER_REFRESH_PER_SWEEP = 25;
2876
+ const MAX_LIFECYCLE_BACKOFF_SECONDS = 900;
2877
+ function rfc3339Microseconds(expression) {
2878
+ return `to_char(${expression} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"')`;
2879
+ }
1803
2880
  function accountLock(accountId) {
1804
2881
  return `adcp-reporting-account:${accountId}`;
1805
2882
  }
2883
+ async function resolveStoredFinalityBaseline(transaction, obligationId) {
2884
+ const latest = await transaction.query(
2885
+ `SELECT transition_id, data->>'finality' AS finality
2886
+ FROM adcp_reporting_transitions
2887
+ WHERE obligation_id = $1 ORDER BY transition_sequence DESC LIMIT 1
2888
+ FOR UPDATE`,
2889
+ [obligationId]
2890
+ );
2891
+ const previous = latest.rows[0];
2892
+ if (!previous) return "none";
2893
+ if (previous.finality) return previous.finality;
2894
+ await transaction.query(
2895
+ `UPDATE adcp_reporting_transitions
2896
+ SET data = data || jsonb_build_object('finality', 'none')
2897
+ WHERE transition_id = $1 AND NOT (data ? 'finality')`,
2898
+ [previous.transition_id]
2899
+ );
2900
+ return "none";
2901
+ }
2902
+ async function assertNoLegacyPendingTransitions(transaction, obligationId) {
2903
+ const pending = await transaction.query(
2904
+ `SELECT transition_id FROM adcp_reporting_transitions
2905
+ WHERE obligation_id = $1 AND data->>'notifiedAt' IS NULL
2906
+ LIMIT 1`,
2907
+ [obligationId]
2908
+ );
2909
+ if (pending.rowCount !== 0) {
2910
+ throw new Error(
2911
+ "Drain or explicitly resolve legacy pending reporting transitions before enabling transactional notification activity"
2912
+ );
2913
+ }
2914
+ }
2915
+ async function assertFinalityWriterFence(work) {
2916
+ try {
2917
+ return await work();
2918
+ } catch (error) {
2919
+ if (isFinalityWriterFenceViolation(error)) {
2920
+ throw new Error(
2921
+ "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.",
2922
+ { cause: error }
2923
+ );
2924
+ }
2925
+ throw error;
2926
+ }
2927
+ }
2928
+ function isFinalityWriterFenceViolation(error) {
2929
+ for (let current = error; current instanceof Error; current = current.cause) {
2930
+ const candidate = current;
2931
+ if (candidate.code === "23514" && candidate.constraint === REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT) return true;
2932
+ }
2933
+ return false;
2934
+ }
1806
2935
  function consumerStatusChainKeyForObligation(obligation) {
1807
2936
  return reportingConsumerStatusChainKeyFromIdentityV1({
1808
2937
  delivery_config_id: obligation.delivery_config_id,
@@ -1882,6 +3011,8 @@ function positiveInteger(value, name) {
1882
3011
  }
1883
3012
  export {
1884
3013
  PostgresReportingLedgerStore,
3014
+ REPORTING_LEDGER_FINALITY_FENCE_CONSTRAINT,
3015
+ REPORTING_LEDGER_FINALITY_WRITER_FENCE_MIGRATION,
1885
3016
  REPORTING_LEDGER_MIGRATION,
1886
3017
  ReportingLedgerContinuityError,
1887
3018
  ReportingLedgerLeaseLostError,