@atproto/ozone 0.1.173 → 0.1.174

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 (411) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +40 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/moderation/emitEvent.d.ts.map +1 -1
  6. package/dist/api/moderation/emitEvent.js +31 -0
  7. package/dist/api/moderation/emitEvent.js.map +1 -1
  8. package/dist/api/queue/assignModerator.d.ts +4 -0
  9. package/dist/api/queue/assignModerator.d.ts.map +1 -0
  10. package/dist/api/queue/assignModerator.js +28 -0
  11. package/dist/api/queue/assignModerator.js.map +1 -0
  12. package/dist/api/queue/createQueue.d.ts +4 -0
  13. package/dist/api/queue/createQueue.d.ts.map +1 -0
  14. package/dist/api/queue/createQueue.js +44 -0
  15. package/dist/api/queue/createQueue.js.map +1 -0
  16. package/dist/api/queue/deleteQueue.d.ts +4 -0
  17. package/dist/api/queue/deleteQueue.d.ts.map +1 -0
  18. package/dist/api/queue/deleteQueue.js +40 -0
  19. package/dist/api/queue/deleteQueue.js.map +1 -0
  20. package/dist/api/queue/getAssignments.d.ts +4 -0
  21. package/dist/api/queue/getAssignments.d.ts.map +1 -0
  22. package/dist/api/queue/getAssignments.js +19 -0
  23. package/dist/api/queue/getAssignments.js.map +1 -0
  24. package/dist/api/queue/listQueues.d.ts +4 -0
  25. package/dist/api/queue/listQueues.d.ts.map +1 -0
  26. package/dist/api/queue/listQueues.js +29 -0
  27. package/dist/api/queue/listQueues.js.map +1 -0
  28. package/dist/api/queue/routeReports.d.ts +4 -0
  29. package/dist/api/queue/routeReports.d.ts.map +1 -0
  30. package/dist/api/queue/routeReports.js +33 -0
  31. package/dist/api/queue/routeReports.js.map +1 -0
  32. package/dist/api/queue/unassignModerator.d.ts +4 -0
  33. package/dist/api/queue/unassignModerator.d.ts.map +1 -0
  34. package/dist/api/queue/unassignModerator.js +24 -0
  35. package/dist/api/queue/unassignModerator.js.map +1 -0
  36. package/dist/api/queue/updateQueue.d.ts +4 -0
  37. package/dist/api/queue/updateQueue.d.ts.map +1 -0
  38. package/dist/api/queue/updateQueue.js +39 -0
  39. package/dist/api/queue/updateQueue.js.map +1 -0
  40. package/dist/api/report/assignModerator.d.ts +4 -0
  41. package/dist/api/report/assignModerator.d.ts.map +1 -0
  42. package/dist/api/report/assignModerator.js +33 -0
  43. package/dist/api/report/assignModerator.js.map +1 -0
  44. package/dist/api/report/createActivity.d.ts +4 -0
  45. package/dist/api/report/createActivity.d.ts.map +1 -0
  46. package/dist/api/report/createActivity.js +44 -0
  47. package/dist/api/report/createActivity.js.map +1 -0
  48. package/dist/api/report/getAssignments.d.ts +4 -0
  49. package/dist/api/report/getAssignments.d.ts.map +1 -0
  50. package/dist/api/report/getAssignments.js +19 -0
  51. package/dist/api/report/getAssignments.js.map +1 -0
  52. package/dist/api/report/getHistoricalStats.d.ts +4 -0
  53. package/dist/api/report/getHistoricalStats.d.ts.map +1 -0
  54. package/dist/api/report/getHistoricalStats.js +32 -0
  55. package/dist/api/report/getHistoricalStats.js.map +1 -0
  56. package/dist/api/report/getLatestReport.d.ts +4 -0
  57. package/dist/api/report/getLatestReport.d.ts.map +1 -0
  58. package/dist/api/report/getLatestReport.js +31 -0
  59. package/dist/api/report/getLatestReport.js.map +1 -0
  60. package/dist/api/report/getLiveStats.d.ts +4 -0
  61. package/dist/api/report/getLiveStats.d.ts.map +1 -0
  62. package/dist/api/report/getLiveStats.js +25 -0
  63. package/dist/api/report/getLiveStats.js.map +1 -0
  64. package/dist/api/report/getReport.d.ts +4 -0
  65. package/dist/api/report/getReport.d.ts.map +1 -0
  66. package/dist/api/report/getReport.js +35 -0
  67. package/dist/api/report/getReport.js.map +1 -0
  68. package/dist/api/report/listActivities.d.ts +4 -0
  69. package/dist/api/report/listActivities.d.ts.map +1 -0
  70. package/dist/api/report/listActivities.js +25 -0
  71. package/dist/api/report/listActivities.js.map +1 -0
  72. package/dist/api/report/queryReports.d.ts +4 -0
  73. package/dist/api/report/queryReports.d.ts.map +1 -0
  74. package/dist/api/report/queryReports.js +29 -0
  75. package/dist/api/report/queryReports.js.map +1 -0
  76. package/dist/api/report/reassignQueue.d.ts +4 -0
  77. package/dist/api/report/reassignQueue.d.ts.map +1 -0
  78. package/dist/api/report/reassignQueue.js +45 -0
  79. package/dist/api/report/reassignQueue.js.map +1 -0
  80. package/dist/api/report/refreshStats.d.ts +4 -0
  81. package/dist/api/report/refreshStats.d.ts.map +1 -0
  82. package/dist/api/report/refreshStats.js +26 -0
  83. package/dist/api/report/refreshStats.js.map +1 -0
  84. package/dist/api/report/unassignModerator.d.ts +4 -0
  85. package/dist/api/report/unassignModerator.d.ts.map +1 -0
  86. package/dist/api/report/unassignModerator.js +21 -0
  87. package/dist/api/report/unassignModerator.js.map +1 -0
  88. package/dist/api/util.d.ts +2 -0
  89. package/dist/api/util.d.ts.map +1 -1
  90. package/dist/api/util.js +9 -1
  91. package/dist/api/util.js.map +1 -1
  92. package/dist/assignment/index.d.ts +89 -0
  93. package/dist/assignment/index.d.ts.map +1 -0
  94. package/dist/assignment/index.js +537 -0
  95. package/dist/assignment/index.js.map +1 -0
  96. package/dist/config/config.d.ts +14 -0
  97. package/dist/config/config.d.ts.map +1 -1
  98. package/dist/config/config.js +9 -0
  99. package/dist/config/config.js.map +1 -1
  100. package/dist/config/env.d.ts +3 -0
  101. package/dist/config/env.d.ts.map +1 -1
  102. package/dist/config/env.js +3 -0
  103. package/dist/config/env.js.map +1 -1
  104. package/dist/context.d.ts +9 -0
  105. package/dist/context.d.ts.map +1 -1
  106. package/dist/context.js +31 -10
  107. package/dist/context.js.map +1 -1
  108. package/dist/daemon/context.d.ts +6 -0
  109. package/dist/daemon/context.d.ts.map +1 -1
  110. package/dist/daemon/context.js +28 -4
  111. package/dist/daemon/context.js.map +1 -1
  112. package/dist/daemon/job-cursor.d.ts +5 -0
  113. package/dist/daemon/job-cursor.d.ts.map +1 -0
  114. package/dist/daemon/job-cursor.js +28 -0
  115. package/dist/daemon/job-cursor.js.map +1 -0
  116. package/dist/daemon/queue-router.d.ts +17 -0
  117. package/dist/daemon/queue-router.d.ts.map +1 -0
  118. package/dist/daemon/queue-router.js +114 -0
  119. package/dist/daemon/queue-router.js.map +1 -0
  120. package/dist/daemon/stats-computer.d.ts +51 -0
  121. package/dist/daemon/stats-computer.d.ts.map +1 -0
  122. package/dist/daemon/stats-computer.js +117 -0
  123. package/dist/daemon/stats-computer.js.map +1 -0
  124. package/dist/daemon/strike-expiry-processor.d.ts.map +1 -1
  125. package/dist/daemon/strike-expiry-processor.js +4 -19
  126. package/dist/daemon/strike-expiry-processor.js.map +1 -1
  127. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts +4 -0
  128. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts.map +1 -0
  129. package/dist/db/migrations/20260219T164523000Z-create-report-table.js +126 -0
  130. package/dist/db/migrations/20260219T164523000Z-create-report-table.js.map +1 -0
  131. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts +4 -0
  132. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts.map +1 -0
  133. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js +35 -0
  134. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js.map +1 -0
  135. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts +4 -0
  136. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts.map +1 -0
  137. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js +36 -0
  138. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js.map +1 -0
  139. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts +4 -0
  140. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts.map +1 -0
  141. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js +39 -0
  142. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js.map +1 -0
  143. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts +4 -0
  144. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts.map +1 -0
  145. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js +34 -0
  146. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js.map +1 -0
  147. package/dist/db/migrations/index.d.ts +5 -0
  148. package/dist/db/migrations/index.d.ts.map +1 -1
  149. package/dist/db/migrations/index.js +6 -1
  150. package/dist/db/migrations/index.js.map +1 -1
  151. package/dist/db/pagination.d.ts +31 -0
  152. package/dist/db/pagination.d.ts.map +1 -1
  153. package/dist/db/pagination.js +74 -1
  154. package/dist/db/pagination.js.map +1 -1
  155. package/dist/db/schema/index.d.ts +6 -1
  156. package/dist/db/schema/index.d.ts.map +1 -1
  157. package/dist/db/schema/index.js.map +1 -1
  158. package/dist/db/schema/moderator_assignment.d.ts +14 -0
  159. package/dist/db/schema/moderator_assignment.d.ts.map +1 -0
  160. package/dist/db/schema/moderator_assignment.js +5 -0
  161. package/dist/db/schema/moderator_assignment.js.map +1 -0
  162. package/dist/db/schema/report.d.ts +25 -0
  163. package/dist/db/schema/report.d.ts.map +1 -0
  164. package/dist/db/schema/report.js +5 -0
  165. package/dist/db/schema/report.js.map +1 -0
  166. package/dist/db/schema/report_activity.d.ts +18 -0
  167. package/dist/db/schema/report_activity.d.ts.map +1 -0
  168. package/dist/db/schema/report_activity.js +5 -0
  169. package/dist/db/schema/report_activity.js.map +1 -0
  170. package/dist/db/schema/report_queue.d.ts +19 -0
  171. package/dist/db/schema/report_queue.d.ts.map +1 -0
  172. package/dist/db/schema/report_queue.js +5 -0
  173. package/dist/db/schema/report_queue.js.map +1 -0
  174. package/dist/db/schema/report_stat.d.ts +20 -0
  175. package/dist/db/schema/report_stat.d.ts.map +1 -0
  176. package/dist/db/schema/report_stat.js +5 -0
  177. package/dist/db/schema/report_stat.js.map +1 -0
  178. package/dist/lexicon/index.d.ts +50 -0
  179. package/dist/lexicon/index.d.ts.map +1 -1
  180. package/dist/lexicon/index.js +120 -2
  181. package/dist/lexicon/index.js.map +1 -1
  182. package/dist/lexicon/lexicons.d.ts +10535 -7389
  183. package/dist/lexicon/lexicons.d.ts.map +1 -1
  184. package/dist/lexicon/lexicons.js +1789 -122
  185. package/dist/lexicon/lexicons.js.map +1 -1
  186. package/dist/lexicon/types/app/bsky/embed/external.d.ts +2 -0
  187. package/dist/lexicon/types/app/bsky/embed/external.d.ts.map +1 -1
  188. package/dist/lexicon/types/app/bsky/embed/external.js.map +1 -1
  189. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +19 -0
  190. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  191. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js +9 -0
  192. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  193. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts +27 -0
  194. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts.map +1 -0
  195. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js +7 -0
  196. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js.map +1 -0
  197. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts +35 -0
  198. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts.map +1 -0
  199. package/dist/lexicon/types/tools/ozone/queue/createQueue.js +7 -0
  200. package/dist/lexicon/types/tools/ozone/queue/createQueue.js.map +1 -0
  201. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts +62 -0
  202. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts.map +1 -0
  203. package/dist/lexicon/types/tools/ozone/queue/defs.js +34 -0
  204. package/dist/lexicon/types/tools/ozone/queue/defs.js.map +1 -0
  205. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts +29 -0
  206. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts.map +1 -0
  207. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js +7 -0
  208. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js.map +1 -0
  209. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts +30 -0
  210. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts.map +1 -0
  211. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js +7 -0
  212. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js.map +1 -0
  213. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts +32 -0
  214. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts.map +1 -0
  215. package/dist/lexicon/types/tools/ozone/queue/listQueues.js +7 -0
  216. package/dist/lexicon/types/tools/ozone/queue/listQueues.js.map +1 -0
  217. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts +31 -0
  218. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts.map +1 -0
  219. package/dist/lexicon/types/tools/ozone/queue/routeReports.js +7 -0
  220. package/dist/lexicon/types/tools/ozone/queue/routeReports.js.map +1 -0
  221. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts +18 -0
  222. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts.map +1 -0
  223. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js +7 -0
  224. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js.map +1 -0
  225. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts +32 -0
  226. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts.map +1 -0
  227. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js +7 -0
  228. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js.map +1 -0
  229. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts +31 -0
  230. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts.map +1 -0
  231. package/dist/lexicon/types/tools/ozone/report/assignModerator.js +7 -0
  232. package/dist/lexicon/types/tools/ozone/report/assignModerator.js.map +1 -0
  233. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts +37 -0
  234. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts.map +1 -0
  235. package/dist/lexicon/types/tools/ozone/report/createActivity.js +7 -0
  236. package/dist/lexicon/types/tools/ozone/report/createActivity.js.map +1 -0
  237. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +185 -0
  238. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  239. package/dist/lexicon/types/tools/ozone/report/defs.js +108 -0
  240. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  241. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts +30 -0
  242. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts.map +1 -0
  243. package/dist/lexicon/types/tools/ozone/report/getAssignments.js +7 -0
  244. package/dist/lexicon/types/tools/ozone/report/getAssignments.js.map +1 -0
  245. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts +36 -0
  246. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts.map +1 -0
  247. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js +7 -0
  248. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js.map +1 -0
  249. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts +21 -0
  250. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts.map +1 -0
  251. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js +7 -0
  252. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js.map +1 -0
  253. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts +27 -0
  254. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts.map +1 -0
  255. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js +7 -0
  256. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js.map +1 -0
  257. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts +22 -0
  258. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts.map +1 -0
  259. package/dist/lexicon/types/tools/ozone/report/getReport.js +7 -0
  260. package/dist/lexicon/types/tools/ozone/report/getReport.js.map +1 -0
  261. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts +26 -0
  262. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts.map +1 -0
  263. package/dist/lexicon/types/tools/ozone/report/listActivities.js +7 -0
  264. package/dist/lexicon/types/tools/ozone/report/listActivities.js.map +1 -0
  265. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts +48 -0
  266. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts.map +1 -0
  267. package/dist/lexicon/types/tools/ozone/report/queryReports.js +7 -0
  268. package/dist/lexicon/types/tools/ozone/report/queryReports.js.map +1 -0
  269. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts +31 -0
  270. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts.map +1 -0
  271. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js +7 -0
  272. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js.map +1 -0
  273. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts +28 -0
  274. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts.map +1 -0
  275. package/dist/lexicon/types/tools/ozone/report/refreshStats.js +7 -0
  276. package/dist/lexicon/types/tools/ozone/report/refreshStats.js.map +1 -0
  277. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts +25 -0
  278. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts.map +1 -0
  279. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js +7 -0
  280. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js.map +1 -0
  281. package/dist/mod-service/index.d.ts +3 -1
  282. package/dist/mod-service/index.d.ts.map +1 -1
  283. package/dist/mod-service/index.js +39 -2
  284. package/dist/mod-service/index.js.map +1 -1
  285. package/dist/mod-service/report.d.ts +64 -0
  286. package/dist/mod-service/report.d.ts.map +1 -0
  287. package/dist/mod-service/report.js +282 -0
  288. package/dist/mod-service/report.js.map +1 -0
  289. package/dist/mod-service/status.d.ts +20 -0
  290. package/dist/mod-service/status.d.ts.map +1 -1
  291. package/dist/queue/service.d.ts +86 -0
  292. package/dist/queue/service.d.ts.map +1 -0
  293. package/dist/queue/service.js +430 -0
  294. package/dist/queue/service.js.map +1 -0
  295. package/dist/report/activity.d.ts +77 -0
  296. package/dist/report/activity.d.ts.map +1 -0
  297. package/dist/report/activity.js +141 -0
  298. package/dist/report/activity.js.map +1 -0
  299. package/dist/report/handle-report-update.d.ts +47 -0
  300. package/dist/report/handle-report-update.d.ts.map +1 -0
  301. package/dist/report/handle-report-update.js +178 -0
  302. package/dist/report/handle-report-update.js.map +1 -0
  303. package/dist/report/reassign.d.ts +10 -0
  304. package/dist/report/reassign.d.ts.map +1 -0
  305. package/dist/report/reassign.js +75 -0
  306. package/dist/report/reassign.js.map +1 -0
  307. package/dist/report/stats.d.ts +105 -0
  308. package/dist/report/stats.d.ts.map +1 -0
  309. package/dist/report/stats.js +619 -0
  310. package/dist/report/stats.js.map +1 -0
  311. package/dist/report/views.d.ts +111 -0
  312. package/dist/report/views.d.ts.map +1 -0
  313. package/dist/report/views.js +156 -0
  314. package/dist/report/views.js.map +1 -0
  315. package/dist/team/index.d.ts +1 -0
  316. package/dist/team/index.d.ts.map +1 -1
  317. package/dist/team/index.js +11 -0
  318. package/dist/team/index.js.map +1 -1
  319. package/package.json +3 -3
  320. package/src/api/index.ts +40 -0
  321. package/src/api/moderation/emitEvent.ts +38 -0
  322. package/src/api/queue/assignModerator.ts +31 -0
  323. package/src/api/queue/createQueue.ts +62 -0
  324. package/src/api/queue/deleteQueue.ts +56 -0
  325. package/src/api/queue/getAssignments.ts +19 -0
  326. package/src/api/queue/listQueues.ts +39 -0
  327. package/src/api/queue/routeReports.ts +44 -0
  328. package/src/api/queue/unassignModerator.ts +26 -0
  329. package/src/api/queue/updateQueue.ts +54 -0
  330. package/src/api/report/assignModerator.ts +36 -0
  331. package/src/api/report/createActivity.ts +57 -0
  332. package/src/api/report/getAssignments.ts +20 -0
  333. package/src/api/report/getHistoricalStats.ts +41 -0
  334. package/src/api/report/getLatestReport.ts +44 -0
  335. package/src/api/report/getLiveStats.ts +26 -0
  336. package/src/api/report/getReport.ts +55 -0
  337. package/src/api/report/listActivities.ts +34 -0
  338. package/src/api/report/queryReports.ts +44 -0
  339. package/src/api/report/reassignQueue.ts +68 -0
  340. package/src/api/report/refreshStats.ts +27 -0
  341. package/src/api/report/unassignModerator.ts +21 -0
  342. package/src/api/util.ts +12 -0
  343. package/src/assignment/index.ts +731 -0
  344. package/src/config/config.ts +27 -0
  345. package/src/config/env.ts +8 -0
  346. package/src/context.ts +31 -0
  347. package/src/daemon/context.ts +34 -0
  348. package/src/daemon/job-cursor.ts +33 -0
  349. package/src/daemon/queue-router.ts +101 -0
  350. package/src/daemon/stats-computer.ts +101 -0
  351. package/src/daemon/strike-expiry-processor.ts +4 -20
  352. package/src/db/migrations/20260219T164523000Z-create-report-table.ts +155 -0
  353. package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +42 -0
  354. package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +41 -0
  355. package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +48 -0
  356. package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +35 -0
  357. package/src/db/migrations/index.ts +5 -0
  358. package/src/db/pagination.ts +85 -0
  359. package/src/db/schema/index.ts +10 -0
  360. package/src/db/schema/moderator_assignment.ts +16 -0
  361. package/src/db/schema/report.ts +27 -0
  362. package/src/db/schema/report_activity.ts +22 -0
  363. package/src/db/schema/report_queue.ts +21 -0
  364. package/src/db/schema/report_stat.ts +27 -0
  365. package/src/lexicon/index.ts +280 -0
  366. package/src/lexicon/lexicons.ts +1910 -160
  367. package/src/lexicon/types/app/bsky/embed/external.ts +2 -0
  368. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +24 -0
  369. package/src/lexicon/types/tools/ozone/queue/assignModerator.ts +46 -0
  370. package/src/lexicon/types/tools/ozone/queue/createQueue.ts +54 -0
  371. package/src/lexicon/types/tools/ozone/queue/defs.ts +99 -0
  372. package/src/lexicon/types/tools/ozone/queue/deleteQueue.ts +48 -0
  373. package/src/lexicon/types/tools/ozone/queue/getAssignments.ts +48 -0
  374. package/src/lexicon/types/tools/ozone/queue/listQueues.ts +50 -0
  375. package/src/lexicon/types/tools/ozone/queue/routeReports.ts +50 -0
  376. package/src/lexicon/types/tools/ozone/queue/unassignModerator.ts +37 -0
  377. package/src/lexicon/types/tools/ozone/queue/updateQueue.ts +51 -0
  378. package/src/lexicon/types/tools/ozone/report/assignModerator.ts +50 -0
  379. package/src/lexicon/types/tools/ozone/report/createActivity.ts +60 -0
  380. package/src/lexicon/types/tools/ozone/report/defs.ts +327 -0
  381. package/src/lexicon/types/tools/ozone/report/getAssignments.ts +48 -0
  382. package/src/lexicon/types/tools/ozone/report/getHistoricalStats.ts +54 -0
  383. package/src/lexicon/types/tools/ozone/report/getLatestReport.ts +39 -0
  384. package/src/lexicon/types/tools/ozone/report/getLiveStats.ts +45 -0
  385. package/src/lexicon/types/tools/ozone/report/getReport.ts +38 -0
  386. package/src/lexicon/types/tools/ozone/report/listActivities.ts +44 -0
  387. package/src/lexicon/types/tools/ozone/report/queryReports.ts +72 -0
  388. package/src/lexicon/types/tools/ozone/report/reassignQueue.ts +55 -0
  389. package/src/lexicon/types/tools/ozone/report/refreshStats.ts +46 -0
  390. package/src/lexicon/types/tools/ozone/report/unassignModerator.ts +44 -0
  391. package/src/mod-service/index.ts +45 -3
  392. package/src/mod-service/report.ts +408 -0
  393. package/src/queue/service.ts +599 -0
  394. package/src/report/activity.ts +234 -0
  395. package/src/report/handle-report-update.ts +209 -0
  396. package/src/report/reassign.ts +109 -0
  397. package/src/report/stats.ts +850 -0
  398. package/src/report/views.ts +241 -0
  399. package/src/team/index.ts +11 -0
  400. package/tests/get-report.test.ts +136 -0
  401. package/tests/query-reports.test.ts +608 -0
  402. package/tests/queue-assignment.test.ts +428 -0
  403. package/tests/queue-router.test.ts +306 -0
  404. package/tests/queues.test.ts +690 -0
  405. package/tests/report-action.test.ts +308 -0
  406. package/tests/report-activity.test.ts +567 -0
  407. package/tests/report-assignment.test.ts +517 -0
  408. package/tests/report-reassign-queue.test.ts +340 -0
  409. package/tests/report-routing.test.ts +245 -0
  410. package/tests/report-stats.test.ts +545 -0
  411. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,599 @@
