@crowdsource.you/core 1.2.0

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 (339) hide show
  1. package/README.md +571 -0
  2. package/dist/cases.d.ts +66 -0
  3. package/dist/cases.d.ts.map +1 -0
  4. package/dist/cases.js +79 -0
  5. package/dist/cases.js.map +1 -0
  6. package/dist/client.d.ts +90 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +125 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/communityNotes.d.ts +49 -0
  11. package/dist/communityNotes.d.ts.map +1 -0
  12. package/dist/communityNotes.js +104 -0
  13. package/dist/communityNotes.js.map +1 -0
  14. package/dist/credential.d.ts +58 -0
  15. package/dist/credential.d.ts.map +1 -0
  16. package/dist/credential.js +71 -0
  17. package/dist/credential.js.map +1 -0
  18. package/dist/defaults.d.ts +61 -0
  19. package/dist/defaults.d.ts.map +1 -0
  20. package/dist/defaults.js +82 -0
  21. package/dist/defaults.js.map +1 -0
  22. package/dist/digest.d.ts +44 -0
  23. package/dist/digest.d.ts.map +1 -0
  24. package/dist/digest.js +79 -0
  25. package/dist/digest.js.map +1 -0
  26. package/dist/envelope.d.ts +143 -0
  27. package/dist/envelope.d.ts.map +1 -0
  28. package/dist/envelope.js +258 -0
  29. package/dist/envelope.js.map +1 -0
  30. package/dist/errors.d.ts +90 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +151 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/esm/cases.js +74 -0
  35. package/dist/esm/cases.js.map +1 -0
  36. package/dist/esm/client.js +121 -0
  37. package/dist/esm/client.js.map +1 -0
  38. package/dist/esm/communityNotes.js +100 -0
  39. package/dist/esm/communityNotes.js.map +1 -0
  40. package/dist/esm/credential.js +67 -0
  41. package/dist/esm/credential.js.map +1 -0
  42. package/dist/esm/defaults.js +77 -0
  43. package/dist/esm/defaults.js.map +1 -0
  44. package/dist/esm/digest.js +74 -0
  45. package/dist/esm/digest.js.map +1 -0
  46. package/dist/esm/envelope.js +252 -0
  47. package/dist/esm/envelope.js.map +1 -0
  48. package/dist/esm/errors.js +140 -0
  49. package/dist/esm/errors.js.map +1 -0
  50. package/dist/esm/express/index.js +29 -0
  51. package/dist/esm/express/index.js.map +1 -0
  52. package/dist/esm/express/middleware.js +293 -0
  53. package/dist/esm/express/middleware.js.map +1 -0
  54. package/dist/esm/express/store.js +69 -0
  55. package/dist/esm/express/store.js.map +1 -0
  56. package/dist/esm/express/verify.js +133 -0
  57. package/dist/esm/express/verify.js.map +1 -0
  58. package/dist/esm/index.js +57 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/outbox/client.js +43 -0
  61. package/dist/esm/outbox/client.js.map +1 -0
  62. package/dist/esm/outbox/decision.js +153 -0
  63. package/dist/esm/outbox/decision.js.map +1 -0
  64. package/dist/esm/outbox/delivery.js +129 -0
  65. package/dist/esm/outbox/delivery.js.map +1 -0
  66. package/dist/esm/outbox/enforcement/executor.js +219 -0
  67. package/dist/esm/outbox/enforcement/executor.js.map +1 -0
  68. package/dist/esm/outbox/enforcement/planner.js +326 -0
  69. package/dist/esm/outbox/enforcement/planner.js.map +1 -0
  70. package/dist/esm/outbox/evidence.js +173 -0
  71. package/dist/esm/outbox/evidence.js.map +1 -0
  72. package/dist/esm/outbox/inbound.js +87 -0
  73. package/dist/esm/outbox/inbound.js.map +1 -0
  74. package/dist/esm/outbox/index.js +71 -0
  75. package/dist/esm/outbox/index.js.map +1 -0
  76. package/dist/esm/outbox/intake.js +140 -0
  77. package/dist/esm/outbox/intake.js.map +1 -0
  78. package/dist/esm/outbox/integration.js +116 -0
  79. package/dist/esm/outbox/integration.js.map +1 -0
  80. package/dist/esm/outbox/outbox/dispatcher.js +114 -0
  81. package/dist/esm/outbox/outbox/dispatcher.js.map +1 -0
  82. package/dist/esm/outbox/outbox/service.js +271 -0
  83. package/dist/esm/outbox/outbox/service.js.map +1 -0
  84. package/dist/esm/outbox/postgres/index.js +41 -0
  85. package/dist/esm/outbox/postgres/index.js.map +1 -0
  86. package/dist/esm/outbox/postgres/registries.js +155 -0
  87. package/dist/esm/outbox/postgres/registries.js.map +1 -0
  88. package/dist/esm/outbox/postgres/reportColumns.js +191 -0
  89. package/dist/esm/outbox/postgres/reportColumns.js.map +1 -0
  90. package/dist/esm/outbox/postgres/store/enforcement.js +158 -0
  91. package/dist/esm/outbox/postgres/store/enforcement.js.map +1 -0
  92. package/dist/esm/outbox/postgres/store/events.js +101 -0
  93. package/dist/esm/outbox/postgres/store/events.js.map +1 -0
  94. package/dist/esm/outbox/postgres/store/index.js +59 -0
  95. package/dist/esm/outbox/postgres/store/index.js.map +1 -0
  96. package/dist/esm/outbox/postgres/store/outbox.js +207 -0
  97. package/dist/esm/outbox/postgres/store/outbox.js.map +1 -0
  98. package/dist/esm/outbox/postgres/store/reports.js +231 -0
  99. package/dist/esm/outbox/postgres/store/reports.js.map +1 -0
  100. package/dist/esm/outbox/postgres/store/transaction.js +29 -0
  101. package/dist/esm/outbox/postgres/store/transaction.js.map +1 -0
  102. package/dist/esm/outbox/postgres/tables.js +270 -0
  103. package/dist/esm/outbox/postgres/tables.js.map +1 -0
  104. package/dist/esm/outbox/reconciliation.js +157 -0
  105. package/dist/esm/outbox/reconciliation.js.map +1 -0
  106. package/dist/esm/outbox/reportStatus.js +22 -0
  107. package/dist/esm/outbox/reportStatus.js.map +1 -0
  108. package/dist/esm/outbox/retention.js +33 -0
  109. package/dist/esm/outbox/retention.js.map +1 -0
  110. package/dist/esm/outbox/store/types.js +30 -0
  111. package/dist/esm/outbox/store/types.js.map +1 -0
  112. package/dist/esm/outbox/types.js +23 -0
  113. package/dist/esm/outbox/types.js.map +1 -0
  114. package/dist/esm/outbox/webhook.js +145 -0
  115. package/dist/esm/outbox/webhook.js.map +1 -0
  116. package/dist/esm/package.json +1 -0
  117. package/dist/esm/reports.js +74 -0
  118. package/dist/esm/reports.js.map +1 -0
  119. package/dist/esm/testing/fixtures.js +132 -0
  120. package/dist/esm/testing/fixtures.js.map +1 -0
  121. package/dist/esm/testing/index.js +33 -0
  122. package/dist/esm/testing/index.js.map +1 -0
  123. package/dist/esm/testing/sandbox.js +337 -0
  124. package/dist/esm/testing/sandbox.js.map +1 -0
  125. package/dist/esm/testing/webhook-simulator.js +93 -0
  126. package/dist/esm/testing/webhook-simulator.js.map +1 -0
  127. package/dist/esm/transport.js +177 -0
  128. package/dist/esm/transport.js.map +1 -0
  129. package/dist/esm/webhookEndpoints.js +139 -0
  130. package/dist/esm/webhookEndpoints.js.map +1 -0
  131. package/dist/express/index.d.ts +32 -0
  132. package/dist/express/index.d.ts.map +1 -0
  133. package/dist/express/index.js +40 -0
  134. package/dist/express/index.js.map +1 -0
  135. package/dist/express/middleware.d.ts +97 -0
  136. package/dist/express/middleware.d.ts.map +1 -0
  137. package/dist/express/middleware.js +298 -0
  138. package/dist/express/middleware.js.map +1 -0
  139. package/dist/express/store.d.ts +45 -0
  140. package/dist/express/store.d.ts.map +1 -0
  141. package/dist/express/store.js +72 -0
  142. package/dist/express/store.js.map +1 -0
  143. package/dist/express/verify.d.ts +86 -0
  144. package/dist/express/verify.d.ts.map +1 -0
  145. package/dist/express/verify.js +138 -0
  146. package/dist/express/verify.js.map +1 -0
  147. package/dist/index.d.ts +67 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +89 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/outbox/client.d.ts +35 -0
  152. package/dist/outbox/client.d.ts.map +1 -0
  153. package/dist/outbox/client.js +46 -0
  154. package/dist/outbox/client.js.map +1 -0
  155. package/dist/outbox/decision.d.ts +36 -0
  156. package/dist/outbox/decision.d.ts.map +1 -0
  157. package/dist/outbox/decision.js +159 -0
  158. package/dist/outbox/decision.js.map +1 -0
  159. package/dist/outbox/delivery.d.ts +50 -0
  160. package/dist/outbox/delivery.d.ts.map +1 -0
  161. package/dist/outbox/delivery.js +135 -0
  162. package/dist/outbox/delivery.js.map +1 -0
  163. package/dist/outbox/enforcement/executor.d.ts +26 -0
  164. package/dist/outbox/enforcement/executor.d.ts.map +1 -0
  165. package/dist/outbox/enforcement/executor.js +222 -0
  166. package/dist/outbox/enforcement/executor.js.map +1 -0
  167. package/dist/outbox/enforcement/planner.d.ts +50 -0
  168. package/dist/outbox/enforcement/planner.d.ts.map +1 -0
  169. package/dist/outbox/enforcement/planner.js +333 -0
  170. package/dist/outbox/enforcement/planner.js.map +1 -0
  171. package/dist/outbox/evidence.d.ts +114 -0
  172. package/dist/outbox/evidence.d.ts.map +1 -0
  173. package/dist/outbox/evidence.js +181 -0
  174. package/dist/outbox/evidence.js.map +1 -0
  175. package/dist/outbox/inbound.d.ts +78 -0
  176. package/dist/outbox/inbound.d.ts.map +1 -0
  177. package/dist/outbox/inbound.js +91 -0
  178. package/dist/outbox/inbound.js.map +1 -0
  179. package/dist/outbox/index.d.ts +79 -0
  180. package/dist/outbox/index.d.ts.map +1 -0
  181. package/dist/outbox/index.js +98 -0
  182. package/dist/outbox/index.js.map +1 -0
  183. package/dist/outbox/intake.d.ts +62 -0
  184. package/dist/outbox/intake.d.ts.map +1 -0
  185. package/dist/outbox/intake.js +145 -0
  186. package/dist/outbox/intake.js.map +1 -0
  187. package/dist/outbox/integration.d.ts +62 -0
  188. package/dist/outbox/integration.d.ts.map +1 -0
  189. package/dist/outbox/integration.js +119 -0
  190. package/dist/outbox/integration.js.map +1 -0
  191. package/dist/outbox/outbox/dispatcher.d.ts +26 -0
  192. package/dist/outbox/outbox/dispatcher.d.ts.map +1 -0
  193. package/dist/outbox/outbox/dispatcher.js +119 -0
  194. package/dist/outbox/outbox/dispatcher.js.map +1 -0
  195. package/dist/outbox/outbox/service.d.ts +124 -0
  196. package/dist/outbox/outbox/service.d.ts.map +1 -0
  197. package/dist/outbox/outbox/service.js +279 -0
  198. package/dist/outbox/outbox/service.js.map +1 -0
  199. package/dist/outbox/postgres/index.d.ts +44 -0
  200. package/dist/outbox/postgres/index.d.ts.map +1 -0
  201. package/dist/outbox/postgres/index.js +51 -0
  202. package/dist/outbox/postgres/index.js.map +1 -0
  203. package/dist/outbox/postgres/registries.d.ts +69 -0
  204. package/dist/outbox/postgres/registries.d.ts.map +1 -0
  205. package/dist/outbox/postgres/registries.js +159 -0
  206. package/dist/outbox/postgres/registries.js.map +1 -0
  207. package/dist/outbox/postgres/reportColumns.d.ts +154 -0
  208. package/dist/outbox/postgres/reportColumns.d.ts.map +1 -0
  209. package/dist/outbox/postgres/reportColumns.js +195 -0
  210. package/dist/outbox/postgres/reportColumns.js.map +1 -0
  211. package/dist/outbox/postgres/store/enforcement.d.ts +33 -0
  212. package/dist/outbox/postgres/store/enforcement.d.ts.map +1 -0
  213. package/dist/outbox/postgres/store/enforcement.js +161 -0
  214. package/dist/outbox/postgres/store/enforcement.js.map +1 -0
  215. package/dist/outbox/postgres/store/events.d.ts +46 -0
  216. package/dist/outbox/postgres/store/events.d.ts.map +1 -0
  217. package/dist/outbox/postgres/store/events.js +104 -0
  218. package/dist/outbox/postgres/store/events.js.map +1 -0
  219. package/dist/outbox/postgres/store/index.d.ts +27 -0
  220. package/dist/outbox/postgres/store/index.d.ts.map +1 -0
  221. package/dist/outbox/postgres/store/index.js +62 -0
  222. package/dist/outbox/postgres/store/index.js.map +1 -0
  223. package/dist/outbox/postgres/store/outbox.d.ts +8 -0
  224. package/dist/outbox/postgres/store/outbox.d.ts.map +1 -0
  225. package/dist/outbox/postgres/store/outbox.js +210 -0
  226. package/dist/outbox/postgres/store/outbox.js.map +1 -0
  227. package/dist/outbox/postgres/store/reports.d.ts +69 -0
  228. package/dist/outbox/postgres/store/reports.d.ts.map +1 -0
  229. package/dist/outbox/postgres/store/reports.js +234 -0
  230. package/dist/outbox/postgres/store/reports.js.map +1 -0
  231. package/dist/outbox/postgres/store/transaction.d.ts +49 -0
  232. package/dist/outbox/postgres/store/transaction.d.ts.map +1 -0
  233. package/dist/outbox/postgres/store/transaction.js +32 -0
  234. package/dist/outbox/postgres/store/transaction.js.map +1 -0
  235. package/dist/outbox/postgres/tables.d.ts +737 -0
  236. package/dist/outbox/postgres/tables.d.ts.map +1 -0
  237. package/dist/outbox/postgres/tables.js +273 -0
  238. package/dist/outbox/postgres/tables.js.map +1 -0
  239. package/dist/outbox/reconciliation.d.ts +49 -0
  240. package/dist/outbox/reconciliation.d.ts.map +1 -0
  241. package/dist/outbox/reconciliation.js +162 -0
  242. package/dist/outbox/reconciliation.js.map +1 -0
  243. package/dist/outbox/reportStatus.d.ts +12 -0
  244. package/dist/outbox/reportStatus.d.ts.map +1 -0
  245. package/dist/outbox/reportStatus.js +25 -0
  246. package/dist/outbox/reportStatus.js.map +1 -0
  247. package/dist/outbox/retention.d.ts +33 -0
  248. package/dist/outbox/retention.d.ts.map +1 -0
  249. package/dist/outbox/retention.js +36 -0
  250. package/dist/outbox/retention.js.map +1 -0
  251. package/dist/outbox/store/types.d.ts +466 -0
  252. package/dist/outbox/store/types.d.ts.map +1 -0
  253. package/dist/outbox/store/types.js +31 -0
  254. package/dist/outbox/store/types.js.map +1 -0
  255. package/dist/outbox/types.d.ts +660 -0
  256. package/dist/outbox/types.d.ts.map +1 -0
  257. package/dist/outbox/types.js +24 -0
  258. package/dist/outbox/types.js.map +1 -0
  259. package/dist/outbox/webhook.d.ts +15 -0
  260. package/dist/outbox/webhook.d.ts.map +1 -0
  261. package/dist/outbox/webhook.js +148 -0
  262. package/dist/outbox/webhook.js.map +1 -0
  263. package/dist/reports.d.ts +73 -0
  264. package/dist/reports.d.ts.map +1 -0
  265. package/dist/reports.js +78 -0
  266. package/dist/reports.js.map +1 -0
  267. package/dist/testing/fixtures.d.ts +62 -0
  268. package/dist/testing/fixtures.d.ts.map +1 -0
  269. package/dist/testing/fixtures.js +137 -0
  270. package/dist/testing/fixtures.js.map +1 -0
  271. package/dist/testing/index.d.ts +36 -0
  272. package/dist/testing/index.d.ts.map +1 -0
  273. package/dist/testing/index.js +43 -0
  274. package/dist/testing/index.js.map +1 -0
  275. package/dist/testing/sandbox.d.ts +109 -0
  276. package/dist/testing/sandbox.d.ts.map +1 -0
  277. package/dist/testing/sandbox.js +342 -0
  278. package/dist/testing/sandbox.js.map +1 -0
  279. package/dist/testing/webhook-simulator.d.ts +78 -0
  280. package/dist/testing/webhook-simulator.d.ts.map +1 -0
  281. package/dist/testing/webhook-simulator.js +98 -0
  282. package/dist/testing/webhook-simulator.js.map +1 -0
  283. package/dist/transport.d.ts +59 -0
  284. package/dist/transport.d.ts.map +1 -0
  285. package/dist/transport.js +181 -0
  286. package/dist/transport.js.map +1 -0
  287. package/dist/webhookEndpoints.d.ts +147 -0
  288. package/dist/webhookEndpoints.d.ts.map +1 -0
  289. package/dist/webhookEndpoints.js +143 -0
  290. package/dist/webhookEndpoints.js.map +1 -0
  291. package/package.json +127 -0
  292. package/src/cases.ts +119 -0
  293. package/src/client.ts +202 -0
  294. package/src/communityNotes.ts +194 -0
  295. package/src/credential.ts +100 -0
  296. package/src/defaults.ts +87 -0
  297. package/src/digest.ts +91 -0
  298. package/src/envelope.ts +432 -0
  299. package/src/errors.ts +165 -0
  300. package/src/express/index.ts +43 -0
  301. package/src/express/middleware.ts +387 -0
  302. package/src/express/store.ts +85 -0
  303. package/src/express/verify.ts +166 -0
  304. package/src/index.ts +107 -0
  305. package/src/outbox/client.ts +75 -0
  306. package/src/outbox/decision.ts +209 -0
  307. package/src/outbox/delivery.ts +163 -0
  308. package/src/outbox/enforcement/executor.ts +288 -0
  309. package/src/outbox/enforcement/planner.ts +377 -0
  310. package/src/outbox/evidence.ts +231 -0
  311. package/src/outbox/inbound.ts +140 -0
  312. package/src/outbox/index.ts +160 -0
  313. package/src/outbox/intake.ts +175 -0
  314. package/src/outbox/integration.ts +205 -0
  315. package/src/outbox/outbox/dispatcher.ts +131 -0
  316. package/src/outbox/outbox/service.ts +444 -0
  317. package/src/outbox/postgres/index.ts +59 -0
  318. package/src/outbox/postgres/registries.ts +177 -0
  319. package/src/outbox/postgres/reportColumns.ts +281 -0
  320. package/src/outbox/postgres/store/enforcement.ts +186 -0
  321. package/src/outbox/postgres/store/events.ts +112 -0
  322. package/src/outbox/postgres/store/index.ts +72 -0
  323. package/src/outbox/postgres/store/outbox.ts +273 -0
  324. package/src/outbox/postgres/store/reports.ts +349 -0
  325. package/src/outbox/postgres/store/transaction.ts +64 -0
  326. package/src/outbox/postgres/tables.ts +339 -0
  327. package/src/outbox/reconciliation.ts +199 -0
  328. package/src/outbox/reportStatus.ts +24 -0
  329. package/src/outbox/retention.ts +34 -0
  330. package/src/outbox/store/types.ts +528 -0
  331. package/src/outbox/types.ts +753 -0
  332. package/src/outbox/webhook.ts +165 -0
  333. package/src/reports.ts +138 -0
  334. package/src/testing/fixtures.ts +183 -0
  335. package/src/testing/index.ts +57 -0
  336. package/src/testing/sandbox.ts +469 -0
  337. package/src/testing/webhook-simulator.ts +166 -0
  338. package/src/transport.ts +250 -0
  339. package/src/webhookEndpoints.ts +256 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../src/outbox/postgres/tables.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAGV,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAiErB;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IAAE,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiPlF;AAED,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moderationTables = moderationTables;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ const pg_core_1 = require("drizzle-orm/pg-core");
