@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,24 @@
1
+ "use strict";
2
+ /**
3
+ * The public surface an application implements.
4
+ *
5
+ * Everything in this file is a seam. Nothing in it knows what a post, a listing,
6
+ * a message or a property is — that knowledge belongs to the application and
7
+ * reaches this package through exactly four ports:
8
+ *
9
+ * 1. {@link ModerationSubjectProvider} — "given one of MY nouns and its id,
10
+ * describe the material".
11
+ * 2. {@link ModerationTaxonomy} — "what my reporters can pick, as universal
12
+ * allegation codes".
13
+ * 3. {@link ModerationEnforcementConfig} — "what I can do about a decision, and
14
+ * how to do and undo it".
15
+ * 4. {@link ModerationStore} — where its reports and this package's own rows are
16
+ * kept, built by the backend factory it chose and passed in whole.
17
+ *
18
+ * Everything else — the outbox, the transaction coupling, delivery, the webhook
19
+ * receiver, deduplication, decision application, enforcement idempotency and
20
+ * reversibility, reconciliation — is identical in every application and is
21
+ * imported, not written.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/outbox/types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG"}
@@ -0,0 +1,15 @@
1
+ import { Router } from 'express';
2
+ import { type ProcessedEventStore } from '../express/index.js';
3
+ import type { InboundService } from './inbound.js';
4
+ import type { ModerationLogger, ModerationMetrics } from './types.js';
5
+ export declare function createWebhookRouter(input: {
6
+ inbound: InboundService;
7
+ store: ProcessedEventStore;
8
+ secret?: string;
9
+ previousSecret?: string;
10
+ logger: ModerationLogger;
11
+ metrics?: ModerationMetrics;
12
+ /** Defaults to `/crowdsource`. */
13
+ path?: string;
14
+ }): Router;
15
+ //# sourceMappingURL=webhook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/outbox/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAiDtE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,OAAO,EAAE,cAAc,CAAC;IACxB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAuGT"}
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWebhookRouter = createWebhookRouter;
4
+ const express_1 = require("express");
5
+ const index_js_1 = require("../express/index.js");
6
+ /**
7
+ * `POST /crowdsource` — where decisions come back.
8
+ *
9
+ * ## The mount is part of the correctness
10
+ *
11
+ * This router MUST be mounted BEFORE `express.json()`. The signature covers
12
+ * `timestamp + "." + rawBody` — the bytes that arrived — and once a JSON parser
13
+ * has run, those bytes are gone. `@crowdsource.you/core/express` looks for a raw
14
+ * Buffer; handed a parsed `req.body` it refuses and raises a configuration error
15
+ * rather than verifying a signature over a re-serialisation. That refusal is the
16
+ * correct behaviour and it is also why the mount order cannot be got wrong
17
+ * silently.
18
+ *
19
+ * The property to TEST is not the mount order — that only proves the order. It
20
+ * is that no parser ran: assert `typeof req.body === 'undefined'` from inside
21
+ * the route.
22
+ *
23
+ * ## What this handler does and does not do
24
+ *
25
+ * It records and returns. A receiver should answer 2xx quickly and queue the
26
+ * processing, and the reason is not latency: applying a decision means reading
27
+ * objects, planning enforcement and writing several collections, and a receiver
28
+ * that did all that inline would time out under a burst and be retried while the
29
+ * first attempt was still running. So the event and a durable `decision.apply`
30
+ * outbox row commit in ONE transaction, and the dispatcher does the work.
31
+ *
32
+ * Nothing here is authenticated by Oxy. The HMAC IS the authentication, and an
33
+ * Oxy session must never satisfy this route — it is not a user endpoint.
34
+ */
35
+ /**
36
+ * One string field out of an event payload this version does not know.
37
+ *
38
+ * A webhook envelope's `data` is deliberately OPAQUE in the contract: an
39
+ * unrecognised event's payload is whatever a newer CrowdSource decided to send,
40
+ * and the exported type says so — property access on it does not compile. That
41
+ * is the contract being honest rather than an obstacle, so this reads the key
42
+ * defensively instead of asserting a shape nobody has verified. Anything that is
43
+ * not a string is treated as absent, which is the only safe reading of a field
44
+ * this deployment has never seen.
45
+ */
46
+ function stringField(source, key) {
47
+ if (typeof source !== 'object' || source === null)
48
+ return undefined;
49
+ const value = Reflect.get(source, key);
50
+ return typeof value === 'string' ? value : undefined;
51
+ }
52
+ function createWebhookRouter(input) {
53
+ const router = (0, express_1.Router)();
54
+ if (!input.secret) {
55
+ /**
56
+ * Not mounted, rather than mounted and permissive.
57
+ *
58
+ * A route that answers anything at all without a secret is a route that will
59
+ * one day be reasoned about as if it verified something. An unconfigured
60
+ * deployment 404s here, which is indistinguishable from not having the
61
+ * feature — which is exactly what it is.
62
+ */
63
+ input.logger.info('[CrowdSource] webhook route not mounted: no webhook secret is configured');
64
+ return router;
65
+ }
66
+ router.post(input.path ?? '/crowdsource', (0, index_js_1.crowdsourceWebhooks)({
67
+ secret: input.secret,
68
+ ...(input.previousSecret === undefined
69
+ ? {}
70
+ : { previousSecret: input.previousSecret }),
71
+ // Shared across tasks: the in-process default would dedupe only the
72
+ // instance that happened to receive both copies of a redelivery.
73
+ store: input.store,
74
+ on: {
75
+ /**
76
+ * A decision, provisional or final. Both are queued: a provisional
77
+ * decision is real and is recorded; what may be ACTED on is decided by
78
+ * the enforcement mode, not by discarding the event here.
79
+ */
80
+ 'case.decided': async (event) => {
81
+ await input.inbound.recordDecisionEvent({
82
+ eventId: event.id,
83
+ type: event.type,
84
+ caseId: event.data.caseId,
85
+ decision: event.data.decision,
86
+ });
87
+ },
88
+ /**
89
+ * A later revision replacing an earlier one. The SAME path: the decision
90
+ * worker compares revisions and the enforcement executor reverses what
91
+ * the superseded revision did. A correction is not a special case with
92
+ * its own code — it is an ordinary decision that supersedes another, and
93
+ * giving it a separate path is how a restore ends up not being
94
+ * idempotent.
95
+ */
96
+ 'decision.corrected': async (event) => {
97
+ await input.inbound.recordDecisionEvent({
98
+ eventId: event.id,
99
+ type: event.type,
100
+ caseId: event.data.caseId,
101
+ decision: event.data.decision,
102
+ });
103
+ },
104
+ /**
105
+ * An appeal's outcome carries a decision too, and it is the current
106
+ * answer for the case, so it takes the same path.
107
+ */
108
+ 'appeal.decided': async (event) => {
109
+ await input.inbound.recordDecisionEvent({
110
+ eventId: event.id,
111
+ type: event.type,
112
+ caseId: event.data.caseId,
113
+ decision: event.data.decision,
114
+ });
115
+ },
116
+ },
117
+ /**
118
+ * Every other event type — including one this version of the contracts
119
+ * package has never heard of.
120
+ *
121
+ * Recorded rather than dropped. `case.created`, `case.escalated` and
122
+ * `case.closed` carry no decision and nothing to enforce, but "did
123
+ * CrowdSource tell us about this case, and when" is the first question
124
+ * asked when a report appears stuck, and the answer has to exist
125
+ * somewhere.
126
+ */
127
+ onUnhandled: async (event) => {
128
+ const caseId = stringField(event.data, 'caseId');
129
+ await input.inbound.recordIgnoredEvent({
130
+ eventId: event.id,
131
+ type: event.type,
132
+ ...(caseId === undefined ? {} : { caseId }),
133
+ });
134
+ },
135
+ /**
136
+ * A refusal reason and nothing else — never a body, a header or a
137
+ * signature. It is a bounded label, so it can be a metric.
138
+ */
139
+ onRejected: (rejection) => {
140
+ input.metrics?.incrementCounter('crowdsource_webhook_rejected_total', 1, {
141
+ rejection,
142
+ });
143
+ input.logger.warn('[CrowdSource] webhook delivery refused', { rejection });
144
+ },
145
+ }));
146
+ return router;
147
+ }
148
+ //# 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,73 @@
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 { type CreateReportResponse, type ReportStatus } from '@crowdsource.you/contracts';
11
+ import { type ReportInput } from './envelope.js';
12
+ import type { Transport } from './transport.js';
13
+ /**
14
+ * `GET /v1/reports/{id}` (§10.2 "receipt, caseId and limited status").
15
+ *
16
+ * Declared here because `@crowdsource.you/contracts` publishes the DOCUMENTS
17
+ * that travel over the API and not every HTTP response shape. The receipt and
18
+ * the case view below both belong in contracts; until they move, these are the
19
+ * types integrators compile against and the loose parse is what keeps a newer
20
+ * server from breaking an older client (§10.11).
21
+ */
22
+ export interface ReportReceipt {
23
+ readonly reportId: string;
24
+ readonly externalReportId: string;
25
+ readonly caseId: string;
26
+ /**
27
+ * §3.2's report states, kept open. §10.11 requires a newer server not to break
28
+ * an older client, and a state added to §3.2 is exactly that case: the union
29
+ * still autocompletes the four this version knows and still type-checks the
30
+ * fifth when it arrives.
31
+ */
32
+ readonly status: ReportStatus | (string & {});
33
+ /** ISO-8601 UTC, as the API sends it. */
34
+ readonly receivedAt: string;
35
+ }
36
+ export interface ReportRequestOptions {
37
+ readonly signal?: AbortSignal;
38
+ }
39
+ export declare class Reports {
40
+ private readonly transport;
41
+ /**
42
+ * Awaited rather than read: with an Oxy service token the client has no
43
+ * credential to read an application id off, so the id is resolved from
44
+ * CrowdSource once and this holds that answer (settled or still in flight).
45
+ * A service key resolves to a plain string, and awaiting one costs nothing.
46
+ */
47
+ private readonly applicationId;
48
+ private readonly environment;
49
+ constructor(input: {
50
+ transport: Transport;
51
+ applicationId: string | Promise<string>;
52
+ environment: 'production' | 'sandbox';
53
+ });
54
+ /**
55
+ * Delivers a report.
56
+ *
57
+ * Call this from a delivery worker draining the application's own outbox, not
58
+ * from the request handler that answered the user (§7.1). The user is told
59
+ * their report was received the moment it is stored locally; this call is what
60
+ * eventually makes it CrowdSource's problem, and it is allowed to fail and be
61
+ * retried without the user ever knowing.
62
+ *
63
+ * Re-delivering the same report is safe and returns the same `reportId`. What
64
+ * is NOT safe is re-delivering the same `externalReportId` with a CHANGED
65
+ * body: §10.5 answers 409 and this client surfaces it as
66
+ * `CrowdSourceApiError` with `isPayloadConflict` and `retryable: false`,
67
+ * because no number of retries makes two different payloads one report.
68
+ */
69
+ create(input: ReportInput, options?: ReportRequestOptions): Promise<CreateReportResponse>;
70
+ /** Reads back the receipt for a report this application delivered. */
71
+ get(reportId: string, options?: ReportRequestOptions): Promise<ReportReceipt>;
72
+ }
73
+ //# sourceMappingURL=reports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAA8C,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAE7F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;IAC9C,yCAAyC;IACzC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAUD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;gBAE3C,KAAK,EAAE;QACjB,SAAS,EAAE,SAAS,CAAC;QACrB,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;KACvC;IAMD;;;;;;;;;;;;;;OAcG;IACG,MAAM,CACV,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,oBAAoB,CAAC;IAwBhC,sEAAsE;IAChE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC;CAgBxF"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * `POST /v1/reports` and `GET /v1/reports/{id}` (§10.2, §10.4).
4
+ *
5
+ * The 202 this returns means what §7.1 says it means: the report is stored and
6
+ * durable rows exist for everything that happens next. It does not mean a jury
7
+ * exists, a decision exists, or that any downstream call succeeded — and an
8
+ * application that treats it as "moderation is done" has misread the contract.
9
+ * The decision comes back later, over a webhook.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Reports = void 0;
13
+ const contracts_1 = require("@crowdsource.you/contracts");
14
+ const zod_1 = require("zod");
15
+ const envelope_js_1 = require("./envelope.js");
16
+ const errors_js_1 = require("./errors.js");
17
+ const ReportReceiptSchema = zod_1.z.looseObject({
18
+ reportId: zod_1.z.string(),
19
+ externalReportId: zod_1.z.string(),
20
+ caseId: zod_1.z.string(),
21
+ status: zod_1.z.string(),
22
+ receivedAt: zod_1.z.string(),
23
+ });
24
+ class Reports {
25
+ constructor(input) {
26
+ this.transport = input.transport;
27
+ this.applicationId = input.applicationId;
28
+ this.environment = input.environment;
29
+ }
30
+ /**
31
+ * Delivers a report.
32
+ *
33
+ * Call this from a delivery worker draining the application's own outbox, not
34
+ * from the request handler that answered the user (§7.1). The user is told
35
+ * their report was received the moment it is stored locally; this call is what
36
+ * eventually makes it CrowdSource's problem, and it is allowed to fail and be
37
+ * retried without the user ever knowing.
38
+ *
39
+ * Re-delivering the same report is safe and returns the same `reportId`. What
40
+ * is NOT safe is re-delivering the same `externalReportId` with a CHANGED
41
+ * body: §10.5 answers 409 and this client surfaces it as
42
+ * `CrowdSourceApiError` with `isPayloadConflict` and `retryable: false`,
43
+ * because no number of retries makes two different payloads one report.
44
+ */
45
+ async create(input, options = {}) {
46
+ const envelope = (0, envelope_js_1.composeCaseEnvelope)(input, {
47
+ applicationId: await this.applicationId,
48
+ environment: this.environment,
49
+ });
50
+ const response = await this.transport.request({
51
+ method: 'POST',
52
+ path: '/v1/reports',
53
+ body: { externalReportId: input.externalReportId, envelope },
54
+ idempotencyKey: input.idempotencyKey ?? (0, envelope_js_1.defaultIdempotencyKey)(input.externalReportId),
55
+ signal: options.signal,
56
+ });
57
+ const parsed = contracts_1.CreateReportResponseSchema.safeParse(response);
58
+ if (!parsed.success) {
59
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource accepted the report but answered with a body this client does not recognise.', { retryable: false, cause: parsed.error });
60
+ }
61
+ return parsed.data;
62
+ }
63
+ /** Reads back the receipt for a report this application delivered. */
64
+ async get(reportId, options = {}) {
65
+ const response = await this.transport.request({
66
+ method: 'GET',
67
+ path: `/v1/reports/${encodeURIComponent(reportId)}`,
68
+ signal: options.signal,
69
+ });
70
+ const parsed = ReportReceiptSchema.safeParse(response);
71
+ if (!parsed.success) {
72
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource answered with a report receipt this client does not recognise.', { retryable: false, cause: parsed.error });
73
+ }
74
+ return parsed.data;
75
+ }
76
+ }
77
+ exports.Reports = Reports;
78
+ //# sourceMappingURL=reports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reports.js","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,0DAIoC;AACpC,6BAAwB;AAExB,+CAA6F;AAC7F,2CAAwD;AA2BxD,MAAM,mBAAmB,GAAG,OAAC,CAAC,WAAW,CAAC;IACxC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;IACpB,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE;IAC5B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE;CACvB,CAAC,CAAC;AAMH,MAAa,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,IAAA,iCAAmB,EAAC,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,IAAA,mCAAqB,EAAC,KAAK,CAAC,gBAAgB,CAAC;YACrF,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,sCAA0B,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,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,qCAAyB,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;AAhFD,0BAgFC"}
@@ -0,0 +1,62 @@
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 { type CaseEnvelope, type Decision, type KnownWebhookEvent, type TaxonomyCode } from '@crowdsource.you/contracts';
15
+ export interface CaseEnvelopeFixtureOptions {
16
+ readonly applicationId?: string;
17
+ readonly externalReportId?: string;
18
+ readonly externalSubjectId?: string;
19
+ readonly text?: string;
20
+ readonly allegations?: readonly TaxonomyCode[];
21
+ /** An `oxy_user` binding carries a proof; §11.14 admits no effect without one. */
22
+ readonly authorOxyUserId?: string;
23
+ readonly allowCommunityReview?: boolean;
24
+ }
25
+ /**
26
+ * A minimal valid Case Envelope: one text resource, one allegation, one author.
27
+ *
28
+ * Deliberately the SMALLEST thing the contract accepts rather than a showcase of
29
+ * every field. A fixture that exercises everything hides which field a test
30
+ * actually depends on, and every optional field it carries is one more thing a
31
+ * contract change can break for a reason unrelated to the test.
32
+ */
33
+ export declare function caseEnvelopeFixture(options?: CaseEnvelopeFixtureOptions): CaseEnvelope;
34
+ export interface DecisionFixtureOptions {
35
+ readonly id?: string;
36
+ readonly caseId?: string;
37
+ readonly revision?: number;
38
+ readonly outcome?: Decision['outcome'];
39
+ readonly status?: Decision['status'];
40
+ readonly findingCode?: TaxonomyCode;
41
+ readonly supersedesDecisionId?: string;
42
+ }
43
+ /**
44
+ * A published decision (Appendix B).
45
+ *
46
+ * The jury arithmetic is real, not decorative: `agreement` must equal
47
+ * `winningVotes / decisiveVotes` or the contract refuses the document. That
48
+ * check is the auditable trace of "one qualified person, one vote", so a fixture
49
+ * that faked it would teach a shape the service will never emit.
50
+ */
51
+ export declare function decisionFixture(options?: DecisionFixtureOptions): Decision;
52
+ export interface WebhookEventFixtureOptions {
53
+ readonly id?: string;
54
+ readonly organizationId?: string;
55
+ readonly applicationId?: string;
56
+ readonly createdAt?: string;
57
+ readonly caseId?: string;
58
+ readonly decision?: Decision;
59
+ }
60
+ /** A `case.decided` delivery (§10.7) — the one event §10.7 specifies in full. */
61
+ export declare function caseDecidedEventFixture(options?: WebhookEventFixtureOptions): KnownWebhookEvent;
62
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/testing/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAQpC,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC/C,kFAAkF;IAClF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,YAAY,CAyC1F;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,QAAQ,CA6C9E;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAC9B;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,0BAA+B,GACvC,iBAAiB,CAWnB"}
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ /**
3
+ * Fixtures.
4
+ *
5
+ * Every builder below runs its result through the published contract before
6
+ * returning it. A fixture that does not validate is a failing test, not
7
+ * something to loosen — it is how an integrator finds out a contract moved, and
8
+ * it is the only mechanism that stops this package from teaching a shape
9
+ * CrowdSource will refuse.
10
+ *
11
+ * Everything here is synthetic. No real reported material, no real evidence, no
12
+ * real reviewer or reporter identity ever ships in a test package: §13.5
13
+ * minimises what exists at all, and a fixture file is forever.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.caseEnvelopeFixture = caseEnvelopeFixture;
17
+ exports.decisionFixture = decisionFixture;
18
+ exports.caseDecidedEventFixture = caseDecidedEventFixture;
19
+ const contracts_1 = require("@crowdsource.you/contracts");
20
+ const node_crypto_1 = require("node:crypto");
21
+ /** Deterministic, so a fixture used twice produces the same bytes. */
22
+ function digestOf(value) {
23
+ return `sha256:${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify(value)).digest('hex')}`;
24
+ }
25
+ /**
26
+ * A minimal valid Case Envelope: one text resource, one allegation, one author.
27
+ *
28
+ * Deliberately the SMALLEST thing the contract accepts rather than a showcase of
29
+ * every field. A fixture that exercises everything hides which field a test
30
+ * actually depends on, and every optional field it carries is one more thing a
31
+ * contract change can break for a reason unrelated to the test.
32
+ */
33
+ function caseEnvelopeFixture(options = {}) {
34
+ const text = options.text ?? 'Synthetic reported text for an integration test.';
35
+ const authorOxyUserId = options.authorOxyUserId ?? 'oxy_test_author';
36
+ return contracts_1.CaseEnvelopeSchema.parse({
37
+ schemaVersion: contracts_1.CASE_ENVELOPE_SCHEMA_VERSION,
38
+ applicationId: options.applicationId ?? 'app_test',
39
+ externalReportId: options.externalReportId ?? 'report_test_1',
40
+ source: { environment: 'sandbox', submittedAt: '2026-07-29T00:00:00.000Z' },
41
+ subject: {
42
+ externalId: options.externalSubjectId ?? 'post_test_1',
43
+ type: 'social.post',
44
+ primaryResourceId: 'res_subject',
45
+ },
46
+ principalBindings: [
47
+ {
48
+ principalRef: 'p_author',
49
+ type: 'oxy_user',
50
+ externalPrincipalId: authorOxyUserId,
51
+ bindingProofId: authorOxyUserId,
52
+ },
53
+ ],
54
+ resources: [
55
+ {
56
+ id: 'res_subject',
57
+ type: 'text',
58
+ role: 'subject',
59
+ language: 'en',
60
+ data: { text },
61
+ sha256: digestOf({ type: 'text', data: { text } }),
62
+ authorPrincipalRef: 'p_author',
63
+ },
64
+ ],
65
+ relations: [],
66
+ allegations: (options.allegations ?? ['harassment.targeted_abuse']).map((code) => ({ code })),
67
+ policy: { policySetId: 'crowdsource.baseline', version: '2026.07' },
68
+ privacy: {
69
+ retentionDays: 30,
70
+ allowCommunityReview: options.allowCommunityReview ?? true,
71
+ },
72
+ });
73
+ }
74
+ /**
75
+ * A published decision (Appendix B).
76
+ *
77
+ * The jury arithmetic is real, not decorative: `agreement` must equal
78
+ * `winningVotes / decisiveVotes` or the contract refuses the document. That
79
+ * check is the auditable trace of "one qualified person, one vote", so a fixture
80
+ * that faked it would teach a shape the service will never emit.
81
+ */
82
+ function decisionFixture(options = {}) {
83
+ const outcome = options.outcome ?? 'violation';
84
+ const revision = options.revision ?? 1;
85
+ return contracts_1.DecisionSchema.parse({
86
+ id: options.id ?? 'dec_test_1',
87
+ caseId: options.caseId ?? 'case_test_1',
88
+ revision,
89
+ status: options.status ?? 'final',
90
+ outcome,
91
+ contextSufficiency: 'sufficient',
92
+ confidence: 1,
93
+ findings: outcome === 'violation'
94
+ ? [
95
+ {
96
+ code: options.findingCode ?? 'harassment.targeted_abuse',
97
+ resourceIds: ['res_subject'],
98
+ severity: 'medium',
99
+ scope: 'application_local',
100
+ attribution: 'author',
101
+ },
102
+ ]
103
+ : [],
104
+ recommendedActions: outcome === 'violation'
105
+ ? [{ action: 'remove_or_restrict', targetResourceIds: ['res_subject'] }]
106
+ : [{ action: 'no_action' }],
107
+ jury: {
108
+ size: 3,
109
+ decisiveVotes: 3,
110
+ winningVotes: 3,
111
+ agreement: 1,
112
+ specialistPresent: false,
113
+ },
114
+ policyVersions: {
115
+ taxonomy: contracts_1.UNIVERSAL_TAXONOMY_VERSION,
116
+ application: '2026.07',
117
+ oxyConduct: '2026.07',
118
+ },
119
+ ...(options.supersedesDecisionId === undefined
120
+ ? {}
121
+ : { supersedesDecisionId: options.supersedesDecisionId }),
122
+ publishedAt: '2026-07-29T12:00:00.000Z',
123
+ });
124
+ }
125
+ /** A `case.decided` delivery (§10.7) — the one event §10.7 specifies in full. */
126
+ function caseDecidedEventFixture(options = {}) {
127
+ const decision = options.decision ?? decisionFixture({ caseId: options.caseId });
128
+ return contracts_1.KnownWebhookEventSchema.parse({
129
+ id: options.id ?? 'evt_test_1',
130
+ type: 'case.decided',
131
+ createdAt: options.createdAt ?? '2026-07-29T12:00:00.000Z',
132
+ organizationId: options.organizationId ?? 'org_test',
133
+ applicationId: options.applicationId ?? 'app_test',
134
+ data: { caseId: options.caseId ?? decision.caseId, decision },
135
+ });
136
+ }
137
+ //# 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,36 @@
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 type { CaseEnvelopeFixtureOptions, DecisionFixtureOptions, WebhookEventFixtureOptions, } from './fixtures.js';
32
+ export { CrowdSourceSandbox, createCrowdSourceSandbox } from './sandbox.js';
33
+ export type { CrowdSourceSandboxOptions, SandboxCase, SandboxDecisionInput, SandboxReport, } from './sandbox.js';
34
+ export { WebhookSimulator, signWebhookDelivery } from './webhook-simulator.js';
35
+ export type { SignWebhookInput, SignedWebhookDelivery, WebhookDeliveryOverrides, WebhookDeliveryResult, WebhookSimulatorOptions, } from './webhook-simulator.js';
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,0BAA0B,EAC1B,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EACV,yBAAyB,EACzB,WAAW,EACX,oBAAoB,EACpB,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC"}