1
+ import { Selectable, sql } from 'kysely'
2
+ import { ToolsOzoneQueueDefs } from '@atproto/api'
3
+ import { AtUri } from '@atproto/syntax'
4
+ import { InvalidRequestError } from '@atproto/xrpc-server'
5
+ import { Database } from '../db'
6
+ import { TimeIdKeyset, paginate } from '../db/pagination'
7
+ import { ReportQueue } from '../db/schema/report_queue'
8
+ import { jsonb } from '../db/types'
9
+ import { handleReportUpdate } from '../report/handle-report-update'
10
+ import { ReportStatsService } from '../report/stats'
11
+ import { viewQueueStats } from '../report/views'
12
+
13
+ const MOD_EVENT_REPORT_ACTION = 'tools.ozone.moderation.defs#modEventReport'
14
+ const REASON_OTHER = 'com.atproto.moderation.defs#reasonOther'
15
+
16
+ type SubjectType = 'account' | 'record' | 'message'
17
+
18
+ type ResolvedAssignment = {
19
+ queueId: number
20
+ queuedAt: string | null
21
+ status: 'queued' | 'open'
22
+ }
23
+
24
+ function resolveAssignment(
25
+ subjectType: SubjectType,
26
+ collection: string | null,
27
+ reportType: string,
28
+ queues: Selectable<ReportQueue>[],
29
+ now: string,
30
+ ): ResolvedAssignment {
31
+ const matched = findMatchingQueue(queues, subjectType, collection, reportType)
32
+ if (matched) return { queueId: matched.id, queuedAt: now, status: 'queued' }
33
+ return { queueId: -1, queuedAt: null, status: 'open' }
34
+ }
35
+
36
+ export type QueueServiceCreator = (db: Database) => QueueService
37
+
38
+ export class QueueService {
39
+ constructor(public db: Database) {}
40
+
41
+ static creator() {
42
+ return (db: Database) => new QueueService(db)
43
+ }
44
+
45
+ async checkConflict({
46
+ subjectTypes,
47
+ collection,
48
+ reportTypes,
49
+ excludeId,
50
+ }: {
51
+ subjectTypes: string[]
52
+ collection?: string | null
53
+ reportTypes: string[]
54
+ excludeId?: number
55
+ }): Promise<void> {
56
+ // It's not ideal to load all rows and perform in memory checks in case we end up with a LOT of queues
57
+ // but we are not foreseeing a lot of queue rows so this should be fine for the
58
+ let qb = this.db.db
59
+ .selectFrom('report_queue')
60
+ .selectAll()
61
+ .where('deletedAt', 'is', null)
62
+
63
+ if (excludeId !== undefined) {
64
+ qb = qb.where('id', '!=', excludeId)
65
+ }
66
+
67
+ const existingQueues = await qb.execute()
68
+
69
+ for (const existing of existingQueues) {
70
+ const subjectTypesOverlap = subjectTypes.some((st) =>
71
+ existing.subjectTypes.includes(st),
72
+ )
73
+ const collectionMatch = (collection ?? null) === existing.collection
74
+ const reportTypesOverlap = reportTypes.some((rt) =>
75
+ existing.reportTypes.includes(rt),
76
+ )
77
+
78
+ if (subjectTypesOverlap && collectionMatch && reportTypesOverlap) {
79
+ throw new InvalidRequestError(
80
+ `Queue configuration conflicts with existing queue: ${existing.name}`,
81
+ 'ConflictingQueue',
82
+ )
83
+ }
84
+ }
85
+ }
86
+
87
+ async create({
88
+ name,
89
+ subjectTypes,
90
+ collection,
91
+ reportTypes,
92
+ description,
93
+ createdBy,
94
+ }: {
95
+ name: string
96
+ subjectTypes: string[]
97
+ collection?: string | null
98
+ reportTypes: string[]
99
+ description?: string | null
100
+ createdBy: string
101
+ }): Promise<Selectable<ReportQueue>> {
102
+ const now = new Date().toISOString()
103
+ return await this.db.db
104
+ .insertInto('report_queue')
105
+ .values({
106
+ name,
107
+ subjectTypes: jsonb(subjectTypes),
108
+ collection: collection ?? null,
109
+ reportTypes: jsonb(reportTypes),
110
+ description: description ?? null,
111
+ createdBy,
112
+ enabled: true,
113
+ createdAt: now,
114
+ updatedAt: now,
115
+ })
116
+ .returningAll()
117
+ .executeTakeFirstOrThrow()
118
+ }
119
+
120
+ async getById(id: number): Promise<Selectable<ReportQueue> | undefined> {
121
+ return await this.db.db
122
+ .selectFrom('report_queue')
123
+ .selectAll()
124
+ .where('id', '=', id)
125
+ .where('deletedAt', 'is', null)
126
+ .executeTakeFirst()
127
+ }
128
+
129
+ async getViewsByIds(
130
+ ids: number[],
131
+ ): Promise<Map<number, ToolsOzoneQueueDefs.QueueView>> {
132
+ if (!ids.length) return new Map()
133
+ const rows = await this.db.db
134
+ .selectFrom('report_queue')
135
+ .selectAll()
136
+ .where('id', 'in', ids)
137
+ .execute()
138
+ return new Map(rows.map((r) => [r.id, this.view(r)]))
139
+ }
140
+
141
+ async update(
142
+ id: number,
143
+ updates: { name?: string; enabled?: boolean; description?: string },
144
+ ): Promise<Selectable<ReportQueue>> {
145
+ const now = new Date().toISOString()
146
+ return await this.db.db
147
+ .updateTable('report_queue')
148
+ .set({ ...updates, updatedAt: now })
149
+ .where('id', '=', id)
150
+ .returningAll()
151
+ .executeTakeFirstOrThrow()
152
+ }
153
+
154
+ async delete(id: number): Promise<void> {
155
+ const now = new Date().toISOString()
156
+ await this.db.db
157
+ .updateTable('report_queue')
158
+ .set({ deletedAt: now })
159
+ .where('id', '=', id)
160
+ .execute()
161
+ }
162
+
163
+ async migrateReports(
164
+ fromQueueId: number,
165
+ toQueueId?: number,
166
+ ): Promise<number> {
167
+ const now = new Date().toISOString()
168
+ const results = await this.db.db
169
+ .updateTable('report')
170
+ .set({
171
+ queueId: toQueueId ?? -1,
172
+ queuedAt: toQueueId ? now : null,
173
+ updatedAt: now,
174
+ })
175
+ .where('queueId', '=', fromQueueId)
176
+ .where('status', '!=', 'closed')
177
+ .execute()
178
+ return results.reduce((sum, r) => sum + Number(r.numUpdatedRows), 0)
179
+ }
180
+
181
+ async list({
182
+ limit,
183
+ cursor,
184
+ enabled,
185
+ subjectType,
186
+ collection,
187
+ reportTypes,
188
+ }: {
189
+ limit: number
190
+ cursor?: string
191
+ enabled?: boolean
192
+ subjectType?: string
193
+ collection?: string
194
+ reportTypes?: string[]
195
+ }): Promise<{ queues: Selectable<ReportQueue>[]; cursor?: string }> {
196
+ const { ref } = this.db.db.dynamic
197
+ let qb = this.db.db
198
+ .selectFrom('report_queue')
199
+ .selectAll()
200
+ .where('deletedAt', 'is', null)
201
+
202
+ if (enabled !== undefined) {
203
+ qb = qb.where('enabled', '=', enabled)
204
+ }
205
+
206
+ if (subjectType !== undefined) {
207
+ qb = qb.where(sql`"subjectTypes" @> ${jsonb([subjectType])}`)
208
+ }
209
+
210
+ if (collection !== undefined) {
211
+ qb = qb.where('collection', '=', collection)
212
+ }
213
+
214
+ if (reportTypes && reportTypes.length > 0) {
215
+ const conditions = reportTypes.map(
216
+ (t) => sql`"reportTypes" @> ${jsonb([t])}`,
217
+ )
218
+ qb = qb.where(sql`(${sql.join(conditions, sql` OR `)})`)
219
+ }
220
+
221
+ const keyset = new TimeIdKeyset(ref('createdAt'), ref('id'))
222
+ const paginatedBuilder = paginate(qb, {
223
+ limit,
224
+ cursor,
225
+ keyset,
226
+ direction: 'asc',
227
+ tryIndex: true,
228
+ })
229
+
230
+ const queues = await paginatedBuilder.execute()
231
+
232
+ return {
233
+ queues,
234
+ cursor: keyset.packFromResult(queues),
235
+ }
236
+ }
237
+
238
+ view(queue: Selectable<ReportQueue>): ToolsOzoneQueueDefs.QueueView {
239
+ return {
240
+ id: queue.id,
241
+ name: queue.name,
242
+ subjectTypes: queue.subjectTypes,
243
+ collection: queue.collection ?? undefined,
244
+ reportTypes: queue.reportTypes,
245
+ description: queue.description ?? undefined,
246
+ createdBy: queue.createdBy,
247
+ createdAt: queue.createdAt,
248
+ updatedAt: queue.updatedAt,
249
+ enabled: queue.enabled,
250
+ deletedAt: queue.deletedAt ?? undefined,
251
+ stats: {
252
+ pendingCount: 0,
253
+ actionedCount: 0,
254
+ escalatedCount: 0,
255
+ inboundCount: 0,
256
+ actionRate: 0,
257
+ },
258
+ }
259
+ }
260
+
261
+ async viewsWithStats(
262
+ queues: Selectable<ReportQueue>[],
263
+ ): Promise<ToolsOzoneQueueDefs.QueueView[]> {
264
+ const statsService = new ReportStatsService(this.db)
265
+ const queueIds = queues.map((q) => q.id)
266
+ const statsMap = await statsService.getLiveStatsForQueues(queueIds)
267
+
268
+ return queues.map((queue) => {
269
+ const view = this.view(queue)
270
+ view.stats = viewQueueStats(statsMap.get(queue.id))
271
+ return view
272
+ })
273
+ }
274
+
275
+ /**
276
+ * Re-route a range of existing reports against the current queue config.
277
+ * Used by the manual `tools.ozone.queue.routeReports` endpoint to pick up
278
+ * reports after queues are created or modified. New reports are inserted
279
+ * by the daemon via `insertReportsFromEvents`, not here.
280
+ */
281
+ async assignReportBatch(
282
+ params: { start: number; end: number; limit: number },
283
+ opts?: { includeUnmatched?: boolean; serviceDid?: string },
284
+ ): Promise<{
285
+ processed: number
286
+ assigned: number
287
+ unmatched: number
288
+ maxId: number
289
+ }> {
290
+ const { queues } = await this.list({ limit: 1000, enabled: true })
291
+
292
+ if (!queues.length) {
293
+ return { processed: 0, assigned: 0, unmatched: 0, maxId: 0 }
294
+ }
295
+
296
+ let query = this.db.db
297
+ .selectFrom('report as r')
298
+ .select([
299
+ 'r.id',
300
+ 'r.status',
301
+ 'r.reportType',
302
+ 'r.recordPath',
303
+ 'r.subjectMessageId',
304
+ ])
305
+ .where('r.status', '!=', 'closed')
306
+ .where('r.id', '>=', params.start)
307
+ .where('r.id', '<=', params.end)
308
+ .orderBy('r.id', 'asc')
309
+ .limit(params.limit)
310
+
311
+ if (opts?.includeUnmatched) {
312
+ query = query.where((qb) => {
313
+ return qb.orWhere('r.queueId', 'is', null).orWhere('r.queueId', '=', -1)
314
+ })
315
+ } else {
316
+ query = query.where('r.queueId', 'is', null)
317
+ }
318
+
319
+ const reports = await query.execute()
320
+
321
+ if (!reports.length) {
322
+ return { processed: 0, assigned: 0, unmatched: 0, maxId: 0 }
323
+ }
324
+
325
+ const now = new Date().toISOString()
326
+
327
+ // Resolve each report's destination in memory — no DB calls in this loop
328
+ type MatchedEntry = {
329
+ id: number
330
+ queueId: number
331
+ nextStatus: string | null
332
+ activity: { activityType: string; previousStatus: string } | null
333
+ }
334
+
335
+ const matchedByQueue = new Map<number, MatchedEntry[]>()
336
+ const unmatchedIds: number[] = []
337
+ let maxReportId = 0
338
+
339
+ for (const report of reports) {
340
+ const subjectType: SubjectType = report.subjectMessageId
341
+ ? 'message'
342
+ : report.recordPath
343
+ ? 'record'
344
+ : 'account'
345
+
346
+ // recordPath is 'collection/rkey' for records, '' for accounts
347
+ const slashIdx = report.recordPath.indexOf('/')
348
+ const collection =
349
+ slashIdx > 0 ? report.recordPath.slice(0, slashIdx) : null
350
+
351
+ const assignment = resolveAssignment(
352
+ subjectType,
353
+ collection,
354
+ report.reportType,
355
+ queues,
356
+ now,
357
+ )
358
+
359
+ if (assignment.queueId !== -1) {
360
+ // Existing-row UPDATE path uses handleReportUpdate so that already
361
+ // escalated/closed/etc. reports keep their status — only open → queued
362
+ // transitions emit a status change and an activity row.
363
+ const result = handleReportUpdate(report.status, { type: 'queue' })
364
+ const group = matchedByQueue.get(assignment.queueId) ?? []
365
+ group.push({
366
+ id: report.id,
367
+ queueId: assignment.queueId,
368
+ nextStatus: result.nextStatus,
369
+ activity: result.activity,
370
+ })
371
+ matchedByQueue.set(assignment.queueId, group)
372
+ } else {
373
+ unmatchedIds.push(report.id)
374
+ }
375
+
376
+ if (report.id > maxReportId) maxReportId = report.id
377
+ }
378
+
379
+ // Bulk UPDATE matched reports — split by whether status should change.
380
+ // handleReportUpdate returns nextStatus only for open → queued;
381
+ // other statuses keep their current status but still get routed.
382
+ for (const [queueId, group] of matchedByQueue) {
383
+ const withTransition = group
384
+ .filter((r) => r.nextStatus !== null)
385
+ .map((r) => r.id)
386
+ const withoutTransition = group
387
+ .filter((r) => r.nextStatus === null)
388
+ .map((r) => r.id)
389
+
390
+ if (withTransition.length) {
391
+ await this.db.db
392
+ .updateTable('report')
393
+ .set({ queueId, queuedAt: now, status: 'queued', updatedAt: now })
394
+ .where('id', 'in', withTransition)
395
+ .execute()
396
+ }
397
+ if (withoutTransition.length) {
398
+ await this.db.db
399
+ .updateTable('report')
400
+ .set({ queueId, queuedAt: now, updatedAt: now })
401
+ .where('id', 'in', withoutTransition)
402
+ .execute()
403
+ }
404
+ }
405
+
406
+ // Bulk UPDATE unmatched reports — status stays unchanged
407
+ if (unmatchedIds.length) {
408
+ await this.db.db
409
+ .updateTable('report')
410
+ .set({ queueId: -1, queuedAt: null, updatedAt: now })
411
+ .where('id', 'in', unmatchedIds)
412
+ .execute()
413
+ }
414
+
415
+ // Bulk INSERT activities for matched reports that changed status.
416
+ if (opts?.serviceDid) {
417
+ const withActivities = [...matchedByQueue.values()]
418
+ .flat()
419
+ .filter((r) => r.activity !== null)
420
+ if (withActivities.length) {
421
+ await this.db.db
422
+ .insertInto('report_activity')
423
+ .values(
424
+ withActivities.map((r) => ({
425
+ reportId: r.id,
426
+ activityType: r.activity!.activityType,
427
+ previousStatus: r.activity!.previousStatus,
428
+ internalNote: null,
429
+ publicNote: null,
430
+ meta: null,
431
+ isAutomated: true,
432
+ createdBy: opts.serviceDid!,
433
+ createdAt: now,
434
+ })),
435
+ )
436
+ .execute()
437
+ }
438
+ }
439
+
440
+ const assigned = [...matchedByQueue.values()].reduce(
441
+ (sum, g) => sum + g.length,
442
+ 0,
443
+ )
444
+
445
+ return {
446
+ processed: reports.length,
447
+ assigned,
448
+ unmatched: unmatchedIds.length,
449
+ maxId: maxReportId,
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Read newly-created modEventReport rows from `moderation_event` and
455
+ * insert corresponding `report` rows with `queueId` already resolved.
456
+ * Used by the queue-router daemon. Idempotent via `ON CONFLICT (eventId)
457
+ * DO NOTHING` — safe to re-run on the same range.
458
+ *
459
+ * Even when no queues are configured, report rows are still inserted with
460
+ * `queueId = -1` so the invariant "every modEventReport has a `report` row"
461
+ * holds.
462
+ */
463
+ async insertReportsFromEvents(params: {
464
+ cursor: number | null
465
+ limit: number
466
+ }): Promise<{
467
+ processed: number
468
+ assigned: number
469
+ unmatched: number
470
+ maxEventId: number
471
+ }> {
472
+ const { queues } = await this.list({ limit: 1000, enabled: true })
473
+
474
+ let query = this.db.db
475
+ .selectFrom('moderation_event')
476
+ .select([
477
+ 'id',
478
+ 'subjectDid',
479
+ 'subjectUri',
480
+ 'subjectMessageId',
481
+ 'meta',
482
+ 'createdAt',
483
+ ])
484
+ .where('action', '=', MOD_EVENT_REPORT_ACTION)
485
+ .orderBy('id', 'asc')
486
+ .limit(params.limit)
487
+
488
+ if (params.cursor !== null) {
489
+ query = query.where('id', '>', params.cursor)
490
+ }
491
+
492
+ const events = await query.execute()
493
+
494
+ if (!events.length) {
495
+ return { processed: 0, assigned: 0, unmatched: 0, maxEventId: 0 }
496
+ }
497
+
498
+ const now = new Date().toISOString()
499
+ let maxEventId = 0
500
+ let assigned = 0
501
+ let unmatched = 0
502
+
503
+ const rows = events.map((event) => {
504
+ const subjectType: SubjectType = event.subjectMessageId
505
+ ? 'message'
506
+ : event.subjectUri
507
+ ? 'record'
508
+ : 'account'
509
+
510
+ let collection: string | null = null
511
+ let recordPath = ''
512
+ if (event.subjectUri) {
513
+ const uri = new AtUri(event.subjectUri)
514
+ collection = uri.collection
515
+ recordPath = `${uri.collection}/${uri.rkey}`
516
+ }
517
+
518
+ const reportType =
519
+ (event.meta?.reportType as string | undefined) ?? REASON_OTHER
520
+
521
+ const assignment = resolveAssignment(
522
+ subjectType,
523
+ collection,
524
+ reportType,
525
+ queues,
526
+ now,
527
+ )
528
+
529
+ if (assignment.queueId === -1) unmatched++
530
+ else assigned++
531
+ if (event.id > maxEventId) maxEventId = event.id
532
+
533
+ const isMuted =
534
+ !!event.meta?.isReporterMuted || !!event.meta?.isSubjectMuted
535
+
536
+ return {
537
+ eventId: event.id,
538
+ queueId: assignment.queueId,
539
+ queuedAt: assignment.queuedAt,
540
+ actionEventIds: null,
541
+ actionNote: null,
542
+ isMuted,
543
+ status: assignment.status,
544
+ reportType,
545
+ did: event.subjectDid,
546
+ recordPath,
547
+ subjectMessageId: event.subjectMessageId,
548
+ createdAt: now,
549
+ updatedAt: now,
550
+ }
551
+ })
552
+
553
+ // ON CONFLICT (eventId) DO NOTHING covers any race where a report row
554
+ // already exists for the event (e.g. transitional code paths or retries
555
+ // after a crash mid-batch).
556
+ await this.db.db
557
+ .insertInto('report')
558
+ .values(rows)
559
+ .onConflict((oc) => oc.column('eventId').doNothing())
560
+ .execute()
561
+
562
+ // Activity rows are intentionally not emitted: a freshly-inserted report
563
+ // has no prior state to "transition" from. Activity rows record state
564
+ // changes, and being born already-queued is not a state change. This
565
+ // matches `handleReportUpdate`'s design where activity is only emitted
566
+ // on real transitions.
567
+
568
+ return {
569
+ processed: events.length,
570
+ assigned,
571
+ unmatched,
572
+ maxEventId,
573
+ }
574
+ }
575
+ }
576
+
577
+ export function findMatchingQueue(
578
+ queues: Selectable<ReportQueue>[],
579
+ subjectType: string,
580
+ collection: string | null,
581
+ reportType: string | undefined,
582
+ ): Selectable<ReportQueue> | null {
583
+ if (!reportType) return null
584
+
585
+ for (const queue of queues) {
586
+ const subjectTypeMatch = queue.subjectTypes.includes(subjectType)
587
+ const collectionMatch =
588
+ subjectType === 'record' && queue.collection !== null
589
+ ? (collection ?? null) === queue.collection
590
+ : true
591
+ const reportTypeMatch = queue.reportTypes.includes(reportType)
592
+
593
+ if (subjectTypeMatch && collectionMatch && reportTypeMatch) {
594
+ return queue
595
+ }
596
+ }
597
+
598
+ return null
599
+ }