@atproto/ozone 0.1.172 → 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 (450) hide show
  1. package/CHANGELOG.md +18 -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/event-reverser.d.ts +1 -0
  113. package/dist/daemon/event-reverser.d.ts.map +1 -1
  114. package/dist/daemon/event-reverser.js +42 -1
  115. package/dist/daemon/event-reverser.js.map +1 -1
  116. package/dist/daemon/job-cursor.d.ts +5 -0
  117. package/dist/daemon/job-cursor.d.ts.map +1 -0
  118. package/dist/daemon/job-cursor.js +28 -0
  119. package/dist/daemon/job-cursor.js.map +1 -0
  120. package/dist/daemon/queue-router.d.ts +17 -0
  121. package/dist/daemon/queue-router.d.ts.map +1 -0
  122. package/dist/daemon/queue-router.js +114 -0
  123. package/dist/daemon/queue-router.js.map +1 -0
  124. package/dist/daemon/stats-computer.d.ts +51 -0
  125. package/dist/daemon/stats-computer.d.ts.map +1 -0
  126. package/dist/daemon/stats-computer.js +117 -0
  127. package/dist/daemon/stats-computer.js.map +1 -0
  128. package/dist/daemon/strike-expiry-processor.d.ts.map +1 -1
  129. package/dist/daemon/strike-expiry-processor.js +4 -19
  130. package/dist/daemon/strike-expiry-processor.js.map +1 -1
  131. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts +4 -0
  132. package/dist/db/migrations/20260219T164523000Z-create-report-table.d.ts.map +1 -0
  133. package/dist/db/migrations/20260219T164523000Z-create-report-table.js +126 -0
  134. package/dist/db/migrations/20260219T164523000Z-create-report-table.js.map +1 -0
  135. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts +4 -0
  136. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.d.ts.map +1 -0
  137. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js +35 -0
  138. package/dist/db/migrations/20260219T165302248Z-moderator-assignment.js.map +1 -0
  139. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts +4 -0
  140. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.d.ts.map +1 -0
  141. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js +36 -0
  142. package/dist/db/migrations/20260225T000000000Z-add-report-queue-table.js.map +1 -0
  143. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts +4 -0
  144. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.d.ts.map +1 -0
  145. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js +39 -0
  146. package/dist/db/migrations/20260313T000000000Z-add-report-activity-table.js.map +1 -0
  147. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts +4 -0
  148. package/dist/db/migrations/20260318T152058935Z-add-report-stat.d.ts.map +1 -0
  149. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js +34 -0
  150. package/dist/db/migrations/20260318T152058935Z-add-report-stat.js.map +1 -0
  151. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.d.ts +4 -0
  152. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.d.ts.map +1 -0
  153. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.js +32 -0
  154. package/dist/db/migrations/20260428T000000000Z-add-expiring-tag-table.js.map +1 -0
  155. package/dist/db/migrations/index.d.ts +6 -0
  156. package/dist/db/migrations/index.d.ts.map +1 -1
  157. package/dist/db/migrations/index.js +7 -1
  158. package/dist/db/migrations/index.js.map +1 -1
  159. package/dist/db/pagination.d.ts +31 -0
  160. package/dist/db/pagination.d.ts.map +1 -1
  161. package/dist/db/pagination.js +74 -1
  162. package/dist/db/pagination.js.map +1 -1
  163. package/dist/db/schema/expiring_tag.d.ts +15 -0
  164. package/dist/db/schema/expiring_tag.d.ts.map +1 -0
  165. package/dist/db/schema/expiring_tag.js +5 -0
  166. package/dist/db/schema/expiring_tag.js.map +1 -0
  167. package/dist/db/schema/index.d.ts +7 -1
  168. package/dist/db/schema/index.d.ts.map +1 -1
  169. package/dist/db/schema/index.js.map +1 -1
  170. package/dist/db/schema/moderator_assignment.d.ts +14 -0
  171. package/dist/db/schema/moderator_assignment.d.ts.map +1 -0
  172. package/dist/db/schema/moderator_assignment.js +5 -0
  173. package/dist/db/schema/moderator_assignment.js.map +1 -0
  174. package/dist/db/schema/report.d.ts +25 -0
  175. package/dist/db/schema/report.d.ts.map +1 -0
  176. package/dist/db/schema/report.js +5 -0
  177. package/dist/db/schema/report.js.map +1 -0
  178. package/dist/db/schema/report_activity.d.ts +18 -0
  179. package/dist/db/schema/report_activity.d.ts.map +1 -0
  180. package/dist/db/schema/report_activity.js +5 -0
  181. package/dist/db/schema/report_activity.js.map +1 -0
  182. package/dist/db/schema/report_queue.d.ts +19 -0
  183. package/dist/db/schema/report_queue.d.ts.map +1 -0
  184. package/dist/db/schema/report_queue.js +5 -0
  185. package/dist/db/schema/report_queue.js.map +1 -0
  186. package/dist/db/schema/report_stat.d.ts +20 -0
  187. package/dist/db/schema/report_stat.d.ts.map +1 -0
  188. package/dist/db/schema/report_stat.js +5 -0
  189. package/dist/db/schema/report_stat.js.map +1 -0
  190. package/dist/lexicon/index.d.ts +50 -0
  191. package/dist/lexicon/index.d.ts.map +1 -1
  192. package/dist/lexicon/index.js +120 -2
  193. package/dist/lexicon/index.js.map +1 -1
  194. package/dist/lexicon/lexicons.d.ts +11255 -7885
  195. package/dist/lexicon/lexicons.d.ts.map +1 -1
  196. package/dist/lexicon/lexicons.js +1900 -120
  197. package/dist/lexicon/lexicons.js.map +1 -1
  198. package/dist/lexicon/types/app/bsky/embed/external.d.ts +2 -0
  199. package/dist/lexicon/types/app/bsky/embed/external.d.ts.map +1 -1
  200. package/dist/lexicon/types/app/bsky/embed/external.js.map +1 -1
  201. package/dist/lexicon/types/chat/bsky/actor/defs.d.ts +8 -2
  202. package/dist/lexicon/types/chat/bsky/actor/defs.d.ts.map +1 -1
  203. package/dist/lexicon/types/chat/bsky/actor/defs.js +9 -0
  204. package/dist/lexicon/types/chat/bsky/actor/defs.js.map +1 -1
  205. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts +37 -10
  206. package/dist/lexicon/types/chat/bsky/convo/defs.d.ts.map +1 -1
  207. package/dist/lexicon/types/chat/bsky/convo/defs.js +9 -0
  208. package/dist/lexicon/types/chat/bsky/convo/defs.js.map +1 -1
  209. package/dist/lexicon/types/chat/bsky/convo/getMessages.d.ts +3 -0
  210. package/dist/lexicon/types/chat/bsky/convo/getMessages.d.ts.map +1 -1
  211. package/dist/lexicon/types/chat/bsky/convo/getMessages.js.map +1 -1
  212. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -0
  213. package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
  214. package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
  215. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts +19 -0
  216. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.d.ts.map +1 -1
  217. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js +9 -0
  218. package/dist/lexicon/types/tools/ozone/moderation/emitEvent.js.map +1 -1
  219. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts +27 -0
  220. package/dist/lexicon/types/tools/ozone/queue/assignModerator.d.ts.map +1 -0
  221. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js +7 -0
  222. package/dist/lexicon/types/tools/ozone/queue/assignModerator.js.map +1 -0
  223. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts +35 -0
  224. package/dist/lexicon/types/tools/ozone/queue/createQueue.d.ts.map +1 -0
  225. package/dist/lexicon/types/tools/ozone/queue/createQueue.js +7 -0
  226. package/dist/lexicon/types/tools/ozone/queue/createQueue.js.map +1 -0
  227. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts +62 -0
  228. package/dist/lexicon/types/tools/ozone/queue/defs.d.ts.map +1 -0
  229. package/dist/lexicon/types/tools/ozone/queue/defs.js +34 -0
  230. package/dist/lexicon/types/tools/ozone/queue/defs.js.map +1 -0
  231. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts +29 -0
  232. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.d.ts.map +1 -0
  233. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js +7 -0
  234. package/dist/lexicon/types/tools/ozone/queue/deleteQueue.js.map +1 -0
  235. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts +30 -0
  236. package/dist/lexicon/types/tools/ozone/queue/getAssignments.d.ts.map +1 -0
  237. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js +7 -0
  238. package/dist/lexicon/types/tools/ozone/queue/getAssignments.js.map +1 -0
  239. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts +32 -0
  240. package/dist/lexicon/types/tools/ozone/queue/listQueues.d.ts.map +1 -0
  241. package/dist/lexicon/types/tools/ozone/queue/listQueues.js +7 -0
  242. package/dist/lexicon/types/tools/ozone/queue/listQueues.js.map +1 -0
  243. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts +31 -0
  244. package/dist/lexicon/types/tools/ozone/queue/routeReports.d.ts.map +1 -0
  245. package/dist/lexicon/types/tools/ozone/queue/routeReports.js +7 -0
  246. package/dist/lexicon/types/tools/ozone/queue/routeReports.js.map +1 -0
  247. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts +18 -0
  248. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.d.ts.map +1 -0
  249. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js +7 -0
  250. package/dist/lexicon/types/tools/ozone/queue/unassignModerator.js.map +1 -0
  251. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts +32 -0
  252. package/dist/lexicon/types/tools/ozone/queue/updateQueue.d.ts.map +1 -0
  253. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js +7 -0
  254. package/dist/lexicon/types/tools/ozone/queue/updateQueue.js.map +1 -0
  255. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts +31 -0
  256. package/dist/lexicon/types/tools/ozone/report/assignModerator.d.ts.map +1 -0
  257. package/dist/lexicon/types/tools/ozone/report/assignModerator.js +7 -0
  258. package/dist/lexicon/types/tools/ozone/report/assignModerator.js.map +1 -0
  259. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts +37 -0
  260. package/dist/lexicon/types/tools/ozone/report/createActivity.d.ts.map +1 -0
  261. package/dist/lexicon/types/tools/ozone/report/createActivity.js +7 -0
  262. package/dist/lexicon/types/tools/ozone/report/createActivity.js.map +1 -0
  263. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +185 -0
  264. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  265. package/dist/lexicon/types/tools/ozone/report/defs.js +108 -0
  266. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  267. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts +30 -0
  268. package/dist/lexicon/types/tools/ozone/report/getAssignments.d.ts.map +1 -0
  269. package/dist/lexicon/types/tools/ozone/report/getAssignments.js +7 -0
  270. package/dist/lexicon/types/tools/ozone/report/getAssignments.js.map +1 -0
  271. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts +36 -0
  272. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.d.ts.map +1 -0
  273. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js +7 -0
  274. package/dist/lexicon/types/tools/ozone/report/getHistoricalStats.js.map +1 -0
  275. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts +21 -0
  276. package/dist/lexicon/types/tools/ozone/report/getLatestReport.d.ts.map +1 -0
  277. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js +7 -0
  278. package/dist/lexicon/types/tools/ozone/report/getLatestReport.js.map +1 -0
  279. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts +27 -0
  280. package/dist/lexicon/types/tools/ozone/report/getLiveStats.d.ts.map +1 -0
  281. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js +7 -0
  282. package/dist/lexicon/types/tools/ozone/report/getLiveStats.js.map +1 -0
  283. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts +22 -0
  284. package/dist/lexicon/types/tools/ozone/report/getReport.d.ts.map +1 -0
  285. package/dist/lexicon/types/tools/ozone/report/getReport.js +7 -0
  286. package/dist/lexicon/types/tools/ozone/report/getReport.js.map +1 -0
  287. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts +26 -0
  288. package/dist/lexicon/types/tools/ozone/report/listActivities.d.ts.map +1 -0
  289. package/dist/lexicon/types/tools/ozone/report/listActivities.js +7 -0
  290. package/dist/lexicon/types/tools/ozone/report/listActivities.js.map +1 -0
  291. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts +48 -0
  292. package/dist/lexicon/types/tools/ozone/report/queryReports.d.ts.map +1 -0
  293. package/dist/lexicon/types/tools/ozone/report/queryReports.js +7 -0
  294. package/dist/lexicon/types/tools/ozone/report/queryReports.js.map +1 -0
  295. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts +31 -0
  296. package/dist/lexicon/types/tools/ozone/report/reassignQueue.d.ts.map +1 -0
  297. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js +7 -0
  298. package/dist/lexicon/types/tools/ozone/report/reassignQueue.js.map +1 -0
  299. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts +28 -0
  300. package/dist/lexicon/types/tools/ozone/report/refreshStats.d.ts.map +1 -0
  301. package/dist/lexicon/types/tools/ozone/report/refreshStats.js +7 -0
  302. package/dist/lexicon/types/tools/ozone/report/refreshStats.js.map +1 -0
  303. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts +25 -0
  304. package/dist/lexicon/types/tools/ozone/report/unassignModerator.d.ts.map +1 -0
  305. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js +7 -0
  306. package/dist/lexicon/types/tools/ozone/report/unassignModerator.js.map +1 -0
  307. package/dist/mod-service/expiring-tags.d.ts +27 -0
  308. package/dist/mod-service/expiring-tags.d.ts.map +1 -0
  309. package/dist/mod-service/expiring-tags.js +62 -0
  310. package/dist/mod-service/expiring-tags.js.map +1 -0
  311. package/dist/mod-service/index.d.ts +3 -1
  312. package/dist/mod-service/index.d.ts.map +1 -1
  313. package/dist/mod-service/index.js +61 -2
  314. package/dist/mod-service/index.js.map +1 -1
  315. package/dist/mod-service/report.d.ts +64 -0
  316. package/dist/mod-service/report.d.ts.map +1 -0
  317. package/dist/mod-service/report.js +282 -0
  318. package/dist/mod-service/report.js.map +1 -0
  319. package/dist/mod-service/status.d.ts +24 -0
  320. package/dist/mod-service/status.d.ts.map +1 -1
  321. package/dist/queue/service.d.ts +86 -0
  322. package/dist/queue/service.d.ts.map +1 -0
  323. package/dist/queue/service.js +430 -0
  324. package/dist/queue/service.js.map +1 -0
  325. package/dist/report/activity.d.ts +77 -0
  326. package/dist/report/activity.d.ts.map +1 -0
  327. package/dist/report/activity.js +141 -0
  328. package/dist/report/activity.js.map +1 -0
  329. package/dist/report/handle-report-update.d.ts +47 -0
  330. package/dist/report/handle-report-update.d.ts.map +1 -0
  331. package/dist/report/handle-report-update.js +178 -0
  332. package/dist/report/handle-report-update.js.map +1 -0
  333. package/dist/report/reassign.d.ts +10 -0
  334. package/dist/report/reassign.d.ts.map +1 -0
  335. package/dist/report/reassign.js +75 -0
  336. package/dist/report/reassign.js.map +1 -0
  337. package/dist/report/stats.d.ts +105 -0
  338. package/dist/report/stats.d.ts.map +1 -0
  339. package/dist/report/stats.js +619 -0
  340. package/dist/report/stats.js.map +1 -0
  341. package/dist/report/views.d.ts +111 -0
  342. package/dist/report/views.d.ts.map +1 -0
  343. package/dist/report/views.js +156 -0
  344. package/dist/report/views.js.map +1 -0
  345. package/dist/team/index.d.ts +1 -0
  346. package/dist/team/index.d.ts.map +1 -1
  347. package/dist/team/index.js +11 -0
  348. package/dist/team/index.js.map +1 -1
  349. package/package.json +3 -3
  350. package/src/api/index.ts +40 -0
  351. package/src/api/moderation/emitEvent.ts +38 -0
  352. package/src/api/queue/assignModerator.ts +31 -0
  353. package/src/api/queue/createQueue.ts +62 -0
  354. package/src/api/queue/deleteQueue.ts +56 -0
  355. package/src/api/queue/getAssignments.ts +19 -0
  356. package/src/api/queue/listQueues.ts +39 -0
  357. package/src/api/queue/routeReports.ts +44 -0
  358. package/src/api/queue/unassignModerator.ts +26 -0
  359. package/src/api/queue/updateQueue.ts +54 -0
  360. package/src/api/report/assignModerator.ts +36 -0
  361. package/src/api/report/createActivity.ts +57 -0
  362. package/src/api/report/getAssignments.ts +20 -0
  363. package/src/api/report/getHistoricalStats.ts +41 -0
  364. package/src/api/report/getLatestReport.ts +44 -0
  365. package/src/api/report/getLiveStats.ts +26 -0
  366. package/src/api/report/getReport.ts +55 -0
  367. package/src/api/report/listActivities.ts +34 -0
  368. package/src/api/report/queryReports.ts +44 -0
  369. package/src/api/report/reassignQueue.ts +68 -0
  370. package/src/api/report/refreshStats.ts +27 -0
  371. package/src/api/report/unassignModerator.ts +21 -0
  372. package/src/api/util.ts +12 -0
  373. package/src/assignment/index.ts +731 -0
  374. package/src/config/config.ts +27 -0
  375. package/src/config/env.ts +8 -0
  376. package/src/context.ts +31 -0
  377. package/src/daemon/context.ts +34 -0
  378. package/src/daemon/event-reverser.ts +50 -1
  379. package/src/daemon/job-cursor.ts +33 -0
  380. package/src/daemon/queue-router.ts +101 -0
  381. package/src/daemon/stats-computer.ts +101 -0
  382. package/src/daemon/strike-expiry-processor.ts +4 -20
  383. package/src/db/migrations/20260219T164523000Z-create-report-table.ts +155 -0
  384. package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +42 -0
  385. package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +41 -0
  386. package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +48 -0
  387. package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +35 -0
  388. package/src/db/migrations/20260428T000000000Z-add-expiring-tag-table.ts +32 -0
  389. package/src/db/migrations/index.ts +6 -0
  390. package/src/db/pagination.ts +85 -0
  391. package/src/db/schema/expiring_tag.ts +17 -0
  392. package/src/db/schema/index.ts +13 -1
  393. package/src/db/schema/moderator_assignment.ts +16 -0
  394. package/src/db/schema/report.ts +27 -0
  395. package/src/db/schema/report_activity.ts +22 -0
  396. package/src/db/schema/report_queue.ts +21 -0
  397. package/src/db/schema/report_stat.ts +27 -0
  398. package/src/lexicon/index.ts +280 -0
  399. package/src/lexicon/lexicons.ts +2083 -214
  400. package/src/lexicon/types/app/bsky/embed/external.ts +2 -0
  401. package/src/lexicon/types/chat/bsky/actor/defs.ts +17 -1
  402. package/src/lexicon/types/chat/bsky/convo/defs.ts +50 -10
  403. package/src/lexicon/types/chat/bsky/convo/getMessages.ts +3 -0
  404. package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
  405. package/src/lexicon/types/tools/ozone/moderation/emitEvent.ts +24 -0
  406. package/src/lexicon/types/tools/ozone/queue/assignModerator.ts +46 -0
  407. package/src/lexicon/types/tools/ozone/queue/createQueue.ts +54 -0
  408. package/src/lexicon/types/tools/ozone/queue/defs.ts +99 -0
  409. package/src/lexicon/types/tools/ozone/queue/deleteQueue.ts +48 -0
  410. package/src/lexicon/types/tools/ozone/queue/getAssignments.ts +48 -0
  411. package/src/lexicon/types/tools/ozone/queue/listQueues.ts +50 -0
  412. package/src/lexicon/types/tools/ozone/queue/routeReports.ts +50 -0
  413. package/src/lexicon/types/tools/ozone/queue/unassignModerator.ts +37 -0
  414. package/src/lexicon/types/tools/ozone/queue/updateQueue.ts +51 -0
  415. package/src/lexicon/types/tools/ozone/report/assignModerator.ts +50 -0
  416. package/src/lexicon/types/tools/ozone/report/createActivity.ts +60 -0
  417. package/src/lexicon/types/tools/ozone/report/defs.ts +327 -0
  418. package/src/lexicon/types/tools/ozone/report/getAssignments.ts +48 -0
  419. package/src/lexicon/types/tools/ozone/report/getHistoricalStats.ts +54 -0
  420. package/src/lexicon/types/tools/ozone/report/getLatestReport.ts +39 -0
  421. package/src/lexicon/types/tools/ozone/report/getLiveStats.ts +45 -0
  422. package/src/lexicon/types/tools/ozone/report/getReport.ts +38 -0
  423. package/src/lexicon/types/tools/ozone/report/listActivities.ts +44 -0
  424. package/src/lexicon/types/tools/ozone/report/queryReports.ts +72 -0
  425. package/src/lexicon/types/tools/ozone/report/reassignQueue.ts +55 -0
  426. package/src/lexicon/types/tools/ozone/report/refreshStats.ts +46 -0
  427. package/src/lexicon/types/tools/ozone/report/unassignModerator.ts +44 -0
  428. package/src/mod-service/expiring-tags.ts +98 -0
  429. package/src/mod-service/index.ts +71 -3
  430. package/src/mod-service/report.ts +408 -0
  431. package/src/queue/service.ts +599 -0
  432. package/src/report/activity.ts +234 -0
  433. package/src/report/handle-report-update.ts +209 -0
  434. package/src/report/reassign.ts +109 -0
  435. package/src/report/stats.ts +850 -0
  436. package/src/report/views.ts +241 -0
  437. package/src/team/index.ts +11 -0
  438. package/tests/expiring-tags.test.ts +231 -0
  439. package/tests/get-report.test.ts +136 -0
  440. package/tests/query-reports.test.ts +608 -0
  441. package/tests/queue-assignment.test.ts +428 -0
  442. package/tests/queue-router.test.ts +306 -0
  443. package/tests/queues.test.ts +690 -0
  444. package/tests/report-action.test.ts +308 -0
  445. package/tests/report-activity.test.ts +567 -0
  446. package/tests/report-assignment.test.ts +517 -0
  447. package/tests/report-reassign-queue.test.ts +340 -0
  448. package/tests/report-routing.test.ts +245 -0
  449. package/tests/report-stats.test.ts +545 -0
  450. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,340 @@
