@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,279 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModerationOutboxTransactionError = void 0;
4
+ exports.reportSubmitEventId = reportSubmitEventId;
5
+ exports.decisionApplyEventId = decisionApplyEventId;
6
+ exports.isRetryableDeliveryError = isRetryableDeliveryError;
7
+ exports.createOutboxService = createOutboxService;
8
+ const crypto_1 = require("crypto");
9
+ const retention_js_1 = require("../retention.js");
10
+ /**
11
+ * Claiming, completing and failing moderation outbox events.
12
+ *
13
+ * The contract is at-least-once: handlers MUST make every downstream effect
14
+ * idempotent using the event `id`, because an expired lease is reclaimable and
15
+ * a worker can die mid-delivery.
16
+ *
17
+ * What differs from an ordinary work queue is where retrying stops. A delivery
18
+ * failure the SDK marks as not retryable is a defect in the payload, not a blip
19
+ * — see {@link OutboxService.fail}.
20
+ *
21
+ * Every decision in this file is a POLICY decision — the retry ceiling, the
22
+ * backoff curve, the lease-length floor, what counts as retryable, how long a
23
+ * row is kept. The queries that carry them out belong to a
24
+ * {@link ModerationOutboxStore}, outside the PostgreSQL query implementation.
25
+ */
26
+ const DEFAULT_LEASE_MS = 60000;
27
+ const DEFAULT_BATCH_SIZE = 50;
28
+ const MAX_BATCH_SIZE = 500;
29
+ const MAX_BACKOFF_MS = 6 * 60 * 60 * 1000;
30
+ const MIN_LEASE_RENEW_INTERVAL_MS = 250;
31
+ /**
32
+ * Attempts after which a retryable failure is treated as permanent.
33
+ *
34
+ * Generous: a retryable failure means CrowdSource might still accept this exact
35
+ * payload, and with `MAX_BACKOFF_MS` capped at six hours this is several days of
36
+ * trying. A report that has not landed by then needs a human, not another
37
+ * attempt.
38
+ */
39
+ const MAX_RETRYABLE_ATTEMPTS = 25;
40
+ /**
41
+ * The event id for delivering a report.
42
+ *
43
+ * Derived from the report, not from the request: a transaction retry or two
44
+ * concurrent duplicate submissions upsert the SAME event rather than queueing
45
+ * two deliveries. There is exactly one delivery event per report for the life of
46
+ * the report, which is also what makes the CrowdSource-side idempotency key
47
+ * stable.
48
+ */
49
+ function reportSubmitEventId(reportId) {
50
+ return `moderation:report.submit:${reportId}`;
51
+ }
52
+ /**
53
+ * The event id for applying an inbound decision.
54
+ *
55
+ * The webhook event id is the key, so a redelivery of the same event can never
56
+ * queue the work twice even if the dedupe claim were somehow released.
57
+ */
58
+ function decisionApplyEventId(eventId) {
59
+ return `moderation:decision.apply:${eventId}`;
60
+ }
61
+ /**
62
+ * Raised when an outbox event is written outside a transaction.
63
+ *
64
+ * Never expected at runtime. It exists so the invariant is ENFORCED rather than
65
+ * reviewed. It is thrown by the STORE — only a backend knows what an open
66
+ * transaction looks like — and it lives here so that every backend throws the
67
+ * same error for the same mistake. See {@link ModerationOutboxStore.enqueue}.
68
+ */
69
+ class ModerationOutboxTransactionError extends Error {
70
+ constructor(eventId) {
71
+ super(`Refusing to enqueue moderation outbox event '${eventId}' outside a transaction: ` +
72
+ 'the domain write and this row must commit together, or a report is answered 201 ' +
73
+ 'and never delivered.');
74
+ this.name = 'ModerationOutboxTransactionError';
75
+ }
76
+ }
77
+ exports.ModerationOutboxTransactionError = ModerationOutboxTransactionError;
78
+ /**
79
+ * A failure that says whether trying the same payload again could ever work.
80
+ *
81
+ * Every error `@crowdsource.you/core` throws carries `retryable`, which is the only
82
+ * thing a delivery worker needs from it. Anything else — a bug in this code, a
83
+ * Mongo error — is treated as retryable, because assuming a defect is permanent
84
+ * is how a recoverable outage becomes lost moderation work.
85
+ */
86
+ function isRetryableDeliveryError(error) {
87
+ if (typeof error === 'object' && error !== null && 'retryable' in error) {
88
+ const retryable = error.retryable;
89
+ if (typeof retryable === 'boolean')
90
+ return retryable;
91
+ }
92
+ return true;
93
+ }
94
+ function nextAttemptAt(attempts, now) {
95
+ const exponent = Math.max(0, Math.min(attempts - 1, 20));
96
+ return new Date(now.getTime() + Math.min(1000 * 2 ** exponent, MAX_BACKOFF_MS));
97
+ }
98
+ function createOutboxService(input) {
99
+ const { store, logger } = input;
100
+ const renew = async (eventId, leaseOwner, leaseMs, now = new Date()) => await store.renew({
101
+ eventId,
102
+ leaseOwner,
103
+ // The floor is policy, not storage: a lease shorter than a second is
104
+ // reclaimable before the delivery it protects has begun.
105
+ leaseUntil: new Date(now.getTime() + Math.max(1000, leaseMs)),
106
+ now,
107
+ });
108
+ function startLeaseHeartbeat(options) {
109
+ const renewIntervalMs = Math.max(MIN_LEASE_RENEW_INTERVAL_MS, Math.floor(options.leaseMs / 3));
110
+ let stopped = false;
111
+ let lost = false;
112
+ let renewalError;
113
+ let renewalInFlight = null;
114
+ const tick = () => {
115
+ if (stopped || lost || renewalInFlight)
116
+ return;
117
+ const renewal = renew(options.eventId, options.leaseOwner, options.leaseMs)
118
+ .then((stillOwner) => {
119
+ if (!stillOwner)
120
+ lost = true;
121
+ })
122
+ .catch((error) => {
123
+ lost = true;
124
+ renewalError = error;
125
+ })
126
+ .finally(() => {
127
+ if (renewalInFlight === renewal)
128
+ renewalInFlight = null;
129
+ });
130
+ renewalInFlight = renewal;
131
+ };
132
+ const timer = setInterval(tick, renewIntervalMs);
133
+ timer.unref?.();
134
+ return {
135
+ async stop() {
136
+ stopped = true;
137
+ clearInterval(timer);
138
+ await renewalInFlight;
139
+ return { lost, error: renewalError };
140
+ },
141
+ };
142
+ }
143
+ const claim = async (options) => {
144
+ const now = options.now ?? new Date();
145
+ const leaseMs = Math.max(1000, options.leaseMs ?? DEFAULT_LEASE_MS);
146
+ return await store.claim({
147
+ leaseOwner: options.leaseOwner,
148
+ leaseUntil: new Date(now.getTime() + leaseMs),
149
+ now,
150
+ ...(options.eventId === undefined ? {} : { eventId: options.eventId }),
151
+ });
152
+ };
153
+ const complete = async (eventId, leaseOwner, now = new Date()) => await store.complete({ eventId, leaseOwner, now });
154
+ const fail = async (event, leaseOwner, error, now = new Date()) => {
155
+ const message = error instanceof Error ? error.message : String(error);
156
+ const retryable = isRetryableDeliveryError(error);
157
+ const deadLettered = !retryable || event.attempts >= MAX_RETRYABLE_ATTEMPTS;
158
+ const released = await store.fail({
159
+ eventId: event.id,
160
+ leaseOwner,
161
+ status: deadLettered ? 'dead_letter' : 'pending',
162
+ availableAt: deadLettered ? now : nextAttemptAt(event.attempts, now),
163
+ // Bounded here rather than by a column width, so both dialects agree: a
164
+ // Mongoose validator throws on overflow and Postgres errors 22001, and
165
+ // neither is what a failed delivery should turn into.
166
+ lastError: message.slice(0, 2000),
167
+ now,
168
+ });
169
+ return { released, deadLettered };
170
+ };
171
+ return {
172
+ async enqueue(enqueueInput, tx) {
173
+ const now = new Date();
174
+ await store.enqueue({
175
+ eventId: enqueueInput.eventId,
176
+ kind: enqueueInput.kind,
177
+ payload: enqueueInput.payload,
178
+ availableAt: now,
179
+ expiresAt: new Date(now.getTime() + retention_js_1.MODERATION_OUTBOX_RETENTION_SECONDS * 1000),
180
+ now,
181
+ }, tx);
182
+ return enqueueInput.eventId;
183
+ },
184
+ claim,
185
+ complete,
186
+ renew,
187
+ fail,
188
+ async statusOf(eventId) {
189
+ return await store.statusOf(eventId);
190
+ },
191
+ async dispatch(options) {
192
+ const leaseOwner = options.leaseOwner ?? `moderation:${process.pid}:${(0, crypto_1.randomUUID)()}`;
193
+ const batchSize = Math.min(Math.max(1, options.batchSize ?? DEFAULT_BATCH_SIZE), MAX_BATCH_SIZE);
194
+ const leaseMs = Math.max(1000, options.leaseMs ?? DEFAULT_LEASE_MS);
195
+ let processed = 0;
196
+ let failed = 0;
197
+ let deadLettered = 0;
198
+ for (let index = 0; index < batchSize; index += 1) {
199
+ // Shutdown stops claiming new work but lets the event already in flight
200
+ // reach a durable state.
201
+ if (options.signal?.aborted)
202
+ break;
203
+ const event = await claim({ leaseOwner, leaseMs });
204
+ if (!event)
205
+ break;
206
+ const heartbeat = startLeaseHeartbeat({
207
+ eventId: event.id,
208
+ leaseOwner,
209
+ leaseMs,
210
+ });
211
+ let deliveryError;
212
+ try {
213
+ await options.handler(event);
214
+ }
215
+ catch (error) {
216
+ deliveryError = error;
217
+ }
218
+ // No completion/failure transition may race an owner-checked renewal.
219
+ const heartbeatResult = await heartbeat.stop();
220
+ if (heartbeatResult.lost) {
221
+ failed += 1;
222
+ logger.warn('[ModerationOutbox] event lease lost during delivery', {
223
+ eventId: event.id,
224
+ kind: event.kind,
225
+ attempts: event.attempts,
226
+ error: heartbeatResult.error instanceof Error
227
+ ? heartbeatResult.error.message
228
+ : heartbeatResult.error
229
+ ? String(heartbeatResult.error)
230
+ : 'owner or lease expiry changed',
231
+ });
232
+ continue;
233
+ }
234
+ if (deliveryError) {
235
+ failed += 1;
236
+ const outcome = await fail(event, leaseOwner, deliveryError);
237
+ const context = {
238
+ eventId: event.id,
239
+ kind: event.kind,
240
+ attempts: event.attempts,
241
+ error: deliveryError instanceof Error
242
+ ? deliveryError.message
243
+ : String(deliveryError),
244
+ };
245
+ // A dead letter is moderation work that will not happen without a
246
+ // human, so it must not be discoverable only by reading a warn-level
247
+ // log line.
248
+ if (outcome.deadLettered) {
249
+ deadLettered += 1;
250
+ logger.error('[ModerationOutbox] event dead-lettered', context);
251
+ }
252
+ else {
253
+ logger.warn('[ModerationOutbox] event delivery failed, will retry', context);
254
+ }
255
+ if (!outcome.released) {
256
+ logger.warn('[ModerationOutbox] lease lost before failure release', {
257
+ eventId: event.id,
258
+ kind: event.kind,
259
+ });
260
+ }
261
+ continue;
262
+ }
263
+ const completed = await complete(event.id, leaseOwner);
264
+ if (!completed) {
265
+ failed += 1;
266
+ logger.warn('[ModerationOutbox] lease lost before completion', {
267
+ eventId: event.id,
268
+ kind: event.kind,
269
+ attempts: event.attempts,
270
+ });
271
+ continue;
272
+ }
273
+ processed += 1;
274
+ }
275
+ return { processed, failed, deadLettered };
276
+ },
277
+ };
278
+ }
279
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/outbox/outbox/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `@crowdsource.you/core/outbox/postgres` — the PostgreSQL half.
3
+ *
4
+ * An adopting application chooses its storage by which subpath it imports, and
5
+ * gets the same moderation pipeline either way. Everything drizzle-shaped lives
6
+ * behind this entry: the three tables this package owns, the columns to spread
7
+ * into your own report table, the two registry fragments to merge into your own
8
+ * gates, and the store the integration is wired with.
9
+ *
10
+ * ```ts
11
+ * import { createModerationIntegration } from '@crowdsource.you/core/outbox';
12
+ * import {
13
+ * moderationTables,
14
+ * moderationReportColumns,
15
+ * moderationReportTableExtras,
16
+ * postgresModerationStore,
17
+ * } from '@crowdsource.you/core/outbox/postgres';
18
+ * ```
19
+ *
20
+ * `drizzle-orm`, `postgres` and `@oxy.so/db` are OPTIONAL peers, which is what this
21
+ * split buys: a deployment on Mongo never installs them, and a bundler never has
22
+ * to resolve them. Importing this subpath without them fails at the import, by
23
+ * name — which is the failure you want, rather than a driver quietly missing at
24
+ * the first write.
25
+ *
26
+ * **This package ships NO migrations.** It ships table DEFINITIONS; your own
27
+ * `drizzle-kit generate` produces the SQL, in your journal. Two journals against
28
+ * one `drizzle.__drizzle_migrations` table interleave, and the loser is skipped
29
+ * silently with exit 0 — so a migrations folder in a library is a way to lose your
30
+ * migration rather than a convenience.
31
+ *
32
+ * The MODERATION_*_RETENTION_SECONDS windows are NOT here. They are policy both
33
+ * backends share, so they stay on the root entry — and `moderationExpirySweepTargets`
34
+ * is what turns them into deletions, because Postgres has no TTL index.
35
+ */
36
+ export { postgresModerationStore } from './store/index.js';
37
+ export { postgresTransactionRunner } from './store/transaction.js';
38
+ export type { ModerationPgHandle } from './store/transaction.js';
39
+ export { moderationTables } from './tables.js';
40
+ export type { ModerationTables } from './tables.js';
41
+ export { moderationReportColumns, moderationReportTableExtras, } from './reportColumns.js';
42
+ export type { ModerationReportBuiltColumns, ModerationReportColumnName, ModerationReportColumnOptions, ModerationReportColumns, ModerationReportTable, } from './reportColumns.js';
43
+ export { moderationExpirySweepTargets, moderationIdColumnsWithoutForeignKey, } from './registries.js';
44
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/outbox/postgres/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACnE,YAAY,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EACL,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,4BAA4B,EAC5B,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,4BAA4B,EAC5B,oCAAoC,GACrC,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * `@crowdsource.you/core/outbox/postgres` — the PostgreSQL half.
4
+ *
5
+ * An adopting application chooses its storage by which subpath it imports, and
6
+ * gets the same moderation pipeline either way. Everything drizzle-shaped lives
7
+ * behind this entry: the three tables this package owns, the columns to spread
8
+ * into your own report table, the two registry fragments to merge into your own
9
+ * gates, and the store the integration is wired with.
10
+ *
11
+ * ```ts
12
+ * import { createModerationIntegration } from '@crowdsource.you/core/outbox';
13
+ * import {
14
+ * moderationTables,
15
+ * moderationReportColumns,
16
+ * moderationReportTableExtras,
17
+ * postgresModerationStore,
18
+ * } from '@crowdsource.you/core/outbox/postgres';
19
+ * ```
20
+ *
21
+ * `drizzle-orm`, `postgres` and `@oxy.so/db` are OPTIONAL peers, which is what this
22
+ * split buys: a deployment on Mongo never installs them, and a bundler never has
23
+ * to resolve them. Importing this subpath without them fails at the import, by
24
+ * name — which is the failure you want, rather than a driver quietly missing at
25
+ * the first write.
26
+ *
27
+ * **This package ships NO migrations.** It ships table DEFINITIONS; your own
28
+ * `drizzle-kit generate` produces the SQL, in your journal. Two journals against
29
+ * one `drizzle.__drizzle_migrations` table interleave, and the loser is skipped
30
+ * silently with exit 0 — so a migrations folder in a library is a way to lose your
31
+ * migration rather than a convenience.
32
+ *
33
+ * The MODERATION_*_RETENTION_SECONDS windows are NOT here. They are policy both
34
+ * backends share, so they stay on the root entry — and `moderationExpirySweepTargets`
35
+ * is what turns them into deletions, because Postgres has no TTL index.
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.moderationIdColumnsWithoutForeignKey = exports.moderationExpirySweepTargets = exports.moderationReportTableExtras = exports.moderationReportColumns = exports.moderationTables = exports.postgresTransactionRunner = exports.postgresModerationStore = void 0;
39
+ var index_js_1 = require("./store/index.js");
40
+ Object.defineProperty(exports, "postgresModerationStore", { enumerable: true, get: function () { return index_js_1.postgresModerationStore; } });
41
+ var transaction_js_1 = require("./store/transaction.js");
42
+ Object.defineProperty(exports, "postgresTransactionRunner", { enumerable: true, get: function () { return transaction_js_1.postgresTransactionRunner; } });
43
+ var tables_js_1 = require("./tables.js");
44
+ Object.defineProperty(exports, "moderationTables", { enumerable: true, get: function () { return tables_js_1.moderationTables; } });
45
+ var reportColumns_js_1 = require("./reportColumns.js");
46
+ Object.defineProperty(exports, "moderationReportColumns", { enumerable: true, get: function () { return reportColumns_js_1.moderationReportColumns; } });
47
+ Object.defineProperty(exports, "moderationReportTableExtras", { enumerable: true, get: function () { return reportColumns_js_1.moderationReportTableExtras; } });
48
+ var registries_js_1 = require("./registries.js");
49
+ Object.defineProperty(exports, "moderationExpirySweepTargets", { enumerable: true, get: function () { return registries_js_1.moderationExpirySweepTargets; } });
50
+ Object.defineProperty(exports, "moderationIdColumnsWithoutForeignKey", { enumerable: true, get: function () { return registries_js_1.moderationIdColumnsWithoutForeignKey; } });
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/outbox/postgres/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;;;AAEH,6CAA2D;AAAlD,mHAAA,uBAAuB,OAAA;AAChC,yDAAmE;AAA1D,2HAAA,yBAAyB,OAAA;AAGlC,yCAA+C;AAAtC,6GAAA,gBAAgB,OAAA;AAGzB,uDAG4B;AAF1B,2HAAA,uBAAuB,OAAA;AACvB,+HAAA,2BAA2B,OAAA;AAU7B,iDAGyB;AAFvB,6HAAA,4BAA4B,OAAA;AAC5B,qIAAA,oCAAoC,OAAA"}
@@ -0,0 +1,69 @@
1
+ import type { ExpirySweepTarget } from '@oxy.so/db/expiry';
2
+ import type { ModerationReportTable } from './reportColumns.js';
3
+ import type { ModerationTables } from './tables.js';
4
+ /**
5
+ * The two tables that carried a Mongo TTL index, as sweep targets.
6
+ *
7
+ * **A TTL index is a behaviour of the SOURCE that does not survive the port.**
8
+ * Mongo reaps; Postgres does not. Both tables here were declared
9
+ * `expireAfterSeconds: 0` on an `expiresAt` the WRITER computes, so
10
+ * `retentionSeconds` is 0: the column already IS the deadline, and the retention
11
+ * window lives in `src/retention.ts` where both backends read it.
12
+ *
13
+ * Without an entry, a table grows forever — no error, no failing test, no
14
+ * symptom of any kind until disk. Nothing about it is visible in a diff, because
15
+ * the thing doing the work was never in the consumer's code to be missed.
16
+ *
17
+ * ## What sweeping these two costs, stated because a registry entry with no such
18
+ * note reads as "unconditionally safe"
19
+ *
20
+ * The outbox holds unprocessed WORK. A stalled dispatcher plus this sweep
21
+ * discards moderation work that was never delivered — which is why the retention
22
+ * window is ninety days rather than hours, and why a `dead_letter` row is
23
+ * evidence somebody still has to look at rather than a row to reap quickly.
24
+ * Operational alerts must fire long before this deadline; the sweep is the
25
+ * backstop for a table nobody drained, not a queue policy.
26
+ *
27
+ * The event log holds the audit trail of what a third party told this deployment
28
+ * to do, and the dedupe claim. CrowdSource's retry schedule ends at 24 hours, so
29
+ * deleting a row after ninety days cannot resurrect a duplicate delivery; what it
30
+ * does cost is the answer to "did CrowdSource tell us about this case, and when",
31
+ * which is the first question asked when a report looks stuck.
32
+ *
33
+ * Neither read path depends on a swept row already being gone: the dispatcher
34
+ * claims by `available_at` and the receiver claims by primary key, so an
35
+ * unswept row is stale at worst and never unsafe.
36
+ */
37
+ export declare function moderationExpirySweepTargets(tables: ModerationTables): readonly ExpirySweepTarget[];
38
+ /**
39
+ * Every id-shaped column in this package's tables that will never carry a
40
+ * foreign key, with the reason.
41
+ *
42
+ * Eight entries, and the count is worth stating because the obvious count is
43
+ * six: `findIdColumnViolations` exempts a column only when `column.primary` is
44
+ * set, and a composite primary key declared in a table's extra config does NOT
45
+ * set it on its members — so `moderation_enforcements.decision_id` is scanned
46
+ * despite being part of the key.
47
+ *
48
+ * They fall into two kinds, and neither can be constrained:
49
+ *
50
+ * - **Ids in CrowdSource's database.** A decision, a case, a report as CrowdSource
51
+ * knows it. There is no local table to point at; the row lives in another
52
+ * service's store, and this deployment holds a copy of its identifier.
53
+ * - **The adopter's own opaque noun ids.** `subject_id` and `reported_id` name a
54
+ * post, a listing, a message — whichever table the adopter's subject provider
55
+ * resolves. The package cannot know which, and a single column cannot reference
56
+ * several tables, which is precisely why they are stored as opaque text.
57
+ *
58
+ * The report table is passed in rather than assumed: the entries embed the
59
+ * adopter's own table name, and a ledger entry naming a table that is not in the
60
+ * gate's `tables` list is reported as `stale_ledger_entry`.
61
+ */
62
+ export declare function moderationIdColumnsWithoutForeignKey(input: {
63
+ tables: ModerationTables;
64
+ reportTable: ModerationReportTable;
65
+ }): readonly {
66
+ column: string;
67
+ reason: string;
68
+ }[];
69
+ //# sourceMappingURL=registries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../../../src/outbox/postgres/registries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK3D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAsCpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,gBAAgB,GACvB,SAAS,iBAAiB,EAAE,CAqB9B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,oCAAoC,CAAC,KAAK,EAAE;IAC1D,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,qBAAqB,CAAC;CACpC,GAAG,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CA2ChD"}
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moderationExpirySweepTargets = moderationExpirySweepTargets;
4
+ exports.moderationIdColumnsWithoutForeignKey = moderationIdColumnsWithoutForeignKey;
5
+ const drizzle_orm_1 = require("drizzle-orm");
6
+ const db_1 = require("@oxy.so/db");
7
+ const retention_js_1 = require("../retention.js");
8
+ /**
9
+ * The two registry FRAGMENTS an adopter merges into its own gates.
10
+ *
11
+ * `@oxy.so/db` holds the mechanisms — the expiry sweep, the id-column
12
+ * classification gate — and the REGISTRIES belong to the consumer, because they
13
+ * name the consumer's own tables. This package's three tables are in the
14
+ * adopter's database, so the entries for them are the adopter's to declare and
15
+ * this package's to KNOW: it is the only place that can say why
16
+ * `crowdsource_case_id` will never carry a foreign key.
17
+ *
18
+ * So each of these returns a fragment to spread, never a whole registry:
19
+ *
20
+ * ```ts
21
+ * const targets = [...myOwnTargets, ...moderationExpirySweepTargets(moderation)];
22
+ * const violations = findIdColumnViolations({
23
+ * tables: [...myTables, moderation.outbox, moderation.events, moderation.enforcements],
24
+ * deferred: myDeferred,
25
+ * withoutForeignKey: [
26
+ * ...myOwnUnclassified,
27
+ * ...moderationIdColumnsWithoutForeignKey({ tables: moderation, reportTable: reports }),
28
+ * ],
29
+ * minimumTables: …,
30
+ * });
31
+ * ```
32
+ *
33
+ * Shipping them is what stops the first adopter writing these reasons by
34
+ * guessing — and every one of the eight id columns below would otherwise fail
35
+ * that adopter's inherited gate as `unclassified_id_column` on the day it adopts,
36
+ * with nothing to tell it whether that is a missing constraint or a decision.
37
+ */
38
+ /** `<table>.<column>`, in the SQL names the gate compares against. */
39
+ function describe(table, column) {
40
+ return `${(0, drizzle_orm_1.getTableName)(table)}.${(0, db_1.sqlColumnName)(column)}`;
41
+ }
42
+ /**
43
+ * The two tables that carried a Mongo TTL index, as sweep targets.
44
+ *
45
+ * **A TTL index is a behaviour of the SOURCE that does not survive the port.**
46
+ * Mongo reaps; Postgres does not. Both tables here were declared
47
+ * `expireAfterSeconds: 0` on an `expiresAt` the WRITER computes, so
48
+ * `retentionSeconds` is 0: the column already IS the deadline, and the retention
49
+ * window lives in `src/retention.ts` where both backends read it.
50
+ *
51
+ * Without an entry, a table grows forever — no error, no failing test, no
52
+ * symptom of any kind until disk. Nothing about it is visible in a diff, because
53
+ * the thing doing the work was never in the consumer's code to be missed.
54
+ *
55
+ * ## What sweeping these two costs, stated because a registry entry with no such
56
+ * note reads as "unconditionally safe"
57
+ *
58
+ * The outbox holds unprocessed WORK. A stalled dispatcher plus this sweep
59
+ * discards moderation work that was never delivered — which is why the retention
60
+ * window is ninety days rather than hours, and why a `dead_letter` row is
61
+ * evidence somebody still has to look at rather than a row to reap quickly.
62
+ * Operational alerts must fire long before this deadline; the sweep is the
63
+ * backstop for a table nobody drained, not a queue policy.
64
+ *
65
+ * The event log holds the audit trail of what a third party told this deployment
66
+ * to do, and the dedupe claim. CrowdSource's retry schedule ends at 24 hours, so
67
+ * deleting a row after ninety days cannot resurrect a duplicate delivery; what it
68
+ * does cost is the answer to "did CrowdSource tell us about this case, and when",
69
+ * which is the first question asked when a report looks stuck.
70
+ *
71
+ * Neither read path depends on a swept row already being gone: the dispatcher
72
+ * claims by `available_at` and the receiver claims by primary key, so an
73
+ * unswept row is stale at worst and never unsafe.
74
+ */
75
+ function moderationExpirySweepTargets(tables) {
76
+ return [
77
+ {
78
+ table: tables.outbox,
79
+ column: tables.outbox.expiresAt,
80
+ retentionSeconds: 0,
81
+ reason: 'Deletes a delivered or dead-lettered moderation outbox event past its ' +
82
+ `${retention_js_1.MODERATION_OUTBOX_RETENTION_SECONDS}s retention. A row still pending is ` +
83
+ 'moderation work that was never delivered — alert long before this.',
84
+ },
85
+ {
86
+ table: tables.events,
87
+ column: tables.events.expiresAt,
88
+ retentionSeconds: 0,
89
+ reason: 'Deletes the audit row and dedupe claim for an inbound decision past its ' +
90
+ `${retention_js_1.MODERATION_EVENT_RETENTION_SECONDS}s retention, long after CrowdSource's ` +
91
+ '24-hour retry schedule can redeliver it.',
92
+ },
93
+ ];
94
+ }
95
+ /**
96
+ * Every id-shaped column in this package's tables that will never carry a
97
+ * foreign key, with the reason.
98
+ *
99
+ * Eight entries, and the count is worth stating because the obvious count is
100
+ * six: `findIdColumnViolations` exempts a column only when `column.primary` is
101
+ * set, and a composite primary key declared in a table's extra config does NOT
102
+ * set it on its members — so `moderation_enforcements.decision_id` is scanned
103
+ * despite being part of the key.
104
+ *
105
+ * They fall into two kinds, and neither can be constrained:
106
+ *
107
+ * - **Ids in CrowdSource's database.** A decision, a case, a report as CrowdSource
108
+ * knows it. There is no local table to point at; the row lives in another
109
+ * service's store, and this deployment holds a copy of its identifier.
110
+ * - **The adopter's own opaque noun ids.** `subject_id` and `reported_id` name a
111
+ * post, a listing, a message — whichever table the adopter's subject provider
112
+ * resolves. The package cannot know which, and a single column cannot reference
113
+ * several tables, which is precisely why they are stored as opaque text.
114
+ *
115
+ * The report table is passed in rather than assumed: the entries embed the
116
+ * adopter's own table name, and a ledger entry naming a table that is not in the
117
+ * gate's `tables` list is reported as `stale_ledger_entry`.
118
+ */
119
+ function moderationIdColumnsWithoutForeignKey(input) {
120
+ const { tables, reportTable } = input;
121
+ return [
122
+ {
123
+ column: describe(tables.events, tables.events.caseId),
124
+ reason: 'Names a case in CrowdSource’s database, not the adopter’s.',
125
+ },
126
+ {
127
+ column: describe(tables.enforcements, tables.enforcements.decisionId),
128
+ reason: 'Names a decision in CrowdSource’s database. Part of the composite ' +
129
+ 'primary key, which does not set `column.primary` on its members.',
130
+ },
131
+ {
132
+ column: describe(tables.enforcements, tables.enforcements.caseId),
133
+ reason: 'Names a case in CrowdSource’s database.',
134
+ },
135
+ {
136
+ column: describe(tables.enforcements, tables.enforcements.subjectId),
137
+ reason: 'The adopter’s own opaque noun id; the package cannot know which table ' +
138
+ 'it points at, and one column cannot reference several.',
139
+ },
140
+ {
141
+ column: describe(reportTable, reportTable.reportedId),
142
+ reason: 'The adopter’s own opaque noun id — same reason as ' +
143
+ '`moderation_enforcements.subject_id`.',
144
+ },
145
+ {
146
+ column: describe(reportTable, reportTable.crowdSourceReportId),
147
+ reason: 'A report id in CrowdSource’s database.',
148
+ },
149
+ {
150
+ column: describe(reportTable, reportTable.crowdSourceCaseId),
151
+ reason: 'A case id in CrowdSource’s database.',
152
+ },
153
+ {
154
+ column: describe(reportTable, reportTable.decisionId),
155
+ reason: 'A decision id in CrowdSource’s database.',
156
+ },
157
+ ];
158
+ }
159
+ //# sourceMappingURL=registries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registries.js","sourceRoot":"","sources":["../../../src/outbox/postgres/registries.ts"],"names":[],"mappings":";;AAiFA,oEAuBC;AA0BD,oFA8CC;AAhLD,6CAA2C;AAG3C,mCAA2C;AAE3C,kDAGyB;AAIzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,sEAAsE;AACtE,SAAS,QAAQ,CAAC,KAAc,EAAE,MAAc;IAC9C,OAAO,GAAG,IAAA,0BAAY,EAAC,KAAK,CAAC,IAAI,IAAA,kBAAa,EAAC,MAAM,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,SAAgB,4BAA4B,CAC1C,MAAwB;IAExB,OAAO;QACL;YACE,KAAK,EAAE,MAAM,CAAC,MAAM;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS;YAC/B,gBAAgB,EAAE,CAAC;YACnB,MAAM,EACJ,wEAAwE;gBACxE,GAAG,kDAAmC,sCAAsC;gBAC5E,oEAAoE;SACvE;QACD;YACE,KAAK,EAAE,MAAM,CAAC,MAAM;YACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS;YAC/B,gBAAgB,EAAE,CAAC;YACnB,MAAM,EACJ,0EAA0E;gBAC1E,GAAG,iDAAkC,wCAAwC;gBAC7E,0CAA0C;SAC7C;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,oCAAoC,CAAC,KAGpD;IACC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IAEtC,OAAO;QACL;YACE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACrD,MAAM,EAAE,4DAA4D;SACrE;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;YACrE,MAAM,EACJ,oEAAoE;gBACpE,kEAAkE;SACrE;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;YACjE,MAAM,EAAE,yCAAyC;SAClD;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;YACpE,MAAM,EACJ,wEAAwE;gBACxE,wDAAwD;SAC3D;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,UAAU,CAAC;YACrD,MAAM,EACJ,oDAAoD;gBACpD,uCAAuC;SAC1C;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,mBAAmB,CAAC;YAC9D,MAAM,EAAE,wCAAwC;SACjD;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,iBAAiB,CAAC;YAC5D,MAAM,EAAE,sCAAsC;SAC/C;QACD;YACE,MAAM,EAAE,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,UAAU,CAAC;YACrD,MAAM,EAAE,0CAA0C;SACnD;KACF,CAAC;AACJ,CAAC"}