@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,155 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ // Report table - bridges report events to action events
5
+ await db.schema
6
+ .createTable('report')
7
+ .addColumn('id', 'serial', (col) => col.primaryKey())
8
+
9
+ // Core link to report event (display data still comes from moderation_event via JOIN)
10
+ .addColumn('eventId', 'integer', (col) => col.notNull().unique())
11
+
12
+ // Queue assignment (computed by background job in future iteration)
13
+ .addColumn('queueId', 'integer') // NULL = not yet assigned, -1 = no matching queue
14
+ .addColumn('queuedAt', 'varchar')
15
+
16
+ // Action linkage (sorted DESC, most recent first)
17
+ .addColumn('actionEventIds', 'jsonb') // Array of event IDs: [newest_id, ..., oldest_id]
18
+
19
+ // Reporter communication
20
+ .addColumn('actionNote', 'text')
21
+
22
+ // Whether the report is muted (reporter was muted or subject was muted at creation time)
23
+ .addColumn('isMuted', 'boolean', (col) => col.notNull().defaultTo(false))
24
+
25
+ // Status of the ticket/report
26
+ .addColumn('status', 'varchar', (col) => col.notNull().defaultTo('open')) // "open", "closed", "escalated"
27
+
28
+ // Denormalized from moderation_event for filtering without JOIN
29
+ .addColumn('reportType', 'varchar', (col) => col.notNull())
30
+ .addColumn('did', 'varchar', (col) => col.notNull())
31
+ .addColumn('recordPath', 'varchar', (col) => col.notNull().defaultTo('')) // '' = account/message, 'collection/rkey' = record
32
+ .addColumn('subjectMessageId', 'varchar') // NULL for non-message subjects
33
+
34
+ // Timestamps
35
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
36
+ .addColumn('updatedAt', 'varchar', (col) => col.notNull())
37
+ .addColumn('assignedTo', 'varchar') // DID of permanently assigned moderator
38
+ .addColumn('assignedAt', 'varchar') // When the permanent assignment was created
39
+ .addColumn('closedAt', 'varchar')
40
+ .execute()
41
+
42
+ // ─── Indexes ───
43
+ // Primary JOIN index - critical for every query that fetches display data from moderation_event
44
+ await db.schema
45
+ .createIndex('idx_report_event')
46
+ .on('report')
47
+ .column('eventId')
48
+ .execute()
49
+
50
+ // ─── Hot path: active reports (status != 'closed') ───
51
+ // Partial filter keeps these tight even as closed reports accumulate (~90% of table long-term).
52
+ // No isMuted in key (low cardinality, rarely filtered) and no INCLUDE columns
53
+ // (display data comes from moderation_event JOIN anyway).
54
+
55
+ // queryReports: queueId + status, paginated by createdAt
56
+ await sql`CREATE INDEX idx_report_active_queue_created ON report
57
+ ("queueId", status, "createdAt" DESC, id DESC)
58
+ WHERE status != 'closed'`.execute(db)
59
+
60
+ // queryReports: queueId + status, paginated by updatedAt
61
+ await sql`CREATE INDEX idx_report_active_queue_updated ON report
62
+ ("queueId", status, "updatedAt" DESC, id DESC)
63
+ WHERE status != 'closed'`.execute(db)
64
+
65
+ // queryReports: status only, paginated by createdAt
66
+ await sql`CREATE INDEX idx_report_active_status_created ON report
67
+ (status, "createdAt" DESC, id DESC)
68
+ WHERE status != 'closed'`.execute(db)
69
+
70
+ // queryReports: status only, paginated by updatedAt
71
+ await sql`CREATE INDEX idx_report_active_status_updated ON report
72
+ (status, "updatedAt" DESC, id DESC)
73
+ WHERE status != 'closed'`.execute(db)
74
+
75
+ // Active reports for a specific account (with optional queueId post-filter)
76
+ await sql`CREATE INDEX idx_report_active_did_created ON report
77
+ (did, status, "createdAt" DESC, id DESC)
78
+ WHERE status != 'closed'`.execute(db)
79
+
80
+ // A moderator's active workload (with optional queueId post-filter)
81
+ await sql`CREATE INDEX idx_report_active_assigned_created ON report
82
+ ("assignedTo", status, "createdAt" DESC, id DESC)
83
+ WHERE status != 'closed'`.execute(db)
84
+
85
+ // findReportsForSubject hot path — always filters NOT IN ('closed').
86
+ // did + recordPath identify the subject (account or specific record).
87
+ await sql`CREATE INDEX idx_report_subject_active ON report
88
+ (did, "recordPath", "createdAt" DESC, id DESC)
89
+ WHERE status != 'closed'`.execute(db)
90
+
91
+ // ─── Closed history (status = 'closed') ───
92
+ // Closed reports are terminal; only sort by createdAt.
93
+
94
+ // Closed pagination per queue
95
+ await sql`CREATE INDEX idx_report_closed_queue_created ON report
96
+ ("queueId", "createdAt" DESC, id DESC)
97
+ WHERE status = 'closed'`.execute(db)
98
+
99
+ // Closed history for an account
100
+ await sql`CREATE INDEX idx_report_closed_did_created ON report
101
+ (did, "createdAt" DESC, id DESC)
102
+ WHERE status = 'closed'`.execute(db)
103
+
104
+ // Moderator's closed-report history
105
+ await sql`CREATE INDEX idx_report_closed_assigned_created ON report
106
+ ("assignedTo", "createdAt" DESC, id DESC)
107
+ WHERE status = 'closed'`.execute(db)
108
+
109
+ // ─── Other access patterns ───
110
+
111
+ // Collection prefix queries: left-anchored LIKE 'app.bsky.feed.post/%' or 'app.bsky.%'
112
+ // text_pattern_ops enables btree-scannable prefix matching (supported since Postgres 8.x)
113
+ await sql`CREATE INDEX idx_report_record_path_pattern ON report
114
+ ("recordPath" text_pattern_ops)`.execute(db)
115
+
116
+ // Queue-router covering partial: index-only scan over unrouted, non-closed rows.
117
+ // Selects exactly the columns the router reads, eliminating heap fetches per batch.
118
+ await sql`CREATE INDEX idx_report_unassigned_id ON report (id)
119
+ INCLUDE (status, "reportType", "recordPath", "subjectMessageId")
120
+ WHERE "queueId" IS NULL AND status != 'closed'`.execute(db)
121
+
122
+ // Index for report statistics
123
+ await db.schema
124
+ .createIndex('idx_report_queue_created_id')
125
+ .on('report')
126
+ .columns(['queueId', 'createdAt', 'id'])
127
+ .execute()
128
+
129
+ // aggregate pending count query
130
+ await sql`CREATE INDEX idx_report_pending ON report (id) WHERE status != 'closed'`.execute(
131
+ db,
132
+ )
133
+ // per-queue pending count query
134
+ await sql`CREATE INDEX idx_report_queue_pending ON report ("queueId") WHERE status != 'closed'`.execute(
135
+ db,
136
+ )
137
+
138
+ // Queue-router event-source partial: scans new modEventReport rows by id
139
+ // for the daemon that inserts report rows from moderation_event.
140
+ await sql`CREATE INDEX moderation_event_report_id_idx
141
+ ON moderation_event (id)
142
+ WHERE action = 'tools.ozone.moderation.defs#modEventReport'`.execute(db)
143
+
144
+ // Stats windowed queries: aggregate/typeWindow filter by createdAt range and
145
+ // include both open and closed reports, so they cannot use the partial indexes
146
+ // above. (createdAt, reportType) ordering serves the date-range scan and
147
+ // satisfies GROUP BY reportType from the index without a heap fetch.
148
+ await sql`CREATE INDEX idx_report_created_type
149
+ ON report ("createdAt", "reportType")`.execute(db)
150
+ }
151
+
152
+ export async function down(db: Kysely<unknown>): Promise<void> {
153
+ await db.schema.dropIndex('moderation_event_report_id_idx').execute()
154
+ await db.schema.dropTable('report').execute()
155
+ }
@@ -0,0 +1,42 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('moderator_assignment')
6
+ .addColumn('id', 'serial', (col) => col.primaryKey())
7
+ // assignee
8
+ .addColumn('did', 'text', (col) => col.notNull())
9
+ // assigned over
10
+ .addColumn('reportId', 'integer')
11
+ .addColumn('queueId', 'integer')
12
+ // validity
13
+ .addColumn('startAt', 'varchar', (col) => col.notNull())
14
+ .addColumn('endAt', 'varchar')
15
+ .execute()
16
+
17
+ // Partial index for getting active queue assignments
18
+ await sql`CREATE INDEX idx_assignment_queue_active ON moderator_assignment ("endAt") WHERE "reportId" IS NULL`.execute(
19
+ db,
20
+ )
21
+
22
+ // Partial index for getting active report assignments for queue
23
+ await sql`CREATE INDEX idx_assignment_report_by_queue ON moderator_assignment ("queueId", "endAt") WHERE "reportId" IS NOT NULL`.execute(
24
+ db,
25
+ )
26
+
27
+ // Index for checking active report assignment
28
+ await db.schema
29
+ .createIndex('idx_assignment_report_active')
30
+ .on('moderator_assignment')
31
+ .columns(['reportId', 'endAt'])
32
+ .execute()
33
+
34
+ // Partial index for permanent report assignments by moderator
35
+ await sql`CREATE INDEX idx_assignment_permanent_did ON moderator_assignment (did, "reportId") WHERE "endAt" IS NULL`.execute(
36
+ db,
37
+ )
38
+ }
39
+
40
+ export async function down(db: Kysely<unknown>): Promise<void> {
41
+ await db.schema.dropTable('moderator_assignment').ifExists().execute()
42
+ }
@@ -0,0 +1,41 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ // Report queue configuration table
5
+ await db.schema
6
+ .createTable('report_queue')
7
+ .addColumn('id', 'serial', (col) => col.primaryKey())
8
+ .addColumn('name', 'varchar', (col) => col.notNull()) // uniqueness enforced via partial index below
9
+ .addColumn('description', 'varchar') // Optional description of the queue
10
+
11
+ // Queue filters (determine assignment)
12
+ .addColumn('subjectTypes', 'jsonb', (col) => col.notNull()) // Array: ['account'] or ['record'] or both
13
+ .addColumn('collection', 'varchar') // Collection name (e.g., 'app.bsky.feed.post'), NULL for accounts
14
+ .addColumn('reportTypes', 'jsonb', (col) => col.notNull()) // Array: report reason types
15
+
16
+ // Metadata
17
+ .addColumn('createdBy', 'varchar', (col) => col.notNull()) // DID of mod who created queue
18
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
19
+ .addColumn('updatedAt', 'varchar', (col) => col.notNull())
20
+ .addColumn('enabled', 'boolean', (col) => col.notNull().defaultTo(true))
21
+ .addColumn('deletedAt', 'varchar') // NULL = active, timestamp = soft-deleted
22
+ .execute()
23
+
24
+ // Partial unique index on name — only enforces uniqueness for non-deleted queues,
25
+ // so a soft-deleted queue's name can be reused by a new queue.
26
+ await sql`CREATE UNIQUE INDEX idx_queue_name_unique ON report_queue (name) WHERE "deletedAt" IS NULL`.execute(
27
+ db,
28
+ )
29
+
30
+ // Partial composite index covers all list queries on active queues: filter by enabled,
31
+ // sort by createdAt/id. The WHERE clause keeps the index small (deleted rows excluded).
32
+ await sql`CREATE INDEX idx_queue_active ON report_queue (enabled, "createdAt", id) WHERE "deletedAt" IS NULL`.execute(
33
+ db,
34
+ )
35
+ }
36
+
37
+ export async function down(db: Kysely<unknown>): Promise<void> {
38
+ await db.schema.dropIndex('idx_queue_name_unique').execute()
39
+ await db.schema.dropIndex('idx_queue_active').execute()
40
+ await db.schema.dropTable('report_queue').execute()
41
+ }
@@ -0,0 +1,48 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('report_activity')
6
+ .addColumn('id', 'serial', (col) => col.primaryKey())
7
+ .addColumn('reportId', 'integer', (col) => col.notNull())
8
+
9
+ // Discriminator: one of queueActivity | assignmentActivity | escalationActivity
10
+ // | closeActivity | internalNoteActivity | publicNoteActivity
11
+ .addColumn('activityType', 'varchar', (col) => col.notNull())
12
+
13
+ // The report's status at the moment this activity was recorded.
14
+ // Populated for state-change activity types; null for note-only activities.
15
+ .addColumn('previousStatus', 'varchar')
16
+
17
+ // Note fields — separated by audience
18
+ .addColumn('internalNote', 'text') // moderator-only
19
+ .addColumn('publicNote', 'text') // potentially reporter-visible
20
+
21
+ // Free-form JSON for loose activity-specific metadata (e.g. { assignmentId: 42 })
22
+ .addColumn('meta', 'jsonb')
23
+
24
+ // True when created by an automated process (e.g. queue router)
25
+ .addColumn('isAutomated', 'boolean', (col) =>
26
+ col.notNull().defaultTo(false),
27
+ )
28
+
29
+ .addColumn('createdBy', 'text', (col) => col.notNull())
30
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
31
+ .execute()
32
+
33
+ // Primary filter: all activities for a given report, sorted most-recent-first
34
+ await db.schema
35
+ .createIndex('idx_report_activity_report_created')
36
+ .on('report_activity')
37
+ .columns(['reportId', 'createdAt', 'id'])
38
+ .execute()
39
+
40
+ // Partial index to efficiently find automated activities per report
41
+ await sql`CREATE INDEX idx_report_activity_automated ON report_activity ("reportId", "createdAt", id) WHERE "isAutomated" = true`.execute(
42
+ db,
43
+ )
44
+ }
45
+
46
+ export async function down(db: Kysely<unknown>): Promise<void> {
47
+ await db.schema.dropTable('report_activity').execute()
48
+ }
@@ -0,0 +1,35 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('report_stat')
6
+
7
+ // metadata
8
+ .addColumn('id', 'serial', (col) => col.primaryKey())
9
+ .addColumn('computedAt', 'varchar', (col) => col.notNull())
10
+
11
+ // group
12
+ .addColumn('date', 'varchar', (col) => col.notNull()) // ISO date e.g. '2026-04-15'
13
+ .addColumn('queueId', 'integer') // NULL = aggregate across all queues
14
+ .addColumn('reportTypes', 'jsonb') // NULL = aggregate across all report types
15
+ .addColumn('moderatorDid', 'varchar') // NULL = aggregate across all moderators
16
+
17
+ // stats
18
+ .addColumn('inboundCount', 'integer')
19
+ .addColumn('pendingCount', 'integer')
20
+ .addColumn('actionedCount', 'integer')
21
+ .addColumn('escalatedCount', 'integer')
22
+ .addColumn('actionRate', 'integer')
23
+ .addColumn('avgHandlingTimeSec', 'integer')
24
+ .execute()
25
+
26
+ // Lookup by date + group dimensions (covers getLiveStats and getHistoricalStats queries)
27
+ await sql`CREATE INDEX idx_report_stat_lookup ON report_stat (
28
+ date, "queueId", "moderatorDid", "reportTypes", "computedAt"
29
+ )`.execute(db)
30
+ }
31
+
32
+ export async function down(db: Kysely<unknown>): Promise<void> {
33
+ await db.schema.dropIndex('idx_report_stat_lookup').ifExists().execute()
34
+ await db.schema.dropTable('report_stat').ifExists().execute()
35
+ }
@@ -0,0 +1,32 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('expiring_tag')
6
+ .addColumn('id', 'serial', (col) => col.primaryKey())
7
+ .addColumn('eventId', 'integer', (col) => col.notNull())
8
+ .addColumn('did', 'varchar', (col) => col.notNull())
9
+ .addColumn('recordPath', 'varchar', (col) => col.notNull())
10
+ .addColumn('tag', 'varchar', (col) => col.notNull())
11
+ .addColumn('expiresAt', 'varchar', (col) => col.notNull())
12
+ .addColumn('createdBy', 'varchar', (col) => col.notNull())
13
+ .execute()
14
+
15
+ // Daemon polls for expired tags
16
+ await db.schema
17
+ .createIndex('idx_expiring_tag_expires_at')
18
+ .on('expiring_tag')
19
+ .column('expiresAt')
20
+ .execute()
21
+
22
+ // Cleanup queries when tags are manually removed
23
+ await db.schema
24
+ .createIndex('idx_expiring_tag_did_record_path')
25
+ .on('expiring_tag')
26
+ .columns(['did', 'recordPath'])
27
+ .execute()
28
+ }
29
+
30
+ export async function down(db: Kysely<unknown>): Promise<void> {
31
+ await db.schema.dropTable('expiring_tag').execute()
32
+ }
@@ -34,3 +34,9 @@ export * as _20250813T000000000Z from './20250813T000000000Z-mod-tool-batch-id-i
34
34
  export * as _20250923T000000000Z from './20250923T000000000Z-scheduled-actions'
