cursedbelt-server 1.0.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 (606) hide show
  1. package/LICENSE +15 -0
  2. package/dist/server/activity/index.d.ts +36 -0
  3. package/dist/server/activity/index.js +36 -0
  4. package/dist/server/activity/migrations.d.ts +21 -0
  5. package/dist/server/activity/migrations.js +55 -0
  6. package/dist/server/activity/shareSource.d.ts +11 -0
  7. package/dist/server/activity/shareSource.js +45 -0
  8. package/dist/server/activity/store.d.ts +123 -0
  9. package/dist/server/activity/store.js +178 -0
  10. package/dist/server/app.d.ts +56 -0
  11. package/dist/server/app.js +45 -0
  12. package/dist/server/audit/index.d.ts +2 -0
  13. package/dist/server/audit/index.js +2 -0
  14. package/dist/server/audit/migrations.d.ts +2 -0
  15. package/dist/server/audit/migrations.js +79 -0
  16. package/dist/server/audit/sqliteSink.d.ts +36 -0
  17. package/dist/server/audit/sqliteSink.js +95 -0
  18. package/dist/server/audit/sqliteSink.test.d.ts +1 -0
  19. package/dist/server/audit/sqliteSink.test.js +180 -0
  20. package/dist/server/auth/cookie.d.ts +22 -0
  21. package/dist/server/auth/cookie.js +35 -0
  22. package/dist/server/auth/csrf.d.ts +42 -0
  23. package/dist/server/auth/csrf.js +36 -0
  24. package/dist/server/auth/fileTokenSigner.d.ts +10 -0
  25. package/dist/server/auth/fileTokenSigner.js +22 -0
  26. package/dist/server/auth/getOnly.d.ts +20 -0
  27. package/dist/server/auth/getOnly.js +27 -0
  28. package/dist/server/auth/index.d.ts +12 -0
  29. package/dist/server/auth/index.js +15 -0
  30. package/dist/server/auth/jwt.d.ts +18 -0
  31. package/dist/server/auth/jwt.js +56 -0
  32. package/dist/server/auth/loginThrottle.d.ts +118 -0
  33. package/dist/server/auth/loginThrottle.js +204 -0
  34. package/dist/server/auth/middleware.d.ts +14 -0
  35. package/dist/server/auth/middleware.js +47 -0
  36. package/dist/server/auth/password.d.ts +14 -0
  37. package/dist/server/auth/password.js +65 -0
  38. package/dist/server/auth/sessionStore.d.ts +19 -0
  39. package/dist/server/auth/sessionStore.js +221 -0
  40. package/dist/server/auth/totp.d.ts +34 -0
  41. package/dist/server/auth/totp.js +81 -0
  42. package/dist/server/auth/types.d.ts +133 -0
  43. package/dist/server/auth/types.js +1 -0
  44. package/dist/server/auth/vault.d.ts +27 -0
  45. package/dist/server/auth/vault.js +57 -0
  46. package/dist/server/context.d.ts +35 -0
  47. package/dist/server/context.js +1 -0
  48. package/dist/server/ctgr.d.ts +32 -0
  49. package/dist/server/ctgr.js +69 -0
  50. package/dist/server/db/kysely.d.ts +14 -0
  51. package/dist/server/db/kysely.js +16 -0
  52. package/dist/server/db/plumbingTypes.d.ts +116 -0
  53. package/dist/server/db/plumbingTypes.js +12 -0
  54. package/dist/server/dropzone/duplicates.d.ts +82 -0
  55. package/dist/server/dropzone/duplicates.js +90 -0
  56. package/dist/server/dropzone/index.d.ts +13 -0
  57. package/dist/server/dropzone/index.js +13 -0
  58. package/dist/server/dropzone/scan.d.ts +166 -0
  59. package/dist/server/dropzone/scan.js +272 -0
  60. package/dist/server/errors.d.ts +7 -0
  61. package/dist/server/errors.js +7 -0
  62. package/dist/server/guard/argon2Binding.d.ts +97 -0
  63. package/dist/server/guard/argon2Binding.js +138 -0
  64. package/dist/server/guard/index.d.ts +4 -0
  65. package/dist/server/guard/index.js +6 -0
  66. package/dist/server/guard/kdfWorker.d.ts +101 -0
  67. package/dist/server/guard/kdfWorker.js +301 -0
  68. package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
  69. package/dist/server/guard/kdfWorkerEntry.js +15 -0
  70. package/dist/server/guard/passwordVerifier.d.ts +65 -0
  71. package/dist/server/guard/passwordVerifier.js +122 -0
  72. package/dist/server/guard/revocationStore.d.ts +132 -0
  73. package/dist/server/guard/revocationStore.js +211 -0
  74. package/dist/server/guard/sessionSigner.d.ts +132 -0
  75. package/dist/server/guard/sessionSigner.js +303 -0
  76. package/dist/server/guard/verifier.d.ts +21 -0
  77. package/dist/server/guard/verifier.js +1 -0
  78. package/dist/server/incidents/channels.d.ts +161 -0
  79. package/dist/server/incidents/channels.js +357 -0
  80. package/dist/server/incidents/checks.d.ts +61 -0
  81. package/dist/server/incidents/checks.js +55 -0
  82. package/dist/server/incidents/index.d.ts +51 -0
  83. package/dist/server/incidents/index.js +51 -0
  84. package/dist/server/incidents/migrations.d.ts +20 -0
  85. package/dist/server/incidents/migrations.js +58 -0
  86. package/dist/server/incidents/notifier.d.ts +79 -0
  87. package/dist/server/incidents/notifier.js +141 -0
  88. package/dist/server/incidents/reporter.d.ts +60 -0
  89. package/dist/server/incidents/reporter.js +49 -0
  90. package/dist/server/incidents/routes.d.ts +91 -0
  91. package/dist/server/incidents/routes.js +202 -0
  92. package/dist/server/incidents/store.d.ts +75 -0
  93. package/dist/server/incidents/store.js +176 -0
  94. package/dist/server/incidents/types.d.ts +107 -0
  95. package/dist/server/incidents/types.js +32 -0
  96. package/dist/server/index.d.ts +25 -0
  97. package/dist/server/index.js +53 -0
  98. package/dist/server/jobs/fireQueue.d.ts +12 -0
  99. package/dist/server/jobs/fireQueue.js +37 -0
  100. package/dist/server/jobs/index.d.ts +6 -0
  101. package/dist/server/jobs/index.js +10 -0
  102. package/dist/server/jobs/logger.d.ts +10 -0
  103. package/dist/server/jobs/logger.js +14 -0
  104. package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
  105. package/dist/server/jobs/plainjobAdapter.js +48 -0
  106. package/dist/server/jobs/polling.d.ts +17 -0
  107. package/dist/server/jobs/polling.js +79 -0
  108. package/dist/server/jobs/runner.d.ts +24 -0
  109. package/dist/server/jobs/runner.js +186 -0
  110. package/dist/server/jobs/timeout.d.ts +28 -0
  111. package/dist/server/jobs/timeout.js +47 -0
  112. package/dist/server/jobs/types.d.ts +167 -0
  113. package/dist/server/jobs/types.js +12 -0
  114. package/dist/server/master-lock/guard.d.ts +53 -0
  115. package/dist/server/master-lock/guard.js +205 -0
  116. package/dist/server/master-lock/index.d.ts +21 -0
  117. package/dist/server/master-lock/index.js +21 -0
  118. package/dist/server/master-lock/lockPage.d.ts +34 -0
  119. package/dist/server/master-lock/lockPage.js +281 -0
  120. package/dist/server/master-lock/masterLock.d.ts +294 -0
  121. package/dist/server/master-lock/masterLock.js +523 -0
  122. package/dist/server/master-lock/principals.d.ts +76 -0
  123. package/dist/server/master-lock/principals.js +157 -0
  124. package/dist/server/master-lock/seed.d.ts +47 -0
  125. package/dist/server/master-lock/seed.js +93 -0
  126. package/dist/server/master-lock/store.d.ts +19 -0
  127. package/dist/server/master-lock/store.js +20 -0
  128. package/dist/server/media-bun/image.d.ts +7 -0
  129. package/dist/server/media-bun/image.js +96 -0
  130. package/dist/server/media-bun/index.d.ts +4 -0
  131. package/dist/server/media-bun/index.js +10 -0
  132. package/dist/server/media-bun/streaming.d.ts +86 -0
  133. package/dist/server/media-bun/streaming.js +237 -0
  134. package/dist/server/media-bun/video.d.ts +35 -0
  135. package/dist/server/media-bun/video.js +181 -0
  136. package/dist/server/metrics/metricsBuffer.d.ts +39 -0
  137. package/dist/server/metrics/metricsBuffer.js +36 -0
  138. package/dist/server/metrics/normalizeRoute.d.ts +5 -0
  139. package/dist/server/metrics/normalizeRoute.js +58 -0
  140. package/dist/server/middleware/correlationId.d.ts +9 -0
  141. package/dist/server/middleware/correlationId.js +20 -0
  142. package/dist/server/middleware/corsAllowlist.d.ts +38 -0
  143. package/dist/server/middleware/corsAllowlist.js +37 -0
  144. package/dist/server/middleware/errorEnvelope.d.ts +17 -0
  145. package/dist/server/middleware/errorEnvelope.js +32 -0
  146. package/dist/server/middleware/index.d.ts +9 -0
  147. package/dist/server/middleware/index.js +10 -0
  148. package/dist/server/middleware/localOrigin.d.ts +71 -0
  149. package/dist/server/middleware/localOrigin.js +119 -0
  150. package/dist/server/middleware/rateLimit.d.ts +56 -0
  151. package/dist/server/middleware/rateLimit.js +86 -0
  152. package/dist/server/middleware/requestLogger.d.ts +29 -0
  153. package/dist/server/middleware/requestLogger.js +47 -0
  154. package/dist/server/middleware/securityHeaders.d.ts +29 -0
  155. package/dist/server/middleware/securityHeaders.js +44 -0
  156. package/dist/server/middleware/zValidator.d.ts +29 -0
  157. package/dist/server/middleware/zValidator.js +57 -0
  158. package/dist/server/migration/runner.d.ts +42 -0
  159. package/dist/server/migration/runner.js +70 -0
  160. package/dist/server/migration/types.d.ts +16 -0
  161. package/dist/server/migration/types.js +1 -0
  162. package/dist/server/net/outboundGuard.d.ts +30 -0
  163. package/dist/server/net/outboundGuard.js +199 -0
  164. package/dist/server/notifications/hub.d.ts +67 -0
  165. package/dist/server/notifications/hub.js +187 -0
  166. package/dist/server/notifications/index.d.ts +47 -0
  167. package/dist/server/notifications/index.js +46 -0
  168. package/dist/server/notifications/migrations.d.ts +13 -0
  169. package/dist/server/notifications/migrations.js +20 -0
  170. package/dist/server/notifications/routes.d.ts +58 -0
  171. package/dist/server/notifications/routes.js +113 -0
  172. package/dist/server/notifications/service.d.ts +58 -0
  173. package/dist/server/notifications/service.js +41 -0
  174. package/dist/server/paths/appDataHome.d.ts +19 -0
  175. package/dist/server/paths/appDataHome.js +68 -0
  176. package/dist/server/private-media/crypto.d.ts +37 -0
  177. package/dist/server/private-media/crypto.js +111 -0
  178. package/dist/server/private-media/index-store.d.ts +31 -0
  179. package/dist/server/private-media/index-store.js +36 -0
  180. package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
  181. package/dist/server/private-media/mountPrivateRoutes.js +219 -0
  182. package/dist/server/private-media/pipeline.d.ts +37 -0
  183. package/dist/server/private-media/pipeline.js +97 -0
  184. package/dist/server/private-media/unlock.d.ts +52 -0
  185. package/dist/server/private-media/unlock.js +55 -0
  186. package/dist/server/resume/index.d.ts +2 -0
  187. package/dist/server/resume/index.js +2 -0
  188. package/dist/server/resume/routes.d.ts +41 -0
  189. package/dist/server/resume/routes.js +50 -0
  190. package/dist/server/resume/store.d.ts +37 -0
  191. package/dist/server/resume/store.js +38 -0
  192. package/dist/server/retention/archive.d.ts +78 -0
  193. package/dist/server/retention/archive.js +284 -0
  194. package/dist/server/retention/fleet.d.ts +50 -0
  195. package/dist/server/retention/fleet.js +66 -0
  196. package/dist/server/retention/index.d.ts +38 -0
  197. package/dist/server/retention/index.js +38 -0
  198. package/dist/server/retention/policy.d.ts +263 -0
  199. package/dist/server/retention/policy.js +264 -0
  200. package/dist/server/retention/store.d.ts +101 -0
  201. package/dist/server/retention/store.js +251 -0
  202. package/dist/server/retention/sweeper.d.ts +69 -0
  203. package/dist/server/retention/sweeper.js +289 -0
  204. package/dist/server/retention/target.d.ts +143 -0
  205. package/dist/server/retention/target.js +438 -0
  206. package/dist/server/search/index.d.ts +2 -0
  207. package/dist/server/search/index.js +6 -0
  208. package/dist/server/search/pgSearchProvider.d.ts +91 -0
  209. package/dist/server/search/pgSearchProvider.js +330 -0
  210. package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
  211. package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
  212. package/dist/server/search/pgSearchSchema.d.ts +81 -0
  213. package/dist/server/search/pgSearchSchema.js +89 -0
  214. package/dist/server/sharing/index.d.ts +18 -0
  215. package/dist/server/sharing/index.js +16 -0
  216. package/dist/server/sharing/migrations.d.ts +6 -0
  217. package/dist/server/sharing/migrations.js +79 -0
  218. package/dist/server/sharing/router.d.ts +111 -0
  219. package/dist/server/sharing/router.js +93 -0
  220. package/dist/server/sharing/sql.d.ts +67 -0
  221. package/dist/server/sharing/sql.js +88 -0
  222. package/dist/server/sharing/store.d.ts +173 -0
  223. package/dist/server/sharing/store.js +484 -0
  224. package/dist/server/sqlite/backup.d.ts +48 -0
  225. package/dist/server/sqlite/backup.js +97 -0
  226. package/dist/server/sqlite/browse.d.ts +42 -0
  227. package/dist/server/sqlite/browse.js +84 -0
  228. package/dist/server/sqlite/export.d.ts +32 -0
  229. package/dist/server/sqlite/export.js +42 -0
  230. package/dist/server/sqlite/fts5.d.ts +106 -0
  231. package/dist/server/sqlite/fts5.js +154 -0
  232. package/dist/server/sqlite/health.d.ts +44 -0
  233. package/dist/server/sqlite/health.js +52 -0
  234. package/dist/server/sqlite/import.d.ts +36 -0
  235. package/dist/server/sqlite/import.js +108 -0
  236. package/dist/server/sqlite/index.d.ts +9 -0
  237. package/dist/server/sqlite/index.js +15 -0
  238. package/dist/server/sqlite/readonlySql.d.ts +15 -0
  239. package/dist/server/sqlite/readonlySql.js +65 -0
  240. package/dist/server/sqlite/stats.d.ts +22 -0
  241. package/dist/server/sqlite/stats.js +32 -0
  242. package/dist/server/sqlite/viewer.d.ts +17 -0
  243. package/dist/server/sqlite/viewer.js +56 -0
  244. package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
  245. package/dist/server/storage/binaryServerProcessing.js +81 -0
  246. package/dist/server/storage/binaryServerStore.d.ts +3 -0
  247. package/dist/server/storage/binaryServerStore.js +95 -0
  248. package/dist/server/storage/diskStore.d.ts +2 -0
  249. package/dist/server/storage/diskStore.js +88 -0
  250. package/dist/server/storage/files/adapter.d.ts +33 -0
  251. package/dist/server/storage/files/adapter.js +203 -0
  252. package/dist/server/storage/files/catalogue.d.ts +72 -0
  253. package/dist/server/storage/files/catalogue.js +1033 -0
  254. package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
  255. package/dist/server/storage/files/chunkedUpload.js +70 -0
  256. package/dist/server/storage/files/fileRoutes.d.ts +51 -0
  257. package/dist/server/storage/files/fileRoutes.js +405 -0
  258. package/dist/server/storage/files/index.d.ts +9 -0
  259. package/dist/server/storage/files/index.js +11 -0
  260. package/dist/server/storage/files/migrations.d.ts +2 -0
  261. package/dist/server/storage/files/migrations.js +163 -0
  262. package/dist/server/storage/files/mimeByExt.d.ts +1 -0
  263. package/dist/server/storage/files/mimeByExt.js +13 -0
  264. package/dist/server/storage/files/stack.d.ts +136 -0
  265. package/dist/server/storage/files/stack.js +132 -0
  266. package/dist/server/storage/files/storageOps.d.ts +54 -0
  267. package/dist/server/storage/files/storageOps.js +110 -0
  268. package/dist/server/storage/files/types.d.ts +261 -0
  269. package/dist/server/storage/files/types.js +22 -0
  270. package/dist/server/storage/imageThumbnail.d.ts +3 -0
  271. package/dist/server/storage/imageThumbnail.js +50 -0
  272. package/dist/server/storage/index.d.ts +15 -0
  273. package/dist/server/storage/index.js +27 -0
  274. package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
  275. package/dist/server/storage/mountMediaRoutes.js +141 -0
  276. package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
  277. package/dist/server/storage/objectStoreMediaStore.js +57 -0
  278. package/dist/server/storage/s3ObjectStore.d.ts +63 -0
  279. package/dist/server/storage/s3ObjectStore.js +187 -0
  280. package/dist/server/storage/sources.d.ts +47 -0
  281. package/dist/server/storage/sources.js +148 -0
  282. package/dist/server/storage/streamRange.d.ts +19 -0
  283. package/dist/server/storage/streamRange.js +69 -0
  284. package/dist/server/storage/streamToken.d.ts +16 -0
  285. package/dist/server/storage/streamToken.js +54 -0
  286. package/dist/server/storage/transcodeJobs.d.ts +65 -0
  287. package/dist/server/storage/transcodeJobs.js +104 -0
  288. package/dist/server/storage/types.d.ts +193 -0
  289. package/dist/server/storage/types.js +41 -0
  290. package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
  291. package/dist/server/storage/videoDerivativeKeys.js +149 -0
  292. package/dist/server/storage/videoProcessor.d.ts +30 -0
  293. package/dist/server/storage/videoProcessor.js +148 -0
  294. package/dist/server/sync/alarm.d.ts +34 -0
  295. package/dist/server/sync/alarm.js +91 -0
  296. package/dist/server/sync/commands.d.ts +87 -0
  297. package/dist/server/sync/commands.js +241 -0
  298. package/dist/server/sync/engine.d.ts +62 -0
  299. package/dist/server/sync/engine.js +184 -0
  300. package/dist/server/sync/http.d.ts +106 -0
  301. package/dist/server/sync/http.js +243 -0
  302. package/dist/server/sync/index.d.ts +41 -0
  303. package/dist/server/sync/index.js +41 -0
  304. package/dist/server/sync/opLog.d.ts +61 -0
  305. package/dist/server/sync/opLog.js +96 -0
  306. package/dist/server/sync/planner.d.ts +31 -0
  307. package/dist/server/sync/planner.js +30 -0
  308. package/dist/server/sync/status.d.ts +63 -0
  309. package/dist/server/sync/status.js +49 -0
  310. package/dist/server/sync/timer.d.ts +52 -0
  311. package/dist/server/sync/timer.js +170 -0
  312. package/dist/server/sync/tokens.d.ts +25 -0
  313. package/dist/server/sync/tokens.js +51 -0
  314. package/dist/server/sync/types.d.ts +101 -0
  315. package/dist/server/sync/types.js +29 -0
  316. package/dist/server/ws/eventBus.d.ts +14 -0
  317. package/dist/server/ws/eventBus.js +42 -0
  318. package/dist/server/ws/hub.d.ts +20 -0
  319. package/dist/server/ws/hub.js +154 -0
  320. package/dist/server/ws/index.d.ts +5 -0
  321. package/dist/server/ws/index.js +7 -0
  322. package/dist/server/ws/presence.d.ts +11 -0
  323. package/dist/server/ws/presence.js +20 -0
  324. package/dist/server/ws/room.d.ts +17 -0
  325. package/dist/server/ws/room.js +23 -0
  326. package/dist/server/ws/types.d.ts +122 -0
  327. package/dist/server/ws/types.js +1 -0
  328. package/package.json +234 -0
  329. package/src/declaredDepsAreImported.spec.ts +87 -0
  330. package/src/leafSubpathsImportNothing.spec.ts +358 -0
  331. package/src/lockfileIsTracked.spec.ts +50 -0
  332. package/src/namedSubpathsResolve.spec.ts +122 -0
  333. package/src/noPathDeps.spec.ts +24 -0
  334. package/src/publishShape.spec.ts +89 -0
  335. package/src/server/activity/index.ts +59 -0
  336. package/src/server/activity/migrations.ts +67 -0
  337. package/src/server/activity/shareSource.ts +57 -0
  338. package/src/server/activity/store.spec.ts +255 -0
  339. package/src/server/activity/store.ts +368 -0
  340. package/src/server/app.spec.ts +243 -0
  341. package/src/server/app.ts +89 -0
  342. package/src/server/audit/index.ts +2 -0
  343. package/src/server/audit/migrations.ts +88 -0
  344. package/src/server/audit/sqliteSink.test.ts +200 -0
  345. package/src/server/audit/sqliteSink.ts +162 -0
  346. package/src/server/auth/cookie.spec.ts +46 -0
  347. package/src/server/auth/cookie.ts +42 -0
  348. package/src/server/auth/csrf.spec.ts +81 -0
  349. package/src/server/auth/csrf.ts +83 -0
  350. package/src/server/auth/fileTokenSigner.ts +40 -0
  351. package/src/server/auth/getOnly.spec.ts +53 -0
  352. package/src/server/auth/getOnly.ts +32 -0
  353. package/src/server/auth/index.ts +51 -0
  354. package/src/server/auth/jwt.spec.ts +106 -0
  355. package/src/server/auth/jwt.ts +75 -0
  356. package/src/server/auth/loginThrottle.spec.ts +213 -0
  357. package/src/server/auth/loginThrottle.ts +261 -0
  358. package/src/server/auth/middleware.spec.ts +148 -0
  359. package/src/server/auth/middleware.ts +56 -0
  360. package/src/server/auth/password.spec.ts +48 -0
  361. package/src/server/auth/password.ts +71 -0
  362. package/src/server/auth/sessionStore.spec.ts +189 -0
  363. package/src/server/auth/sessionStore.ts +261 -0
  364. package/src/server/auth/totp.spec.ts +73 -0
  365. package/src/server/auth/totp.ts +92 -0
  366. package/src/server/auth/types.ts +146 -0
  367. package/src/server/auth/vault.spec.ts +51 -0
  368. package/src/server/auth/vault.ts +71 -0
  369. package/src/server/context.ts +37 -0
  370. package/src/server/ctgr.spec.ts +138 -0
  371. package/src/server/ctgr.ts +108 -0
  372. package/src/server/db/kysely.spec.ts +62 -0
  373. package/src/server/db/kysely.ts +21 -0
  374. package/src/server/db/plumbingTypes.ts +126 -0
  375. package/src/server/dropzone/duplicates.ts +121 -0
  376. package/src/server/dropzone/index.ts +30 -0
  377. package/src/server/dropzone/scan.spec.ts +255 -0
  378. package/src/server/dropzone/scan.ts +342 -0
  379. package/src/server/errors.spec.ts +76 -0
  380. package/src/server/errors.ts +22 -0
  381. package/src/server/guard/argon2Binding.spec.ts +117 -0
  382. package/src/server/guard/argon2Binding.ts +195 -0
  383. package/src/server/guard/argon2Kat.spec.ts +250 -0
  384. package/src/server/guard/guard.spec.ts +390 -0
  385. package/src/server/guard/index.ts +6 -0
  386. package/src/server/guard/kdfWorker.spec.ts +283 -0
  387. package/src/server/guard/kdfWorker.ts +367 -0
  388. package/src/server/guard/kdfWorkerEntry.ts +16 -0
  389. package/src/server/guard/passwordVerifier.spec.ts +71 -0
  390. package/src/server/guard/passwordVerifier.ts +175 -0
  391. package/src/server/guard/revocationStore.spec.ts +135 -0
  392. package/src/server/guard/revocationStore.ts +291 -0
  393. package/src/server/guard/sessionSigner.ts +425 -0
  394. package/src/server/guard/verifier.ts +27 -0
  395. package/src/server/incidents/channels.ts +487 -0
  396. package/src/server/incidents/checks.ts +120 -0
  397. package/src/server/incidents/incidents.spec.ts +463 -0
  398. package/src/server/incidents/index.ts +96 -0
  399. package/src/server/incidents/migrations.ts +66 -0
  400. package/src/server/incidents/notifier.ts +215 -0
  401. package/src/server/incidents/reporter.ts +110 -0
  402. package/src/server/incidents/routes.ts +314 -0
  403. package/src/server/incidents/store.ts +329 -0
  404. package/src/server/incidents/types.ts +116 -0
  405. package/src/server/index.ts +248 -0
  406. package/src/server/jobs/fireQueue.spec.ts +85 -0
  407. package/src/server/jobs/fireQueue.ts +37 -0
  408. package/src/server/jobs/index.ts +26 -0
  409. package/src/server/jobs/logger.ts +16 -0
  410. package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
  411. package/src/server/jobs/plainjobAdapter.ts +54 -0
  412. package/src/server/jobs/polling.spec.ts +219 -0
  413. package/src/server/jobs/polling.ts +81 -0
  414. package/src/server/jobs/runner.spec.ts +213 -0
  415. package/src/server/jobs/runner.ts +213 -0
  416. package/src/server/jobs/timeout.spec.ts +35 -0
  417. package/src/server/jobs/timeout.ts +53 -0
  418. package/src/server/jobs/types.ts +188 -0
  419. package/src/server/master-lock/guard.spec.ts +347 -0
  420. package/src/server/master-lock/guard.ts +290 -0
  421. package/src/server/master-lock/index.ts +44 -0
  422. package/src/server/master-lock/lockPage.spec.ts +88 -0
  423. package/src/server/master-lock/lockPage.ts +313 -0
  424. package/src/server/master-lock/masterLock.spec.ts +338 -0
  425. package/src/server/master-lock/masterLock.ts +627 -0
  426. package/src/server/master-lock/principals.spec.ts +378 -0
  427. package/src/server/master-lock/principals.ts +175 -0
  428. package/src/server/master-lock/seed.spec.ts +69 -0
  429. package/src/server/master-lock/seed.ts +112 -0
  430. package/src/server/master-lock/store.ts +35 -0
  431. package/src/server/media-bun/image.spec.ts +115 -0
  432. package/src/server/media-bun/image.ts +134 -0
  433. package/src/server/media-bun/index.ts +52 -0
  434. package/src/server/media-bun/streaming.spec.ts +134 -0
  435. package/src/server/media-bun/streaming.ts +347 -0
  436. package/src/server/media-bun/video.spec.ts +115 -0
  437. package/src/server/media-bun/video.ts +232 -0
  438. package/src/server/metrics/metricsBuffer.ts +92 -0
  439. package/src/server/metrics/normalizeRoute.spec.ts +51 -0
  440. package/src/server/metrics/normalizeRoute.ts +63 -0
  441. package/src/server/middleware/correlationId.ts +24 -0
  442. package/src/server/middleware/corsAllowlist.spec.ts +118 -0
  443. package/src/server/middleware/corsAllowlist.ts +69 -0
  444. package/src/server/middleware/errorEnvelope.ts +38 -0
  445. package/src/server/middleware/index.ts +19 -0
  446. package/src/server/middleware/localOrigin.spec.ts +168 -0
  447. package/src/server/middleware/localOrigin.ts +139 -0
  448. package/src/server/middleware/rateLimit.spec.ts +72 -0
  449. package/src/server/middleware/rateLimit.ts +125 -0
  450. package/src/server/middleware/requestLogger.ts +95 -0
  451. package/src/server/middleware/securityHeaders.ts +78 -0
  452. package/src/server/middleware/zValidator.spec.ts +251 -0
  453. package/src/server/middleware/zValidator.ts +114 -0
  454. package/src/server/migration/runner.spec.ts +88 -0
  455. package/src/server/migration/runner.ts +87 -0
  456. package/src/server/migration/types.ts +17 -0
  457. package/src/server/net/outboundGuard.spec.ts +90 -0
  458. package/src/server/net/outboundGuard.ts +205 -0
  459. package/src/server/notifications/hub.ts +245 -0
  460. package/src/server/notifications/index.ts +77 -0
  461. package/src/server/notifications/migrations.ts +39 -0
  462. package/src/server/notifications/routes.ts +177 -0
  463. package/src/server/notifications/service.spec.ts +305 -0
  464. package/src/server/notifications/service.ts +112 -0
  465. package/src/server/paths/appDataHome.spec.ts +87 -0
  466. package/src/server/paths/appDataHome.ts +84 -0
  467. package/src/server/private-media/blobRoute.spec.ts +165 -0
  468. package/src/server/private-media/crypto.spec.ts +81 -0
  469. package/src/server/private-media/crypto.ts +155 -0
  470. package/src/server/private-media/gatePolicy.spec.ts +98 -0
  471. package/src/server/private-media/index-store.ts +83 -0
  472. package/src/server/private-media/mountPrivateRoutes.ts +265 -0
  473. package/src/server/private-media/pipeline.spec.ts +111 -0
  474. package/src/server/private-media/pipeline.ts +170 -0
  475. package/src/server/private-media/routes.spec.ts +265 -0
  476. package/src/server/private-media/unlock.ts +112 -0
  477. package/src/server/resume/index.ts +8 -0
  478. package/src/server/resume/resume.spec.ts +118 -0
  479. package/src/server/resume/routes.ts +92 -0
  480. package/src/server/resume/store.ts +82 -0
  481. package/src/server/retention/archive.spec.ts +181 -0
  482. package/src/server/retention/archive.ts +353 -0
  483. package/src/server/retention/fleet.ts +104 -0
  484. package/src/server/retention/index.ts +101 -0
  485. package/src/server/retention/policy.spec.ts +132 -0
  486. package/src/server/retention/policy.ts +392 -0
  487. package/src/server/retention/store.spec.ts +236 -0
  488. package/src/server/retention/store.ts +338 -0
  489. package/src/server/retention/sweeper.spec.ts +321 -0
  490. package/src/server/retention/sweeper.ts +367 -0
  491. package/src/server/retention/target.spec.ts +592 -0
  492. package/src/server/retention/target.ts +613 -0
  493. package/src/server/search/index.ts +12 -0
  494. package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
  495. package/src/server/search/pgSearchProvider.ts +421 -0
  496. package/src/server/search/pgSearchSchema.ts +147 -0
  497. package/src/server/sharing/index.ts +65 -0
  498. package/src/server/sharing/migrations.ts +128 -0
  499. package/src/server/sharing/router.spec.ts +167 -0
  500. package/src/server/sharing/router.ts +194 -0
  501. package/src/server/sharing/sql.ts +109 -0
  502. package/src/server/sharing/store.spec.ts +408 -0
  503. package/src/server/sharing/store.ts +737 -0
  504. package/src/server/sqlite/backup.spec.ts +114 -0
  505. package/src/server/sqlite/backup.ts +154 -0
  506. package/src/server/sqlite/browse.spec.ts +70 -0
  507. package/src/server/sqlite/browse.ts +120 -0
  508. package/src/server/sqlite/export.spec.ts +43 -0
  509. package/src/server/sqlite/export.ts +74 -0
  510. package/src/server/sqlite/fts5.spec.ts +175 -0
  511. package/src/server/sqlite/fts5.ts +255 -0
  512. package/src/server/sqlite/health.spec.ts +79 -0
  513. package/src/server/sqlite/health.ts +105 -0
  514. package/src/server/sqlite/import.spec.ts +75 -0
  515. package/src/server/sqlite/import.ts +141 -0
  516. package/src/server/sqlite/index.ts +64 -0
  517. package/src/server/sqlite/readonlySql.spec.ts +58 -0
  518. package/src/server/sqlite/readonlySql.ts +67 -0
  519. package/src/server/sqlite/stats.spec.ts +21 -0
  520. package/src/server/sqlite/stats.ts +61 -0
  521. package/src/server/sqlite/viewer.spec.ts +127 -0
  522. package/src/server/sqlite/viewer.ts +72 -0
  523. package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
  524. package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
  525. package/src/server/storage/binaryServerProcessing.ts +127 -0
  526. package/src/server/storage/binaryServerStore.ts +102 -0
  527. package/src/server/storage/chunked.integration.spec.ts +185 -0
  528. package/src/server/storage/diskStore.ts +83 -0
  529. package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
  530. package/src/server/storage/files/adapter.photo.spec.ts +128 -0
  531. package/src/server/storage/files/adapter.spec.ts +121 -0
  532. package/src/server/storage/files/adapter.ts +255 -0
  533. package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
  534. package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
  535. package/src/server/storage/files/catalogue.private.spec.ts +106 -0
  536. package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
  537. package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
  538. package/src/server/storage/files/catalogue.spec.ts +133 -0
  539. package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
  540. package/src/server/storage/files/catalogue.ts +1326 -0
  541. package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
  542. package/src/server/storage/files/chunkedUpload.ts +146 -0
  543. package/src/server/storage/files/collections.spec.ts +294 -0
  544. package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
  545. package/src/server/storage/files/fileRoutes.spec.ts +327 -0
  546. package/src/server/storage/files/fileRoutes.ts +462 -0
  547. package/src/server/storage/files/index.ts +66 -0
  548. package/src/server/storage/files/migrations.ts +187 -0
  549. package/src/server/storage/files/mimeByExt.ts +14 -0
  550. package/src/server/storage/files/stack.spec.ts +258 -0
  551. package/src/server/storage/files/stack.ts +284 -0
  552. package/src/server/storage/files/storageOps.spec.ts +126 -0
  553. package/src/server/storage/files/storageOps.ts +213 -0
  554. package/src/server/storage/files/types.ts +285 -0
  555. package/src/server/storage/imageThumbnail.spec.ts +81 -0
  556. package/src/server/storage/imageThumbnail.ts +60 -0
  557. package/src/server/storage/index.ts +122 -0
  558. package/src/server/storage/mountMediaRoutes.ts +231 -0
  559. package/src/server/storage/objectStore.spec.ts +272 -0
  560. package/src/server/storage/objectStoreMediaStore.ts +63 -0
  561. package/src/server/storage/s3ObjectStore.ts +239 -0
  562. package/src/server/storage/sources.ts +245 -0
  563. package/src/server/storage/storage.spec.ts +219 -0
  564. package/src/server/storage/streamRange.ts +92 -0
  565. package/src/server/storage/streamToken.ts +76 -0
  566. package/src/server/storage/transcode.integration.spec.ts +134 -0
  567. package/src/server/storage/transcodeJobs.spec.ts +152 -0
  568. package/src/server/storage/transcodeJobs.ts +171 -0
  569. package/src/server/storage/types.ts +230 -0
  570. package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
  571. package/src/server/storage/videoDerivativeKeys.ts +172 -0
  572. package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
  573. package/src/server/storage/videoProcessor.ts +229 -0
  574. package/src/server/sync/alarm.spec.ts +149 -0
  575. package/src/server/sync/alarm.ts +137 -0
  576. package/src/server/sync/commands.spec.ts +145 -0
  577. package/src/server/sync/commands.ts +361 -0
  578. package/src/server/sync/engine.spec.ts +496 -0
  579. package/src/server/sync/engine.ts +255 -0
  580. package/src/server/sync/http.ts +316 -0
  581. package/src/server/sync/httpRemote.spec.ts +158 -0
  582. package/src/server/sync/index.ts +90 -0
  583. package/src/server/sync/opLog.spec.ts +110 -0
  584. package/src/server/sync/opLog.ts +189 -0
  585. package/src/server/sync/planner.spec.ts +53 -0
  586. package/src/server/sync/planner.ts +62 -0
  587. package/src/server/sync/status.spec.ts +92 -0
  588. package/src/server/sync/status.ts +108 -0
  589. package/src/server/sync/timer.spec.ts +150 -0
  590. package/src/server/sync/timer.ts +207 -0
  591. package/src/server/sync/tokens.spec.ts +38 -0
  592. package/src/server/sync/tokens.ts +94 -0
  593. package/src/server/sync/types.ts +108 -0
  594. package/src/server/ws/eventBus.spec.ts +67 -0
  595. package/src/server/ws/eventBus.ts +46 -0
  596. package/src/server/ws/hub.integration.spec.ts +93 -0
  597. package/src/server/ws/hub.spec.ts +282 -0
  598. package/src/server/ws/hub.ts +168 -0
  599. package/src/server/ws/index.ts +20 -0
  600. package/src/server/ws/presence.spec.ts +27 -0
  601. package/src/server/ws/presence.ts +26 -0
  602. package/src/server/ws/room.spec.ts +36 -0
  603. package/src/server/ws/room.ts +29 -0
  604. package/src/server/ws/types.ts +134 -0
  605. package/src/skippedTestsAreAnswered.spec.ts +215 -0
  606. package/src/testTmpRoot.spec.ts +102 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `cursedbelt-server/retention` — THE fleet's one retention standard.
