@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,145 @@
1
+ import { Router } from 'express';
2
+ import { crowdsourceWebhooks } from '../express/index.js';
3
+ /**
4
+ * `POST /crowdsource` — where decisions come back.
5
+ *
6
+ * ## The mount is part of the correctness
7
+ *
8
+ * This router MUST be mounted BEFORE `express.json()`. The signature covers
9
+ * `timestamp + "." + rawBody` — the bytes that arrived — and once a JSON parser
10
+ * has run, those bytes are gone. `@crowdsource.you/core/express` looks for a raw
11
+ * Buffer; handed a parsed `req.body` it refuses and raises a configuration error
12
+ * rather than verifying a signature over a re-serialisation. That refusal is the
13
+ * correct behaviour and it is also why the mount order cannot be got wrong
14
+ * silently.
15
+ *
16
+ * The property to TEST is not the mount order — that only proves the order. It
17
+ * is that no parser ran: assert `typeof req.body === 'undefined'` from inside
18
+ * the route.
19
+ *
20
+ * ## What this handler does and does not do
21
+ *
22
+ * It records and returns. A receiver should answer 2xx quickly and queue the
23
+ * processing, and the reason is not latency: applying a decision means reading
24
+ * objects, planning enforcement and writing several collections, and a receiver
25
+ * that did all that inline would time out under a burst and be retried while the
26
+ * first attempt was still running. So the event and a durable `decision.apply`
27
+ * outbox row commit in ONE transaction, and the dispatcher does the work.
28
+ *
29
+ * Nothing here is authenticated by Oxy. The HMAC IS the authentication, and an
30
+ * Oxy session must never satisfy this route — it is not a user endpoint.
31
+ */
32
+ /**
33
+ * One string field out of an event payload this version does not know.
34
+ *
35
+ * A webhook envelope's `data` is deliberately OPAQUE in the contract: an
36
+ * unrecognised event's payload is whatever a newer CrowdSource decided to send,
37
+ * and the exported type says so — property access on it does not compile. That
38
+ * is the contract being honest rather than an obstacle, so this reads the key
39
+ * defensively instead of asserting a shape nobody has verified. Anything that is
40
+ * not a string is treated as absent, which is the only safe reading of a field
41
+ * this deployment has never seen.
42
+ */
43
+ function stringField(source, key) {
44
+ if (typeof source !== 'object' || source === null)
45
+ return undefined;
46
+ const value = Reflect.get(source, key);
47
+ return typeof value === 'string' ? value : undefined;
48
+ }
49
+ export function createWebhookRouter(input) {
50
+ const router = Router();
51
+ if (!input.secret) {
52
+ /**
53
+ * Not mounted, rather than mounted and permissive.
54
+ *
55
+ * A route that answers anything at all without a secret is a route that will
56
+ * one day be reasoned about as if it verified something. An unconfigured
57
+ * deployment 404s here, which is indistinguishable from not having the
58
+ * feature — which is exactly what it is.
59
+ */
60
+ input.logger.info('[CrowdSource] webhook route not mounted: no webhook secret is configured');
61
+ return router;
62
+ }
63
+ router.post(input.path ?? '/crowdsource', crowdsourceWebhooks({
64
+ secret: input.secret,
65
+ ...(input.previousSecret === undefined
66
+ ? {}
67
+ : { previousSecret: input.previousSecret }),
68
+ // Shared across tasks: the in-process default would dedupe only the
69
+ // instance that happened to receive both copies of a redelivery.
70
+ store: input.store,
71
+ on: {
72
+ /**
73
+ * A decision, provisional or final. Both are queued: a provisional
74
+ * decision is real and is recorded; what may be ACTED on is decided by
75
+ * the enforcement mode, not by discarding the event here.
76
+ */
77
+ 'case.decided': async (event) => {
78
+ await input.inbound.recordDecisionEvent({
79
+ eventId: event.id,
80
+ type: event.type,
81
+ caseId: event.data.caseId,
82
+ decision: event.data.decision,
83
+ });
84
+ },
85
+ /**
86
+ * A later revision replacing an earlier one. The SAME path: the decision
87
+ * worker compares revisions and the enforcement executor reverses what
88
+ * the superseded revision did. A correction is not a special case with
89
+ * its own code — it is an ordinary decision that supersedes another, and
90
+ * giving it a separate path is how a restore ends up not being
91
+ * idempotent.
92
+ */
93
+ 'decision.corrected': async (event) => {
94
+ await input.inbound.recordDecisionEvent({
95
+ eventId: event.id,
96
+ type: event.type,
97
+ caseId: event.data.caseId,
98
+ decision: event.data.decision,
99
+ });
100
+ },
101
+ /**
102
+ * An appeal's outcome carries a decision too, and it is the current
103
+ * answer for the case, so it takes the same path.
104
+ */
105
+ 'appeal.decided': async (event) => {
106
+ await input.inbound.recordDecisionEvent({
107
+ eventId: event.id,
108
+ type: event.type,
109
+ caseId: event.data.caseId,
110
+ decision: event.data.decision,
111
+ });
112
+ },
113
+ },
114
+ /**
115
+ * Every other event type — including one this version of the contracts
116
+ * package has never heard of.
117
+ *
118
+ * Recorded rather than dropped. `case.created`, `case.escalated` and
119
+ * `case.closed` carry no decision and nothing to enforce, but "did
120
+ * CrowdSource tell us about this case, and when" is the first question
121
+ * asked when a report appears stuck, and the answer has to exist
122
+ * somewhere.
123
+ */
124
+ onUnhandled: async (event) => {
125
+ const caseId = stringField(event.data, 'caseId');
126
+ await input.inbound.recordIgnoredEvent({
127
+ eventId: event.id,
128
+ type: event.type,
129
+ ...(caseId === undefined ? {} : { caseId }),
130
+ });
131
+ },
132
+ /**
133
+ * A refusal reason and nothing else — never a body, a header or a
134
+ * signature. It is a bounded label, so it can be a metric.
135
+ */
136
+ onRejected: (rejection) => {
137
+ input.metrics?.incrementCounter('crowdsource_webhook_rejected_total', 1, {
138
+ rejection,
139
+ });
140
+ input.logger.warn('[CrowdSource] webhook delivery refused', { rejection });
141
+ },
142
+ }));
143
+ return router;
144
+ }
145
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/outbox/webhook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `POST /v1/reports` and `GET /v1/reports/{id}` (§10.2, §10.4).
3
+ *
4
+ * The 202 this returns means what §7.1 says it means: the report is stored and
5
+ * durable rows exist for everything that happens next. It does not mean a jury
6
+ * exists, a decision exists, or that any downstream call succeeded — and an
7
+ * application that treats it as "moderation is done" has misread the contract.
8
+ * The decision comes back later, over a webhook.
9
+ */
10
+ import { CreateReportResponseSchema, } from '@crowdsource.you/contracts';
11
+ import { z } from 'zod';
12
+ import { composeCaseEnvelope, defaultIdempotencyKey } from './envelope.js';
13
+ import { CrowdSourceTransportError } from './errors.js';
14
+ const ReportReceiptSchema = z.looseObject({
15
+ reportId: z.string(),
16
+ externalReportId: z.string(),
17
+ caseId: z.string(),
18
+ status: z.string(),
19
+ receivedAt: z.string(),
20
+ });
21
+ export class Reports {
22
+ constructor(input) {
23
+ this.transport = input.transport;
24
+ this.applicationId = input.applicationId;
25
+ this.environment = input.environment;
26
+ }
27
+ /**
28
+ * Delivers a report.
29
+ *
30
+ * Call this from a delivery worker draining the application's own outbox, not
31
+ * from the request handler that answered the user (§7.1). The user is told
32
+ * their report was received the moment it is stored locally; this call is what
33
+ * eventually makes it CrowdSource's problem, and it is allowed to fail and be
34
+ * retried without the user ever knowing.
35
+ *
36
+ * Re-delivering the same report is safe and returns the same `reportId`. What
37
+ * is NOT safe is re-delivering the same `externalReportId` with a CHANGED
38
+ * body: §10.5 answers 409 and this client surfaces it as
39
+ * `CrowdSourceApiError` with `isPayloadConflict` and `retryable: false`,
40
+ * because no number of retries makes two different payloads one report.
41
+ */
42
+ async create(input, options = {}) {
43
+ const envelope = composeCaseEnvelope(input, {
44
+ applicationId: await this.applicationId,
45
+ environment: this.environment,
46
+ });
47
+ const response = await this.transport.request({
48
+ method: 'POST',
49
+ path: '/v1/reports',
50
+ body: { externalReportId: input.externalReportId, envelope },
51
+ idempotencyKey: input.idempotencyKey ?? defaultIdempotencyKey(input.externalReportId),
52
+ signal: options.signal,
53
+ });
54
+ const parsed = CreateReportResponseSchema.safeParse(response);
55
+ if (!parsed.success) {
56
+ throw new CrowdSourceTransportError('CrowdSource accepted the report but answered with a body this client does not recognise.', { retryable: false, cause: parsed.error });
57
+ }
58
+ return parsed.data;
59
+ }
60
+ /** Reads back the receipt for a report this application delivered. */
61
+ async get(reportId, options = {}) {
62
+ const response = await this.transport.request({
63
+ method: 'GET',
64
+ path: `/v1/reports/${encodeURIComponent(reportId)}`,
65
+ signal: options.signal,
66
+ });
67
+ const parsed = ReportReceiptSchema.safeParse(response);
68
+ if (!parsed.success) {
69
+ throw new CrowdSourceTransportError('CrowdSource answered with a report receipt this client does not recognise.', { retryable: false, cause: parsed.error });
70
+ }
71
+ return parsed.data;
72
+ }
73
+ }
74
+ //# sourceMappingURL=reports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reports.js","sourceRoot":"","sources":["../../src/reports.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,0BAA0B,GAG3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAoB,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AA2BxD,MAAM,mBAAmB,GAAG,CAAC,CAAC,WAAW,CAAC;IACxC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;CACvB,CAAC,CAAC;AAMH,MAAM,OAAO,OAAO;IAWlB,YAAY,KAIX;QACC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;QACzC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACvC,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,MAAM,CACV,KAAkB,EAClB,UAAgC,EAAE;QAElC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,EAAE;YAC1C,aAAa,EAAE,MAAM,IAAI,CAAC,aAAa;YACvC,WAAW,EAAE,IAAI,CAAC,WAAW;SAC9B,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,EAAE,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,EAAE,QAAQ,EAAE;YAC5D,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,qBAAqB,CAAC,KAAK,CAAC,gBAAgB,CAAC;YACrF,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,yBAAyB,CACjC,0FAA0F,EAC1F,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,sEAAsE;IACtE,KAAK,CAAC,GAAG,CAAC,QAAgB,EAAE,UAAgC,EAAE;QAC5D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,eAAe,kBAAkB,CAAC,QAAQ,CAAC,EAAE;YACnD,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,yBAAyB,CACjC,4EAA4E,EAC5E,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Fixtures.
3
+ *
4
+ * Every builder below runs its result through the published contract before
5
+ * returning it. A fixture that does not validate is a failing test, not
6
+ * something to loosen — it is how an integrator finds out a contract moved, and
7
+ * it is the only mechanism that stops this package from teaching a shape
8
+ * CrowdSource will refuse.
9
+ *
10
+ * Everything here is synthetic. No real reported material, no real evidence, no
11
+ * real reviewer or reporter identity ever ships in a test package: §13.5
12
+ * minimises what exists at all, and a fixture file is forever.
13
+ */
14
+ import { CASE_ENVELOPE_SCHEMA_VERSION, CaseEnvelopeSchema, DecisionSchema, KnownWebhookEventSchema, UNIVERSAL_TAXONOMY_VERSION, } from '@crowdsource.you/contracts';
15
+ import { createHash } from 'node:crypto';
16
+ /** Deterministic, so a fixture used twice produces the same bytes. */
17
+ function digestOf(value) {
18
+ return `sha256:${createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
19
+ }
20
+ /**
21
+ * A minimal valid Case Envelope: one text resource, one allegation, one author.
22
+ *
23
+ * Deliberately the SMALLEST thing the contract accepts rather than a showcase of
24
+ * every field. A fixture that exercises everything hides which field a test
25
+ * actually depends on, and every optional field it carries is one more thing a
26
+ * contract change can break for a reason unrelated to the test.
27
+ */
28
+ export function caseEnvelopeFixture(options = {}) {
29
+ const text = options.text ?? 'Synthetic reported text for an integration test.';
30
+ const authorOxyUserId = options.authorOxyUserId ?? 'oxy_test_author';
31
+ return CaseEnvelopeSchema.parse({
32
+ schemaVersion: CASE_ENVELOPE_SCHEMA_VERSION,
33
+ applicationId: options.applicationId ?? 'app_test',
34
+ externalReportId: options.externalReportId ?? 'report_test_1',
35
+ source: { environment: 'sandbox', submittedAt: '2026-07-29T00:00:00.000Z' },
36
+ subject: {
37
+ externalId: options.externalSubjectId ?? 'post_test_1',
38
+ type: 'social.post',
39
+ primaryResourceId: 'res_subject',
40
+ },
41
+ principalBindings: [
42
+ {
43
+ principalRef: 'p_author',
44
+ type: 'oxy_user',
45
+ externalPrincipalId: authorOxyUserId,
46
+ bindingProofId: authorOxyUserId,
47
+ },
48
+ ],
49
+ resources: [
50
+ {
51
+ id: 'res_subject',
52
+ type: 'text',
53
+ role: 'subject',
54
+ language: 'en',
55
+ data: { text },
56
+ sha256: digestOf({ type: 'text', data: { text } }),
57
+ authorPrincipalRef: 'p_author',
58
+ },
59
+ ],
60
+ relations: [],
61
+ allegations: (options.allegations ?? ['harassment.targeted_abuse']).map((code) => ({ code })),
62
+ policy: { policySetId: 'crowdsource.baseline', version: '2026.07' },
63
+ privacy: {
64
+ retentionDays: 30,
65
+ allowCommunityReview: options.allowCommunityReview ?? true,
66
+ },
67
+ });
68
+ }
69
+ /**
70
+ * A published decision (Appendix B).
71
+ *
72
+ * The jury arithmetic is real, not decorative: `agreement` must equal
73
+ * `winningVotes / decisiveVotes` or the contract refuses the document. That
74
+ * check is the auditable trace of "one qualified person, one vote", so a fixture
75
+ * that faked it would teach a shape the service will never emit.
76
+ */
77
+ export function decisionFixture(options = {}) {
78
+ const outcome = options.outcome ?? 'violation';
79
+ const revision = options.revision ?? 1;
80
+ return DecisionSchema.parse({
81
+ id: options.id ?? 'dec_test_1',
82
+ caseId: options.caseId ?? 'case_test_1',
83
+ revision,
84
+ status: options.status ?? 'final',
85
+ outcome,
86
+ contextSufficiency: 'sufficient',
87
+ confidence: 1,
88
+ findings: outcome === 'violation'
89
+ ? [
90
+ {
91
+ code: options.findingCode ?? 'harassment.targeted_abuse',
92
+ resourceIds: ['res_subject'],
93
+ severity: 'medium',
94
+ scope: 'application_local',
95
+ attribution: 'author',
96
+ },
97
+ ]
98
+ : [],
99
+ recommendedActions: outcome === 'violation'
100
+ ? [{ action: 'remove_or_restrict', targetResourceIds: ['res_subject'] }]
101
+ : [{ action: 'no_action' }],
102
+ jury: {
103
+ size: 3,
104
+ decisiveVotes: 3,
105
+ winningVotes: 3,
106
+ agreement: 1,
107
+ specialistPresent: false,
108
+ },
109
+ policyVersions: {
110
+ taxonomy: UNIVERSAL_TAXONOMY_VERSION,
111
+ application: '2026.07',
112
+ oxyConduct: '2026.07',
113
+ },
114
+ ...(options.supersedesDecisionId === undefined
115
+ ? {}
116
+ : { supersedesDecisionId: options.supersedesDecisionId }),
117
+ publishedAt: '2026-07-29T12:00:00.000Z',
118
+ });
119
+ }
120
+ /** A `case.decided` delivery (§10.7) — the one event §10.7 specifies in full. */
121
+ export function caseDecidedEventFixture(options = {}) {
122
+ const decision = options.decision ?? decisionFixture({ caseId: options.caseId });
123
+ return KnownWebhookEventSchema.parse({
124
+ id: options.id ?? 'evt_test_1',
125
+ type: 'case.decided',
126
+ createdAt: options.createdAt ?? '2026-07-29T12:00:00.000Z',
127
+ organizationId: options.organizationId ?? 'org_test',
128
+ applicationId: options.applicationId ?? 'app_test',
129
+ data: { caseId: options.caseId ?? decision.caseId, decision },
130
+ });
131
+ }
132
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../../../src/testing/fixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@crowdsource.you/core/testing` — integrate against CrowdSource before a jury
3
+ * exists.
4
+ *
5
+ * ```ts
6
+ * import { createCrowdSourceSandbox } from '@crowdsource.you/core/testing';
7
+ * import { CrowdSource } from '@crowdsource.you/core';
8
+ *
9
+ * const sandbox = createCrowdSourceSandbox();
10
+ * const crowdsource = new CrowdSource({
11
+ * serviceKey: sandbox.serviceKey,
12
+ * baseUrl: sandbox.baseUrl,
13
+ * fetch: sandbox.fetch,
14
+ * });
15
+ *
16
+ * const { caseId } = await crowdsource.reports.create({ ... });
17
+ * const decision = sandbox.decide(caseId, { outcome: 'violation' });
18
+ * await sandbox.deliver('http://localhost:3000/webhooks/crowdsource', sandbox.eventFor(decision));
19
+ * ```
20
+ *
21
+ * The report goes through the real client, the sandbox applies the real rules
22
+ * (tenant from the credential, idempotency, 409 on a changed body, one case per
23
+ * reported version), and the webhook that comes back is genuinely signed — so
24
+ * the receiver being tested is the receiver that will run in production.
25
+ *
26
+ * The simulator can also deliver a stale, forged or tampered event on purpose.
27
+ * Asserting that a receiver REFUSES those is the half of a webhook test that
28
+ * actually proves something.
29
+ */
30
+ export { caseDecidedEventFixture, caseEnvelopeFixture, decisionFixture, } from './fixtures.js';
31
+ export { CrowdSourceSandbox, createCrowdSourceSandbox } from './sandbox.js';
32
+ export { WebhookSimulator, signWebhookDelivery } from './webhook-simulator.js';
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAQ5E,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}