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,161 @@
1
+ /**
2
+ * How an incident reaches the owner. Three channels, and a hard rule about which
3
+ * incidents are allowed which.
4
+ *
5
+ * ── The severity split is the feature ───────────────────────────────────────
6
+ * The owner asked for "an email and a text message about blocking issues like
7
+ * these (can't deploy, can't ingest files)". A warning that texts him is a
8
+ * warning that gets the channel muted, and a muted channel puts him back where
9
+ * this whole unit started — finding out days later, by accident. So: in-app for
10
+ * everything, email + text for `blocking` only, and `notifier.ts` is the only
11
+ * place that decision is made.
12
+ *
13
+ * ── 🔴 "Sent" is a claim; this module deals in evidence ─────────────────────
14
+ * `fleet/rules/automation-over-documentation.md` — *"treat 'I could not prove it
15
+ * worked' as a failure rather than a pass"*. Both remote channels here can
16
+ * succeed loudly and deliver nothing:
17
+ *
18
+ * - **iMessage.** `osascript` exits 0 the moment Messages accepts the string.
19
+ * Whether anything left the machine is a separate question, and the answer is
20
+ * in `~/Library/Messages/chat.db` — so {@link createTextChannel} reads back
21
+ * an outgoing row with `is_sent = 1` before it will call the send delivered.
22
+ * Without that read it is `unproven`, which is NOT success.
23
+ * - **Email.** The fleet's mailer is best-effort by contract and returns
24
+ * `false` on both "mail is off here" and "SES refused". That is right for a
25
+ * signup mail and wrong for an outage alert, so the result is carried, not
26
+ * swallowed, and the console shows it on the incident.
27
+ *
28
+ * ── Why the text channel is AppleScript and not an SMS API ──────────────────
29
+ * Measured on this Mac, 2026-08-20, while building this: neither IAM user the
30
+ * console holds can call SES (`satellite-cloud` and `satellite-console-prod`
31
+ * both answer `AccessDenied` on `ses:GetSendQuota`), and the Gmail refresh token
32
+ * in `openclaw-digest.env` is revoked (`invalid_grant`). Messages.app is signed
33
+ * in, has an enabled iMessage account, and its Automation grant is already in
34
+ * place — one `osascript` reached the owner's phone in under a second with
35
+ * `is_sent = 1, is_delivered = 1` in `chat.db`. It costs nothing, needs no
36
+ * credential, and — the property that decides it — **it does not depend on the
37
+ * prod box**, which is exactly the thing most likely to be down when an alert
38
+ * matters. An SMS provider would have been a new bill and a new secret to reach
39
+ * the same phone less reliably.
40
+ */
41
+ /** What a channel did. `unproven` is deliberately distinct from `delivered`. */
42
+ export type ChannelOutcome = "delivered" | "unproven" | "skipped" | "failed";
43
+ export interface ChannelResult {
44
+ channel: string;
45
+ outcome: ChannelOutcome;
46
+ /** One line, for the incident row and the console. Never carries the message body. */
47
+ detail: string;
48
+ }
49
+ /** One line of a notification. Channels render it themselves — see `notifier.ts`. */
50
+ export interface ChannelMessage {
51
+ subject: string;
52
+ /** Plain text. Short enough for a phone; the console holds the full record. */
53
+ text: string;
54
+ /** Longer HTML, for email only. Optional — a channel may ignore it. */
55
+ html?: string;
56
+ /** `blocking` or `warning`, so a channel may refuse what it is not for. */
57
+ severity: "blocking" | "warning";
58
+ /**
59
+ * Channels this message must NOT use, by key. The notifier sets it when the
60
+ * message is ABOUT a channel being broken — telling somebody down a wire that
61
+ * the wire is down is the loop this field exists to cut. **Enforced in
62
+ * `notifier.ts`, once**, so a channel added later cannot forget it.
63
+ */
64
+ avoid?: readonly string[];
65
+ }
66
+ export interface IncidentChannel {
67
+ readonly key: string;
68
+ /** Human name for the console's channel-health row. */
69
+ readonly label: string;
70
+ send: (message: ChannelMessage) => Promise<ChannelResult>;
71
+ }
72
+ export interface TextChannelOptions {
73
+ /** The handle to text — a phone number in E.164, or an iMessage address. */
74
+ handle: string;
75
+ /** Overridable for tests. Default: run `osascript`. */
76
+ send?: (handle: string, body: string) => Promise<{
77
+ ok: boolean;
78
+ detail: string;
79
+ }>;
80
+ /**
81
+ * Overridable for tests. Default: read `~/Library/Messages/chat.db` for an
82
+ * outgoing row newer than `sinceAppleTime`. Returns null when the database
83
+ * cannot be read at all — which is `unproven`, not `failed`.
84
+ */
85
+ proveSent?: (sinceAppleTime: number) => Promise<boolean | null>;
86
+ /** How long to wait for the proof row. Default 8s; one arrived in <1s when measured. */
87
+ proofTimeoutMs?: number;
88
+ }
89
+ /**
90
+ * Text the owner from this Mac, and prove it left. Mac-only by construction; the
91
+ * prod deployment of the console never registers it (see `notifier.ts`).
92
+ */
93
+ export declare function createTextChannel(options: TextChannelOptions): IncidentChannel;
94
+ /** The one thing this module needs from a mail sender. Matches the fleet's `Mailer`. */
95
+ export type SendEmail = (params: {
96
+ to: string;
97
+ subject: string;
98
+ text: string;
99
+ html: string;
100
+ template?: string;
101
+ }) => Promise<boolean>;
102
+ export interface EmailChannelOptions {
103
+ to: string;
104
+ send: SendEmail;
105
+ /**
106
+ * Why this deployment might not be able to send — surfaced verbatim when the
107
+ * send returns false, because "false" from a best-effort mailer means both
108
+ * "mail is off on this host" and "SES refused" and the owner needs to know
109
+ * which. E.g. "MAIL_ON is unset on the local-mac console".
110
+ */
111
+ unavailableHint?: string;
112
+ }
113
+ export declare function createEmailChannel(options: EmailChannelOptions): IncidentChannel;
114
+ /**
115
+ * Email, sent by SOMEBODY ELSE. The Mac cannot reach SES (see the module
116
+ * header), and the prod box can — so the local console hands the message to the
117
+ * prod console over the wire the two already share.
118
+ *
119
+ * 🔴 It is a fallback and never the only path. The failure mode it cannot cover
120
+ * is the one where prod is down, which is precisely when an alert matters most —
121
+ * that case belongs to the text channel, which runs entirely on this Mac.
122
+ */
123
+ export declare function createRelayEmailChannel(options: {
124
+ /** e.g. `https://admin.cursedalchemy.com/api/station/incidents/mail/relay-email`. */
125
+ url: string;
126
+ /** Shared bearer — the console's existing sync token. */
127
+ token: string;
128
+ /**
129
+ * 🔴 Headers the EDGE needs, distinct from the bearer the app needs.
130
+ *
131
+ * The prod console sits behind a Cloudflare Access application, so a plain
132
+ * bearer request never reaches the origin at all — Access answers 302 to its
133
+ * own login page. Measured 2026-08-21: `fetch` FOLLOWED that redirect, got a
134
+ * 200 of HTML, and this channel reported "the prod console accepted the
135
+ * request but its mailer declined". Every word of that was wrong and it named
136
+ * the wrong host's component. The caller supplies the service-token headers
137
+ * (satellite-kit's `accessHeadersForUrl`); this package must not know about
138
+ * Cloudflare.
139
+ */
140
+ edgeHeaders?: () => Record<string, string>;
141
+ fetchImpl?: typeof fetch;
142
+ timeoutMs?: number;
143
+ }): IncidentChannel;
144
+ /**
145
+ * The in-app channel is the fleet's EXISTING notification primitive
146
+ * (`cursedbelt-server/notifications` over `cwip/notifications`), not a second
147
+ * inbox. Pass its `notify` and the incident's fingerprint becomes the
148
+ * notification's `groupKey`, so the database's partial unique index coalesces
149
+ * repeats for free — the same guarantee the store makes, enforced a second time
150
+ * one layer down.
151
+ */
152
+ export declare function createInAppChannel(options: {
153
+ notify: (input: {
154
+ title: string;
155
+ body: string;
156
+ groupKey: string;
157
+ severity: "blocking" | "warning";
158
+ }) => Promise<void> | void;
159
+ /** The fingerprint of the incident being announced. Set per-message by the notifier. */
160
+ groupKeyOf: (message: ChannelMessage) => string;
161
+ }): IncidentChannel;
@@ -0,0 +1,357 @@
1
+ /**
2
+ * How an incident reaches the owner. Three channels, and a hard rule about which
3
+ * incidents are allowed which.
4
+ *
5
+ * ── The severity split is the feature ───────────────────────────────────────
6
+ * The owner asked for "an email and a text message about blocking issues like
7
+ * these (can't deploy, can't ingest files)". A warning that texts him is a
8
+ * warning that gets the channel muted, and a muted channel puts him back where
9
+ * this whole unit started — finding out days later, by accident. So: in-app for
10
+ * everything, email + text for `blocking` only, and `notifier.ts` is the only
11
+ * place that decision is made.
12
+ *
13
+ * ── 🔴 "Sent" is a claim; this module deals in evidence ─────────────────────
14
+ * `fleet/rules/automation-over-documentation.md` — *"treat 'I could not prove it
15
+ * worked' as a failure rather than a pass"*. Both remote channels here can
16
+ * succeed loudly and deliver nothing:
17
+ *
18
+ * - **iMessage.** `osascript` exits 0 the moment Messages accepts the string.
19
+ * Whether anything left the machine is a separate question, and the answer is
20
+ * in `~/Library/Messages/chat.db` — so {@link createTextChannel} reads back
21
+ * an outgoing row with `is_sent = 1` before it will call the send delivered.
22
+ * Without that read it is `unproven`, which is NOT success.
23
+ * - **Email.** The fleet's mailer is best-effort by contract and returns
24
+ * `false` on both "mail is off here" and "SES refused". That is right for a
25
+ * signup mail and wrong for an outage alert, so the result is carried, not
26
+ * swallowed, and the console shows it on the incident.
27
+ *
28
+ * ── Why the text channel is AppleScript and not an SMS API ──────────────────
29
+ * Measured on this Mac, 2026-08-20, while building this: neither IAM user the
30
+ * console holds can call SES (`satellite-cloud` and `satellite-console-prod`
31
+ * both answer `AccessDenied` on `ses:GetSendQuota`), and the Gmail refresh token
32
+ * in `openclaw-digest.env` is revoked (`invalid_grant`). Messages.app is signed
33
+ * in, has an enabled iMessage account, and its Automation grant is already in
34
+ * place — one `osascript` reached the owner's phone in under a second with
35
+ * `is_sent = 1, is_delivered = 1` in `chat.db`. It costs nothing, needs no
36
+ * credential, and — the property that decides it — **it does not depend on the
37
+ * prod box**, which is exactly the thing most likely to be down when an alert
38
+ * matters. An SMS provider would have been a new bill and a new secret to reach
39
+ * the same phone less reliably.
40
+ */
41
+ // ── text: iMessage from this Mac ──────────────────────────────────────────────
42
+ /**
43
+ * The AppleScript, as a file-less `osascript -` script taking `argv`, so no part
44
+ * of the message is ever interpolated into source. A message containing a quote
45
+ * would otherwise be a syntax error at best.
46
+ *
47
+ * It walks the accounts by index inside a `try` rather than asking for
48
+ * `1st account whose service type = iMessage`: on this Mac four of the six
49
+ * accounts throw `-1728` on any property access, and the `whose` form propagates
50
+ * that instead of skipping them (measured 2026-08-20 — the clean-looking form is
51
+ * the one that does not work).
52
+ */
53
+ const SEND_SCRIPT = `on run argv
54
+ set targetHandle to item 1 of argv
55
+ set msg to item 2 of argv
56
+ tell application "Messages"
57
+ set n to (count of accounts)
58
+ repeat with i from 1 to n
59
+ try
60
+ set acct to account i
61
+ if (service type of acct) is iMessage and (enabled of acct) then
62
+ send msg to participant targetHandle of acct
63
+ return "sent"
64
+ end if
65
+ end try
66
+ end repeat
67
+ end tell
68
+ return "no-imessage-account"
69
+ end run`;
70
+ /**
71
+ * What macOS has actually decided about this process talking to Messages.
72
+ *
73
+ * The user's TCC database is readable with Full Disk Access, which this Mac's
74
+ * agents have, and it is the only place the answer exists — `osascript` reports
75
+ * a timeout, not a refusal. Unreadable ⇒ `unknown`, never a guess.
76
+ */
77
+ async function automationDecision() {
78
+ try {
79
+ const { Database } = await import("bun:sqlite");
80
+ const { homedir } = await import("node:os");
81
+ const { join } = await import("node:path");
82
+ const db = new Database(join(homedir(), "Library", "Application Support", "com.apple.TCC", "TCC.db"), { readonly: true });
83
+ try {
84
+ const row = db
85
+ .prepare(`SELECT auth_value FROM access
86
+ WHERE service = 'kTCCServiceAppleEvents'
87
+ AND indirect_object_identifier = 'com.apple.MobileSMS'
88
+ AND client LIKE '%bun%'
89
+ ORDER BY last_modified DESC LIMIT 1`)
90
+ .get();
91
+ if (!row)
92
+ return "unknown";
93
+ return row.auth_value === 0 ? "denied" : "allowed";
94
+ }
95
+ finally {
96
+ db.close();
97
+ }
98
+ }
99
+ catch {
100
+ return "unknown";
101
+ }
102
+ }
103
+ /** Apple's epoch is 2001-01-01; `message.date` is nanoseconds since then. */
104
+ const APPLE_EPOCH_OFFSET_S = 978_307_200;
105
+ const appleTimeNow = () => (Date.now() / 1000 - APPLE_EPOCH_OFFSET_S) * 1e9;
106
+ const defaultSend = async (handle, body) => {
107
+ const proc = Bun.spawn(["osascript", "-", handle, body], {
108
+ stdin: new TextEncoder().encode(SEND_SCRIPT),
109
+ stdout: "pipe",
110
+ stderr: "pipe",
111
+ });
112
+ const [out, err, code] = await Promise.all([
113
+ new Response(proc.stdout).text(),
114
+ new Response(proc.stderr).text(),
115
+ proc.exited,
116
+ ]);
117
+ const trimmed = out.trim();
118
+ if (code !== 0) {
119
+ // 🔴 Two different macOS errors mean the same thing, and neither says so.
120
+ // `-1743` is an outright refusal. `-1712` is an AppleEvent TIMEOUT, which
121
+ // is what a DAEMON gets: the consent prompt is raised against the launchd
122
+ // job's responsible process, nobody answers it, and the event times out.
123
+ // Measured on this Mac 2026-08-21 — the console's first send produced
124
+ // `-1712`, and `TCC.db` had a fresh `kTCCServiceAppleEvents` row for
125
+ // `bun.exe → com.apple.MobileSMS` with `auth_value = 0` stamped at the same
126
+ // minute. So the raw error is a red herring and the decision is a FACT that
127
+ // can be read, which is what `automationDecision` does. Naming the state
128
+ // beats printing an error code nobody can act on.
129
+ const looksLikeTcc = err.includes("-1743") ||
130
+ err.includes("-1712") ||
131
+ err.toLowerCase().includes("not authorized");
132
+ if (looksLikeTcc) {
133
+ const decision = await automationDecision();
134
+ return {
135
+ ok: false,
136
+ detail: decision === "denied"
137
+ ? "macOS has Automation access to Messages DENIED for this console's daemon. Turn it back on in System Settings ▸ Privacy & Security ▸ Automation (the entry is 'bun'), then press Send a test."
138
+ : "macOS did not grant Automation access to Messages — a consent prompt was raised against a background daemon and nothing answered it. Approve it in System Settings ▸ Privacy & Security ▸ Automation, then press Send a test.",
139
+ };
140
+ }
141
+ return { ok: false, detail: `osascript exited ${code}: ${err.trim().slice(0, 200)}` };
142
+ }
143
+ if (trimmed !== "sent") {
144
+ return { ok: false, detail: `Messages has no enabled iMessage account (${trimmed})` };
145
+ }
146
+ return { ok: true, detail: "Messages accepted the send" };
147
+ };
148
+ const defaultProveSent = async (sinceAppleTime) => {
149
+ try {
150
+ const { Database } = await import("bun:sqlite");
151
+ const { homedir } = await import("node:os");
152
+ const { join } = await import("node:path");
153
+ // Read-only, and never written: this is Apple's database, not ours.
154
+ const db = new Database(join(homedir(), "Library", "Messages", "chat.db"), {
155
+ readonly: true,
156
+ });
157
+ try {
158
+ const row = db
159
+ .prepare("SELECT COUNT(*) AS n FROM message WHERE is_from_me = 1 AND is_sent = 1 AND date > ?")
160
+ .get(sinceAppleTime);
161
+ return (row?.n ?? 0) > 0;
162
+ }
163
+ finally {
164
+ db.close();
165
+ }
166
+ }
167
+ catch {
168
+ // Full Disk Access is not granted, or the schema moved. We cannot prove
169
+ // anything — which is `unproven`, and the caller must not read it as a send
170
+ // that failed OR as one that worked.
171
+ return null;
172
+ }
173
+ };
174
+ /**
175
+ * Text the owner from this Mac, and prove it left. Mac-only by construction; the
176
+ * prod deployment of the console never registers it (see `notifier.ts`).
177
+ */
178
+ export function createTextChannel(options) {
179
+ const send = options.send ?? defaultSend;
180
+ const prove = options.proveSent ?? defaultProveSent;
181
+ const timeoutMs = options.proofTimeoutMs ?? 8_000;
182
+ return {
183
+ key: "text",
184
+ label: "Text message (iMessage from this Mac)",
185
+ async send(message) {
186
+ if (message.severity !== "blocking") {
187
+ return { channel: "text", outcome: "skipped", detail: "not a blocking incident" };
188
+ }
189
+ const before = appleTimeNow();
190
+ const attempt = await send(options.handle, `${message.subject}\n\n${message.text}`);
191
+ if (!attempt.ok)
192
+ return { channel: "text", outcome: "failed", detail: attempt.detail };
193
+ const deadline = Date.now() + timeoutMs;
194
+ let proofAvailable = true;
195
+ while (Date.now() < deadline) {
196
+ const proved = await prove(before);
197
+ if (proved === null) {
198
+ proofAvailable = false;
199
+ break;
200
+ }
201
+ if (proved) {
202
+ return { channel: "text", outcome: "delivered", detail: "chat.db shows it sent" };
203
+ }
204
+ await Bun.sleep(500);
205
+ }
206
+ return {
207
+ channel: "text",
208
+ outcome: "unproven",
209
+ detail: proofAvailable
210
+ ? `Messages accepted it but no sent row appeared within ${Math.round(timeoutMs / 1000)}s`
211
+ : "Messages accepted it; chat.db is unreadable (Full Disk Access), so delivery is unproven",
212
+ };
213
+ },
214
+ };
215
+ }
216
+ export function createEmailChannel(options) {
217
+ return {
218
+ key: "email",
219
+ label: `Email to ${options.to}`,
220
+ async send(message) {
221
+ if (message.severity !== "blocking") {
222
+ return { channel: "email", outcome: "skipped", detail: "not a blocking incident" };
223
+ }
224
+ try {
225
+ const ok = await options.send({
226
+ to: options.to,
227
+ subject: message.subject,
228
+ text: message.text,
229
+ html: message.html ?? `<pre>${escapeHtml(message.text)}</pre>`,
230
+ template: "fleet.incident",
231
+ });
232
+ return ok
233
+ ? { channel: "email", outcome: "delivered", detail: "accepted by the mailer" }
234
+ : {
235
+ channel: "email",
236
+ outcome: "failed",
237
+ detail: options.unavailableHint ?? "the mailer declined to send",
238
+ };
239
+ }
240
+ catch (err) {
241
+ return {
242
+ channel: "email",
243
+ outcome: "failed",
244
+ detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
245
+ };
246
+ }
247
+ },
248
+ };
249
+ }
250
+ /**
251
+ * Email, sent by SOMEBODY ELSE. The Mac cannot reach SES (see the module
252
+ * header), and the prod box can — so the local console hands the message to the
253
+ * prod console over the wire the two already share.
254
+ *
255
+ * 🔴 It is a fallback and never the only path. The failure mode it cannot cover
256
+ * is the one where prod is down, which is precisely when an alert matters most —
257
+ * that case belongs to the text channel, which runs entirely on this Mac.
258
+ */
259
+ export function createRelayEmailChannel(options) {
260
+ const doFetch = options.fetchImpl ?? fetch;
261
+ return {
262
+ key: "email",
263
+ label: "Email (relayed through the prod console)",
264
+ async send(message) {
265
+ if (message.severity !== "blocking") {
266
+ return { channel: "email", outcome: "skipped", detail: "not a blocking incident" };
267
+ }
268
+ try {
269
+ const res = await doFetch(options.url, {
270
+ method: "POST",
271
+ headers: {
272
+ "content-type": "application/json",
273
+ authorization: `Bearer ${options.token}`,
274
+ ...(options.edgeHeaders?.() ?? {}),
275
+ },
276
+ body: JSON.stringify({
277
+ subject: message.subject,
278
+ text: message.text,
279
+ html: message.html,
280
+ }),
281
+ // 🔴 `manual`, so an edge redirect is a FAILURE rather than a
282
+ // successful fetch of a login page. Following it is what turned an
283
+ // Access refusal into a 200 and a wrong diagnosis.
284
+ redirect: "manual",
285
+ signal: AbortSignal.timeout(options.timeoutMs ?? 15_000),
286
+ });
287
+ if (res.status >= 300 && res.status < 400) {
288
+ return {
289
+ channel: "email",
290
+ outcome: "failed",
291
+ detail: `the request was redirected (${res.status}) before it reached the console — ` +
292
+ "an edge access gate is refusing it, so the service-token headers are missing or expired",
293
+ };
294
+ }
295
+ if (!res.ok) {
296
+ return {
297
+ channel: "email",
298
+ outcome: "failed",
299
+ detail: `the prod console answered ${res.status}`,
300
+ };
301
+ }
302
+ const body = (await res.json().catch(() => null));
303
+ return body?.sent
304
+ ? { channel: "email", outcome: "delivered", detail: "sent by the prod console" }
305
+ : {
306
+ channel: "email",
307
+ outcome: "failed",
308
+ // The relaying host is the only one that knows why, so it says so.
309
+ detail: body?.reason
310
+ ? `the prod console could not send it: ${body.reason}`
311
+ : "the prod console accepted the request but its mailer declined, and gave no reason",
312
+ };
313
+ }
314
+ catch (err) {
315
+ return {
316
+ channel: "email",
317
+ outcome: "failed",
318
+ detail: `could not reach the prod console: ${err instanceof Error ? err.message.slice(0, 160) : String(err)}`,
319
+ };
320
+ }
321
+ },
322
+ };
323
+ }
324
+ // ── in-app ────────────────────────────────────────────────────────────────────
325
+ /**
326
+ * The in-app channel is the fleet's EXISTING notification primitive
327
+ * (`cursedbelt-server/notifications` over `cwip/notifications`), not a second
328
+ * inbox. Pass its `notify` and the incident's fingerprint becomes the
329
+ * notification's `groupKey`, so the database's partial unique index coalesces
330
+ * repeats for free — the same guarantee the store makes, enforced a second time
331
+ * one layer down.
332
+ */
333
+ export function createInAppChannel(options) {
334
+ return {
335
+ key: "in-app",
336
+ label: "The console's notification bell",
337
+ async send(message) {
338
+ try {
339
+ await options.notify({
340
+ title: message.subject,
341
+ body: message.text,
342
+ groupKey: options.groupKeyOf(message),
343
+ severity: message.severity,
344
+ });
345
+ return { channel: "in-app", outcome: "delivered", detail: "filed in the inbox" };
346
+ }
347
+ catch (err) {
348
+ return {
349
+ channel: "in-app",
350
+ outcome: "failed",
351
+ detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
352
+ };
353
+ }
354
+ },
355
+ };
356
+ }
357
+ const escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The check contract and the sweep that runs it.
3
+ *
4
+ * A check is a pure-ish function from "the machine" to "the conditions that are
5
+ * true right now". It does not know whether any of them are new, it never
6
+ * notifies, and it cannot resolve anything — all three belong to the store, so
7
+ * that one implementation of the dedupe rule covers every detector anybody adds
8
+ * later. Adding a detector is therefore a function and a row, not a design.
9
+ *
10
+ * ── 🔴 A check that FAILS resolves nothing ──────────────────────────────────
11
+ * This is the invariant the whole feature's credibility rests on. If a detector
12
+ * throws — its command is missing, the disk it stats is unmounted, a fetch times
13
+ * out — the sweep records the failure and SKIPS the reconcile for that check.
14
+ * Reconciling would have handed the store an empty report set, and an empty
15
+ * report set from an authoritative check means "all clear", which would send the
16
+ * owner a cheerful "cleared: production is down" the moment the detector broke.
17
+ * The same reasoning applies one level up, in `runIncidentSweep`'s return value:
18
+ * a sweep reports its own failures so the console can show a detector that has
19
+ * stopped working, rather than showing an empty incident list and looking calm.
20
+ */
21
+ import type { IncidentStore } from "./store";
22
+ import type { IncidentReport, IncidentTransitions } from "./types";
23
+ export interface IncidentCheck {
24
+ /** Stable key. Owns every incident it has ever opened — see `reconcile`. */
25
+ readonly key: string;
26
+ /** The app or service this check speaks for: `binary-server`, `station`, `orch`. */
27
+ readonly source: string;
28
+ /**
29
+ * Every `kind` this check can produce. Declaring them is what lets a run that
30
+ * finds nothing RESOLVE its own past incidents — see `ReconcileOptions`.
31
+ * A kind missing from this list can be opened but never automatically cleared,
32
+ * so the list is not optional decoration.
33
+ */
34
+ readonly kinds: readonly string[];
35
+ /** Human name, for the console's detector table. */
36
+ readonly label: string;
37
+ run: () => Promise<IncidentReport[]> | IncidentReport[];
38
+ }
39
+ export interface CheckOutcome {
40
+ key: string;
41
+ label: string;
42
+ /** `ok` when it ran, whatever it found. `failed` when it threw. */
43
+ status: "ok" | "failed";
44
+ reported: number;
45
+ durationMs: number;
46
+ error?: string;
47
+ }
48
+ export interface SweepResult {
49
+ at: number;
50
+ outcomes: CheckOutcome[];
51
+ transitions: IncidentTransitions;
52
+ }
53
+ /**
54
+ * Run every check once, reconcile each one's results independently, and return
55
+ * the union of the transitions plus a per-check outcome.
56
+ *
57
+ * Checks run in PARALLEL (they are mostly I/O — a stat, a fetch, a sqlite read)
58
+ * but reconcile in SERIES against one sqlite handle, which keeps the store's
59
+ * writes single-threaded without needing a transaction across unrelated checks.
60
+ */
61
+ export declare function runIncidentSweep(store: IncidentStore, checks: readonly IncidentCheck[], now?: number): Promise<SweepResult>;
@@ -0,0 +1,55 @@
1
+ const emptyTransitions = () => ({
2
+ opened: [],
3
+ escalated: [],
4
+ resolved: [],
5
+ unchanged: [],
6
+ });
7
+ /**
8
+ * Run every check once, reconcile each one's results independently, and return
9
+ * the union of the transitions plus a per-check outcome.
10
+ *
11
+ * Checks run in PARALLEL (they are mostly I/O — a stat, a fetch, a sqlite read)
12
+ * but reconcile in SERIES against one sqlite handle, which keeps the store's
13
+ * writes single-threaded without needing a transaction across unrelated checks.
14
+ */
15
+ export async function runIncidentSweep(store, checks, now = Date.now()) {
16
+ const settled = await Promise.all(checks.map(async (check) => {
17
+ const started = Date.now();
18
+ try {
19
+ const reports = await check.run();
20
+ return { check, reports, error: null, durationMs: Date.now() - started };
21
+ }
22
+ catch (err) {
23
+ return {
24
+ check,
25
+ reports: [],
26
+ error: err instanceof Error ? err.message.slice(0, 300) : String(err),
27
+ durationMs: Date.now() - started,
28
+ };
29
+ }
30
+ }));
31
+ const transitions = emptyTransitions();
32
+ const outcomes = [];
33
+ for (const { check, reports, error, durationMs } of settled) {
34
+ outcomes.push({
35
+ key: check.key,
36
+ label: check.label,
37
+ status: error ? "failed" : "ok",
38
+ reported: reports.length,
39
+ durationMs,
40
+ ...(error ? { error } : {}),
41
+ });
42
+ // The invariant in the header: a failed check reconciles nothing at all.
43
+ if (error)
44
+ continue;
45
+ const t = store.reconcile(check.key, reports, {
46
+ namespaces: check.kinds.map((k) => `${check.source}:${k}:`),
47
+ now,
48
+ });
49
+ transitions.opened.push(...t.opened);
50
+ transitions.escalated.push(...t.escalated);
51
+ transitions.resolved.push(...t.resolved);
52
+ transitions.unchanged.push(...t.unchanged);
53
+ }
54
+ return { at: now, outcomes, transitions };
55
+ }