3
+ *
4
+ * ── Why this lives in cursedbelt ────────────────────────────────────────────
5
+ * Retention used to be spoken in three unrelated vocabularies: this
6
+ * category-based opt-in design (born in `cursed-satellites/satellite-kit`), the
7
+ * DB-backed audit|meter WINDOWS in `cursedbelt-cc/drivers`' retentionPolicy
8
+ * (billing evidence — a genuinely different concern that keeps the name
9
+ * {@link RetentionWindows}), and `deploy-engine/backupRetention`'s artifact
10
+ * pruning. DEDUP-8(c) made THIS one canonical and moved it here, so a single
11
+ * copy serves cursed-core, cursed-satellites and every generated app, and
12
+ * `RetentionPolicy` names exactly one thing across the fleet. `satellite-kit`'s
13
+ * `./retention` subpath is now a thin re-export of this module.
14
+ *
15
+ * An app declares what it owns and starts a sweeper; the station reads and
16
+ * edits the policies. Nothing else in the fleet should contain a timer that
17
+ * deletes old rows.
18
+ *
19
+ * const sweeper = createRetentionSweeper({
20
+ * app: "orch",
21
+ * targets: [
22
+ * createSqliteTableTarget({ db, declaration: {...}, table: "events", timeColumn: "at" }),
23
+ * createFileDirTarget({ declaration: {...}, dir: attemptLogs.dir, match: /\.jsonl$/ }),
24
+ * ],
25
+ * });
26
+ * sweeper.start();
27
+ *
28
+ * See `policy.ts` for the rule that decides whether a resource may be governed
29
+ * at all — retention is opt-in by category, and no category can describe data
30
+ * the owner would miss (which is exactly why billing evidence is NOT a category
31
+ * and lives in `retentionPolicy.ts` instead).
32
+ */
33
+ export {
34
+ archiveStamp,
35
+ createJsonlArchive,
36
+ DEFAULT_PENDING_ARCHIVE_BUDGET_BYTES,
37
+ resolveArchiveHome,
38
+ verifyArchive,
39
+ type ArchiveHome,
40
+ type ArchiveSegment,
41
+ type ArchiveSink,
42
+ type JsonlArchiveOptions,
43
+ } from "./archive";
44
+ export {
45
+ ARCHIVING_RETENTION_CATEGORIES,
46
+ assertRetentionDeclaration,
47
+ categoryArchivesBeforeDelete,
48
+ cutoffFor,
49
+ DAY_MS,
50
+ FLEET_DEFAULT_RETENTION_DAYS,
51
+ RETENTION_CATEGORIES,
52
+ RETENTION_CATEGORY_KEYS,
53
+ RETENTION_MAX_DAYS,
54
+ RETENTION_MIN_DAYS,
55
+ resolveRetentionPolicy,
56
+ retentionKey,
57
+ type EffectiveRetentionPolicy,
58
+ // The fleet's one `RetentionPolicy`: the resolved, in-effect policy a sweep
59
+ // runs with. An alias, so callers can spell the canonical name and the two
60
+ // specialized concepts (billing `RetentionWindows`, backup
61
+ // `BackupRetentionPolicy`) no longer collide on it.
62
+ type RetentionPolicy,
63
+ type RetentionCategory,
64
+ type RetentionDeclaration,
65
+ type RetentionFieldSource,
66
+ type RetentionOverride,
67
+ type RetentionUnit,
68
+ } from "./policy";
69
+ export {
70
+ publishRetention,
71
+ readRetentionOverrides,
72
+ readRetentionRegistry,
73
+ resolveRetentionHome,
74
+ setRetentionHomeForTests,
75
+ writeRetentionOverride,
76
+ type PublishedResource,
77
+ type RetentionMeasurement,
78
+ type RetentionOverridesFile,
79
+ type RetentionPublication,
80
+ type RetentionSweepReport,
81
+ } from "./store";
82
+ export {
83
+ createArchivingSqliteTableTarget,
84
+ createFileDirTarget,
85
+ createSqliteTableTarget,
86
+ type ArchivingSqliteTableTargetOptions,
87
+ type FileDirTargetOptions,
88
+ type RetentionPreview,
89
+ type RetentionTarget,
90
+ type SqliteTableTargetOptions,
91
+ } from "./target";
92
+ export {
93
+ createRetentionSweeper,
94
+ DEFAULT_SWEEP_INTERVAL_MS,
95
+ OVERRIDES_PULSE_MS,
96
+ type CreateRetentionSweeperOptions,
97
+ type ResourceOutcome,
98
+ type RetentionRunReport,
99
+ type RetentionSweeper,
100
+ } from "./sweeper";
101
+ export { buildRetentionFleetView, type RetentionFleetRow, type RetentionFleetView } from "./fleet";
@@ -0,0 +1,132 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ ARCHIVING_RETENTION_CATEGORIES,
4
+ assertRetentionDeclaration,
5
+ categoryArchivesBeforeDelete,
6
+ cutoffFor,
7
+ DAY_MS,
8
+ FLEET_DEFAULT_RETENTION_DAYS,
9
+ RETENTION_CATEGORIES,
10
+ RETENTION_CATEGORY_KEYS,
11
+ resolveRetentionPolicy,
12
+ retentionKey,
13
+ type RetentionDeclaration,
14
+ } from "./policy";
15
+
16
+ const decl = (over: Partial<RetentionDeclaration> = {}): RetentionDeclaration => ({
17
+ app: "orch",
18
+ id: "events",
19
+ label: "Event trail",
20
+ category: "event-log",
21
+ unit: "rows",
22
+ location: "orch.sqlite · events",
23
+ consequence: "the narrative of past runs",
24
+ ...over,
25
+ });
26
+
27
+ describe("the category vocabulary is the guard on long-term data", () => {
28
+ test("no DELETING category defaults longer than the owner's two-week standard", () => {
29
+ // The standard is the whole point: a category wanting longer would be
30
+ // describing data that belongs to the user, which this vocabulary must not
31
+ // be able to express. The one exemption is a category that ARCHIVES first —
32
+ // its number says where the history lives, not how long it exists — and it
33
+ // is tested separately below so the exemption cannot widen quietly.
34
+ for (const key of RETENTION_CATEGORY_KEYS) {
35
+ expect(RETENTION_CATEGORIES[key].defaultDays).toBeGreaterThan(0);
36
+ if (categoryArchivesBeforeDelete(key)) continue;
37
+ expect(RETENTION_CATEGORIES[key].defaultDays, `${key} deletes, so it is capped`).toBeLessThanOrEqual(
38
+ FLEET_DEFAULT_RETENTION_DAYS,
39
+ );
40
+ }
41
+ });
42
+
43
+ test("the longer-than-the-standard exemption belongs to archiving categories ONLY", () => {
44
+ // Read this the other way round: any category that is over the standard has
45
+ // to be one that archives. A future entry that reaches for 90 days without
46
+ // an archive behind it fails here rather than at a prod DELETE.
47
+ const overTheStandard = RETENTION_CATEGORY_KEYS.filter(
48
+ (key) => RETENTION_CATEGORIES[key].defaultDays > FLEET_DEFAULT_RETENTION_DAYS,
49
+ );
50
+ expect(overTheStandard.sort()).toEqual([...ARCHIVING_RETENTION_CATEGORIES].sort());
51
+ // And the exemption exists at all — a green test over an empty set would
52
+ // keep passing if `archived-history` were deleted tomorrow.
53
+ expect(ARCHIVING_RETENTION_CATEGORIES).toContain("archived-history");
54
+ expect(categoryArchivesBeforeDelete("archived-history")).toBe(true);
55
+ expect(categoryArchivesBeforeDelete("event-log")).toBe(false);
56
+ });
57
+
58
+ test("every category explains itself — the UI shows the summary, not the key", () => {
59
+ for (const key of RETENTION_CATEGORY_KEYS) {
60
+ expect(RETENTION_CATEGORIES[key].summary.length).toBeGreaterThan(20);
61
+ expect(RETENTION_CATEGORIES[key].label.length).toBeGreaterThan(2);
62
+ }
63
+ });
64
+
65
+ test("an unknown category is refused, and the message names the closed set", () => {
66
+ expect(() => assertRetentionDeclaration(decl({ category: "user-notes" as never }))).toThrow(
67
+ /unknown category/,
68
+ );
69
+ expect(() => assertRetentionDeclaration(decl({ category: "user-notes" as never }))).toThrow(
70
+ /opt-in/,
71
+ );
72
+ });
73
+
74
+ test("a declaration must say what is lost — the sentence the owner reads before changing it", () => {
75
+ expect(() => assertRetentionDeclaration(decl({ consequence: " " }))).toThrow(/what is lost/);
76
+ });
77
+
78
+ test("ids and app keys must be usable as an override key", () => {
79
+ expect(() => assertRetentionDeclaration(decl({ id: "Events" }))).toThrow(/lower-kebab/);
80
+ expect(() => assertRetentionDeclaration(decl({ app: "orch/../etc" }))).toThrow(/lower-kebab/);
81
+ expect(retentionKey("orch", "events")).toBe("orch:events");
82
+ });
83
+ });
84
+
85
+ describe("resolveRetentionPolicy", () => {
86
+ test("with no override it is the category default, sourced as such", () => {
87
+ const policy = resolveRetentionPolicy(decl());
88
+ expect(policy.days).toBe(RETENTION_CATEGORIES["event-log"].defaultDays);
89
+ expect(policy.sources.days).toBe("default");
90
+ expect(policy.enabled).toBe(true);
91
+ });
92
+
93
+ test("a declaration may set a shorter default than its category", () => {
94
+ expect(resolveRetentionPolicy(decl({ defaultDays: 3 })).days).toBe(3);
95
+ });
96
+
97
+ test("an override wins and is sourced as an override", () => {
98
+ const policy = resolveRetentionPolicy(decl(), { days: 30 });
99
+ expect(policy.days).toBe(30);
100
+ expect(policy.sources.days).toBe("override");
101
+ });
102
+
103
+ test("an out-of-bounds override is clamped and REPORTS that it was", () => {
104
+ // Silently clamping would show the owner a number the sweep does not use.
105
+ const policy = resolveRetentionPolicy(decl({ maxDays: 90 }), { days: 4000 });
106
+ expect(policy.days).toBe(90);
107
+ expect(policy.clamped).toEqual({ field: "days", requested: 4000, applied: 90 });
108
+ });
109
+
110
+ test("days: 0 means keep forever and is NOT clamped up to minDays", () => {
111
+ // Clamping it would silently re-arm a sweep the owner deliberately turned off.
112
+ const policy = resolveRetentionPolicy(decl({ minDays: 7 }), { days: 0 });
113
+ expect(policy.days).toBe(0);
114
+ expect(policy.clamped).toBeUndefined();
115
+ expect(cutoffFor(policy, 1_000_000)).toBeNull();
116
+ });
117
+
118
+ test("enabled: false suspends the age rule entirely", () => {
119
+ const policy = resolveRetentionPolicy(decl(), { enabled: false });
120
+ expect(policy.enabled).toBe(false);
121
+ expect(cutoffFor(policy, 1_000_000)).toBeNull();
122
+ });
123
+
124
+ test("the floor survives into the effective policy", () => {
125
+ expect(resolveRetentionPolicy(decl({ floorItems: 25 })).floorItems).toBe(25);
126
+ });
127
+
128
+ test("cutoff is the age rule in one place", () => {
129
+ const now = 10 * DAY_MS;
130
+ expect(cutoffFor(resolveRetentionPolicy(decl(), { days: 3 }), now)).toBe(now - 3 * DAY_MS);
131
+ });
132
+ });
@@ -0,0 +1,392 @@
1
+ /**
2
+ * The fleet's retention VOCABULARY — one place that decides how long
3
+ * regenerable, diagnostic data lives, and what may never be governed this way.
4
+ *
5
+ * ── Why this exists ─────────────────────────────────────────────────────────
6
+ * Before this module, every app that bounded a growing table did it by hand and
7
+ * decided the number privately: `watchdog_runs` keeps the newest N on insert,
8
+ * `ai_check_runs` keeps N per check, the proposal ledger caps at 200,
9
+ * orch's own `eventRetention` was a whole bespoke sweeper with its own timer, and `deploy-engine/backupRetention.ts` a second one with different
10
+ * rules. Meanwhile the things nobody had written a rule for grew without limit:
11
+ * orch's own state directory reached **1.0 GB** — 74 MB of attempt transcripts and a
12
+ * single 70 MB peer-link `dedupe.json` holding 118 entries — with no policy, no
13
+ * backup, and nothing that could report it.
14
+ *
15
+ * The owner's ask (2026-08-01) was for ONE standard: a default that keeps
16
+ * diagnostic data no more than two weeks, per-resource overrides, a UI to see
17
+ * and change them, and no per-app copies of the logic.
18
+ *
19
+ * ── The rule that makes this safe: retention is OPT-IN, by CATEGORY ─────────
20
+ * A resource can only be swept if it declares a `RetentionCategory`, and the
21
+ * categories below describe exactly one kind of data: **a record that exists so
22
+ * a human can find out what happened, and whose loss costs nothing but
23
+ * hindsight.** There is deliberately no category that could describe a note, a
24
+ * saved setting, a password, a save state, a shelf row or a vault entry — so
25
+ * "should this table have a policy?" is answered by whether an honest category
26
+ * exists for it, not by an agent's judgment in the moment.
27
+ *
28
+ * Long-term data is therefore protected by ABSENCE, not by a denylist a future
29
+ * table would have to be remembered into. Two further gates back it up:
30
+ * `scripts/retentionPolicies.test.ts` pins the exact (app, resource) set that
31
+ * sweeps anywhere in this repo — adding one is an edit to a reviewed list, never
32
+ * a side effect — and `floorItems` below means even a mis-set policy cannot
33
+ * empty a resource.
34
+ *
35
+ * ── The missing middle, added 2026-08-25: ARCHIVE-then-trim ─────────────────
36
+ * Until now a resource was either governed (swept, gone forever) or undeclared
37
+ * (kept forever on a 6.7 GB prod root). Neither describes an append-only history
38
+ * whose recent window is load-bearing and whose tail is worth keeping but not
39
+ * worth keeping THERE. `archived-history` is that third answer: the instance
40
+ * keeps a bounded window and everything older is written to a verified,
41
+ * append-only archive BEFORE a single row is deleted.
42
+ *
43
+ * That is why it is the one category allowed a default longer than the owner's
44
+ * two-week standard, and the exemption is narrow by construction rather than by
45
+ * convention: `archivesBeforeDelete` is a property of the CATEGORY, the
46
+ * plain-delete targets in `target.ts` REFUSE a declaration that carries it, and
47
+ * the archiving target refuses to delete a row it cannot prove it has archived.
48
+ * A category that could be swept by a target that only deletes would be a
49
+ * longer retention with no archive at the end of it — strictly worse than having
50
+ * no category at all.
51
+ *
52
+ * ── Anything that grows without bound and is NOT sweepable is a design bug ──
53
+ * The other half of the standard. If a resource grows forever and no category
54
+ * fits it, the answer is not a longer retention — it is that the thing should
55
+ * not be accumulating (peer-link's dedupe file was storing 600 KB command
56
+ * RESULTS to answer a replay question that needs only an id).
57
+ */
58
+
59
+ /**
60
+ * The closed set of things a retention policy may govern.
61
+ *
62
+ * Each entry's `defaultDays` is the fleet default for that shape. The owner's
63
+ * standard is two weeks; a DELETING category is shorter only where the data is
64
+ * cheaper than that to lose, and none is longer — a deleting category that
65
+ * wanted a longer default would be describing data that belongs to the user,
66
+ * which is the exact case this vocabulary refuses to express.
67
+ *
68
+ * `archivesBeforeDelete` is the single, narrow exemption: the rows leave the
69
+ * instance but not existence, so the number is a WINDOW rather than a lifetime
70
+ * and may honestly be longer. `policy.spec.ts` pins both halves of that rule.
71
+ */
72
+ export const RETENTION_CATEGORIES = {
73
+ "audit-log": {
74
+ label: "Audit log",
75
+ defaultDays: 14,
76
+ summary: "Who did what, kept so an incident can be reconstructed. Read only after a problem.",
77
+ },
78
+ "event-log": {
79
+ label: "Event log",
80
+ defaultDays: 14,
81
+ summary: "State transitions and activity trails. The narrative of a run, not the run's result.",
82
+ },
83
+ "run-log": {
84
+ label: "Run log",
85
+ defaultDays: 14,
86
+ summary: "One row per execution of a job, check or sweep — its outcome ledger.",
87
+ },
88
+ transcript: {
89
+ label: "Transcript",
90
+ defaultDays: 14,
91
+ summary: "Verbatim worker/tool output. Large, read once during triage, never again.",
92
+ },
93
+ metric: {
94
+ label: "Metric",
95
+ defaultDays: 14,
96
+ summary: "Sampled counters and telemetry. Interesting in aggregate and recent, not per-row.",
97
+ },
98
+ cache: {
99
+ label: "Cache",
100
+ defaultDays: 7,
101
+ summary: "Recomputable from its source. Shorter by default because losing it costs a refetch.",
102
+ },
103
+ "replica-mirror": {
104
+ label: "Replica mirror",
105
+ defaultDays: 7,
106
+ summary: "A copy of state that another instance owns authoritatively. Never the only copy.",
107
+ },
108
+ "queue-result": {
109
+ label: "Queue result",
110
+ defaultDays: 3,
111
+ summary:
112
+ "Completed command/job rows and their payloads, kept only long enough to answer a retry or a replay.",
113
+ },
114
+ "scratch-artifact": {
115
+ label: "Scratch artifact",
116
+ defaultDays: 14,
117
+ summary:
118
+ "Generated files kept for inspection — renders, dumps, exports. Reproducible by rerunning.",
119
+ },
120
+ "archived-history": {
121
+ label: "Archived history",
122
+ defaultDays: 30,
123
+ archivesBeforeDelete: true,
124
+ summary:
125
+ "Append-only history whose recent window is load-bearing and whose tail is not. The instance keeps the window; everything older is written to a verified archive first and only then deleted, so the number is where the history LIVES, never how long it exists.",
126
+ },
127
+ } as const satisfies Record<
128
+ string,
129
+ { label: string; defaultDays: number; summary: string; archivesBeforeDelete?: boolean }
130
+ >;
131
+
132
+ export type RetentionCategory = keyof typeof RETENTION_CATEGORIES;
133
+
134
+ export const RETENTION_CATEGORY_KEYS = Object.keys(RETENTION_CATEGORIES) as RetentionCategory[];
135
+
136
+ /**
137
+ * Does this category promise the rows survive the sweep somewhere else?
138
+ *
139
+ * The one question that decides which TARGET may govern a resource, so it is
140
+ * asked as a function rather than by reading a property at four call sites — a
141
+ * plain-delete target checks it to refuse, the archiving target checks it to
142
+ * insist, and `policy.spec.ts` checks it to allow the longer default.
143
+ */
144
+ export function categoryArchivesBeforeDelete(category: RetentionCategory): boolean {
145
+ // An UNKNOWN category answers `false` rather than throwing. It is asked at
146
+ // target construction, which runs before `assertRetentionDeclaration` gets to
147
+ // say "unknown category (the closed set is …)" — and a TypeError about an
148
+ // undefined property would replace that message with one that names nothing.
149
+ const entry = RETENTION_CATEGORIES[category] as { archivesBeforeDelete?: boolean } | undefined;
150
+ return entry?.archivesBeforeDelete === true;
151
+ }
152
+
153
+ /** The categories whose rows are archived rather than destroyed. */
154
+ export const ARCHIVING_RETENTION_CATEGORIES = RETENTION_CATEGORY_KEYS.filter(
155
+ categoryArchivesBeforeDelete,
156
+ );
157
+
158
+ /**
159
+ * The owner's standard (2026-08-01): diagnostic data lives two weeks unless
160
+ * something says otherwise. Exported because the UI states it as the house rule
161
+ * rather than repeating a literal, and because a category's default is only ever
162
+ * allowed to be this or shorter (pinned by `policy.test.ts`).
163
+ */
164
+ export const FLEET_DEFAULT_RETENTION_DAYS = 14;
165
+
166
+ /** Bounds an override is clamped into unless a declaration narrows them. */
167
+ export const RETENTION_MIN_DAYS = 1;
168
+ export const RETENTION_MAX_DAYS = 365;
169
+
170
+ /** What a resource is counted in — decides the UI's noun and the target's `measure`. */
171
+ export type RetentionUnit = "rows" | "files";
172
+
173
+ /**
174
+ * One governable resource, declared by the app that owns it.
175
+ *
176
+ * `id` is stable and app-scoped (`events`, `attempt-transcripts`), because it is
177
+ * the override key — renaming one silently orphans the owner's setting, which is
178
+ * why `retentionPolicies.test.ts` pins the ids too.
179
+ */
180
+ export interface RetentionDeclaration {
181
+ /** App key, matching the fleet registry's app-tracking rows (`orch`, `notes`, …). */
182
+ app: string;
183
+ /** Stable, app-scoped id. The override key is `<app>:<id>`. */
184
+ id: string;
185
+ /** Human label for the UI. */
186
+ label: string;
187
+ category: RetentionCategory;
188
+ unit: RetentionUnit;
189
+ /** Where it physically lives, for the UI: `orch.sqlite · events`, `logs/attempts/*.jsonl`. */
190
+ location: string;
191
+ /** One sentence: what is lost when this ages out. The UI shows it beside the control. */
192
+ consequence: string;
193
+ /** Defaults to the category's `defaultDays`. */
194
+ defaultDays?: number;
195
+ /**
196
+ * Absolute item-count backstop, liveness ignored, applied after the age
197
+ * sweep. `0` disables it. This is the bound that must hold when the age sweep
198
+ * cannot — a runaway loop writing rows against one live task is the realistic
199
+ * way that happens. A bound that politely declines to bind is not a bound.
200
+ */
201
+ defaultMaxItems?: number;
202
+ /**
203
+ * Byte budget for the resource, applied like `defaultMaxItems` but measured in
204
+ * size — the right bound when items vary by orders of magnitude, which is why
205
+ * `orch`'s worker transcripts use it (one deep run can be 5 MB, one trivial
206
+ * attempt 4 KB, so a count cap bounds nothing useful). `0` disables it.
207
+ * Honored by targets that can measure bytes; a sqlite-table target ignores it.
208
+ */
209
+ defaultMaxBytes?: number;
210
+ /**
211
+ * Never delete the newest N, at any age, for any reason — the rule that makes
212
+ * an unattended sweep safe (learned in `deploy-engine/backupRetention.ts`: a
213
+ * naive age rule deletes every artifact of an app that stopped being
214
+ * deployed). A resource with a floor converges to the floor and stays there.
215
+ */
216
+ floorItems?: number;
217
+ /** Narrowed bounds for this resource's override. */
218
+ minDays?: number;
219
+ maxDays?: number;
220
+ }
221
+
222
+ /** The owner's per-resource change, made in the station UI. */
223
+ export interface RetentionOverride {
224
+ days?: number;
225
+ maxItems?: number;
226
+ maxBytes?: number;
227
+ /** `false` suspends the sweep and keeps everything — deliberate, and shown as such. */
228
+ enabled?: boolean;
229
+ note?: string;
230
+ updatedAt?: number;
231
+ updatedBy?: string;
232
+ }
233
+
234
+ export type RetentionFieldSource = "default" | "override";
235
+
236
+ /**
237
+ * The fleet's canonical `RetentionPolicy` — an alias for the resolved,
238
+ * in-effect policy a sweep runs with. Named here (DEDUP-8(c)) so exactly one
239
+ * module owns the word: the billing/audit WINDOWS carry `RetentionWindows` and
240
+ * backup pruning carries `BackupRetentionPolicy`, both deliberately distinct.
241
+ */
242
+ export type RetentionPolicy = EffectiveRetentionPolicy;
243
+
244
+ /** What a sweep actually runs with, plus where each number came from. */
245
+ export interface EffectiveRetentionPolicy {
246
+ key: string;
247
+ app: string;
248
+ id: string;
249
+ enabled: boolean;
250
+ /** Age cutoff in days. `0` means the age sweep is off (the cap may still apply). */
251
+ days: number;
252
+ maxItems: number;
253
+ maxBytes: number;
254
+ floorItems: number;
255
+ minDays: number;
256
+ maxDays: number;
257
+ sources: {
258
+ days: RetentionFieldSource;
259
+ maxItems: RetentionFieldSource;
260
+ maxBytes: RetentionFieldSource;
261
+ enabled: RetentionFieldSource;
262
+ };
263
+ /** Set when an override asked for something out of bounds and was clamped. */
264
+ clamped?: { field: "days" | "maxItems" | "maxBytes"; requested: number; applied: number };
265
+ }
266
+
267
+ /** The override key. One function so the UI, the store and the sweeper agree. */
268
+ export function retentionKey(app: string, id: string): string {
269
+ return `${app}:${id}`;
270
+ }
271
+
272
+ const ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
273
+
274
+ /**
275
+ * Reject a malformed declaration at construction rather than at sweep time — a
276
+ * resource whose id cannot be an override key is a control the owner would
277
+ * change with no effect.
278
+ */
279
+ export function assertRetentionDeclaration(declaration: RetentionDeclaration): void {
280
+ const { app, id } = declaration;
281
+ if (!ID_PATTERN.test(app)) throw new Error(`retention: app key "${app}" must be lower-kebab`);
282
+ if (!ID_PATTERN.test(id)) throw new Error(`retention: resource id "${id}" must be lower-kebab`);
283
+ if (!(declaration.category in RETENTION_CATEGORIES)) {
284
+ throw new Error(
285
+ `retention: "${retentionKey(app, id)}" declares unknown category "${declaration.category}". ` +
286
+ `Retention is opt-in by category (${RETENTION_CATEGORY_KEYS.join(", ")}); data that fits ` +
287
+ "none of them must not be swept.",
288
+ );
289
+ }
290
+ if (!declaration.consequence.trim()) {
291
+ throw new Error(
292
+ `retention: "${retentionKey(app, id)}" must say what is lost when it ages out — the owner ` +
293
+ "reads that sentence before changing the number.",
294
+ );
295
+ }
296
+ for (const [field, value] of [
297
+ ["defaultDays", declaration.defaultDays],
298
+ ["defaultMaxItems", declaration.defaultMaxItems],
299
+ ["defaultMaxBytes", declaration.defaultMaxBytes],
300
+ ["floorItems", declaration.floorItems],
301
+ ] as const) {
302
+ if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
303
+ throw new Error(
304
+ `retention: "${retentionKey(app, id)}" ${field} must be a non-negative number`,
305
+ );
306
+ }
307
+ }
308
+ }
309
+
310
+ const clamp = (value: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, value));
311
+
312
+ /**
313
+ * Merge a declaration with the owner's override into the numbers a sweep runs
314
+ * with. Pure, so both the sweeper and the UI's "what would this delete?" preview
315
+ * resolve identically — a preview computed by different code than the sweep is a
316
+ * preview that can lie.
317
+ *
318
+ * `days: 0` is preserved rather than clamped up to `minDays`: it is the explicit
319
+ * "keep forever" the UI offers, and clamping it would silently re-arm a sweep the
320
+ * owner turned off.
321
+ */
322
+ export function resolveRetentionPolicy(
323
+ declaration: RetentionDeclaration,
324
+ override?: RetentionOverride | null,
325
+ ): EffectiveRetentionPolicy {
326
+ assertRetentionDeclaration(declaration);
327
+ const category = RETENTION_CATEGORIES[declaration.category];
328
+ const minDays = declaration.minDays ?? RETENTION_MIN_DAYS;
329
+ const maxDays = declaration.maxDays ?? RETENTION_MAX_DAYS;
330
+ const defaultDays = declaration.defaultDays ?? category.defaultDays;
331
+
332
+ let days = defaultDays;
333
+ let daysSource: RetentionFieldSource = "default";
334
+ let clamped: EffectiveRetentionPolicy["clamped"];
335
+ if (typeof override?.days === "number" && Number.isFinite(override.days)) {
336
+ const requested = Math.round(override.days);
337
+ const applied = requested === 0 ? 0 : clamp(requested, minDays, maxDays);
338
+ if (applied !== requested) clamped = { field: "days", requested, applied };
339
+ days = applied;
340
+ daysSource = "override";
341
+ }
342
+
343
+ let maxItems = declaration.defaultMaxItems ?? 0;
344
+ let maxItemsSource: RetentionFieldSource = "default";
345
+ if (typeof override?.maxItems === "number" && Number.isFinite(override.maxItems)) {
346
+ const requested = Math.round(override.maxItems);
347
+ const applied = Math.max(0, requested);
348
+ if (applied !== requested) clamped = { field: "maxItems", requested, applied };
349
+ maxItems = applied;
350
+ maxItemsSource = "override";
351
+ }
352
+
353
+ let maxBytes = declaration.defaultMaxBytes ?? 0;
354
+ let maxBytesSource: RetentionFieldSource = "default";
355
+ if (typeof override?.maxBytes === "number" && Number.isFinite(override.maxBytes)) {
356
+ const requested = Math.round(override.maxBytes);
357
+ const applied = Math.max(0, requested);
358
+ if (applied !== requested) clamped = { field: "maxBytes", requested, applied };
359
+ maxBytes = applied;
360
+ maxBytesSource = "override";
361
+ }
362
+
363
+ const enabled = override?.enabled ?? true;
364
+
365
+ return {
366
+ key: retentionKey(declaration.app, declaration.id),
367
+ app: declaration.app,
368
+ id: declaration.id,
369
+ enabled,
370
+ days,
371
+ maxItems,
372
+ maxBytes,
373
+ floorItems: declaration.floorItems ?? 0,
374
+ minDays,
375
+ maxDays,
376
+ sources: {
377
+ days: daysSource,
378
+ maxItems: maxItemsSource,
379
+ maxBytes: maxBytesSource,
380
+ enabled: override?.enabled === undefined ? "default" : "override",
381
+ },
382
+ ...(clamped ? { clamped } : {}),
383
+ };
384
+ }
385
+
386
+ export const DAY_MS = 86_400_000;
387
+
388
+ /** The age cutoff a sweep compares against, or `null` when the age sweep is off. */
389
+ export function cutoffFor(policy: EffectiveRetentionPolicy, now: number): number | null {
390
+ if (!policy.enabled || policy.days <= 0) return null;
391
+ return now - policy.days * DAY_MS;
392
+ }