6
+ const db_1 = require("@oxy.so/db");
7
+ /**
8
+ * The three tables this package owns, in Postgres.
9
+ *
10
+ * These names and constraints are stable application data contracts. The
11
+ * reasoning for each field is repeated on the column because it is not carried
12
+ * by the DDL.
13
+ *
14
+ * Three things differ, and each is a property of Postgres rather than a choice:
15
+ *
16
+ * 1. **A closed value set is `text` plus a CHECK built from the same tuple that
17
+ * types it**, never a Postgres `enum` type. Adding an enforcement action
18
+ * becomes a migration where Mongo only needed a restart, which is the correct
19
+ * trade: a stored action outside the declared set is exactly what the
20
+ * constraint exists to refuse.
21
+ * 2. **The TTL indexes have no counterpart.** Postgres does not reap. Each
22
+ * `expires_at` keeps its index — the sweep's predicate needs it — and the
23
+ * reaping itself becomes an entry in the adopter's expiry registry; see
24
+ * `moderationExpirySweepTargets` in `registries.ts`. A table ported without
25
+ * one grows forever, with no error and no failing test.
26
+ * 3. **The enforcement unique index IS the primary key.** Mongo needed a
27
+ * surrogate `_id` plus a unique index on the idempotency triple; here the
28
+ * triple is the primary key, so there is no second object to keep in step.
29
+ *
30
+ * Every column is named EXPLICITLY. Drizzle can derive a snake_case name from
31
+ * the property, and its derivation mangles digit- and capital-adjacent names
32
+ * (`cacheS3Key` becomes `cache_s_3_key` — a working column with a name nobody
33
+ * chose). The SQL name has to match what the adopter's migration created, so it
34
+ * is written down here rather than computed.
35
+ */
36
+ /**
37
+ * `timestamptz()` from `@oxy.so/db` takes NO name argument, so these column names
38
+ * are DERIVED from the property by `DATABASE_CASING` rather than written out.
39
+ *
40
+ * That is the one exception to naming every column explicitly, and it is the
41
+ * package's decision rather than this one's: the builder's whole point is that
42
+ * "timestamps are `timestamptz`" is decided once, and re-implementing it here
43
+ * with `timestamp(name, { withTimezone: true })` would be a second copy of the
44
+ * rule that could drift. Every derived name in this file is a plain two-word
45
+ * property (`availableAt`, `expiresAt`, …) with no digit and no adjacent capital,
46
+ * so the derivation has nothing to mangle — and `postgresSchema.test.ts` asserts
47
+ * the EXACT column-name set of every table against the real catalogue, which is
48
+ * what would catch a derivation surprise rather than this comment.
49
+ */
50
+ /**
51
+ * The three table names are kept beside their definitions. The schema tests
52
+ * assert their exact values so a rename cannot strand deployed data.
53
+ */
54
+ const OUTBOX_TABLE = 'moderation_outbox';
55
+ const EVENT_TABLE = 'moderation_events';
56
+ const ENFORCEMENT_TABLE = 'moderation_enforcements';
57
+ /** The two kinds of work this package enqueues. Types the column and its CHECK. */
58
+ const OUTBOX_KINDS = ['report.submit', 'decision.apply'];
59
+ const OUTBOX_STATUSES = ['pending', 'processing', 'processed', 'dead_letter'];
60
+ const EVENT_STATES = ['claimed', 'queued', 'ignored'];
61
+ const ENFORCEMENT_MODES = [
62
+ 'observe',
63
+ 'manual',
64
+ 'automatic',
65
+ ];
66
+ /**
67
+ * Build the three tables.
68
+ *
69
+ * Call this ONCE and export the result. Two calls produce two distinct sets of
70
+ * drizzle table objects for the same SQL names, and drizzle-kit would then see
71
+ * the schema twice.
72
+ *
73
+ * `enforcementActions` is the adopter's own action union. It types the CHECK on
74
+ * `action` and `recorded_as`, which is why the tables are a function rather than
75
+ * module-level constants.
76
+ */
77
+ function moderationTables(options) {
78
+ const actions = (0, db_1.inList)(options.enforcementActions);
79
+ /**
80
+ * The durable record of moderation work that has to happen but has not
81
+ * happened yet.
82
+ *
83
+ * This table is what makes the intake guarantee true. A 201 from an
84
+ * application's report route means the report and its outbox row committed in
85
+ * ONE transaction — not that a call to CrowdSource succeeded. Delivery is a
86
+ * separate, retried step, and the user is never made to wait for a third party
87
+ * to be reachable.
88
+ *
89
+ * The same shape carries work in the other direction. A decision arriving over
90
+ * a webhook is answered 2xx as soon as it is recorded here, and applied
91
+ * afterwards. Nothing is enqueued that is not already written down: if the
92
+ * dispatcher, the process or the whole task disappears, every pending piece of
93
+ * moderation work is re-derivable by reading this table.
94
+ */
95
+ const outbox = (0, pg_core_1.pgTable)(OUTBOX_TABLE, {
96
+ /**
97
+ * The deterministic event id — `moderation:report.submit:<reportId>` or
98
+ * `moderation:decision.apply:<eventId>`.
99
+ *
100
+ * The primary key, and that is the mechanism rather than a convention: it
101
+ * is what makes a repeated enqueue a no-op instead of a second delivery.
102
+ * `text` rather than a generated id, because the WRITER computes it from
103
+ * the report or the inbound event.
104
+ */
105
+ id: (0, pg_core_1.text)('id').primaryKey(),
106
+ /**
107
+ * `$type` on the closed sets, so a row read back is typed as the union the
108
+ * CHECK already enforces rather than as a bare `string` the store would
109
+ * have to assert. TypeScript-only: it changes no DDL.
110
+ */
111
+ kind: (0, pg_core_1.text)('kind').$type().notNull(),
112
+ /**
113
+ * The payload, stored whole and opaque.
114
+ *
115
+ * `jsonb` rather than columns: a decision document is deliberately loose,
116
+ * and a projection would silently drop whatever a newer CrowdSource added.
117
+ * It is validated against the published contract when it is READ, so an
118
+ * event is never lost to a schema this deployment has not caught up with.
119
+ */
120
+ payload: (0, pg_core_1.jsonb)('payload').$type().notNull(),
121
+ status: (0, pg_core_1.text)('status').$type().notNull().default('pending'),
122
+ /** Counted by the claim; the retry ceiling reads it. */
123
+ attempts: (0, pg_core_1.integer)('attempts').notNull().default(0),
124
+ availableAt: (0, db_1.timestamptz)().notNull(),
125
+ leaseOwner: (0, pg_core_1.text)('lease_owner'),
126
+ leaseUntil: (0, db_1.timestamptz)(),
127
+ /**
128
+ * The last delivery error, bounded.
129
+ *
130
+ * `varchar(2000)` matches the Mongoose `maxlength`, and the application
131
+ * slices to the same 2000 before writing. Both halves are needed: a
132
+ * Mongoose validator THROWS on overflow and Postgres errors `22001`, so
133
+ * the slice is what makes the two dialects agree rather than one of them
134
+ * failing a delivery over an error message.
135
+ */
136
+ lastError: (0, pg_core_1.varchar)('last_error', { length: 2000 }),
137
+ processedAt: (0, db_1.timestamptz)(),
138
+ /** The retention deadline. Swept, not reaped — see the registry. */
139
+ expiresAt: (0, db_1.timestamptz)().notNull(),
140
+ createdAt: (0, db_1.createdAt)(),
141
+ updatedAt: (0, db_1.updatedAt)(),
142
+ }, (t) => [
143
+ (0, pg_core_1.check)('moderation_outbox_kind_check', (0, drizzle_orm_1.sql) `${t.kind} in (${drizzle_orm_1.sql.raw((0, db_1.inList)(OUTBOX_KINDS))})`),
144
+ (0, pg_core_1.check)('moderation_outbox_status_check', (0, drizzle_orm_1.sql) `${t.status} in (${drizzle_orm_1.sql.raw((0, db_1.inList)(OUTBOX_STATUSES))})`),
145
+ // Due work and expired claims are separate bounded scans.
146
+ (0, pg_core_1.index)('moderation_outbox_due_idx').on(t.status, t.availableAt, t.createdAt),
147
+ (0, pg_core_1.index)('moderation_outbox_lease_idx').on(t.status, t.leaseUntil, t.createdAt),
148
+ // The sweep's predicate is `expires_at <= now()`; without this it is a
149
+ // full scan on every run — the cost Mongo's TTL index hid.
150
+ (0, pg_core_1.index)('moderation_outbox_expires_at_idx').on(t.expiresAt),
151
+ ]);
152
+ /**
153
+ * Every webhook event CrowdSource has delivered to this deployment.
154
+ *
155
+ * Two jobs, and they are the same row on purpose.
156
+ *
157
+ * **Deduplication.** A receiver must record the processed event id. `id` IS
158
+ * the event id, so the primary key is the dedupe: a redelivery cannot insert a
159
+ * second row, and a claim therefore cannot succeed twice. Doing this in the
160
+ * database rather than in the SDK's default in-process store is not
161
+ * optimisation — an application running several tasks behind one load balancer
162
+ * would otherwise dedupe only whichever task happened to receive both copies.
163
+ *
164
+ * **Audit.** What arrived, when, and whether it was acted on. `payload` is the
165
+ * event's `data` exactly as delivered.
166
+ *
167
+ * The stored payload is a decision — an outcome, findings, policy versions, a
168
+ * jury summary. It is not the reported material and must not become a place
169
+ * where reported material is kept: nothing in this table is read into a log
170
+ * line.
171
+ */
172
+ const events = (0, pg_core_1.pgTable)(EVENT_TABLE, {
173
+ /** The webhook event id. The primary key IS the deduplication. */
174
+ id: (0, pg_core_1.text)('id').primaryKey(),
175
+ type: (0, pg_core_1.text)('type'),
176
+ caseId: (0, pg_core_1.text)('case_id'),
177
+ payload: (0, pg_core_1.jsonb)('payload'),
178
+ state: (0, pg_core_1.text)('state').$type().notNull().default('claimed'),
179
+ receivedAt: (0, db_1.timestamptz)().notNull(),
180
+ queuedAt: (0, db_1.timestamptz)(),
181
+ expiresAt: (0, db_1.timestamptz)().notNull(),
182
+ createdAt: (0, db_1.createdAt)(),
183
+ updatedAt: (0, db_1.updatedAt)(),
184
+ }, (t) => [
185
+ (0, pg_core_1.check)('moderation_events_state_check', (0, drizzle_orm_1.sql) `${t.state} in (${drizzle_orm_1.sql.raw((0, db_1.inList)(EVENT_STATES))})`),
186
+ (0, pg_core_1.index)('moderation_events_case_id_idx').on(t.caseId),
187
+ // Operational: what arrived recently, and what never got past `claimed`.
188
+ (0, pg_core_1.index)('moderation_events_state_received_at_idx').on(t.state, t.receivedAt),
189
+ (0, pg_core_1.index)('moderation_events_expires_at_idx').on(t.expiresAt),
190
+ ]);
191
+ /**
192
+ * What the application did about a decision — one row per action, and the
193
+ * reason it is impossible to do twice.
194
+ *
195
+ * The idempotency key is `decision_id + decision_revision + action`, and here
196
+ * it is the PRIMARY KEY. That is the whole mechanism: a redelivered webhook, a
197
+ * reclaimed outbox lease and a manual replay all try to insert the same row,
198
+ * and only the first can. Checking "have I done this?" with a read before a
199
+ * write would leave the window between them, which is precisely the window a
200
+ * redelivery arrives in.
201
+ *
202
+ * `decision_revision` is in the key for a reason of its own. A correction is a
203
+ * NEW revision that supersedes the old one, so the restore it asks for is a
204
+ * different action from the removal that came before and must be allowed to
205
+ * happen — while still being impossible to apply twice itself.
206
+ *
207
+ * `previous_state` is what makes reversibility real rather than aspirational.
208
+ * It is the application's own opaque record of what an effect replaced, so a
209
+ * restore returns an object to what it WAS and a correction does not silently
210
+ * lift a content warning moderation never set.
211
+ */
212
+ const enforcements = (0, pg_core_1.pgTable)(ENFORCEMENT_TABLE, {
213
+ decisionId: (0, pg_core_1.text)('decision_id').notNull(),
214
+ decisionRevision: (0, pg_core_1.integer)('decision_revision').notNull(),
215
+ action: (0, pg_core_1.text)('action').notNull(),
216
+ caseId: (0, pg_core_1.text)('case_id').notNull(),
217
+ /** The application's own noun. Never a CrowdSource resource id. */
218
+ subjectType: (0, pg_core_1.text)('subject_type').notNull(),
219
+ subjectId: (0, pg_core_1.text)('subject_id').notNull(),
220
+ outcome: (0, pg_core_1.text)('outcome').notNull(),
221
+ recommendedAction: (0, pg_core_1.text)('recommended_action'),
222
+ /**
223
+ * What the action amounted to, when the effect said it was not what was
224
+ * planned. The `action` column stays the PLANNED one — it is part of the
225
+ * primary key and it is what was decided.
226
+ */
227
+ recordedAs: (0, pg_core_1.text)('recorded_as'),
228
+ /** Why this action, in words an operator can read. Never reported material. */
229
+ reason: (0, pg_core_1.varchar)('reason', { length: 500 }).notNull(),
230
+ mode: (0, pg_core_1.text)('mode').$type().notNull(),
231
+ /**
232
+ * Whether the effect was actually carried out.
233
+ *
234
+ * `false` in `observe` mode for every action, which is the point of the
235
+ * mode: the plan is recorded and auditable, and nothing is removed.
236
+ */
237
+ applied: (0, pg_core_1.boolean)('applied').notNull().default(false),
238
+ appliedAt: (0, db_1.timestamptz)(),
239
+ /** Why an action was recorded but not carried out. */
240
+ skippedReason: (0, pg_core_1.varchar)('skipped_reason', { length: 300 }),
241
+ /**
242
+ * What to put back on a reversal. Only set for an action that changed
243
+ * state, so NULL here means "this action displaced nothing" — which is a
244
+ * different claim from "we did not look", and is why the reversal lookup
245
+ * filters on `applied` rather than reading whichever row is newest.
246
+ */
247
+ previousState: (0, pg_core_1.jsonb)('previous_state').$type(),
248
+ createdAt: (0, db_1.createdAt)(),
249
+ updatedAt: (0, db_1.updatedAt)(),
250
+ }, (t) => [
251
+ /**
252
+ * The idempotency key. Unique because it is the PRIMARY KEY, and
253
+ * load-bearing: without it a redelivered decision removes an object twice,
254
+ * and a redelivered correction restores it twice.
255
+ */
256
+ (0, pg_core_1.primaryKey)({
257
+ name: 'moderation_enforcements_pkey',
258
+ columns: [t.decisionId, t.decisionRevision, t.action],
259
+ }),
260
+ (0, pg_core_1.check)('moderation_enforcements_revision_check', (0, drizzle_orm_1.sql) `${t.decisionRevision} >= 1`),
261
+ (0, pg_core_1.check)('moderation_enforcements_action_check', (0, drizzle_orm_1.sql) `${t.action} in (${drizzle_orm_1.sql.raw(actions)})`),
262
+ (0, pg_core_1.check)('moderation_enforcements_recorded_as_check', (0, drizzle_orm_1.sql) `${t.recordedAs} is null or ${t.recordedAs} in (${drizzle_orm_1.sql.raw(actions)})`),
263
+ (0, pg_core_1.check)('moderation_enforcements_mode_check', (0, drizzle_orm_1.sql) `${t.mode} in (${drizzle_orm_1.sql.raw((0, db_1.inList)(ENFORCEMENT_MODES))})`),
264
+ (0, pg_core_1.index)('moderation_enforcements_case_id_idx').on(t.caseId),
265
+ // Operational: what has been done to this object, newest first.
266
+ (0, pg_core_1.index)('moderation_enforcements_subject_chrono_idx').on(t.subjectType, t.subjectId, t.createdAt.desc()),
267
+ // The reversal lookup: the most recent APPLIED row for one action set on
268
+ // one object. `applied` is in the index because it is in every such query.
269
+ (0, pg_core_1.index)('moderation_enforcements_subject_action_applied_idx').on(t.subjectType, t.subjectId, t.action, t.applied, t.createdAt.desc()),
270
+ ]);
271
+ return { outbox, events, enforcements };
272
+ }
273
+ //# sourceMappingURL=tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.js","sourceRoot":"","sources":["../../../src/outbox/postgres/tables.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { type OutboxService } from './outbox/service.js';
2
+ import type { ModerationReportStore, ModerationTransactionRunner } from './store/types.js';
3
+ import type { ModerationLogger, ModerationReconciliationResult, ModerationReportFields } from './types.js';
4
+ export type ReconcileModerationReports = (options?: {
5
+ batchSize?: number;
6
+ now?: Date;
7
+ }) => Promise<ModerationReconciliationResult>;
8
+ /**
9
+ * One sweep. Bounded, idempotent, safe to run on every task.
10
+ *
11
+ * Reads `queued` and `delivery_failed` reports oldest-first — the index on
12
+ * `{ localStatus, createdAt }` exists for this query — and re-enqueues only
13
+ * those with no outbox event at all.
14
+ */
15
+ export declare function createReconciliation<TReport extends ModerationReportFields, TTx>(input: {
16
+ transaction: ModerationTransactionRunner<TTx>;
17
+ reports: ModerationReportStore<TReport, TTx>;
18
+ outbox: OutboxService<TTx>;
19
+ logger: ModerationLogger;
20
+ staleSubmittedHours?: number;
21
+ }): ReconcileModerationReports;
22
+ /**
23
+ * The reconciliation sweep, on a timer.
24
+ *
25
+ * Start it from a LEADER-elected scheduler only. Unlike the outbox dispatcher,
26
+ * whose per-event lease makes it safe on every task, this sweep scans and counts
27
+ * across the whole collection; running it on every task would multiply that work
28
+ * by the task count for no benefit.
29
+ *
30
+ * Long interval on purpose. It is a safety net for divergences the durable path
31
+ * is supposed to prevent, not a delivery mechanism — a report that needs this
32
+ * sweep to be delivered is already a report something went wrong with.
33
+ */
34
+ export declare class ModerationReconciliationJob {
35
+ private readonly options;
36
+ private timer;
37
+ private inFlight;
38
+ private running;
39
+ constructor(options: {
40
+ reconcile: ReconcileModerationReports;
41
+ logger: ModerationLogger;
42
+ enabled: boolean;
43
+ intervalMs?: number;
44
+ });
45
+ start(): void;
46
+ stop(): void;
47
+ private tick;
48
+ }
49
+ //# sourceMappingURL=reconciliation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciliation.d.ts","sourceRoot":"","sources":["../../src/outbox/reconciliation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EACV,qBAAqB,EACrB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,gBAAgB,EAChB,8BAA8B,EAC9B,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAuCpB,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,CAAC,EAAE;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KAAK,OAAO,CAAC,8BAA8B,CAAC,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,sBAAsB,EAAE,GAAG,EAAE,KAAK,EAAE;IACvF,WAAW,EAAE,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,EAAE,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7C,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,0BAA0B,CAoE7B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,2BAA2B;IAMpC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,OAAO,CAAS;gBAGL,OAAO,EAAE;QACxB,SAAS,EAAE,0BAA0B,CAAC;QACtC,MAAM,EAAE,gBAAgB,CAAC;QACzB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;IAGH,KAAK,IAAI,IAAI;IAYb,IAAI,IAAI,IAAI;YAQE,IAAI;CAgBnB"}
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModerationReconciliationJob = void 0;
4
+ exports.createReconciliation = createReconciliation;
5
+ const service_js_1 = require("./outbox/service.js");
6
+ /**
7
+ * Finding the reports the pipeline lost sight of.
8
+ *
9
+ * The outbox makes delivery durable, not infallible. Four divergences are
10
+ * possible and none of them announces itself:
11
+ *
12
+ * 1. A report that should have a delivery event and does not — one whose event
13
+ * was dropped by a retention TTL while the deployment was down for longer
14
+ * than the retention window, or one whose enqueue was lost to an operator's
15
+ * intervention.
16
+ * 2. A report stuck at `delivery_failed` whose outbox event has been
17
+ * dead-lettered. That one is not re-queued: something about the payload has
18
+ * to change first, and re-queueing it would spin. It is COUNTED, because the
19
+ * count is the alert.
20
+ * 3. A report `submitted` long ago whose case never came back. Nothing to do
21
+ * locally — the decision is CrowdSource's to publish — but a rising count is
22
+ * how a broken webhook endpoint or a rotated secret becomes visible before
23
+ * somebody notices a quiet moderation queue.
24
+ * 4. A report that was never going anywhere: `received`, because its type has no
25
+ * subject provider, or because it predates the integration. This is the one
26
+ * divergence that is not a fault, so it is counted and NEVER re-queued —
27
+ * re-deriving a delivery event for a report nothing can describe would send
28
+ * it straight to the dead-letter queue and turn a deliberate local-only
29
+ * report into a recurring alert. Counting it is still worth doing: it is the
30
+ * only number that makes "reports stored here that no jury will ever see"
31
+ * visible at all, and that is precisely the cost of accepting them.
32
+ *
33
+ * A sweep only ever RE-DERIVES work from the reports; it never invents any.
34
+ * Everything it enqueues uses the same deterministic event id as the original,
35
+ * so a report that did have an event is untouched rather than delivered twice.
36
+ */
37
+ const DEFAULT_BATCH_SIZE = 200;
38
+ const MAX_BATCH_SIZE = 1000;
39
+ const DEFAULT_STALE_SUBMITTED_HOURS = 72;
40
+ const DEFAULT_INTERVAL_MS = 15 * 60 * 1000;
41
+ /**
42
+ * One sweep. Bounded, idempotent, safe to run on every task.
43
+ *
44
+ * Reads `queued` and `delivery_failed` reports oldest-first — the index on
45
+ * `{ localStatus, createdAt }` exists for this query — and re-enqueues only
46
+ * those with no outbox event at all.
47
+ */
48
+ function createReconciliation(input) {
49
+ const staleHours = input.staleSubmittedHours ?? DEFAULT_STALE_SUBMITTED_HOURS;
50
+ return async (options = {}) => {
51
+ const batchSize = Math.min(Math.max(1, options.batchSize ?? DEFAULT_BATCH_SIZE), MAX_BATCH_SIZE);
52
+ const now = options.now ?? new Date();
53
+ const result = {
54
+ requeued: 0,
55
+ deadLettered: 0,
56
+ awaitingDecision: 0,
57
+ localOnly: 0,
58
+ };
59
+ /**
60
+ * `queued` and `delivery_failed` only — the store's own query says so, and
61
+ * the omission of `received` is the safety property rather than an
62
+ * oversight: those reports have no subject provider, so an event re-derived
63
+ * for one would fail as `ModerationSubjectUnsupportedError` on its first
64
+ * attempt and dead-letter. They are counted below instead.
65
+ */
66
+ const pending = await input.reports.findPendingOldestFirst(batchSize);
67
+ for (const reportId of pending) {
68
+ const eventId = (0, service_js_1.reportSubmitEventId)(reportId);
69
+ const status = await input.outbox.statusOf(eventId);
70
+ if (status === 'dead_letter') {
71
+ result.deadLettered += 1;
72
+ continue;
73
+ }
74
+ if (status !== null)
75
+ continue;
76
+ /**
77
+ * A transaction for a single upsert, for consistency with intake rather
78
+ * than for atomicity: the enqueue requires a transaction precisely so that
79
+ * no path in this package can write an outbox event outside one. A
80
+ * signature that made it optional would be the crack the next caller slips
81
+ * through, and the runner is what makes taking one here cost a line.
82
+ */
83
+ await input.transaction.run(async (tx) => {
84
+ await input.outbox.enqueue({ eventId, kind: 'report.submit', payload: { reportId } }, tx);
85
+ });
86
+ result.requeued += 1;
87
+ }
88
+ result.awaitingDecision = await input.reports.countAwaitingDecision(new Date(now.getTime() - staleHours * 60 * 60 * 1000));
89
+ result.localOnly = await input.reports.countLocalOnly();
90
+ if (result.requeued > 0 || result.deadLettered > 0) {
91
+ input.logger.warn('[CrowdSource] reconciliation found divergence', { ...result });
92
+ }
93
+ else if (result.awaitingDecision > 0 || result.localOnly > 0) {
94
+ input.logger.info('[CrowdSource] reports with no decision to apply', {
95
+ awaitingDecision: result.awaitingDecision,
96
+ olderThanHours: staleHours,
97
+ localOnly: result.localOnly,
98
+ });
99
+ }
100
+ return result;
101
+ };
102
+ }
103
+ /**
104
+ * The reconciliation sweep, on a timer.
105
+ *
106
+ * Start it from a LEADER-elected scheduler only. Unlike the outbox dispatcher,
107
+ * whose per-event lease makes it safe on every task, this sweep scans and counts
108
+ * across the whole collection; running it on every task would multiply that work
109
+ * by the task count for no benefit.
110
+ *
111
+ * Long interval on purpose. It is a safety net for divergences the durable path
112
+ * is supposed to prevent, not a delivery mechanism — a report that needs this
113
+ * sweep to be delivered is already a report something went wrong with.
114
+ */
115
+ class ModerationReconciliationJob {
116
+ constructor(options) {
117
+ this.options = options;
118
+ this.timer = null;
119
+ this.inFlight = null;
120
+ this.running = false;
121
+ }
122
+ start() {
123
+ if (this.running)
124
+ return;
125
+ if (!this.options.enabled)
126
+ return;
127
+ const intervalMs = this.options.intervalMs ?? DEFAULT_INTERVAL_MS;
128
+ this.running = true;
129
+ this.timer = setInterval(() => {
130
+ void this.tick();
131
+ }, intervalMs);
132
+ this.timer.unref?.();
133
+ this.options.logger.info('[CrowdSource] reconciliation job started', { intervalMs });
134
+ }
135
+ stop() {
136
+ this.running = false;
137
+ if (this.timer) {
138
+ clearInterval(this.timer);
139
+ this.timer = null;
140
+ }
141
+ }
142
+ async tick() {
143
+ if (!this.running || this.inFlight)
144
+ return;
145
+ const work = this.options
146
+ .reconcile()
147
+ .then(() => undefined)
148
+ .catch((error) => {
149
+ this.options.logger.error('[CrowdSource] reconciliation sweep failed', {
150
+ error: error instanceof Error ? error.message : String(error),
151
+ });
152
+ })
153
+ .finally(() => {
154
+ if (this.inFlight === work)
155
+ this.inFlight = null;
156
+ });
157
+ this.inFlight = work;
158
+ await work;
159
+ }
160
+ }
161
+ exports.ModerationReconciliationJob = ModerationReconciliationJob;
162
+ //# sourceMappingURL=reconciliation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconciliation.js","sourceRoot":"","sources":["../../src/outbox/reconciliation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { ModerationLocalStatus } from './types.js';
2
+ /**
3
+ * Where a decision leaves the report.
4
+ *
5
+ * Takes the decision status as a plain STRING on purpose. This is reached with a
6
+ * value that came off the wire, and a newer CrowdSource introducing a status
7
+ * this version has never seen must be handled rather than throw. An unrecognised
8
+ * status leaves the report open, which is the only safe reading: closing a
9
+ * report on a status nobody has defined would retire a case that may still move.
10
+ */
11
+ export declare function localStatusForDecision(decisionStatus: string): ModerationLocalStatus;
12
+ //# sourceMappingURL=reportStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportStatus.d.ts","sourceRoot":"","sources":["../../src/outbox/reportStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAYxD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAEpF"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.localStatusForDecision = localStatusForDecision;
4
+ /**
5
+ * Decision statuses that end the application's side of the case.
6
+ *
7
+ * A `provisional` decision leaves the report at `submitted`: a later revision
8
+ * may supersede it, and a report the application had already closed would have
9
+ * to be reopened. `superseded` is not here either — a superseded revision is not
10
+ * the current answer and must never be the one that closes the report.
11
+ */
12
+ const TERMINAL_DECISION_STATUSES = new Set(['final', 'corrected']);
13
+ /**
14
+ * Where a decision leaves the report.
15
+ *
16
+ * Takes the decision status as a plain STRING on purpose. This is reached with a
17
+ * value that came off the wire, and a newer CrowdSource introducing a status
18
+ * this version has never seen must be handled rather than throw. An unrecognised
19
+ * status leaves the report open, which is the only safe reading: closing a
20
+ * report on a status nobody has defined would retire a case that may still move.
21
+ */
22
+ function localStatusForDecision(decisionStatus) {
23
+ return TERMINAL_DECISION_STATUSES.has(decisionStatus) ? 'closed' : 'submitted';
24
+ }
25
+ //# sourceMappingURL=reportStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reportStatus.js","sourceRoot":"","sources":["../../src/outbox/reportStatus.ts"],"names":[],"mappings":";;AAqBA,wDAEC;AArBD;;;;;;;GAOG;AACH,MAAM,0BAA0B,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;AAExF;;;;;;;;GAQG;AACH,SAAgB,sBAAsB,CAAC,cAAsB;IAC3D,OAAO,0BAA0B,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;AACjF,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * How long this package keeps the two things it writes down for its own sake.
3
+ *
4
+ * Policy, not storage, which is why it lives in the shared half: a deployment
5
+ * whose outbox rows expired after an hour and whose event rows expired after
6
+ * ninety days would have two different answers to "what happened to that
7
+ * report", and neither backend would consider that an error. Both stores are
8
+ * handed the computed `expiresAt` rather than the window, so the window has one
9
+ * definition.
10
+ *
11
+ * How the deadline is ENFORCED is a backend's business — a Mongo TTL index on
12
+ * `expiresAt`, an expiry sweep on Postgres — but a table registered with
13
+ * neither grows forever with no error and no failing test.
14
+ */
15
+ /**
16
+ * The outbox.
17
+ *
18
+ * A retention ceiling, so a stalled dispatcher cannot turn the outbox into an
19
+ * unbounded table. Long, because a moderation case can legitimately sit open for
20
+ * weeks and a `dead_letter` event is evidence somebody still has to look at.
21
+ * Operational alerts must fire long before this deadline.
22
+ */
23
+ export declare const MODERATION_OUTBOX_RETENTION_SECONDS: number;
24
+ /**
25
+ * Inbound webhook events.
26
+ *
27
+ * CrowdSource's retry schedule ends at 24 hours, so a dedupe row only has to
28
+ * outlive that. It is kept far longer because the row is also the audit trail of
29
+ * what a third party told this deployment to do, and an enforcement question
30
+ * asked weeks later is answered from here.
31
+ */
32
+ export declare const MODERATION_EVENT_RETENTION_SECONDS: number;
33
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/outbox/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,mCAAmC,QAAoB,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,kCAAkC,QAAoB,CAAC"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * How long this package keeps the two things it writes down for its own sake.
4
+ *
5
+ * Policy, not storage, which is why it lives in the shared half: a deployment
6
+ * whose outbox rows expired after an hour and whose event rows expired after
7
+ * ninety days would have two different answers to "what happened to that
8
+ * report", and neither backend would consider that an error. Both stores are
9
+ * handed the computed `expiresAt` rather than the window, so the window has one
10
+ * definition.
11
+ *
12
+ * How the deadline is ENFORCED is a backend's business — a Mongo TTL index on
13
+ * `expiresAt`, an expiry sweep on Postgres — but a table registered with
14
+ * neither grows forever with no error and no failing test.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.MODERATION_EVENT_RETENTION_SECONDS = exports.MODERATION_OUTBOX_RETENTION_SECONDS = void 0;
18
+ /**
19
+ * The outbox.
20
+ *
21
+ * A retention ceiling, so a stalled dispatcher cannot turn the outbox into an
22
+ * unbounded table. Long, because a moderation case can legitimately sit open for
23
+ * weeks and a `dead_letter` event is evidence somebody still has to look at.
24
+ * Operational alerts must fire long before this deadline.
25
+ */
26
+ exports.MODERATION_OUTBOX_RETENTION_SECONDS = 90 * 24 * 60 * 60;
27
+ /**
28
+ * Inbound webhook events.
29
+ *
30
+ * CrowdSource's retry schedule ends at 24 hours, so a dedupe row only has to
31
+ * outlive that. It is kept far longer because the row is also the audit trail of
32
+ * what a third party told this deployment to do, and an enforcement question
33
+ * asked weeks later is answered from here.
34
+ */
35
+ exports.MODERATION_EVENT_RETENTION_SECONDS = 90 * 24 * 60 * 60;
36
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/outbox/retention.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAEH;;;;;;;GAOG;AACU,QAAA,mCAAmC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAErE;;;;;;;GAOG;AACU,QAAA,kCAAkC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC"}