35
35
  export * as _20251008T120000000Z from './20251008T120000000Z-add-strike-system'
36
36
  export * as _20260210T154806448Z from './20260210T154806448Z-mod-event-created-by-indexes'
37
+ export * as _20260219T164523000Z from './20260219T164523000Z-create-report-table'
38
+ export * as _20260219T165302248Z from './20260219T165302248Z-moderator-assignment'
39
+ export * as _20260225T000000000Z from './20260225T000000000Z-add-report-queue-table'
40
+ export * as _20260313T000000000Z from './20260313T000000000Z-add-report-activity-table'
41
+ export * as _20260318T152058935Z from './20260318T152058935Z-add-report-stat'
42
+ export * as _20260428T000000000Z from './20260428T000000000Z-add-expiring-tag-table'
@@ -204,6 +204,91 @@ export class CreatedAtUriKeyset extends GenericKeyset<
204
204
  }
205
205
  }
206
206
 
207
+ type EndAtIdKeysetParam = {
208
+ id: number
209
+ endAt: string | null
210
+ }
211
+
212
+ // Special value used here to represent a "permanent" endAt (i.e. a record that should be sorted as if it has an endAt infinitely far in the future).
213
+ // Chosen to sort before all real timestamps in DESC order.
214
+ const PERMANENT_ENDSAT = '9999-12-31T23:59:59.999Z'
215
+
216
+ export class EndAtIdKeyset extends GenericKeyset<EndAtIdKeysetParam, Cursor> {
217
+ labelResult(result: EndAtIdKeysetParam): Cursor
218
+ labelResult(result: EndAtIdKeysetParam) {
219
+ return {
220
+ primary: result.endAt ?? PERMANENT_ENDSAT,
221
+ secondary: result.id.toString(),
222
+ }
223
+ }
224
+ labeledResultToCursor(labeled: Cursor) {
225
+ return {
226
+ primary: new Date(labeled.primary).getTime().toString(),
227
+ secondary: labeled.secondary,
228
+ }
229
+ }
230
+ cursorToLabeledResult(cursor: Cursor) {
231
+ const primaryDate = new Date(parseInt(cursor.primary, 10))
232
+ if (isNaN(primaryDate.getTime())) {
233
+ throw new InvalidRequestError('Malformed cursor')
234
+ }
235
+ return {
236
+ primary: primaryDate.toISOString(),
237
+ secondary: cursor.secondary,
238
+ }
239
+ }
240
+ // Override to substitute the PERMANENT_ENDSAT sentinel for NULL endAt rows
241
+ // so cursor pagination works across permanent (endAt IS NULL) assignments.
242
+ getSql(labeled?: Cursor, direction?: 'asc' | 'desc', tryIndex?: boolean) {
243
+ if (labeled === undefined) return
244
+ const primaryRef = sql`COALESCE(${this.primary}, ${PERMANENT_ENDSAT})`
245
+ if (tryIndex) {
246
+ if (direction === 'asc') {
247
+ return sql`((${primaryRef}, ${this.secondary}) > (${labeled.primary}, ${labeled.secondary}))`
248
+ } else {
249
+ return sql`((${primaryRef}, ${this.secondary}) < (${labeled.primary}, ${labeled.secondary}))`
250
+ }
251
+ } else {
252
+ if (direction === 'asc') {
253
+ return sql`((${primaryRef} > ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} > ${labeled.secondary}))`
254
+ } else {
255
+ return sql`((${primaryRef} < ${labeled.primary}) or (${primaryRef} = ${labeled.primary} and ${this.secondary} < ${labeled.secondary}))`
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ type ComputedAtIdKeysetParam = {
262
+ id: number
263
+ computedAt: string | Date
264
+ }
265
+
266
+ export class ComputedAtIdKeyset extends GenericKeyset<
267
+ ComputedAtIdKeysetParam,
268
+ Cursor
269
+ > {
270
+ labelResult(result: ComputedAtIdKeysetParam): Cursor
271
+ labelResult(result: ComputedAtIdKeysetParam) {
272
+ return { primary: result.computedAt, secondary: result.id.toString() }
273
+ }
274
+ labeledResultToCursor(labeled: Cursor) {
275
+ return {
276
+ primary: new Date(labeled.primary).getTime().toString(),
277
+ secondary: labeled.secondary,
278
+ }
279
+ }
280
+ cursorToLabeledResult(cursor: Cursor) {
281
+ const primaryDate = new Date(parseInt(cursor.primary, 10))
282
+ if (isNaN(primaryDate.getTime())) {
283
+ throw new InvalidRequestError('Malformed cursor')
284
+ }
285
+ return {
286
+ primary: primaryDate.toISOString(),
287
+ secondary: cursor.secondary,
288
+ }
289
+ }
290
+ }
291
+
207
292
  export const paginate = <
208
293
  QB extends AnyQb,
209
294
  K extends GenericKeyset<unknown, any>,
@@ -0,0 +1,17 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const tableName = 'expiring_tag'
4
+
5
+ export interface ExpiringTag {
6
+ id: Generated<number>
7
+ eventId: number
8
+ did: string
9
+ recordPath: string
10
+ tag: string
11
+ expiresAt: string
12
+ createdBy: string
13
+ }
14
+
15
+ export type PartialDB = {
16
+ [tableName]: ExpiringTag
17
+ }
@@ -5,16 +5,22 @@ import * as accountRecordStatusStats from './account_record_status_stats'
5
5
  import * as accountStrike from './account_strike'
6
6
  import * as blobPushEvent from './blob_push_event'
7
7
  import * as communicationTemplate from './communication_template'
8
+ import * as expiringTag from './expiring_tag'
8
9
  import * as firehoseCursor from './firehose_cursor'
9
10
  import * as jobCursor from './job_cursor'
10
11
  import * as label from './label'
11
12
  import * as member from './member'
12
13
  import * as modEvent from './moderation_event'
13
14
  import * as modSubjectStatus from './moderation_subject_status'
15
+ import * as moderatorAssignment from './moderator_assignment'
14
16
  import * as set from './ozone_set'
15
17
  import * as recordEventsStats from './record_events_stats'
16
18
  import * as recordPushEvent from './record_push_event'
17
19
  import * as repoPushEvent from './repo_push_event'
20
+ import * as report from './report'
21
+ import * as reportActivity from './report_activity'
22
+ import * as reportQueue from './report_queue'
23
+ import * as reportStat from './report_stat'
18
24
  import * as safelink from './safelink'
19
25
  import * as scheduledAction from './scheduled-action'
20
26
  import * as setting from './setting'
@@ -23,6 +29,9 @@ import * as verification from './verification'
23
29
 
24
30
  export type DatabaseSchemaType = modEvent.PartialDB &
25
31
  modSubjectStatus.PartialDB &
32
+ report.PartialDB &
33
+ reportActivity.PartialDB &
34
+ reportQueue.PartialDB &
26
35
  label.PartialDB &
27
36
  signingKey.PartialDB &
28
37
  repoPushEvent.PartialDB &
@@ -41,7 +50,10 @@ export type DatabaseSchemaType = modEvent.PartialDB &
41
50
  firehoseCursor.PartialDB &
42
51
  jobCursor.PartialDB &
43
52
  safelink.PartialDB &
44
- scheduledAction.PartialDB
53
+ scheduledAction.PartialDB &
54
+ moderatorAssignment.PartialDB &
55
+ reportStat.PartialDB &
56
+ expiringTag.PartialDB
45
57
 
46
58
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
47
59
 
@@ -0,0 +1,16 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const moderatorAssignmentTableName = 'moderator_assignment'
4
+
5
+ export interface ModeratorAssignment {
6
+ id: Generated<number>
7
+ did: string
8
+ reportId: number | null
9
+ queueId: number | null
10
+ startAt: string
11
+ endAt: string | null
12
+ }
13
+
14
+ export type PartialDB = {
15
+ [moderatorAssignmentTableName]: ModeratorAssignment
16
+ }
@@ -0,0 +1,27 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const reportTableName = 'report'
4
+
5
+ export interface Report {
6
+ id: Generated<number>
7
+ eventId: number // References moderation_event.id
8
+ queueId: number | null // NULL = not yet assigned, -1 = no matching queue
9
+ queuedAt: string | null
10
+ actionEventIds: number[] | null // Array of event IDs, sorted DESC [newest, ..., oldest]
11
+ actionNote: string | null
12
+ isMuted: boolean
13
+ status: string // 'open', 'closed', 'escalated', 'queued', 'assigned'
14
+ reportType: string // Denormalized from moderation_event.meta.reportType
15
+ did: string // Denormalized from moderation_event.subjectDid
16
+ recordPath: string // '' = account/message, 'collection/rkey' = record
17
+ subjectMessageId: string | null // Denormalized from moderation_event.subjectMessageId
18
+ createdAt: string
19
+ updatedAt: string
20
+ assignedTo: string | null // DID of permanently assigned moderator, null if unassigned
21
+ assignedAt: string | null // When the permanent assignment was created
22
+ closedAt: string | null
23
+ }
24
+
25
+ export type PartialDB = {
26
+ [reportTableName]: Report
27
+ }
@@ -0,0 +1,22 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const reportActivityTableName = 'report_activity'
4
+
5
+ export interface ReportActivity {
6
+ id: Generated<number>
7
+ reportId: number
8
+ // One of: queueActivity | assignmentActivity | escalationActivity
9
+ // | closeActivity | internalNoteActivity | publicNoteActivity
10
+ activityType: string
11
+ previousStatus: string | null // report status before this activity; null for note-only types
12
+ internalNote: string | null // moderator-only note
13
+ publicNote: string | null // potentially reporter-visible note
14
+ meta: unknown | null // loose activity-specific metadata (e.g. { assignmentId: 42 })
15
+ isAutomated: boolean
16
+ createdBy: string // DID of actor (or service DID for automated activities)
17
+ createdAt: string // ISO string
18
+ }
19
+
20
+ export type PartialDB = {
21
+ [reportActivityTableName]: ReportActivity
22
+ }
@@ -0,0 +1,21 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const reportQueueTableName = 'report_queue'
4
+
5
+ export interface ReportQueue {
6
+ id: Generated<number>
7
+ name: string
8
+ subjectTypes: string[] // ['account'] or ['record'] or ['account', 'record']
9
+ collection: string | null // Collection name (e.g., 'app.bsky.feed.post'), NULL for accounts
10
+ reportTypes: string[] // Array of report reason types (fully qualified NSIDs)
11
+ description: string | null // Optional description of the queue
12
+ createdBy: string // DID of moderator who created this queue
13
+ createdAt: string
14
+ updatedAt: string
15
+ enabled: boolean
16
+ deletedAt: string | null // NULL = active, timestamp = soft-deleted
17
+ }
18
+
19
+ export type PartialDB = {
20
+ [reportQueueTableName]: ReportQueue
21
+ }
@@ -0,0 +1,27 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const reportStatTableName = 'report_stat'
4
+
5
+ export interface ReportStat {
6
+ // metadata
7
+ id: Generated<number>
8
+ computedAt: string // When this snapshot was last computed
9
+
10
+ // group
11
+ date: string // ISO date e.g. '2026-04-15' — the calendar day this snapshot covers
12
+ queueId: number | null // NULL = aggregate across all queues
13
+ reportTypes: string[] | null // NULL = aggregate across all report types
14
+ moderatorDid: string | null // NULL = aggregate, non-null = per-moderator
15
+
16
+ // stats
17
+ inboundCount: number | null // Reports received during this calendar day
18
+ pendingCount: number | null // Reports with status != 'closed' at time of computation
19
+ actionedCount: number | null // Reports closed during this calendar day
20
+ escalatedCount: number | null // Reports escalated during this calendar day
21
+ actionRate: number | null // actionedCount / inboundCount * 100
22
+ avgHandlingTimeSec: number | null // Average time from creation/assignment to close, in seconds
23
+ }
24
+
25
+ export type PartialDB = {
26
+ [reportStatTableName]: ReportStat
27
+ }