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.
- package/LICENSE +15 -0
- package/dist/server/activity/index.d.ts +36 -0
- package/dist/server/activity/index.js +36 -0
- package/dist/server/activity/migrations.d.ts +21 -0
- package/dist/server/activity/migrations.js +55 -0
- package/dist/server/activity/shareSource.d.ts +11 -0
- package/dist/server/activity/shareSource.js +45 -0
- package/dist/server/activity/store.d.ts +123 -0
- package/dist/server/activity/store.js +178 -0
- package/dist/server/app.d.ts +56 -0
- package/dist/server/app.js +45 -0
- package/dist/server/audit/index.d.ts +2 -0
- package/dist/server/audit/index.js +2 -0
- package/dist/server/audit/migrations.d.ts +2 -0
- package/dist/server/audit/migrations.js +79 -0
- package/dist/server/audit/sqliteSink.d.ts +36 -0
- package/dist/server/audit/sqliteSink.js +95 -0
- package/dist/server/audit/sqliteSink.test.d.ts +1 -0
- package/dist/server/audit/sqliteSink.test.js +180 -0
- package/dist/server/auth/cookie.d.ts +22 -0
- package/dist/server/auth/cookie.js +35 -0
- package/dist/server/auth/csrf.d.ts +42 -0
- package/dist/server/auth/csrf.js +36 -0
- package/dist/server/auth/fileTokenSigner.d.ts +10 -0
- package/dist/server/auth/fileTokenSigner.js +22 -0
- package/dist/server/auth/getOnly.d.ts +20 -0
- package/dist/server/auth/getOnly.js +27 -0
- package/dist/server/auth/index.d.ts +12 -0
- package/dist/server/auth/index.js +15 -0
- package/dist/server/auth/jwt.d.ts +18 -0
- package/dist/server/auth/jwt.js +56 -0
- package/dist/server/auth/loginThrottle.d.ts +118 -0
- package/dist/server/auth/loginThrottle.js +204 -0
- package/dist/server/auth/middleware.d.ts +14 -0
- package/dist/server/auth/middleware.js +47 -0
- package/dist/server/auth/password.d.ts +14 -0
- package/dist/server/auth/password.js +65 -0
- package/dist/server/auth/sessionStore.d.ts +19 -0
- package/dist/server/auth/sessionStore.js +221 -0
- package/dist/server/auth/totp.d.ts +34 -0
- package/dist/server/auth/totp.js +81 -0
- package/dist/server/auth/types.d.ts +133 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/vault.d.ts +27 -0
- package/dist/server/auth/vault.js +57 -0
- package/dist/server/context.d.ts +35 -0
- package/dist/server/context.js +1 -0
- package/dist/server/ctgr.d.ts +32 -0
- package/dist/server/ctgr.js +69 -0
- package/dist/server/db/kysely.d.ts +14 -0
- package/dist/server/db/kysely.js +16 -0
- package/dist/server/db/plumbingTypes.d.ts +116 -0
- package/dist/server/db/plumbingTypes.js +12 -0
- package/dist/server/dropzone/duplicates.d.ts +82 -0
- package/dist/server/dropzone/duplicates.js +90 -0
- package/dist/server/dropzone/index.d.ts +13 -0
- package/dist/server/dropzone/index.js +13 -0
- package/dist/server/dropzone/scan.d.ts +166 -0
- package/dist/server/dropzone/scan.js +272 -0
- package/dist/server/errors.d.ts +7 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/guard/argon2Binding.d.ts +97 -0
- package/dist/server/guard/argon2Binding.js +138 -0
- package/dist/server/guard/index.d.ts +4 -0
- package/dist/server/guard/index.js +6 -0
- package/dist/server/guard/kdfWorker.d.ts +101 -0
- package/dist/server/guard/kdfWorker.js +301 -0
- package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
- package/dist/server/guard/kdfWorkerEntry.js +15 -0
- package/dist/server/guard/passwordVerifier.d.ts +65 -0
- package/dist/server/guard/passwordVerifier.js +122 -0
- package/dist/server/guard/revocationStore.d.ts +132 -0
- package/dist/server/guard/revocationStore.js +211 -0
- package/dist/server/guard/sessionSigner.d.ts +132 -0
- package/dist/server/guard/sessionSigner.js +303 -0
- package/dist/server/guard/verifier.d.ts +21 -0
- package/dist/server/guard/verifier.js +1 -0
- package/dist/server/incidents/channels.d.ts +161 -0
- package/dist/server/incidents/channels.js +357 -0
- package/dist/server/incidents/checks.d.ts +61 -0
- package/dist/server/incidents/checks.js +55 -0
- package/dist/server/incidents/index.d.ts +51 -0
- package/dist/server/incidents/index.js +51 -0
- package/dist/server/incidents/migrations.d.ts +20 -0
- package/dist/server/incidents/migrations.js +58 -0
- package/dist/server/incidents/notifier.d.ts +79 -0
- package/dist/server/incidents/notifier.js +141 -0
- package/dist/server/incidents/reporter.d.ts +60 -0
- package/dist/server/incidents/reporter.js +49 -0
- package/dist/server/incidents/routes.d.ts +91 -0
- package/dist/server/incidents/routes.js +202 -0
- package/dist/server/incidents/store.d.ts +75 -0
- package/dist/server/incidents/store.js +176 -0
- package/dist/server/incidents/types.d.ts +107 -0
- package/dist/server/incidents/types.js +32 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +53 -0
- package/dist/server/jobs/fireQueue.d.ts +12 -0
- package/dist/server/jobs/fireQueue.js +37 -0
- package/dist/server/jobs/index.d.ts +6 -0
- package/dist/server/jobs/index.js +10 -0
- package/dist/server/jobs/logger.d.ts +10 -0
- package/dist/server/jobs/logger.js +14 -0
- package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
- package/dist/server/jobs/plainjobAdapter.js +48 -0
- package/dist/server/jobs/polling.d.ts +17 -0
- package/dist/server/jobs/polling.js +79 -0
- package/dist/server/jobs/runner.d.ts +24 -0
- package/dist/server/jobs/runner.js +186 -0
- package/dist/server/jobs/timeout.d.ts +28 -0
- package/dist/server/jobs/timeout.js +47 -0
- package/dist/server/jobs/types.d.ts +167 -0
- package/dist/server/jobs/types.js +12 -0
- package/dist/server/master-lock/guard.d.ts +53 -0
- package/dist/server/master-lock/guard.js +205 -0
- package/dist/server/master-lock/index.d.ts +21 -0
- package/dist/server/master-lock/index.js +21 -0
- package/dist/server/master-lock/lockPage.d.ts +34 -0
- package/dist/server/master-lock/lockPage.js +281 -0
- package/dist/server/master-lock/masterLock.d.ts +294 -0
- package/dist/server/master-lock/masterLock.js +523 -0
- package/dist/server/master-lock/principals.d.ts +76 -0
- package/dist/server/master-lock/principals.js +157 -0
- package/dist/server/master-lock/seed.d.ts +47 -0
- package/dist/server/master-lock/seed.js +93 -0
- package/dist/server/master-lock/store.d.ts +19 -0
- package/dist/server/master-lock/store.js +20 -0
- package/dist/server/media-bun/image.d.ts +7 -0
- package/dist/server/media-bun/image.js +96 -0
- package/dist/server/media-bun/index.d.ts +4 -0
- package/dist/server/media-bun/index.js +10 -0
- package/dist/server/media-bun/streaming.d.ts +86 -0
- package/dist/server/media-bun/streaming.js +237 -0
- package/dist/server/media-bun/video.d.ts +35 -0
- package/dist/server/media-bun/video.js +181 -0
- package/dist/server/metrics/metricsBuffer.d.ts +39 -0
- package/dist/server/metrics/metricsBuffer.js +36 -0
- package/dist/server/metrics/normalizeRoute.d.ts +5 -0
- package/dist/server/metrics/normalizeRoute.js +58 -0
- package/dist/server/middleware/correlationId.d.ts +9 -0
- package/dist/server/middleware/correlationId.js +20 -0
- package/dist/server/middleware/corsAllowlist.d.ts +38 -0
- package/dist/server/middleware/corsAllowlist.js +37 -0
- package/dist/server/middleware/errorEnvelope.d.ts +17 -0
- package/dist/server/middleware/errorEnvelope.js +32 -0
- package/dist/server/middleware/index.d.ts +9 -0
- package/dist/server/middleware/index.js +10 -0
- package/dist/server/middleware/localOrigin.d.ts +71 -0
- package/dist/server/middleware/localOrigin.js +119 -0
- package/dist/server/middleware/rateLimit.d.ts +56 -0
- package/dist/server/middleware/rateLimit.js +86 -0
- package/dist/server/middleware/requestLogger.d.ts +29 -0
- package/dist/server/middleware/requestLogger.js +47 -0
- package/dist/server/middleware/securityHeaders.d.ts +29 -0
- package/dist/server/middleware/securityHeaders.js +44 -0
- package/dist/server/middleware/zValidator.d.ts +29 -0
- package/dist/server/middleware/zValidator.js +57 -0
- package/dist/server/migration/runner.d.ts +42 -0
- package/dist/server/migration/runner.js +70 -0
- package/dist/server/migration/types.d.ts +16 -0
- package/dist/server/migration/types.js +1 -0
- package/dist/server/net/outboundGuard.d.ts +30 -0
- package/dist/server/net/outboundGuard.js +199 -0
- package/dist/server/notifications/hub.d.ts +67 -0
- package/dist/server/notifications/hub.js +187 -0
- package/dist/server/notifications/index.d.ts +47 -0
- package/dist/server/notifications/index.js +46 -0
- package/dist/server/notifications/migrations.d.ts +13 -0
- package/dist/server/notifications/migrations.js +20 -0
- package/dist/server/notifications/routes.d.ts +58 -0
- package/dist/server/notifications/routes.js +113 -0
- package/dist/server/notifications/service.d.ts +58 -0
- package/dist/server/notifications/service.js +41 -0
- package/dist/server/paths/appDataHome.d.ts +19 -0
- package/dist/server/paths/appDataHome.js +68 -0
- package/dist/server/private-media/crypto.d.ts +37 -0
- package/dist/server/private-media/crypto.js +111 -0
- package/dist/server/private-media/index-store.d.ts +31 -0
- package/dist/server/private-media/index-store.js +36 -0
- package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
- package/dist/server/private-media/mountPrivateRoutes.js +219 -0
- package/dist/server/private-media/pipeline.d.ts +37 -0
- package/dist/server/private-media/pipeline.js +97 -0
- package/dist/server/private-media/unlock.d.ts +52 -0
- package/dist/server/private-media/unlock.js +55 -0
- package/dist/server/resume/index.d.ts +2 -0
- package/dist/server/resume/index.js +2 -0
- package/dist/server/resume/routes.d.ts +41 -0
- package/dist/server/resume/routes.js +50 -0
- package/dist/server/resume/store.d.ts +37 -0
- package/dist/server/resume/store.js +38 -0
- package/dist/server/retention/archive.d.ts +78 -0
- package/dist/server/retention/archive.js +284 -0
- package/dist/server/retention/fleet.d.ts +50 -0
- package/dist/server/retention/fleet.js +66 -0
- package/dist/server/retention/index.d.ts +38 -0
- package/dist/server/retention/index.js +38 -0
- package/dist/server/retention/policy.d.ts +263 -0
- package/dist/server/retention/policy.js +264 -0
- package/dist/server/retention/store.d.ts +101 -0
- package/dist/server/retention/store.js +251 -0
- package/dist/server/retention/sweeper.d.ts +69 -0
- package/dist/server/retention/sweeper.js +289 -0
- package/dist/server/retention/target.d.ts +143 -0
- package/dist/server/retention/target.js +438 -0
- package/dist/server/search/index.d.ts +2 -0
- package/dist/server/search/index.js +6 -0
- package/dist/server/search/pgSearchProvider.d.ts +91 -0
- package/dist/server/search/pgSearchProvider.js +330 -0
- package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
- package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
- package/dist/server/search/pgSearchSchema.d.ts +81 -0
- package/dist/server/search/pgSearchSchema.js +89 -0
- package/dist/server/sharing/index.d.ts +18 -0
- package/dist/server/sharing/index.js +16 -0
- package/dist/server/sharing/migrations.d.ts +6 -0
- package/dist/server/sharing/migrations.js +79 -0
- package/dist/server/sharing/router.d.ts +111 -0
- package/dist/server/sharing/router.js +93 -0
- package/dist/server/sharing/sql.d.ts +67 -0
- package/dist/server/sharing/sql.js +88 -0
- package/dist/server/sharing/store.d.ts +173 -0
- package/dist/server/sharing/store.js +484 -0
- package/dist/server/sqlite/backup.d.ts +48 -0
- package/dist/server/sqlite/backup.js +97 -0
- package/dist/server/sqlite/browse.d.ts +42 -0
- package/dist/server/sqlite/browse.js +84 -0
- package/dist/server/sqlite/export.d.ts +32 -0
- package/dist/server/sqlite/export.js +42 -0
- package/dist/server/sqlite/fts5.d.ts +106 -0
- package/dist/server/sqlite/fts5.js +154 -0
- package/dist/server/sqlite/health.d.ts +44 -0
- package/dist/server/sqlite/health.js +52 -0
- package/dist/server/sqlite/import.d.ts +36 -0
- package/dist/server/sqlite/import.js +108 -0
- package/dist/server/sqlite/index.d.ts +9 -0
- package/dist/server/sqlite/index.js +15 -0
- package/dist/server/sqlite/readonlySql.d.ts +15 -0
- package/dist/server/sqlite/readonlySql.js +65 -0
- package/dist/server/sqlite/stats.d.ts +22 -0
- package/dist/server/sqlite/stats.js +32 -0
- package/dist/server/sqlite/viewer.d.ts +17 -0
- package/dist/server/sqlite/viewer.js +56 -0
- package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
- package/dist/server/storage/binaryServerProcessing.js +81 -0
- package/dist/server/storage/binaryServerStore.d.ts +3 -0
- package/dist/server/storage/binaryServerStore.js +95 -0
- package/dist/server/storage/diskStore.d.ts +2 -0
- package/dist/server/storage/diskStore.js +88 -0
- package/dist/server/storage/files/adapter.d.ts +33 -0
- package/dist/server/storage/files/adapter.js +203 -0
- package/dist/server/storage/files/catalogue.d.ts +72 -0
- package/dist/server/storage/files/catalogue.js +1033 -0
- package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
- package/dist/server/storage/files/chunkedUpload.js +70 -0
- package/dist/server/storage/files/fileRoutes.d.ts +51 -0
- package/dist/server/storage/files/fileRoutes.js +405 -0
- package/dist/server/storage/files/index.d.ts +9 -0
- package/dist/server/storage/files/index.js +11 -0
- package/dist/server/storage/files/migrations.d.ts +2 -0
- package/dist/server/storage/files/migrations.js +163 -0
- package/dist/server/storage/files/mimeByExt.d.ts +1 -0
- package/dist/server/storage/files/mimeByExt.js +13 -0
- package/dist/server/storage/files/stack.d.ts +136 -0
- package/dist/server/storage/files/stack.js +132 -0
- package/dist/server/storage/files/storageOps.d.ts +54 -0
- package/dist/server/storage/files/storageOps.js +110 -0
- package/dist/server/storage/files/types.d.ts +261 -0
- package/dist/server/storage/files/types.js +22 -0
- package/dist/server/storage/imageThumbnail.d.ts +3 -0
- package/dist/server/storage/imageThumbnail.js +50 -0
- package/dist/server/storage/index.d.ts +15 -0
- package/dist/server/storage/index.js +27 -0
- package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
- package/dist/server/storage/mountMediaRoutes.js +141 -0
- package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
- package/dist/server/storage/objectStoreMediaStore.js +57 -0
- package/dist/server/storage/s3ObjectStore.d.ts +63 -0
- package/dist/server/storage/s3ObjectStore.js +187 -0
- package/dist/server/storage/sources.d.ts +47 -0
- package/dist/server/storage/sources.js +148 -0
- package/dist/server/storage/streamRange.d.ts +19 -0
- package/dist/server/storage/streamRange.js +69 -0
- package/dist/server/storage/streamToken.d.ts +16 -0
- package/dist/server/storage/streamToken.js +54 -0
- package/dist/server/storage/transcodeJobs.d.ts +65 -0
- package/dist/server/storage/transcodeJobs.js +104 -0
- package/dist/server/storage/types.d.ts +193 -0
- package/dist/server/storage/types.js +41 -0
- package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
- package/dist/server/storage/videoDerivativeKeys.js +149 -0
- package/dist/server/storage/videoProcessor.d.ts +30 -0
- package/dist/server/storage/videoProcessor.js +148 -0
- package/dist/server/sync/alarm.d.ts +34 -0
- package/dist/server/sync/alarm.js +91 -0
- package/dist/server/sync/commands.d.ts +87 -0
- package/dist/server/sync/commands.js +241 -0
- package/dist/server/sync/engine.d.ts +62 -0
- package/dist/server/sync/engine.js +184 -0
- package/dist/server/sync/http.d.ts +106 -0
- package/dist/server/sync/http.js +243 -0
- package/dist/server/sync/index.d.ts +41 -0
- package/dist/server/sync/index.js +41 -0
- package/dist/server/sync/opLog.d.ts +61 -0
- package/dist/server/sync/opLog.js +96 -0
- package/dist/server/sync/planner.d.ts +31 -0
- package/dist/server/sync/planner.js +30 -0
- package/dist/server/sync/status.d.ts +63 -0
- package/dist/server/sync/status.js +49 -0
- package/dist/server/sync/timer.d.ts +52 -0
- package/dist/server/sync/timer.js +170 -0
- package/dist/server/sync/tokens.d.ts +25 -0
- package/dist/server/sync/tokens.js +51 -0
- package/dist/server/sync/types.d.ts +101 -0
- package/dist/server/sync/types.js +29 -0
- package/dist/server/ws/eventBus.d.ts +14 -0
- package/dist/server/ws/eventBus.js +42 -0
- package/dist/server/ws/hub.d.ts +20 -0
- package/dist/server/ws/hub.js +154 -0
- package/dist/server/ws/index.d.ts +5 -0
- package/dist/server/ws/index.js +7 -0
- package/dist/server/ws/presence.d.ts +11 -0
- package/dist/server/ws/presence.js +20 -0
- package/dist/server/ws/room.d.ts +17 -0
- package/dist/server/ws/room.js +23 -0
- package/dist/server/ws/types.d.ts +122 -0
- package/dist/server/ws/types.js +1 -0
- package/package.json +234 -0
- package/src/declaredDepsAreImported.spec.ts +87 -0
- package/src/leafSubpathsImportNothing.spec.ts +358 -0
- package/src/lockfileIsTracked.spec.ts +50 -0
- package/src/namedSubpathsResolve.spec.ts +122 -0
- package/src/noPathDeps.spec.ts +24 -0
- package/src/publishShape.spec.ts +89 -0
- package/src/server/activity/index.ts +59 -0
- package/src/server/activity/migrations.ts +67 -0
- package/src/server/activity/shareSource.ts +57 -0
- package/src/server/activity/store.spec.ts +255 -0
- package/src/server/activity/store.ts +368 -0
- package/src/server/app.spec.ts +243 -0
- package/src/server/app.ts +89 -0
- package/src/server/audit/index.ts +2 -0
- package/src/server/audit/migrations.ts +88 -0
- package/src/server/audit/sqliteSink.test.ts +200 -0
- package/src/server/audit/sqliteSink.ts +162 -0
- package/src/server/auth/cookie.spec.ts +46 -0
- package/src/server/auth/cookie.ts +42 -0
- package/src/server/auth/csrf.spec.ts +81 -0
- package/src/server/auth/csrf.ts +83 -0
- package/src/server/auth/fileTokenSigner.ts +40 -0
- package/src/server/auth/getOnly.spec.ts +53 -0
- package/src/server/auth/getOnly.ts +32 -0
- package/src/server/auth/index.ts +51 -0
- package/src/server/auth/jwt.spec.ts +106 -0
- package/src/server/auth/jwt.ts +75 -0
- package/src/server/auth/loginThrottle.spec.ts +213 -0
- package/src/server/auth/loginThrottle.ts +261 -0
- package/src/server/auth/middleware.spec.ts +148 -0
- package/src/server/auth/middleware.ts +56 -0
- package/src/server/auth/password.spec.ts +48 -0
- package/src/server/auth/password.ts +71 -0
- package/src/server/auth/sessionStore.spec.ts +189 -0
- package/src/server/auth/sessionStore.ts +261 -0
- package/src/server/auth/totp.spec.ts +73 -0
- package/src/server/auth/totp.ts +92 -0
- package/src/server/auth/types.ts +146 -0
- package/src/server/auth/vault.spec.ts +51 -0
- package/src/server/auth/vault.ts +71 -0
- package/src/server/context.ts +37 -0
- package/src/server/ctgr.spec.ts +138 -0
- package/src/server/ctgr.ts +108 -0
- package/src/server/db/kysely.spec.ts +62 -0
- package/src/server/db/kysely.ts +21 -0
- package/src/server/db/plumbingTypes.ts +126 -0
- package/src/server/dropzone/duplicates.ts +121 -0
- package/src/server/dropzone/index.ts +30 -0
- package/src/server/dropzone/scan.spec.ts +255 -0
- package/src/server/dropzone/scan.ts +342 -0
- package/src/server/errors.spec.ts +76 -0
- package/src/server/errors.ts +22 -0
- package/src/server/guard/argon2Binding.spec.ts +117 -0
- package/src/server/guard/argon2Binding.ts +195 -0
- package/src/server/guard/argon2Kat.spec.ts +250 -0
- package/src/server/guard/guard.spec.ts +390 -0
- package/src/server/guard/index.ts +6 -0
- package/src/server/guard/kdfWorker.spec.ts +283 -0
- package/src/server/guard/kdfWorker.ts +367 -0
- package/src/server/guard/kdfWorkerEntry.ts +16 -0
- package/src/server/guard/passwordVerifier.spec.ts +71 -0
- package/src/server/guard/passwordVerifier.ts +175 -0
- package/src/server/guard/revocationStore.spec.ts +135 -0
- package/src/server/guard/revocationStore.ts +291 -0
- package/src/server/guard/sessionSigner.ts +425 -0
- package/src/server/guard/verifier.ts +27 -0
- package/src/server/incidents/channels.ts +487 -0
- package/src/server/incidents/checks.ts +120 -0
- package/src/server/incidents/incidents.spec.ts +463 -0
- package/src/server/incidents/index.ts +96 -0
- package/src/server/incidents/migrations.ts +66 -0
- package/src/server/incidents/notifier.ts +215 -0
- package/src/server/incidents/reporter.ts +110 -0
- package/src/server/incidents/routes.ts +314 -0
- package/src/server/incidents/store.ts +329 -0
- package/src/server/incidents/types.ts +116 -0
- package/src/server/index.ts +248 -0
- package/src/server/jobs/fireQueue.spec.ts +85 -0
- package/src/server/jobs/fireQueue.ts +37 -0
- package/src/server/jobs/index.ts +26 -0
- package/src/server/jobs/logger.ts +16 -0
- package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
- package/src/server/jobs/plainjobAdapter.ts +54 -0
- package/src/server/jobs/polling.spec.ts +219 -0
- package/src/server/jobs/polling.ts +81 -0
- package/src/server/jobs/runner.spec.ts +213 -0
- package/src/server/jobs/runner.ts +213 -0
- package/src/server/jobs/timeout.spec.ts +35 -0
- package/src/server/jobs/timeout.ts +53 -0
- package/src/server/jobs/types.ts +188 -0
- package/src/server/master-lock/guard.spec.ts +347 -0
- package/src/server/master-lock/guard.ts +290 -0
- package/src/server/master-lock/index.ts +44 -0
- package/src/server/master-lock/lockPage.spec.ts +88 -0
- package/src/server/master-lock/lockPage.ts +313 -0
- package/src/server/master-lock/masterLock.spec.ts +338 -0
- package/src/server/master-lock/masterLock.ts +627 -0
- package/src/server/master-lock/principals.spec.ts +378 -0
- package/src/server/master-lock/principals.ts +175 -0
- package/src/server/master-lock/seed.spec.ts +69 -0
- package/src/server/master-lock/seed.ts +112 -0
- package/src/server/master-lock/store.ts +35 -0
- package/src/server/media-bun/image.spec.ts +115 -0
- package/src/server/media-bun/image.ts +134 -0
- package/src/server/media-bun/index.ts +52 -0
- package/src/server/media-bun/streaming.spec.ts +134 -0
- package/src/server/media-bun/streaming.ts +347 -0
- package/src/server/media-bun/video.spec.ts +115 -0
- package/src/server/media-bun/video.ts +232 -0
- package/src/server/metrics/metricsBuffer.ts +92 -0
- package/src/server/metrics/normalizeRoute.spec.ts +51 -0
- package/src/server/metrics/normalizeRoute.ts +63 -0
- package/src/server/middleware/correlationId.ts +24 -0
- package/src/server/middleware/corsAllowlist.spec.ts +118 -0
- package/src/server/middleware/corsAllowlist.ts +69 -0
- package/src/server/middleware/errorEnvelope.ts +38 -0
- package/src/server/middleware/index.ts +19 -0
- package/src/server/middleware/localOrigin.spec.ts +168 -0
- package/src/server/middleware/localOrigin.ts +139 -0
- package/src/server/middleware/rateLimit.spec.ts +72 -0
- package/src/server/middleware/rateLimit.ts +125 -0
- package/src/server/middleware/requestLogger.ts +95 -0
- package/src/server/middleware/securityHeaders.ts +78 -0
- package/src/server/middleware/zValidator.spec.ts +251 -0
- package/src/server/middleware/zValidator.ts +114 -0
- package/src/server/migration/runner.spec.ts +88 -0
- package/src/server/migration/runner.ts +87 -0
- package/src/server/migration/types.ts +17 -0
- package/src/server/net/outboundGuard.spec.ts +90 -0
- package/src/server/net/outboundGuard.ts +205 -0
- package/src/server/notifications/hub.ts +245 -0
- package/src/server/notifications/index.ts +77 -0
- package/src/server/notifications/migrations.ts +39 -0
- package/src/server/notifications/routes.ts +177 -0
- package/src/server/notifications/service.spec.ts +305 -0
- package/src/server/notifications/service.ts +112 -0
- package/src/server/paths/appDataHome.spec.ts +87 -0
- package/src/server/paths/appDataHome.ts +84 -0
- package/src/server/private-media/blobRoute.spec.ts +165 -0
- package/src/server/private-media/crypto.spec.ts +81 -0
- package/src/server/private-media/crypto.ts +155 -0
- package/src/server/private-media/gatePolicy.spec.ts +98 -0
- package/src/server/private-media/index-store.ts +83 -0
- package/src/server/private-media/mountPrivateRoutes.ts +265 -0
- package/src/server/private-media/pipeline.spec.ts +111 -0
- package/src/server/private-media/pipeline.ts +170 -0
- package/src/server/private-media/routes.spec.ts +265 -0
- package/src/server/private-media/unlock.ts +112 -0
- package/src/server/resume/index.ts +8 -0
- package/src/server/resume/resume.spec.ts +118 -0
- package/src/server/resume/routes.ts +92 -0
- package/src/server/resume/store.ts +82 -0
- package/src/server/retention/archive.spec.ts +181 -0
- package/src/server/retention/archive.ts +353 -0
- package/src/server/retention/fleet.ts +104 -0
- package/src/server/retention/index.ts +101 -0
- package/src/server/retention/policy.spec.ts +132 -0
- package/src/server/retention/policy.ts +392 -0
- package/src/server/retention/store.spec.ts +236 -0
- package/src/server/retention/store.ts +338 -0
- package/src/server/retention/sweeper.spec.ts +321 -0
- package/src/server/retention/sweeper.ts +367 -0
- package/src/server/retention/target.spec.ts +592 -0
- package/src/server/retention/target.ts +613 -0
- package/src/server/search/index.ts +12 -0
- package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
- package/src/server/search/pgSearchProvider.ts +421 -0
- package/src/server/search/pgSearchSchema.ts +147 -0
- package/src/server/sharing/index.ts +65 -0
- package/src/server/sharing/migrations.ts +128 -0
- package/src/server/sharing/router.spec.ts +167 -0
- package/src/server/sharing/router.ts +194 -0
- package/src/server/sharing/sql.ts +109 -0
- package/src/server/sharing/store.spec.ts +408 -0
- package/src/server/sharing/store.ts +737 -0
- package/src/server/sqlite/backup.spec.ts +114 -0
- package/src/server/sqlite/backup.ts +154 -0
- package/src/server/sqlite/browse.spec.ts +70 -0
- package/src/server/sqlite/browse.ts +120 -0
- package/src/server/sqlite/export.spec.ts +43 -0
- package/src/server/sqlite/export.ts +74 -0
- package/src/server/sqlite/fts5.spec.ts +175 -0
- package/src/server/sqlite/fts5.ts +255 -0
- package/src/server/sqlite/health.spec.ts +79 -0
- package/src/server/sqlite/health.ts +105 -0
- package/src/server/sqlite/import.spec.ts +75 -0
- package/src/server/sqlite/import.ts +141 -0
- package/src/server/sqlite/index.ts +64 -0
- package/src/server/sqlite/readonlySql.spec.ts +58 -0
- package/src/server/sqlite/readonlySql.ts +67 -0
- package/src/server/sqlite/stats.spec.ts +21 -0
- package/src/server/sqlite/stats.ts +61 -0
- package/src/server/sqlite/viewer.spec.ts +127 -0
- package/src/server/sqlite/viewer.ts +72 -0
- package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
- package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
- package/src/server/storage/binaryServerProcessing.ts +127 -0
- package/src/server/storage/binaryServerStore.ts +102 -0
- package/src/server/storage/chunked.integration.spec.ts +185 -0
- package/src/server/storage/diskStore.ts +83 -0
- package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
- package/src/server/storage/files/adapter.photo.spec.ts +128 -0
- package/src/server/storage/files/adapter.spec.ts +121 -0
- package/src/server/storage/files/adapter.ts +255 -0
- package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
- package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
- package/src/server/storage/files/catalogue.private.spec.ts +106 -0
- package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
- package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
- package/src/server/storage/files/catalogue.spec.ts +133 -0
- package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
- package/src/server/storage/files/catalogue.ts +1326 -0
- package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
- package/src/server/storage/files/chunkedUpload.ts +146 -0
- package/src/server/storage/files/collections.spec.ts +294 -0
- package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
- package/src/server/storage/files/fileRoutes.spec.ts +327 -0
- package/src/server/storage/files/fileRoutes.ts +462 -0
- package/src/server/storage/files/index.ts +66 -0
- package/src/server/storage/files/migrations.ts +187 -0
- package/src/server/storage/files/mimeByExt.ts +14 -0
- package/src/server/storage/files/stack.spec.ts +258 -0
- package/src/server/storage/files/stack.ts +284 -0
- package/src/server/storage/files/storageOps.spec.ts +126 -0
- package/src/server/storage/files/storageOps.ts +213 -0
- package/src/server/storage/files/types.ts +285 -0
- package/src/server/storage/imageThumbnail.spec.ts +81 -0
- package/src/server/storage/imageThumbnail.ts +60 -0
- package/src/server/storage/index.ts +122 -0
- package/src/server/storage/mountMediaRoutes.ts +231 -0
- package/src/server/storage/objectStore.spec.ts +272 -0
- package/src/server/storage/objectStoreMediaStore.ts +63 -0
- package/src/server/storage/s3ObjectStore.ts +239 -0
- package/src/server/storage/sources.ts +245 -0
- package/src/server/storage/storage.spec.ts +219 -0
- package/src/server/storage/streamRange.ts +92 -0
- package/src/server/storage/streamToken.ts +76 -0
- package/src/server/storage/transcode.integration.spec.ts +134 -0
- package/src/server/storage/transcodeJobs.spec.ts +152 -0
- package/src/server/storage/transcodeJobs.ts +171 -0
- package/src/server/storage/types.ts +230 -0
- package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
- package/src/server/storage/videoDerivativeKeys.ts +172 -0
- package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
- package/src/server/storage/videoProcessor.ts +229 -0
- package/src/server/sync/alarm.spec.ts +149 -0
- package/src/server/sync/alarm.ts +137 -0
- package/src/server/sync/commands.spec.ts +145 -0
- package/src/server/sync/commands.ts +361 -0
- package/src/server/sync/engine.spec.ts +496 -0
- package/src/server/sync/engine.ts +255 -0
- package/src/server/sync/http.ts +316 -0
- package/src/server/sync/httpRemote.spec.ts +158 -0
- package/src/server/sync/index.ts +90 -0
- package/src/server/sync/opLog.spec.ts +110 -0
- package/src/server/sync/opLog.ts +189 -0
- package/src/server/sync/planner.spec.ts +53 -0
- package/src/server/sync/planner.ts +62 -0
- package/src/server/sync/status.spec.ts +92 -0
- package/src/server/sync/status.ts +108 -0
- package/src/server/sync/timer.spec.ts +150 -0
- package/src/server/sync/timer.ts +207 -0
- package/src/server/sync/tokens.spec.ts +38 -0
- package/src/server/sync/tokens.ts +94 -0
- package/src/server/sync/types.ts +108 -0
- package/src/server/ws/eventBus.spec.ts +67 -0
- package/src/server/ws/eventBus.ts +46 -0
- package/src/server/ws/hub.integration.spec.ts +93 -0
- package/src/server/ws/hub.spec.ts +282 -0
- package/src/server/ws/hub.ts +168 -0
- package/src/server/ws/index.ts +20 -0
- package/src/server/ws/presence.spec.ts +27 -0
- package/src/server/ws/presence.ts +26 -0
- package/src/server/ws/room.spec.ts +36 -0
- package/src/server/ws/room.ts +29 -0
- package/src/server/ws/types.ts +134 -0
- package/src/skippedTestsAreAnswered.spec.ts +215 -0
- package/src/testTmpRoot.spec.ts +102 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { assertNoContent } from "./store";
|
|
2
|
+
const noStore = { "cache-control": "no-store" };
|
|
3
|
+
/** Long enough for three real sentences, short enough that nobody pastes a log in. */
|
|
4
|
+
const MAX_FIELD = 600;
|
|
5
|
+
const MAX_REPORTS = 100;
|
|
6
|
+
const str = (v, fallback = "") => typeof v === "string" ? v.slice(0, MAX_FIELD) : fallback;
|
|
7
|
+
/** Parse one posted report defensively. Throws with a usable message. */
|
|
8
|
+
function parseReport(raw) {
|
|
9
|
+
if (!raw || typeof raw !== "object")
|
|
10
|
+
throw new Error("each report must be an object");
|
|
11
|
+
const r = raw;
|
|
12
|
+
const severity = r.severity === "blocking" ? "blocking" : "warning";
|
|
13
|
+
const report = {
|
|
14
|
+
source: str(r.source),
|
|
15
|
+
kind: str(r.kind),
|
|
16
|
+
subject: str(r.subject),
|
|
17
|
+
severity,
|
|
18
|
+
title: str(r.title),
|
|
19
|
+
whatBroke: str(r.whatBroke),
|
|
20
|
+
whatItBlocks: str(r.whatItBlocks),
|
|
21
|
+
whatToDo: str(r.whatToDo),
|
|
22
|
+
...(r.facts && typeof r.facts === "object"
|
|
23
|
+
? { facts: r.facts }
|
|
24
|
+
: {}),
|
|
25
|
+
};
|
|
26
|
+
for (const field of ["source", "kind", "subject", "title", "whatBroke"]) {
|
|
27
|
+
if (!report[field])
|
|
28
|
+
throw new Error(`report is missing "${field}"`);
|
|
29
|
+
}
|
|
30
|
+
// The privacy gate, applied to off-box input before it can reach a phone.
|
|
31
|
+
assertNoContent(report);
|
|
32
|
+
return report;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The OWNER's surface: the list, the acks, the health strip, the manual sweep,
|
|
36
|
+
* the channel test. Mount it INSIDE the app's session gate.
|
|
37
|
+
*/
|
|
38
|
+
export function mountIncidentOwnerRoutes(app, options, basePath = "/incidents") {
|
|
39
|
+
const { store } = options;
|
|
40
|
+
const guard = async (c) => (await options.authorize(c)) ? null : c.json({ error: "forbidden" }, 403);
|
|
41
|
+
app.get(`${basePath}`, async (c) => {
|
|
42
|
+
const denied = await guard(c);
|
|
43
|
+
if (denied)
|
|
44
|
+
return denied;
|
|
45
|
+
const state = c.req.query("state");
|
|
46
|
+
const incidents = store.list({
|
|
47
|
+
state: state === "all"
|
|
48
|
+
? undefined
|
|
49
|
+
: state === "resolved"
|
|
50
|
+
? "resolved"
|
|
51
|
+
: state === "open"
|
|
52
|
+
? "open"
|
|
53
|
+
: "active",
|
|
54
|
+
limit: Number(c.req.query("limit") ?? 200),
|
|
55
|
+
});
|
|
56
|
+
return c.json({ incidents }, 200, noStore);
|
|
57
|
+
});
|
|
58
|
+
app.post(`${basePath}/:fingerprint/ack`, async (c) => {
|
|
59
|
+
const denied = await guard(c);
|
|
60
|
+
if (denied)
|
|
61
|
+
return denied;
|
|
62
|
+
const ok = store.acknowledge(decodeURIComponent(c.req.param("fingerprint")));
|
|
63
|
+
return c.json({ ok }, ok ? 200 : 404, noStore);
|
|
64
|
+
});
|
|
65
|
+
app.get(`${basePath}/health`, async (c) => {
|
|
66
|
+
const denied = await guard(c);
|
|
67
|
+
if (denied)
|
|
68
|
+
return denied;
|
|
69
|
+
return c.json({
|
|
70
|
+
channels: (options.channelsOf?.() ?? options.notifier?.channels ?? []).map((ch) => ({
|
|
71
|
+
key: ch.key,
|
|
72
|
+
label: ch.label,
|
|
73
|
+
})),
|
|
74
|
+
lastSweep: options.lastSweep?.() ?? null,
|
|
75
|
+
sweepable: Boolean(options.sweep),
|
|
76
|
+
}, 200, noStore);
|
|
77
|
+
});
|
|
78
|
+
if (options.sweep) {
|
|
79
|
+
app.post(`${basePath}/sweep`, async (c) => {
|
|
80
|
+
const denied = await guard(c);
|
|
81
|
+
if (denied)
|
|
82
|
+
return denied;
|
|
83
|
+
const result = await options.sweep?.();
|
|
84
|
+
if (!result)
|
|
85
|
+
return c.json({ error: "no detectors on this host" }, 400, noStore);
|
|
86
|
+
const announced = options.notifier ? await options.notifier.announce(result.transitions) : [];
|
|
87
|
+
return c.json({ result, announced }, 200, noStore);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (options.channelsOf) {
|
|
91
|
+
app.post(`${basePath}/test/:channel`, async (c) => {
|
|
92
|
+
const denied = await guard(c);
|
|
93
|
+
if (denied)
|
|
94
|
+
return denied;
|
|
95
|
+
const key = c.req.param("channel");
|
|
96
|
+
const channel = options.channelsOf?.().find((ch) => ch.key === key);
|
|
97
|
+
if (!channel)
|
|
98
|
+
return c.json({ error: `no channel "${key}" on this host` }, 404, noStore);
|
|
99
|
+
// Sent at `blocking` because that is the severity whose delivery matters —
|
|
100
|
+
// a test that exercises the in-app path and skips the phone proves nothing.
|
|
101
|
+
const result = await channel.send({
|
|
102
|
+
severity: "blocking",
|
|
103
|
+
subject: "[test] fleet incident alerts",
|
|
104
|
+
text: "This is the console proving the channel works. Nothing is wrong. No action needed.",
|
|
105
|
+
});
|
|
106
|
+
return c.json({ result }, 200, noStore);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The MACHINE doors: `POST /report`, which another service beats its findings
|
|
112
|
+
* into, and `POST /relay-email`, which a host that cannot send mail hands a
|
|
113
|
+
* message to. Both carry a shared bearer.
|
|
114
|
+
*
|
|
115
|
+
* 🔴 **Mount this BEFORE the session gate, and never call it with `authorize`
|
|
116
|
+
* expecting it to matter — it registers no route a browser should reach.**
|
|
117
|
+
* Mounting the two together inside a cookie gate is the trap this split exists
|
|
118
|
+
* to make impossible: every beat from binary-server and orch came back
|
|
119
|
+
* `{"error":"unauthorized"}` from the gate, so no log line in this module said
|
|
120
|
+
* anything was wrong and the console rendered a perfectly calm, empty board.
|
|
121
|
+
* A reporter cannot report the failure to reach the reporting door.
|
|
122
|
+
*
|
|
123
|
+
* Keeping them in one function also risked the opposite mistake — registering
|
|
124
|
+
* the owner's `GET /incidents` outside the gate, where Hono's first-match would
|
|
125
|
+
* have shadowed the real one for ever.
|
|
126
|
+
*/
|
|
127
|
+
export function mountIncidentMachineRoutes(app, options, basePath = "/incidents") {
|
|
128
|
+
const { store } = options;
|
|
129
|
+
if (options.reporterToken) {
|
|
130
|
+
app.post(`${basePath}/report`, async (c) => {
|
|
131
|
+
const header = c.req.header("authorization") ?? "";
|
|
132
|
+
const presented = header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
133
|
+
// Length-first compare then constant-time-ish equality; the token is a
|
|
134
|
+
// shared secret between our own daemons, not a user credential.
|
|
135
|
+
if (presented.length !== options.reporterToken?.length ||
|
|
136
|
+
presented !== options.reporterToken) {
|
|
137
|
+
return c.json({ error: "forbidden" }, 403, noStore);
|
|
138
|
+
}
|
|
139
|
+
const body = (await c.req.json().catch(() => null));
|
|
140
|
+
const checkKey = str(body?.checkKey);
|
|
141
|
+
const source = str(body?.source);
|
|
142
|
+
const rawReports = Array.isArray(body?.reports) ? body.reports : null;
|
|
143
|
+
if (!checkKey || !source || !rawReports) {
|
|
144
|
+
return c.json({ error: "checkKey, source and reports[] are required" }, 400, noStore);
|
|
145
|
+
}
|
|
146
|
+
if (rawReports.length > MAX_REPORTS) {
|
|
147
|
+
return c.json({ error: `at most ${MAX_REPORTS} reports per call` }, 400, noStore);
|
|
148
|
+
}
|
|
149
|
+
let reports;
|
|
150
|
+
try {
|
|
151
|
+
reports = rawReports.map(parseReport);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
return c.json({ error: err instanceof Error ? err.message : "bad report" }, 400, noStore);
|
|
155
|
+
}
|
|
156
|
+
// A reporter declares the kinds it is authoritative for, exactly as a
|
|
157
|
+
// local check does — without them a remote service could never clear its
|
|
158
|
+
// own incidents, and every transient blip would stay red forever.
|
|
159
|
+
const kinds = Array.isArray(body?.kinds)
|
|
160
|
+
? body.kinds.filter((k) => typeof k === "string")
|
|
161
|
+
: [...new Set(reports.map((r) => r.kind))];
|
|
162
|
+
const transitions = store.reconcile(checkKey, reports, {
|
|
163
|
+
namespaces: kinds.map((k) => `${source}:${k}:`),
|
|
164
|
+
});
|
|
165
|
+
const announced = options.notifier ? await options.notifier.announce(transitions) : [];
|
|
166
|
+
return c.json({
|
|
167
|
+
opened: transitions.opened.length,
|
|
168
|
+
escalated: transitions.escalated.length,
|
|
169
|
+
resolved: transitions.resolved.length,
|
|
170
|
+
unchanged: transitions.unchanged.length,
|
|
171
|
+
announced,
|
|
172
|
+
}, 200, noStore);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (options.relayEmail) {
|
|
176
|
+
app.post(`${basePath}/relay-email`, async (c) => {
|
|
177
|
+
const header = c.req.header("authorization") ?? "";
|
|
178
|
+
const presented = header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
179
|
+
if (!options.reporterToken ||
|
|
180
|
+
presented.length !== options.reporterToken.length ||
|
|
181
|
+
presented !== options.reporterToken) {
|
|
182
|
+
return c.json({ error: "forbidden" }, 403, noStore);
|
|
183
|
+
}
|
|
184
|
+
const body = (await c.req.json().catch(() => null));
|
|
185
|
+
const subject = str(body?.subject);
|
|
186
|
+
const text = typeof body?.text === "string" ? body.text.slice(0, 4000) : "";
|
|
187
|
+
if (!subject || !text)
|
|
188
|
+
return c.json({ error: "subject and text required" }, 400, noStore);
|
|
189
|
+
// 🔴 The REASON crosses the wire, not just the boolean. `{sent:false}`
|
|
190
|
+
// alone made the local console report "the prod console accepted the
|
|
191
|
+
// request but its mailer declined" — true, useless, and indistinguishable
|
|
192
|
+
// between "mail is off on that host" and "SES refused the address". The
|
|
193
|
+
// relay is the only place that knows.
|
|
194
|
+
const outcome = await options.relayEmail?.({
|
|
195
|
+
subject,
|
|
196
|
+
text,
|
|
197
|
+
...(typeof body?.html === "string" ? { html: body.html.slice(0, 20_000) } : {}),
|
|
198
|
+
});
|
|
199
|
+
return c.json({ sent: Boolean(outcome?.sent), ...(outcome?.reason ? { reason: outcome.reason } : {}) }, 200, noStore);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The incident store, and the reconcile step that is the whole dedupe promise.
|
|
3
|
+
*
|
|
4
|
+
* ── The one rule ────────────────────────────────────────────────────────────
|
|
5
|
+
* `reconcile()` is the ONLY way an incident changes state, and it is what
|
|
6
|
+
* decides whether anything is notified. A detector hands it the complete set of
|
|
7
|
+
* conditions that are true RIGHT NOW; the store diffs that against what it
|
|
8
|
+
* already holds. Three things can come out of the diff — opened, escalated,
|
|
9
|
+
* resolved — and everything else is a counter bump that notifies nobody.
|
|
10
|
+
* Because the diff is set-shaped rather than event-shaped, a poller running
|
|
11
|
+
* every five minutes for a week produces exactly two messages about one broken
|
|
12
|
+
* thing: one when it broke, one when it was fixed.
|
|
13
|
+
*
|
|
14
|
+
* 🔴 The subtle half is the RESOLVE, and it is where a naive version of this
|
|
15
|
+
* lies to its owner. A detector that throws, times out, or is not registered on
|
|
16
|
+
* this host reports nothing — and "reported nothing" must never be read as
|
|
17
|
+
* "everything it watches is fixed", or a crashed detector cheerfully announces
|
|
18
|
+
* that an outage is over. Two things prevent that: `reconcile` only ever
|
|
19
|
+
* resolves fingerprints belonging to the check it was called for AND inside the
|
|
20
|
+
* namespaces that check declares it is authoritative for, and `runIncidentSweep`
|
|
21
|
+
* (`checks.ts`) does not call `reconcile` at all for a check that threw.
|
|
22
|
+
*
|
|
23
|
+
* ── Where the file lives ────────────────────────────────────────────────────
|
|
24
|
+
* Whatever `Database` the caller opens. The console keeps it in an incidents
|
|
25
|
+
* database of its own under the host's app-data root, deliberately NOT in the
|
|
26
|
+
* metrics database: metrics are capped, regenerable telemetry that trims on write, and
|
|
27
|
+
* an incident is a governed record whose whole value is that it survives — most
|
|
28
|
+
* of all across the restart a deploy of the console itself performs.
|
|
29
|
+
*/
|
|
30
|
+
import type { Database } from "bun:sqlite";
|
|
31
|
+
import { type Incident, type IncidentReport, type IncidentTransitions } from "./types";
|
|
32
|
+
/**
|
|
33
|
+
* The privacy gate, enforced rather than reviewed. `facts` is for numbers —
|
|
34
|
+
* bytes free, items failed, commits behind — and a string slipping in there is
|
|
35
|
+
* how a file name reaches an SMS. See the header of `types.ts`.
|
|
36
|
+
*
|
|
37
|
+
* It throws rather than sanitising, because a detector author meets the failure
|
|
38
|
+
* in their own test run, which is the only moment the mistake is cheap.
|
|
39
|
+
*/
|
|
40
|
+
export declare function assertNoContent(report: IncidentReport): void;
|
|
41
|
+
export interface ReconcileOptions {
|
|
42
|
+
/**
|
|
43
|
+
* The `source:kind:` prefixes this check is authoritative for on this run.
|
|
44
|
+
* Resolving is confined to them, so one detector can never resolve another's
|
|
45
|
+
* incidents — and a check that RAN and found nothing can still clear its own,
|
|
46
|
+
* which a report list alone cannot express (an empty array is
|
|
47
|
+
* indistinguishable from "did not run"). `runIncidentSweep` derives these
|
|
48
|
+
* from the check's declared `kinds`; omitted, they fall back to whatever the
|
|
49
|
+
* reports mention, which is the safe direction to be wrong in.
|
|
50
|
+
*/
|
|
51
|
+
namespaces?: string[];
|
|
52
|
+
now?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface IncidentStore {
|
|
55
|
+
readonly db: Database;
|
|
56
|
+
/** Newest activity first. `state: "active"` means open + acknowledged. */
|
|
57
|
+
list: (opts?: {
|
|
58
|
+
state?: Incident["state"] | "active";
|
|
59
|
+
limit?: number;
|
|
60
|
+
}) => Incident[];
|
|
61
|
+
get: (fp: string) => Incident | null;
|
|
62
|
+
/** The dedupe engine. See the module header. */
|
|
63
|
+
reconcile: (checkKey: string, reports: IncidentReport[], opts?: ReconcileOptions) => IncidentTransitions;
|
|
64
|
+
/** Record that the OPEN notification for these fingerprints went out. */
|
|
65
|
+
markNotified: (fps: string[], result: string, now?: number) => void;
|
|
66
|
+
/** Record that the RESOLVED notification went out. */
|
|
67
|
+
markResolvedNotified: (fps: string[], now?: number) => void;
|
|
68
|
+
/** The owner has seen it: stop counting it as needing attention, keep it listed. */
|
|
69
|
+
acknowledge: (fp: string, now?: number) => boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Open the store over an existing database. The caller owns the `Database` (and
|
|
73
|
+
* its lifetime) — this primitive owns one table in it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createIncidentStore(db: Database): IncidentStore;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { runMigrations } from "../migration/runner";
|
|
2
|
+
import { INCIDENT_MIGRATIONS, INCIDENTS_TABLE } from "./migrations";
|
|
3
|
+
import { fingerprint, } from "./types";
|
|
4
|
+
const toIncident = (r) => ({
|
|
5
|
+
fingerprint: r.fingerprint,
|
|
6
|
+
source: r.source,
|
|
7
|
+
kind: r.kind,
|
|
8
|
+
subject: r.subject,
|
|
9
|
+
severity: r.severity === "blocking" ? "blocking" : "warning",
|
|
10
|
+
state: r.state === "open" ? "open" : r.state === "acknowledged" ? "acknowledged" : "resolved",
|
|
11
|
+
title: r.title,
|
|
12
|
+
whatBroke: r.what_broke,
|
|
13
|
+
whatItBlocks: r.what_it_blocks,
|
|
14
|
+
whatToDo: r.what_to_do,
|
|
15
|
+
facts: r.facts_json ? JSON.parse(r.facts_json) : undefined,
|
|
16
|
+
firstSeen: r.first_seen,
|
|
17
|
+
lastSeen: r.last_seen,
|
|
18
|
+
seenCount: r.seen_count,
|
|
19
|
+
episodes: r.episodes,
|
|
20
|
+
acknowledgedAt: r.acknowledged_at,
|
|
21
|
+
resolvedAt: r.resolved_at,
|
|
22
|
+
notifiedAt: r.notified_at,
|
|
23
|
+
notifyResult: r.notify_result,
|
|
24
|
+
resolvedNotifiedAt: r.resolved_notified_at,
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* The privacy gate, enforced rather than reviewed. `facts` is for numbers —
|
|
28
|
+
* bytes free, items failed, commits behind — and a string slipping in there is
|
|
29
|
+
* how a file name reaches an SMS. See the header of `types.ts`.
|
|
30
|
+
*
|
|
31
|
+
* It throws rather than sanitising, because a detector author meets the failure
|
|
32
|
+
* in their own test run, which is the only moment the mistake is cheap.
|
|
33
|
+
*/
|
|
34
|
+
export function assertNoContent(report) {
|
|
35
|
+
for (const [key, value] of Object.entries(report.facts ?? {})) {
|
|
36
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null)
|
|
37
|
+
continue;
|
|
38
|
+
throw new Error(`incident ${fingerprint(report)}: fact "${key}" is a ${typeof value}. ` +
|
|
39
|
+
"facts take numbers, booleans and null only — a string here is how private " +
|
|
40
|
+
"content reaches a text message. Put the words in whatBroke / whatItBlocks / " +
|
|
41
|
+
"whatToDo, which a detector's author writes and reviews.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Open the store over an existing database. The caller owns the `Database` (and
|
|
46
|
+
* its lifetime) — this primitive owns one table in it.
|
|
47
|
+
*/
|
|
48
|
+
export function createIncidentStore(db) {
|
|
49
|
+
runMigrations(db, INCIDENT_MIGRATIONS);
|
|
50
|
+
const selectOne = db.prepare(`SELECT * FROM ${INCIDENTS_TABLE} WHERE fingerprint = ?`);
|
|
51
|
+
const insert = db.prepare(`INSERT INTO ${INCIDENTS_TABLE} (
|
|
52
|
+
fingerprint, source, check_key, kind, subject, severity, state, title,
|
|
53
|
+
what_broke, what_it_blocks, what_to_do, facts_json,
|
|
54
|
+
first_seen, last_seen, seen_count, episodes
|
|
55
|
+
) VALUES (?, ?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, ?, 1, 1)`);
|
|
56
|
+
/** A NEW episode of a fingerprint that had resolved. Counters restart; `episodes` does not. */
|
|
57
|
+
const reopen = db.prepare(`UPDATE ${INCIDENTS_TABLE} SET
|
|
58
|
+
state = 'open', severity = ?, title = ?, what_broke = ?, what_it_blocks = ?,
|
|
59
|
+
what_to_do = ?, facts_json = ?, first_seen = ?, last_seen = ?, seen_count = 1,
|
|
60
|
+
episodes = episodes + 1, acknowledged_at = NULL, resolved_at = NULL,
|
|
61
|
+
notified_at = NULL, notify_result = NULL, resolved_notified_at = NULL
|
|
62
|
+
WHERE fingerprint = ?`);
|
|
63
|
+
/**
|
|
64
|
+
* Observed again. The words and the facts are REFRESHED — the numbers move,
|
|
65
|
+
* and "17 GB free" must not stay frozen at whatever it was on Tuesday — but
|
|
66
|
+
* nothing about the lifecycle moves, so no transition is emitted and nothing
|
|
67
|
+
* is sent. This statement is the dedupe, in one place.
|
|
68
|
+
*/
|
|
69
|
+
const touch = db.prepare(`UPDATE ${INCIDENTS_TABLE} SET
|
|
70
|
+
last_seen = ?, seen_count = seen_count + 1, title = ?, what_broke = ?,
|
|
71
|
+
what_it_blocks = ?, what_to_do = ?, facts_json = ?, severity = ?
|
|
72
|
+
WHERE fingerprint = ?`);
|
|
73
|
+
const resolveStmt = db.prepare(`UPDATE ${INCIDENTS_TABLE} SET state = 'resolved', resolved_at = ? WHERE fingerprint = ?`);
|
|
74
|
+
const store = {
|
|
75
|
+
db,
|
|
76
|
+
list(opts = {}) {
|
|
77
|
+
const limit = opts.limit ?? 200;
|
|
78
|
+
if (opts.state === "active") {
|
|
79
|
+
return db
|
|
80
|
+
.prepare(`SELECT * FROM ${INCIDENTS_TABLE} WHERE state IN ('open','acknowledged')
|
|
81
|
+
ORDER BY last_seen DESC LIMIT ?`)
|
|
82
|
+
.all(limit).map(toIncident);
|
|
83
|
+
}
|
|
84
|
+
if (opts.state) {
|
|
85
|
+
return db
|
|
86
|
+
.prepare(`SELECT * FROM ${INCIDENTS_TABLE} WHERE state = ? ORDER BY last_seen DESC LIMIT ?`)
|
|
87
|
+
.all(opts.state, limit).map(toIncident);
|
|
88
|
+
}
|
|
89
|
+
return db
|
|
90
|
+
.prepare(`SELECT * FROM ${INCIDENTS_TABLE} ORDER BY last_seen DESC LIMIT ?`)
|
|
91
|
+
.all(limit).map(toIncident);
|
|
92
|
+
},
|
|
93
|
+
get(fp) {
|
|
94
|
+
const row = selectOne.get(fp);
|
|
95
|
+
return row ? toIncident(row) : null;
|
|
96
|
+
},
|
|
97
|
+
reconcile(checkKey, reports, opts = {}) {
|
|
98
|
+
const now = opts.now ?? Date.now();
|
|
99
|
+
const out = { opened: [], escalated: [], resolved: [], unchanged: [] };
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
for (const report of reports) {
|
|
102
|
+
assertNoContent(report);
|
|
103
|
+
const fp = fingerprint(report);
|
|
104
|
+
seen.add(fp);
|
|
105
|
+
const facts = report.facts ? JSON.stringify(report.facts) : null;
|
|
106
|
+
const existing = selectOne.get(fp);
|
|
107
|
+
if (!existing) {
|
|
108
|
+
insert.run(fp, report.source, checkKey, report.kind, report.subject, report.severity, report.title, report.whatBroke, report.whatItBlocks, report.whatToDo, facts, now, now);
|
|
109
|
+
const opened = store.get(fp);
|
|
110
|
+
if (opened)
|
|
111
|
+
out.opened.push(opened);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (existing.state === "resolved") {
|
|
115
|
+
reopen.run(report.severity, report.title, report.whatBroke, report.whatItBlocks, report.whatToDo, facts, now, now, fp);
|
|
116
|
+
const reopened = store.get(fp);
|
|
117
|
+
if (reopened)
|
|
118
|
+
out.opened.push(reopened);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// Still open, or acknowledged. The one thing left that can be worth a
|
|
122
|
+
// message is a WARNING that has become BLOCKING, because that changes
|
|
123
|
+
// which channels the incident is entitled to. Everything else is noise.
|
|
124
|
+
const escalating = existing.severity !== "blocking" && report.severity === "blocking";
|
|
125
|
+
touch.run(now, report.title, report.whatBroke, report.whatItBlocks, report.whatToDo, facts, report.severity, fp);
|
|
126
|
+
const current = store.get(fp);
|
|
127
|
+
if (!current)
|
|
128
|
+
continue;
|
|
129
|
+
if (escalating)
|
|
130
|
+
out.escalated.push(current);
|
|
131
|
+
else
|
|
132
|
+
out.unchanged.push(current);
|
|
133
|
+
}
|
|
134
|
+
const prefixes = opts.namespaces ?? [
|
|
135
|
+
...new Set(reports.map((r) => `${r.source}:${r.kind}:`)),
|
|
136
|
+
];
|
|
137
|
+
const candidates = db
|
|
138
|
+
.prepare(`SELECT * FROM ${INCIDENTS_TABLE} WHERE check_key = ? AND state IN ('open','acknowledged')`)
|
|
139
|
+
.all(checkKey);
|
|
140
|
+
for (const row of candidates) {
|
|
141
|
+
if (seen.has(row.fingerprint))
|
|
142
|
+
continue;
|
|
143
|
+
// Outside the namespaces this run is authoritative for — leave it alone.
|
|
144
|
+
// With no namespaces at all (a check that declared none and reported
|
|
145
|
+
// none) nothing resolves, which is the correct reading of "I have no
|
|
146
|
+
// idea" rather than "everything is fine".
|
|
147
|
+
if (!prefixes.some((p) => row.fingerprint.startsWith(p)))
|
|
148
|
+
continue;
|
|
149
|
+
resolveStmt.run(now, row.fingerprint);
|
|
150
|
+
const resolved = store.get(row.fingerprint);
|
|
151
|
+
if (resolved)
|
|
152
|
+
out.resolved.push(resolved);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
},
|
|
156
|
+
markNotified(fps, result, now = Date.now()) {
|
|
157
|
+
const stmt = db.prepare(`UPDATE ${INCIDENTS_TABLE} SET notified_at = ?, notify_result = ? WHERE fingerprint = ?`);
|
|
158
|
+
for (const fp of fps)
|
|
159
|
+
stmt.run(now, result, fp);
|
|
160
|
+
},
|
|
161
|
+
markResolvedNotified(fps, now = Date.now()) {
|
|
162
|
+
const stmt = db.prepare(`UPDATE ${INCIDENTS_TABLE} SET resolved_notified_at = ? WHERE fingerprint = ?`);
|
|
163
|
+
for (const fp of fps)
|
|
164
|
+
stmt.run(now, fp);
|
|
165
|
+
},
|
|
166
|
+
acknowledge(fp, now = Date.now()) {
|
|
167
|
+
const row = selectOne.get(fp);
|
|
168
|
+
if (!row || row.state !== "open")
|
|
169
|
+
return false;
|
|
170
|
+
db.prepare(`UPDATE ${INCIDENTS_TABLE} SET state = 'acknowledged', acknowledged_at = ?
|
|
171
|
+
WHERE fingerprint = ?`).run(now, fp);
|
|
172
|
+
return true;
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
return store;
|
|
176
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The incident contract — what a detector reports, and what the store keeps.
|
|
3
|
+
*
|
|
4
|
+
* ── Why an INCIDENT and not an event (2026-08-13, the owner) ────────────────
|
|
5
|
+
* "auto notifying me once per incident (it needs to avoid sending me the same
|
|
6
|
+
* problem over and over in duplicated notifications if it's polling for
|
|
7
|
+
* problems)". A poller that re-sends every five minutes is the failure mode
|
|
8
|
+
* that gets the whole feature muted, which puts him back where he started but
|
|
9
|
+
* with more noise. So the unit of this module is a *condition that is true for
|
|
10
|
+
* a while*, identified by a stable {@link fingerprint}, with a lifecycle —
|
|
11
|
+
* and a notification is attached to a TRANSITION of that lifecycle, never to
|
|
12
|
+
* an observation. Observing the same broken thing for the ninth hour running
|
|
13
|
+
* bumps a counter and sends nothing.
|
|
14
|
+
*
|
|
15
|
+
* ── 🔴 An incident carries WHAT failed, never the content ───────────────────
|
|
16
|
+
* These messages go to a phone and to an inbox, and several apps on this fleet
|
|
17
|
+
* are private (notes) or zero-knowledge by construction (vault). A file name,
|
|
18
|
+
* a note title or a vault key in an SMS is a leak by a different door. So the
|
|
19
|
+
* shape below has no free-form payload: `subject` is a stable IDENTIFIER (an
|
|
20
|
+
* app key, a launchd label, a queue name), the three human fields are written
|
|
21
|
+
* by the detector's own author, and {@link IncidentReport.facts} takes NUMBERS
|
|
22
|
+
* and enum-ish strings only. `assertNoContent` in `store.ts` enforces the last
|
|
23
|
+
* one at write time rather than trusting a reviewer.
|
|
24
|
+
*/
|
|
25
|
+
/** How loud an incident is. There are only two, on purpose — see `notifier.ts`. */
|
|
26
|
+
export type IncidentSeverity =
|
|
27
|
+
/** The owner's own examples: "can't deploy, can't ingest files". Email AND text. */
|
|
28
|
+
"blocking"
|
|
29
|
+
/** Worth knowing, in-app only. A warning that texts him is a warning that mutes the channel. */
|
|
30
|
+
| "warning";
|
|
31
|
+
/** Where an incident is in its life. */
|
|
32
|
+
export type IncidentState =
|
|
33
|
+
/** Still true, not yet dealt with. */
|
|
34
|
+
"open"
|
|
35
|
+
/** Still true; the owner has seen it and does not want to be told again. */
|
|
36
|
+
| "acknowledged"
|
|
37
|
+
/** No longer reported by its detector. */
|
|
38
|
+
| "resolved";
|
|
39
|
+
/** A fact attached to an incident. Numbers and enum-ish strings only — see the header. */
|
|
40
|
+
export type IncidentFact = number | boolean | null;
|
|
41
|
+
/**
|
|
42
|
+
* One condition, as a detector reports it. The detector does NOT decide whether
|
|
43
|
+
* this is new, whether to notify, or what the current state is — that is the
|
|
44
|
+
* store's job, and keeping it there is the whole dedupe guarantee.
|
|
45
|
+
*/
|
|
46
|
+
export interface IncidentReport {
|
|
47
|
+
/** Which app or service noticed it: `binary-server`, `station`, `orch`. */
|
|
48
|
+
source: string;
|
|
49
|
+
/** The detector that produced it, e.g. `disk-below-reserve`. Stable across runs. */
|
|
50
|
+
kind: string;
|
|
51
|
+
/**
|
|
52
|
+
* WHAT the condition is about, as a stable identifier — an app key, a launchd
|
|
53
|
+
* label, a queue name. Never a file name, a title, or anything a person typed.
|
|
54
|
+
*/
|
|
55
|
+
subject: string;
|
|
56
|
+
severity: IncidentSeverity;
|
|
57
|
+
/** One line, for a notification subject and a table row. Names the app. */
|
|
58
|
+
title: string;
|
|
59
|
+
/** ELI5: what actually broke. Assume he does not know what your daemon is. */
|
|
60
|
+
whatBroke: string;
|
|
61
|
+
/** ELI5: what it stops him doing. This is the sentence that earns the text message. */
|
|
62
|
+
whatItBlocks: string;
|
|
63
|
+
/** ELI5: what to do about it. A runnable command carries its own `cd` (fleet rule). */
|
|
64
|
+
whatToDo: string;
|
|
65
|
+
/** Numbers only — bytes free, items failed, commits behind. See the header. */
|
|
66
|
+
facts?: Record<string, IncidentFact>;
|
|
67
|
+
}
|
|
68
|
+
/** A stored incident: a report, plus everything the store knows about its life. */
|
|
69
|
+
export interface Incident extends IncidentReport {
|
|
70
|
+
fingerprint: string;
|
|
71
|
+
state: IncidentState;
|
|
72
|
+
/** Epoch ms of the first observation of this episode. */
|
|
73
|
+
firstSeen: number;
|
|
74
|
+
/** Epoch ms of the most recent observation. */
|
|
75
|
+
lastSeen: number;
|
|
76
|
+
/** How many sweeps have observed it in this episode. */
|
|
77
|
+
seenCount: number;
|
|
78
|
+
/** How many times this fingerprint has opened, ever. A flapper is visible. */
|
|
79
|
+
episodes: number;
|
|
80
|
+
acknowledgedAt: number | null;
|
|
81
|
+
resolvedAt: number | null;
|
|
82
|
+
/** When the OPEN notification went out, and what happened to each channel. */
|
|
83
|
+
notifiedAt: number | null;
|
|
84
|
+
notifyResult: string | null;
|
|
85
|
+
/** When the RESOLVED notification went out. */
|
|
86
|
+
resolvedNotifiedAt: number | null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* What one sweep changed. Only these three lists produce notifications — an
|
|
90
|
+
* incident merely observed again appears in none of them.
|
|
91
|
+
*/
|
|
92
|
+
export interface IncidentTransitions {
|
|
93
|
+
/** Newly true, or true again after a resolve. Notify. */
|
|
94
|
+
opened: Incident[];
|
|
95
|
+
/** Was a warning, is now blocking. Notify — the channel set changed. */
|
|
96
|
+
escalated: Incident[];
|
|
97
|
+
/** No longer reported. Notify once, so "is it fixed?" is answered without asking. */
|
|
98
|
+
resolved: Incident[];
|
|
99
|
+
/** Observed again while already open. **Never notified.** Here so a test can see it. */
|
|
100
|
+
unchanged: Incident[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A stable identity for a condition. Deliberately NOT a hash of the message:
|
|
104
|
+
* an author who improves the wording of `whatToDo` must not thereby re-alert
|
|
105
|
+
* the owner about a problem he already knows about.
|
|
106
|
+
*/
|
|
107
|
+
export declare function fingerprint(report: Pick<IncidentReport, "source" | "kind" | "subject">): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The incident contract — what a detector reports, and what the store keeps.
|
|
3
|
+
*
|
|
4
|
+
* ── Why an INCIDENT and not an event (2026-08-13, the owner) ────────────────
|
|
5
|
+
* "auto notifying me once per incident (it needs to avoid sending me the same
|
|
6
|
+
* problem over and over in duplicated notifications if it's polling for
|
|
7
|
+
* problems)". A poller that re-sends every five minutes is the failure mode
|
|
8
|
+
* that gets the whole feature muted, which puts him back where he started but
|
|
9
|
+
* with more noise. So the unit of this module is a *condition that is true for
|
|
10
|
+
* a while*, identified by a stable {@link fingerprint}, with a lifecycle —
|
|
11
|
+
* and a notification is attached to a TRANSITION of that lifecycle, never to
|
|
12
|
+
* an observation. Observing the same broken thing for the ninth hour running
|
|
13
|
+
* bumps a counter and sends nothing.
|
|
14
|
+
*
|
|
15
|
+
* ── 🔴 An incident carries WHAT failed, never the content ───────────────────
|
|
16
|
+
* These messages go to a phone and to an inbox, and several apps on this fleet
|
|
17
|
+
* are private (notes) or zero-knowledge by construction (vault). A file name,
|
|
18
|
+
* a note title or a vault key in an SMS is a leak by a different door. So the
|
|
19
|
+
* shape below has no free-form payload: `subject` is a stable IDENTIFIER (an
|
|
20
|
+
* app key, a launchd label, a queue name), the three human fields are written
|
|
21
|
+
* by the detector's own author, and {@link IncidentReport.facts} takes NUMBERS
|
|
22
|
+
* and enum-ish strings only. `assertNoContent` in `store.ts` enforces the last
|
|
23
|
+
* one at write time rather than trusting a reviewer.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* A stable identity for a condition. Deliberately NOT a hash of the message:
|
|
27
|
+
* an author who improves the wording of `whatToDo` must not thereby re-alert
|
|
28
|
+
* the owner about a problem he already knows about.
|
|
29
|
+
*/
|
|
30
|
+
export function fingerprint(report) {
|
|
31
|
+
return `${report.source}:${report.kind}:${report.subject}`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { ChunkStore, type ChunkStoreOpts, CTGR_WIRE_VERSION, type CtgrDecoded, type CtgrMethod, decodeRequest, type EncodedRequest, type EncodeOpts, encodeRequest, type UploadedFile, } from 'cursedbelt-core/ctgr';
|
|
2
|
+
export { type CreateAppOpts, createApp, type ServeBunOpts, serveBun } from './app';
|
|
3
|
+
export { type AppJwtPayload, AUTH_COOKIE_NAME, type AuthEnv, type AuthUser, type AuthVariables, type CreateSessionInput, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type CsrfOptions, clearAuthCookie, createAuthMiddleware, createCsrfMiddleware, clientKeyOf, clientKeyOfContext, createLoginThrottle, createSessionStore, createRs256FileTokenSigner, DEFAULT_TOTP_ISSUER, decryptVaultItem, deriveVaultKey, encryptVaultItem, generateRecoveryCodes, generateTotpSecret, getOnlyEnforcement, getTotpUri, hashPassword, isGetOnlyViolation, type LoginThrottle, type LoginThrottleOptions, type ThrottleVerdict, needsRehash, type Rs256FileTokenSignerOptions, type Session, type SessionStore, type SessionStoreOptions, signToken, type TokenPurpose, verifyPassword, verifyPurposeToken, verifyRecoveryCode, verifyToken, verifyTotpCode, } from './auth';
|
|
4
|
+
export type { CursedbeltEnv, CursedbeltVariables } from './context';
|
|
5
|
+
export { buildRoutes, CTGR_ROUTE_PATTERN, getCtgrDecoded, unGETify } from './ctgr';
|
|
6
|
+
export { createKysely, type PlumbingDb } from './db/kysely';
|
|
7
|
+
export type { AppConfigRow, AuthSessionRow, EmailFailureRow, EventLogRow, JobRunRow, JobStateRow, MigrationRow, PlumbingSchema, RequestMetricRow, SystemSampleRow, } from './db/plumbingTypes';
|
|
8
|
+
export { ApiError, type ApiErrorOptions, type ErrorEnvelope, isApiError, toErrorEnvelope, toValidationIssues, VALIDATION_ERROR_CODE, type ValidationIssue, validationEnvelope, } from './errors';
|
|
9
|
+
export { type CreateJobRunnerOptions, createFireQueue, createJobRunner, createPollingJob, DEFAULT_FIRE_INTERVAL_MS, DEFAULT_JOB_TIMEOUT_MS, defaultJobLogger, type FireQueue, type FireQueueOpts, type JobContext, type JobDef, type JobLogger, type JobRunner, type JobRunRecord, JobTimeoutError, type PollingJob, type PollingJobOpts, } from './jobs';
|
|
10
|
+
export { buildProbeArgs, buildThumbnailArgs, buildTrimArgs, extractThumbnail, ffmpegTimeToSeconds, isFfmpegAvailable, isFfprobeAvailable, type ProbeResult, parseFfmpegProgress, parseFfprobe, probeMedia, renderImage, trimVideo, type VideoCodecPlan, videoCodecPlan, } from './media-bun';
|
|
11
|
+
export { createMetricsBuffer, type MetricRow, type MetricsBuffer, type MetricsBufferOptions, } from './metrics/metricsBuffer';
|
|
12
|
+
export { normalizeRoute } from './metrics/normalizeRoute';
|
|
13
|
+
export { correlationId } from './middleware/correlationId';
|
|
14
|
+
export { type CorsOpts, corsAllowlist, corsOptsFromEnv } from './middleware/corsAllowlist';
|
|
15
|
+
export { errorEnvelope } from './middleware/errorEnvelope';
|
|
16
|
+
export { crossOriginRefusal } from './middleware/localOrigin';
|
|
17
|
+
export { clientIp, createRateLimiter, type RateLimitOpts } from './middleware/rateLimit';
|
|
18
|
+
export { type RequestLoggerOpts, requestLogger } from './middleware/requestLogger';
|
|
19
|
+
export { type CspDirectives, type SecurityHeadersOpts, securityHeaders, } from './middleware/securityHeaders';
|
|
20
|
+
export { type ZValidatorTarget, zValidator } from './middleware/zValidator';
|
|
21
|
+
export { addColumnIfMissing, applyRecommendedPragmas, columnExists, runMigrations } from './migration/runner';
|
|
22
|
+
export type { Migration } from './migration/types';
|
|
23
|
+
export { AUDIT_LOG_MIGRATIONS, auditStateAt, auditStateAtVersion, auditStep, createSqliteAuditSink, } from './audit';
|
|
24
|
+
export { applyFtsSchema, type BackupInfo, buildFtsSchema, clampLimit, clampOffset, collectSqliteStats, createFts5Search, createSqliteBackupService, createSqliteHealthChecks, createSqliteViewerMiddleware, type DbStats, exportSqliteSnapshot, exportSqliteTable, type Fts5Search, type FtsHighlightOpts, type FtsResult, type FtsSearchOpts, type FtsSnippetOpts, type FtsTableSpec, type ImportMode, type ImportResult, importRowsIntoSqliteTable, isKnownTable, isReadOnlySql, listSqliteTables, NotReadOnlyError, quoteIdent, type RestoreResult, readSqliteTable, restoreSqliteSnapshot, runReadonlySqlite, type SnapshotExport, type SqliteBackupOpts, type SqliteBackupS3Opts, type SqliteBackupService, type SqliteHealthCheck, type SqliteHealthOpts, type SqliteHealthResult, type SqliteHealthStatus, type SqliteTableInfo, type SqliteTablePage, sanitizeFtsQuery, snapshotSqlite, type TableExport, type TableExportFormat, type TableStat, UnknownTableError, } from './sqlite';
|
|
25
|
+
export { computePresence, createEventBus, createWsHub, type EventBus, type EventBusData, joinRoom, leaveRoom, type PresenceEntry, type PresenceState, ROOM_META_KEY, roomOf, type WsChannel, type WsClient, type WsData, type WsHandshake, type WsHub, type WsHubOpts, type WsMessage, } from './ws';
|