1
+ import AtpAgent from '@atproto/api'
2
+ import { SeedClient, TestNetwork, basicSeed } from '@atproto/dev-env'
3
+ import { ids } from '../src/lexicon/lexicons'
4
+ import { REASONSPAM } from '../src/lexicon/types/com/atproto/moderation/defs'
5
+
6
+ const DEFS = 'tools.ozone.report.defs'
7
+
8
+ describe('report-reassign-queue', () => {
9
+ let network: TestNetwork
10
+ let agent: AtpAgent
11
+ let sc: SeedClient
12
+
13
+ // Track queue IDs created during each test so afterEach can soft-delete them.
14
+ // Soft-deleted queues are filtered out of `checkConflict`, so tests are free
15
+ // to reuse the same queue configuration without tripping ConflictingQueue.
16
+ const createdQueueIds: number[] = []
17
+
18
+ const modHeaders = async (
19
+ nsid: string,
20
+ role: 'admin' | 'moderator' | 'triage' = 'admin',
21
+ ) => network.ozone.modHeaders(nsid, role)
22
+
23
+ const createReport = async (subjectDid: string) => {
24
+ await sc.createReport({
25
+ reasonType: REASONSPAM,
26
+ subject: {
27
+ $type: 'com.atproto.admin.defs#repoRef',
28
+ did: subjectDid,
29
+ },
30
+ reportedBy: sc.dids.bob,
31
+ })
32
+ await network.processAll()
33
+
34
+ const { data } = await agent.tools.ozone.report.queryReports(
35
+ { status: 'open', subject: subjectDid },
36
+ { headers: await modHeaders(ids.ToolsOzoneReportQueryReports) },
37
+ )
38
+ const report = data.reports[0]
39
+ if (!report) throw new Error(`No report found for subject ${subjectDid}`)
40
+ return report
41
+ }
42
+
43
+ const createQueue = async (
44
+ overrides: {
45
+ name?: string
46
+ subjectTypes?: string[]
47
+ reportTypes?: string[]
48
+ } = {},
49
+ ) => {
50
+ const name = overrides.name ?? `q-${Date.now()}-${Math.random()}`
51
+ const input = {
52
+ name,
53
+ subjectTypes: overrides.subjectTypes ?? ['account'],
54
+ reportTypes: overrides.reportTypes ?? [
55
+ 'com.atproto.moderation.defs#reasonSpam',
56
+ ],
57
+ }
58
+ const { data } = await agent.tools.ozone.queue.createQueue(input, {
59
+ encoding: 'application/json',
60
+ headers: await modHeaders(ids.ToolsOzoneQueueCreateQueue),
61
+ })
62
+ createdQueueIds.push(data.queue.id)
63
+ return data
64
+ }
65
+
66
+ const deleteQueue = async (queueId: number) =>
67
+ agent.tools.ozone.queue.deleteQueue(
68
+ { queueId },
69
+ {
70
+ encoding: 'application/json',
71
+ headers: await modHeaders(ids.ToolsOzoneQueueDeleteQueue),
72
+ },
73
+ )
74
+
75
+ const closeReport = async (reportId: number) => {
76
+ await agent.tools.ozone.report.createActivity(
77
+ {
78
+ reportId,
79
+ activity: { $type: `${DEFS}#closeActivity` },
80
+ },
81
+ {
82
+ encoding: 'application/json',
83
+ headers: await modHeaders(ids.ToolsOzoneReportCreateActivity),
84
+ },
85
+ )
86
+ }
87
+
88
+ const disableQueue = async (queueId: number) => {
89
+ return agent.tools.ozone.queue.updateQueue(
90
+ { queueId, enabled: false },
91
+ {
92
+ encoding: 'application/json',
93
+ headers: await modHeaders(ids.ToolsOzoneQueueUpdateQueue),
94
+ },
95
+ )
96
+ }
97
+
98
+ const escalateReport = async (reportId: number) => {
99
+ await agent.tools.ozone.report.createActivity(
100
+ {
101
+ reportId,
102
+ activity: { $type: `${DEFS}#escalationActivity` },
103
+ },
104
+ {
105
+ encoding: 'application/json',
106
+ headers: await modHeaders(ids.ToolsOzoneReportCreateActivity),
107
+ },
108
+ )
109
+ }
110
+
111
+ const reassignQueue = async (
112
+ input: { reportId: number; queueId: number; comment?: string },
113
+ role: 'admin' | 'moderator' | 'triage' = 'admin',
114
+ ) => {
115
+ return agent.tools.ozone.report.reassignQueue(input, {
116
+ encoding: 'application/json',
117
+ headers: await modHeaders(ids.ToolsOzoneReportReassignQueue, role),
118
+ })
119
+ }
120
+
121
+ const listActivities = async (reportId: number) => {
122
+ const { data } = await agent.tools.ozone.report.listActivities(
123
+ { reportId },
124
+ { headers: await modHeaders(ids.ToolsOzoneReportListActivities) },
125
+ )
126
+ return data
127
+ }
128
+
129
+ beforeAll(async () => {
130
+ network = await TestNetwork.create({
131
+ dbPostgresSchema: 'ozone_report_reassign_queue',
132
+ })
133
+ agent = network.ozone.getAgent()
134
+ sc = network.getSeedClient()
135
+ await basicSeed(sc)
136
+ await network.processAll()
137
+ })
138
+
139
+ afterEach(async () => {
140
+ await Promise.all(
141
+ createdQueueIds.splice(0).map((id) => deleteQueue(id).catch(() => {})),
142
+ )
143
+ })
144
+
145
+ afterAll(async () => {
146
+ await network.close()
147
+ })
148
+
149
+ describe('happy path: assigning to a real queue', () => {
150
+ it('transitions open → queued and writes an activity row', async () => {
151
+ const report = await createReport(sc.dids.alice)
152
+ const queue = await createQueue()
153
+
154
+ const { data } = await reassignQueue({
155
+ reportId: report.id,
156
+ queueId: queue.queue.id,
157
+ comment: 'Routing to spam queue for review',
158
+ })
159
+
160
+ expect(data.report.id).toBe(report.id)
161
+ expect(data.report.status).toBe('queued')
162
+ expect(data.report.queue?.id).toBe(queue.queue.id)
163
+ expect(data.report.queuedAt).toBeDefined()
164
+
165
+ const activities = await listActivities(report.id)
166
+ const queueActivity = activities.activities.find(
167
+ (a) => a.activity.$type === `${DEFS}#queueActivity`,
168
+ )
169
+ expect(queueActivity).toBeDefined()
170
+ expect(queueActivity!.internalNote).toBe(
171
+ 'Routing to spam queue for review',
172
+ )
173
+ expect(queueActivity!.publicNote).toBeUndefined()
174
+ expect(queueActivity!.isAutomated).toBe(false)
175
+ expect((queueActivity!.activity as any).previousStatus).toBe('open')
176
+ expect(queueActivity!.meta!.toQueueId).toEqual(queue.queue.id)
177
+ })
178
+ })
179
+
180
+ describe('queue-to-queue reassignment', () => {
181
+ it('keeps status queued and records fromQueueId correctly', async () => {
182
+ const report = await createReport(sc.dids.carol)
183
+ const queueA = await createQueue()
184
+ // Second queue needs a distinct config to avoid ConflictingQueue.
185
+ const queueB = await createQueue({
186
+ reportTypes: ['com.atproto.moderation.defs#reasonViolation'],
187
+ })
188
+
189
+ await reassignQueue({ reportId: report.id, queueId: queueA.queue.id })
190
+
191
+ const { data } = await reassignQueue({
192
+ reportId: report.id,
193
+ queueId: queueB.queue.id,
194
+ })
195
+
196
+ expect(data.report.status).toBe('queued')
197
+ expect(data.report.queue?.id).toBe(queueB.queue.id)
198
+
199
+ const activities = await listActivities(report.id)
200
+ const queueActivities = activities.activities.filter(
201
+ (a) => a.activity.$type === `${DEFS}#queueActivity`,
202
+ )
203
+ // Sorted DESC, so [0] is the most recent.
204
+ expect(queueActivities.length).toBeGreaterThanOrEqual(2)
205
+ expect((queueActivities[0].activity as any).previousStatus).toBe('queued')
206
+ expect(queueActivities[0].meta).toEqual({
207
+ fromQueueId: queueA.queue.id,
208
+ toQueueId: queueB.queue.id,
209
+ })
210
+ })
211
+ })
212
+
213
+ describe('unassignment (queueId = -1)', () => {
214
+ it('transitions queued → open and clears queuedAt', async () => {
215
+ const report = await createReport(sc.dids.dan)
216
+ const queue = await createQueue()
217
+
218
+ await reassignQueue({ reportId: report.id, queueId: queue.queue.id })
219
+
220
+ const { data } = await reassignQueue({
221
+ reportId: report.id,
222
+ queueId: -1,
223
+ })
224
+
225
+ expect(data.report.status).toBe('open')
226
+ expect(data.report.queue).toBeUndefined()
227
+ expect(data.report.queuedAt).toBeUndefined()
228
+
229
+ const activities = await listActivities(report.id)
230
+ const latest = activities.activities[0]
231
+ expect(latest.activity.$type).toBe(`${DEFS}#queueActivity`)
232
+ expect((latest.activity as any).previousStatus).toBe('queued')
233
+ expect(latest.meta).toEqual({
234
+ fromQueueId: queue.queue.id,
235
+ toQueueId: -1,
236
+ })
237
+ })
238
+ })
239
+
240
+ describe('errors', () => {
241
+ it('throws ReportClosed when report is closed', async () => {
242
+ const report = await createReport(sc.dids.alice)
243
+ await closeReport(report.id)
244
+ const queue = await createQueue()
245
+
246
+ await expect(
247
+ reassignQueue({ reportId: report.id, queueId: queue.queue.id }),
248
+ ).rejects.toThrow(/ReportClosed|closed/)
249
+ })
250
+
251
+ it('throws AlreadyInTargetQueue when target equals current queue', async () => {
252
+ const report = await createReport(sc.dids.alice)
253
+ const queue = await createQueue()
254
+ await reassignQueue({ reportId: report.id, queueId: queue.queue.id })
255
+
256
+ await expect(
257
+ reassignQueue({ reportId: report.id, queueId: queue.queue.id }),
258
+ ).rejects.toThrow(/AlreadyInTargetQueue|already/)
259
+ })
260
+
261
+ it('throws AlreadyInTargetQueue when unassigning a never-queued report', async () => {
262
+ const report = await createReport(sc.dids.alice)
263
+
264
+ await expect(
265
+ reassignQueue({ reportId: report.id, queueId: -1 }),
266
+ ).rejects.toThrow(/AlreadyInTargetQueue|already/)
267
+ })
268
+
269
+ it('throws QueueNotFound when target queue does not exist', async () => {
270
+ const report = await createReport(sc.dids.alice)
271
+
272
+ await expect(
273
+ reassignQueue({ reportId: report.id, queueId: 999999 }),
274
+ ).rejects.toThrow(/QueueNotFound|not found/)
275
+ })
276
+
277
+ it('throws QueueNotFound when target queue is soft-deleted', async () => {
278
+ const report = await createReport(sc.dids.alice)
279
+ const queue = await createQueue()
280
+ await deleteQueue(queue.queue.id)
281
+
282
+ await expect(
283
+ reassignQueue({ reportId: report.id, queueId: queue.queue.id }),
284
+ ).rejects.toThrow(/QueueNotFound|not found/)
285
+ })
286
+
287
+ it('throws QueueDisabled when target queue is disabled', async () => {
288
+ const report = await createReport(sc.dids.alice)
289
+ const queue = await createQueue()
290
+ await disableQueue(queue.queue.id)
291
+
292
+ await expect(
293
+ reassignQueue({ reportId: report.id, queueId: queue.queue.id }),
294
+ ).rejects.toThrow(/QueueDisabled|disabled/)
295
+ })
296
+
297
+ it('throws ReportNotFound when report does not exist', async () => {
298
+ const queue = await createQueue()
299
+
300
+ await expect(
301
+ reassignQueue({ reportId: 999999, queueId: queue.queue.id }),
302
+ ).rejects.toThrow(/ReportNotFound|not found/)
303
+ })
304
+ })
305
+
306
+ describe('status-preserving cases', () => {
307
+ it('keeps escalated status when reassigning an escalated report', async () => {
308
+ const report = await createReport(sc.dids.alice)
309
+ await escalateReport(report.id)
310
+ const queue = await createQueue()
311
+
312
+ const { data } = await reassignQueue({
313
+ reportId: report.id,
314
+ queueId: queue.queue.id,
315
+ })
316
+
317
+ expect(data.report.status).toBe('escalated')
318
+ expect(data.report.queue?.id).toBe(queue.queue.id)
319
+
320
+ const activities = await listActivities(report.id)
321
+ const latest = activities.activities[0]
322
+ expect(latest.activity.$type).toBe(`${DEFS}#queueActivity`)
323
+ expect((latest.activity as any).previousStatus).toBe('escalated')
324
+ })
325
+ })
326
+
327
+ describe('auth', () => {
328
+ it('allows triage role to reassign', async () => {
329
+ const report = await createReport(sc.dids.alice)
330
+ const queue = await createQueue()
331
+
332
+ const { data } = await reassignQueue(
333
+ { reportId: report.id, queueId: queue.queue.id },
334
+ 'triage',
335
+ )
336
+
337
+ expect(data.report.status).toBe('queued')
338
+ })
339
+ })
340
+ })
@@ -0,0 +1,245 @@
1
+ import AtpAgent from '@atproto/api'
2
+ import {
3
+ ModeratorClient,
4
+ SeedClient,
5
+ TestNetwork,
6
+ basicSeed,
7
+ } from '@atproto/dev-env'
8
+ import { ids } from '../src/lexicon/lexicons'
9
+
10
+ const REASON_SPAM = 'com.atproto.moderation.defs#reasonSpam'
11
+ const REASON_THREAT = 'tools.ozone.report.defs#reasonViolenceThreats'
12
+ const REASON_MISLEADING = 'com.atproto.moderation.defs#reasonMisleading'
13
+
14
+ describe('queue-router', () => {
15
+ let network: TestNetwork
16
+ let agent: AtpAgent
17
+ let sc: SeedClient
18
+ let modClient: ModeratorClient
19
+
20
+ const modHeaders = (nsid: string) => network.ozone.modHeaders(nsid, 'admin')
21
+
22
+ const createQueue = async (input: {
23
+ name: string
24
+ subjectTypes: string[]
25
+ reportTypes: string[]
26
+ collection?: string
27
+ }) => {
28
+ const { data } = await agent.tools.ozone.queue.createQueue(input, {
29
+ encoding: 'application/json',
30
+ headers: await modHeaders(ids.ToolsOzoneQueueCreateQueue),
31
+ })
32
+ return data.queue
33
+ }
34
+
35
+ const deleteQueue = async (queueId: number) => {
36
+ await agent.tools.ozone.queue.deleteQueue(
37
+ { queueId },
38
+ {
39
+ encoding: 'application/json',
40
+ headers: await modHeaders(ids.ToolsOzoneQueueDeleteQueue),
41
+ },
42
+ )
43
+ }
44
+
45
+ // Creates a report event (account-level) directly via modClient for a given DID + reason
46
+ const reportAccount = async (did: string, reportType: string) => {
47
+ await modClient.emitEvent({
48
+ event: {
49
+ $type: 'tools.ozone.moderation.defs#modEventReport',
50
+ reportType,
51
+ comment: 'automated test report',
52
+ },
53
+ subject: { $type: 'com.atproto.admin.defs#repoRef', did },
54
+ })
55
+ }
56
+
57
+ // Returns the most recent report row for a subject directly from the DB.
58
+ // Pass a DID for account subjects or an at:// URI for record subjects.
59
+ const getLatestReportForSubject = async (subjectOrUri: string) => {
60
+ const db = network.ozone.daemon.ctx.db
61
+ const isDid = subjectOrUri.startsWith('did:')
62
+ let query = db.db
63
+ .selectFrom('report as r')
64
+ .innerJoin('moderation_event as me', 'me.id', 'r.eventId')
65
+ .select(['r.id', 'r.queueId', 'r.queuedAt', 'r.status'])
66
+ .orderBy('r.id', 'desc')
67
+ .limit(1)
68
+ if (isDid) {
69
+ query = query
70
+ .where('me.subjectDid', '=', subjectOrUri)
71
+ .where('me.subjectUri', 'is', null)
72
+ } else {
73
+ query = query.where('me.subjectUri', '=', subjectOrUri)
74
+ }
75
+ return query.executeTakeFirstOrThrow()
76
+ }
77
+
78
+ const getLatest = async () => {
79
+ const { data } = await agent.tools.ozone.report.getLatestReport(
80
+ {},
81
+ { headers: await modHeaders(ids.ToolsOzoneReportGetLatestReport) },
82
+ )
83
+ return data.report
84
+ }
85
+
86
+ const routeReports = async (startReportId: number, endReportId: number) => {
87
+ const { data } = await agent.tools.ozone.queue.routeReports(
88
+ { startReportId, endReportId },
89
+ {
90
+ encoding: 'application/json',
91
+ headers: await modHeaders(ids.ToolsOzoneQueueRouteReports),
92
+ },
93
+ )
94
+ return data
95
+ }
96
+
97
+ const clearQueues = async () => {
98
+ const db = network.ozone.ctx.db.db
99
+ await db.deleteFrom('report_queue').execute()
100
+ }
101
+
102
+ let spamAccountQueueId: number
103
+
104
+ beforeAll(async () => {
105
+ network = await TestNetwork.create({
106
+ dbPostgresSchema: 'ozone_report_routing',
107
+ })
108
+ agent = network.ozone.getAgent()
109
+ sc = network.getSeedClient()
110
+ modClient = network.ozone.getModClient()
111
+ await basicSeed(sc)
112
+ await network.processAll()
113
+ await clearQueues()
114
+ const [spamAccountQueue] = await Promise.all([
115
+ createQueue({
116
+ name: 'QR: Spam Accounts',
117
+ subjectTypes: ['account'],
118
+ reportTypes: [REASON_SPAM],
119
+ }),
120
+ ])
121
+ spamAccountQueueId = spamAccountQueue.id
122
+ })
123
+
124
+ afterAll(async () => {
125
+ await network.close()
126
+ })
127
+
128
+ it('routes unassigned AND unmatched reports to a newly created queue', async () => {
129
+ // Create unmatchable report (queueId will be set to -1 by daemon)
130
+ await reportAccount(sc.dids.bob, REASON_MISLEADING)
131
+ await network.ozone.daemon.ctx.queueRouter.routeReports()
132
+ const unmatchedReport = await getLatestReportForSubject(sc.dids.bob)
133
+ expect(unmatchedReport.queueId).toBe(-1)
134
+
135
+ // Create an unassigned (queueId IS NULL) report — this state no longer
136
+ // arises from the normal flow, since the daemon always sets a queueId,
137
+ // but the manual routeReports endpoint still handles legacy NULL rows.
138
+ // Simulate it via a direct DB insert of a report event + report row.
139
+ await reportAccount(sc.dids.carol, REASON_MISLEADING)
140
+ const carolEvent = await network.ozone.daemon.ctx.db.db
141
+ .selectFrom('moderation_event')
142
+ .select(['id', 'subjectDid', 'meta'])
143
+ .where('subjectDid', '=', sc.dids.carol)
144
+ .where('action', '=', 'tools.ozone.moderation.defs#modEventReport')
145
+ .orderBy('id', 'desc')
146
+ .limit(1)
147
+ .executeTakeFirstOrThrow()
148
+ const now = new Date().toISOString()
149
+ await network.ozone.daemon.ctx.db.db
150
+ .insertInto('report')
151
+ .values({
152
+ eventId: carolEvent.id,
153
+ queueId: null,
154
+ actionEventIds: null,
155
+ actionNote: null,
156
+ isMuted: false,
157
+ status: 'open',
158
+ reportType: REASON_MISLEADING,
159
+ did: carolEvent.subjectDid,
160
+ recordPath: '',
161
+ subjectMessageId: null,
162
+ createdAt: now,
163
+ updatedAt: now,
164
+ })
165
+ .execute()
166
+ const unassignedReport = await getLatestReportForSubject(sc.dids.carol)
167
+ expect(unassignedReport.queueId).toBeNull()
168
+
169
+ // Create a queue that now matches misleading account reports
170
+ const misleadingQueue = await createQueue({
171
+ name: 'QR: Misleading Accounts',
172
+ subjectTypes: ['account'],
173
+ reportTypes: [REASON_MISLEADING],
174
+ })
175
+
176
+ // Re-route both reports
177
+ const startId = Math.min(unmatchedReport.id, unassignedReport.id)
178
+ const endId = Math.max(unmatchedReport.id, unassignedReport.id)
179
+ const result = await routeReports(startId, endId)
180
+ expect(result.assigned).toBe(2)
181
+ expect(result.unmatched).toBe(0)
182
+
183
+ // Verify both reports match
184
+ const unmatchedAfter = await getLatestReportForSubject(sc.dids.bob)
185
+ expect(unmatchedAfter.queueId).toBe(misleadingQueue.id)
186
+ const unassignedAfter = await getLatestReportForSubject(sc.dids.carol)
187
+ expect(unassignedAfter.queueId).toBe(misleadingQueue.id)
188
+
189
+ // cleanup
190
+ await deleteQueue(misleadingQueue.id)
191
+ })
192
+
193
+ it('skips reports already assigned to a valid queue', async () => {
194
+ await reportAccount(sc.dids.bob, REASON_SPAM)
195
+ await network.ozone.daemon.ctx.queueRouter.routeReports()
196
+
197
+ const reportBob = await getLatestReportForSubject(sc.dids.bob)
198
+ expect(reportBob.queueId).toBe(spamAccountQueueId)
199
+
200
+ // Report is already assigned — endpoint only processes null/unmatched
201
+ const result = await routeReports(reportBob.id, reportBob.id)
202
+ expect(result.assigned).toBe(0)
203
+ expect(result.unmatched).toBe(0)
204
+ })
205
+
206
+ it('rejects when startReportId > endReportId', async () => {
207
+ await expect(routeReports(100, 50)).rejects.toThrow(
208
+ 'startReportId must be less than or equal to endReportId',
209
+ )
210
+ })
211
+
212
+ it('rejects when more than 5000 reports', async () => {
213
+ await expect(routeReports(100, 5101)).rejects.toThrow(
214
+ 'Cannot route more than 5000 reports at a time',
215
+ )
216
+ })
217
+
218
+ describe('get latest report', () => {
219
+ it('returns latest report', async () => {
220
+ // Create a new report so we know what the latest should be
221
+ await reportAccount(sc.dids.dan, REASON_SPAM)
222
+ // Report rows are inserted asynchronously by the queue-router daemon —
223
+ // drain it before querying.
224
+ await network.processAll()
225
+
226
+ const latest = await getLatest()
227
+ expect(latest).toBeDefined()
228
+ expect(latest.id).toBeGreaterThan(0)
229
+
230
+ // Verify it matches the DB
231
+ const dbReport = await getLatestReportForSubject(sc.dids.dan)
232
+ expect(latest.id).toBe(dbReport.id)
233
+ })
234
+
235
+ it('returns a newer report after creating one', async () => {
236
+ const first = await getLatest()
237
+
238
+ await reportAccount(sc.dids.alice, REASON_THREAT)
239
+ await network.processAll()
240
+
241
+ const second = await getLatest()
242
+ expect(second.id).toBeGreaterThan(first.id)
243
+ })
244
+ })
245
+ })