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,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/incidents` — the fleet's ONE owner-alert primitive: a
|
|
3
|
+
* condition that is true for a while, told to the owner exactly once, and told
|
|
4
|
+
* to him again when it clears.
|
|
5
|
+
*
|
|
6
|
+
* ── Why it exists (the owner, 2026-08-13) ───────────────────────────────────
|
|
7
|
+
* *"I found out from an agent my collection uploads were stuck … there are
|
|
8
|
+
* probably all kinds of possible problems I miss over time, such as every orch
|
|
9
|
+
* task failing to deploy and live sites falling over 10 commits behind."*
|
|
10
|
+
* The incident behind it: 42 videos / 35 GB sat in the collections dropzone and
|
|
11
|
+
* every scheduled ingest failed all 42 with EPIPE against binary-server, while
|
|
12
|
+
* binary-server logged that it was under its 12 GB disk reserve — and the
|
|
13
|
+
* dropzone deletes originals once they reach the catalog. Nothing told him. Two
|
|
14
|
+
* separate deterministic checks would each have caught it on the first run.
|
|
15
|
+
*
|
|
16
|
+
* ── Why it is HERE and not in `satellite-kit` ───────────────────────────────
|
|
17
|
+
* The queued task suggested satellite-kit ("beside the metrics buffer"), and
|
|
18
|
+
* that would have been wrong for one measurable reason: **binary-server is one
|
|
19
|
+
* of the three apps the owner named, it is a different repo, and it depends on
|
|
20
|
+
* `cursedbelt` and `cwip` — not on satellite-kit.** Putting the engine in the
|
|
21
|
+
* kit would have meant bs either growing its own copy or growing a dependency on
|
|
22
|
+
* the satellites' internal kit, and the first of those is the drift this whole
|
|
23
|
+
* queue exists to end. The fleet-shaped DETECTORS (the deploy ledger, launchd,
|
|
24
|
+
* backups) are satellite-shaped and do live in the kit; the engine is here.
|
|
25
|
+
*
|
|
26
|
+
* ── The four pieces ─────────────────────────────────────────────────────────
|
|
27
|
+
* - {@link createIncidentStore} — one row per condition, and `reconcile`, which
|
|
28
|
+
* is the entire dedupe guarantee. Nothing else in the system decides whether
|
|
29
|
+
* something is new.
|
|
30
|
+
* - {@link runIncidentSweep} — runs checks; a check that THROWS resolves
|
|
31
|
+
* nothing, so a broken detector can never announce that an outage is over.
|
|
32
|
+
* - channels — in-app (the fleet's existing notification centre), email, and a
|
|
33
|
+
* text message from this Mac that reads `chat.db` back to prove it left.
|
|
34
|
+
* - {@link createIncidentNotifier} — composes the three-sentence message and
|
|
35
|
+
* sends it on OPEN, on ESCALATE and on RESOLVE. Never on an observation.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const store = createIncidentStore(db);
|
|
39
|
+
* const notifier = createIncidentNotifier({ store, channels });
|
|
40
|
+
* const result = await runIncidentSweep(store, checks);
|
|
41
|
+
* await notifier.announce(result.transitions); // usually sends nothing
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export { type ChannelMessage, type ChannelOutcome, type ChannelResult, createEmailChannel, createInAppChannel, createRelayEmailChannel, createTextChannel, type EmailChannelOptions, type IncidentChannel, type SendEmail, type TextChannelOptions, } from "./channels";
|
|
45
|
+
export { type CheckOutcome, type IncidentCheck, runIncidentSweep, type SweepResult, } from "./checks";
|
|
46
|
+
export { INCIDENT_MIGRATIONS, INCIDENTS_TABLE } from "./migrations";
|
|
47
|
+
export { type AnnounceResult, composeOpened, composeResolved, type CreateNotifierOptions, createIncidentNotifier, type IncidentNotifier, } from "./notifier";
|
|
48
|
+
export { createIncidentReporter, type IncidentReporter, type IncidentReporterOptions, } from "./reporter";
|
|
49
|
+
export { type IncidentRoutesOptions, mountIncidentMachineRoutes, mountIncidentOwnerRoutes, } from "./routes";
|
|
50
|
+
export { assertNoContent, createIncidentStore, type IncidentStore, type ReconcileOptions, } from "./store";
|
|
51
|
+
export { fingerprint, type Incident, type IncidentFact, type IncidentReport, type IncidentSeverity, type IncidentState, type IncidentTransitions, } from "./types";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/incidents` — the fleet's ONE owner-alert primitive: a
|
|
3
|
+
* condition that is true for a while, told to the owner exactly once, and told
|
|
4
|
+
* to him again when it clears.
|
|
5
|
+
*
|
|
6
|
+
* ── Why it exists (the owner, 2026-08-13) ───────────────────────────────────
|
|
7
|
+
* *"I found out from an agent my collection uploads were stuck … there are
|
|
8
|
+
* probably all kinds of possible problems I miss over time, such as every orch
|
|
9
|
+
* task failing to deploy and live sites falling over 10 commits behind."*
|
|
10
|
+
* The incident behind it: 42 videos / 35 GB sat in the collections dropzone and
|
|
11
|
+
* every scheduled ingest failed all 42 with EPIPE against binary-server, while
|
|
12
|
+
* binary-server logged that it was under its 12 GB disk reserve — and the
|
|
13
|
+
* dropzone deletes originals once they reach the catalog. Nothing told him. Two
|
|
14
|
+
* separate deterministic checks would each have caught it on the first run.
|
|
15
|
+
*
|
|
16
|
+
* ── Why it is HERE and not in `satellite-kit` ───────────────────────────────
|
|
17
|
+
* The queued task suggested satellite-kit ("beside the metrics buffer"), and
|
|
18
|
+
* that would have been wrong for one measurable reason: **binary-server is one
|
|
19
|
+
* of the three apps the owner named, it is a different repo, and it depends on
|
|
20
|
+
* `cursedbelt` and `cwip` — not on satellite-kit.** Putting the engine in the
|
|
21
|
+
* kit would have meant bs either growing its own copy or growing a dependency on
|
|
22
|
+
* the satellites' internal kit, and the first of those is the drift this whole
|
|
23
|
+
* queue exists to end. The fleet-shaped DETECTORS (the deploy ledger, launchd,
|
|
24
|
+
* backups) are satellite-shaped and do live in the kit; the engine is here.
|
|
25
|
+
*
|
|
26
|
+
* ── The four pieces ─────────────────────────────────────────────────────────
|
|
27
|
+
* - {@link createIncidentStore} — one row per condition, and `reconcile`, which
|
|
28
|
+
* is the entire dedupe guarantee. Nothing else in the system decides whether
|
|
29
|
+
* something is new.
|
|
30
|
+
* - {@link runIncidentSweep} — runs checks; a check that THROWS resolves
|
|
31
|
+
* nothing, so a broken detector can never announce that an outage is over.
|
|
32
|
+
* - channels — in-app (the fleet's existing notification centre), email, and a
|
|
33
|
+
* text message from this Mac that reads `chat.db` back to prove it left.
|
|
34
|
+
* - {@link createIncidentNotifier} — composes the three-sentence message and
|
|
35
|
+
* sends it on OPEN, on ESCALATE and on RESOLVE. Never on an observation.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* const store = createIncidentStore(db);
|
|
39
|
+
* const notifier = createIncidentNotifier({ store, channels });
|
|
40
|
+
* const result = await runIncidentSweep(store, checks);
|
|
41
|
+
* await notifier.announce(result.transitions); // usually sends nothing
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export { createEmailChannel, createInAppChannel, createRelayEmailChannel, createTextChannel, } from "./channels";
|
|
45
|
+
export { runIncidentSweep, } from "./checks";
|
|
46
|
+
export { INCIDENT_MIGRATIONS, INCIDENTS_TABLE } from "./migrations";
|
|
47
|
+
export { composeOpened, composeResolved, createIncidentNotifier, } from "./notifier";
|
|
48
|
+
export { createIncidentReporter, } from "./reporter";
|
|
49
|
+
export { mountIncidentMachineRoutes, mountIncidentOwnerRoutes, } from "./routes";
|
|
50
|
+
export { assertNoContent, createIncidentStore, } from "./store";
|
|
51
|
+
export { fingerprint, } from "./types";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Migration } from "../migration/types";
|
|
2
|
+
/** The one table this primitive owns. */
|
|
3
|
+
export declare const INCIDENTS_TABLE = "incidents";
|
|
4
|
+
/**
|
|
5
|
+
* ONE row per CONDITION, keyed by its fingerprint — not one row per observation.
|
|
6
|
+
*
|
|
7
|
+
* That is the whole dedupe design expressed as a schema: a poller that notices
|
|
8
|
+
* the same broken thing 288 times a day cannot produce 288 rows, because there
|
|
9
|
+
* is nowhere to put them. `seen_count` and `last_seen` absorb the repetition,
|
|
10
|
+
* and the notification columns record that the owner was told ONCE.
|
|
11
|
+
*
|
|
12
|
+
* 🔴 **Never retention-managed.** Every other telemetry table in this package
|
|
13
|
+
* is capped or aged out; this one is not, and must not be declared as a
|
|
14
|
+
* retention target. An incident is the record of something having gone wrong on
|
|
15
|
+
* a fleet whose owner's stated problem is *"I missed this for a long time"* —
|
|
16
|
+
* the row that answers "has this happened before?" six months later is the
|
|
17
|
+
* expensive one to have thrown away. The table is bounded by reality instead:
|
|
18
|
+
* a fingerprint is stable, so a recurring problem re-uses its row forever.
|
|
19
|
+
*/
|
|
20
|
+
export declare const INCIDENT_MIGRATIONS: readonly Migration[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** The one table this primitive owns. */
|
|
2
|
+
export const INCIDENTS_TABLE = "incidents";
|
|
3
|
+
/**
|
|
4
|
+
* ONE row per CONDITION, keyed by its fingerprint — not one row per observation.
|
|
5
|
+
*
|
|
6
|
+
* That is the whole dedupe design expressed as a schema: a poller that notices
|
|
7
|
+
* the same broken thing 288 times a day cannot produce 288 rows, because there
|
|
8
|
+
* is nowhere to put them. `seen_count` and `last_seen` absorb the repetition,
|
|
9
|
+
* and the notification columns record that the owner was told ONCE.
|
|
10
|
+
*
|
|
11
|
+
* 🔴 **Never retention-managed.** Every other telemetry table in this package
|
|
12
|
+
* is capped or aged out; this one is not, and must not be declared as a
|
|
13
|
+
* retention target. An incident is the record of something having gone wrong on
|
|
14
|
+
* a fleet whose owner's stated problem is *"I missed this for a long time"* —
|
|
15
|
+
* the row that answers "has this happened before?" six months later is the
|
|
16
|
+
* expensive one to have thrown away. The table is bounded by reality instead:
|
|
17
|
+
* a fingerprint is stable, so a recurring problem re-uses its row forever.
|
|
18
|
+
*/
|
|
19
|
+
export const INCIDENT_MIGRATIONS = [
|
|
20
|
+
{
|
|
21
|
+
name: "0001_incidents",
|
|
22
|
+
up(db) {
|
|
23
|
+
db.run(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS ${INCIDENTS_TABLE} (
|
|
25
|
+
fingerprint TEXT PRIMARY KEY,
|
|
26
|
+
source TEXT NOT NULL,
|
|
27
|
+
check_key TEXT NOT NULL,
|
|
28
|
+
kind TEXT NOT NULL,
|
|
29
|
+
subject TEXT NOT NULL,
|
|
30
|
+
severity TEXT NOT NULL
|
|
31
|
+
CHECK (severity IN ('blocking', 'warning')),
|
|
32
|
+
state TEXT NOT NULL
|
|
33
|
+
CHECK (state IN ('open', 'acknowledged', 'resolved')),
|
|
34
|
+
title TEXT NOT NULL,
|
|
35
|
+
what_broke TEXT NOT NULL,
|
|
36
|
+
what_it_blocks TEXT NOT NULL,
|
|
37
|
+
what_to_do TEXT NOT NULL,
|
|
38
|
+
facts_json TEXT,
|
|
39
|
+
first_seen INTEGER NOT NULL,
|
|
40
|
+
last_seen INTEGER NOT NULL,
|
|
41
|
+
seen_count INTEGER NOT NULL DEFAULT 1,
|
|
42
|
+
episodes INTEGER NOT NULL DEFAULT 1,
|
|
43
|
+
acknowledged_at INTEGER,
|
|
44
|
+
resolved_at INTEGER,
|
|
45
|
+
notified_at INTEGER,
|
|
46
|
+
notify_result TEXT,
|
|
47
|
+
resolved_notified_at INTEGER
|
|
48
|
+
)
|
|
49
|
+
`);
|
|
50
|
+
// The read every screen starts from: what is wrong right now.
|
|
51
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_incidents_state
|
|
52
|
+
ON ${INCIDENTS_TABLE} (state, severity, last_seen DESC)`);
|
|
53
|
+
// The read `reconcile` does on every sweep, for every check.
|
|
54
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_incidents_check
|
|
55
|
+
ON ${INCIDENTS_TABLE} (check_key, state)`);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a state TRANSITION into a message, and sending it exactly once.
|
|
3
|
+
*
|
|
4
|
+
* Everything that decides "does the owner hear about this?" is here, and there
|
|
5
|
+
* is very little of it, because the hard part was done in `store.ts`: this
|
|
6
|
+
* module only ever sees `opened`, `escalated` and `resolved`. There is no
|
|
7
|
+
* de-duplication logic in this file and there must never be — a second dedupe,
|
|
8
|
+
* on a timer, is how a system ends up with two disagreeing answers to "have I
|
|
9
|
+
* already said this?".
|
|
10
|
+
*
|
|
11
|
+
* ── The message the owner actually gets ─────────────────────────────────────
|
|
12
|
+
* The owner's standing complaint about agent hand-offs is that they name a
|
|
13
|
+
* symptom, a command and a credential without saying what any of them are. So
|
|
14
|
+
* an incident message is three sentences with fixed jobs, in this order:
|
|
15
|
+
*
|
|
16
|
+
* WHAT BROKE — in words, naming the app. Assume he does not know what
|
|
17
|
+
* your daemon, flag, tab or port is.
|
|
18
|
+
* WHAT IT BLOCKS — what he cannot do until it is fixed. This is the sentence
|
|
19
|
+
* that earns a text message at 2am; if you cannot write it,
|
|
20
|
+
* the incident is a `warning`.
|
|
21
|
+
* WHAT TO DO — a next step. A command carries its own `cd`, because he
|
|
22
|
+
* opens terminals anywhere (`fleet/rules/
|
|
23
|
+
* commands-must-run-from-anywhere.md`).
|
|
24
|
+
*
|
|
25
|
+
* ── 🔴 What is deliberately NOT in a message ────────────────────────────────
|
|
26
|
+
* File names, note titles, vault keys, e-mail addresses, anything a person
|
|
27
|
+
* typed. `subject` is an identifier and `facts` are numbers, both enforced at
|
|
28
|
+
* the store (`assertNoContent`). A text message is the least private surface
|
|
29
|
+
* this fleet has and several of its apps are private by construction.
|
|
30
|
+
*/
|
|
31
|
+
import type { ChannelMessage, ChannelResult, IncidentChannel } from "./channels";
|
|
32
|
+
import type { IncidentStore } from "./store";
|
|
33
|
+
import type { Incident, IncidentTransitions } from "./types";
|
|
34
|
+
export interface IncidentNotifier {
|
|
35
|
+
/** Send for every transition in `t`. Returns one result per message sent. */
|
|
36
|
+
announce: (t: IncidentTransitions) => Promise<AnnounceResult[]>;
|
|
37
|
+
/** The channels registered on this host, for the console's health row. */
|
|
38
|
+
readonly channels: readonly IncidentChannel[];
|
|
39
|
+
}
|
|
40
|
+
export interface AnnounceResult {
|
|
41
|
+
fingerprint: string;
|
|
42
|
+
transition: "opened" | "escalated" | "resolved";
|
|
43
|
+
results: ChannelResult[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* How long to wait before trying a BLOCKING incident again when no channel
|
|
47
|
+
* managed to deliver it.
|
|
48
|
+
*
|
|
49
|
+
* 🔴 This exists because of a real hole found on the first live sweep: `roms`'
|
|
50
|
+
* overdue backup opened as blocking while the email channel was still
|
|
51
|
+
* misconfigured, both channels failed, and — because `notifiedAt` was stamped —
|
|
52
|
+
* nothing would ever have tried again. The owner would have had a blocking
|
|
53
|
+
* incident on a board he had no reason to open. The dedupe promise is "you are
|
|
54
|
+
* told ONCE", not "we attempt once": an attempt that reached nobody has not told
|
|
55
|
+
* him anything.
|
|
56
|
+
*
|
|
57
|
+
* It retries at most hourly, and it STOPS the moment any channel reports
|
|
58
|
+
* `delivered` — so a permanently broken channel costs one send an hour, and a
|
|
59
|
+
* working one costs exactly one message, which is the guarantee.
|
|
60
|
+
*/
|
|
61
|
+
export declare const UNDELIVERED_RETRY_MS: number;
|
|
62
|
+
/** The message for a newly-open (or newly-blocking) incident. */
|
|
63
|
+
export declare function composeOpened(incident: Incident): ChannelMessage;
|
|
64
|
+
/**
|
|
65
|
+
* The message for a resolve. It exists so "is it fixed?" is answered without
|
|
66
|
+
* him having to go and look — the same reason the open message exists.
|
|
67
|
+
*/
|
|
68
|
+
export declare function composeResolved(incident: Incident): ChannelMessage;
|
|
69
|
+
export interface CreateNotifierOptions {
|
|
70
|
+
store: IncidentStore;
|
|
71
|
+
channels: readonly IncidentChannel[];
|
|
72
|
+
/**
|
|
73
|
+
* Called with every channel result, so a host can record channel health.
|
|
74
|
+
* `detectChannelHealth` in `detectors.ts` turns a run of failures into its
|
|
75
|
+
* own incident — which is why `ChannelMessage.avoid` exists.
|
|
76
|
+
*/
|
|
77
|
+
onChannelResult?: (fingerprint: string, result: ChannelResult) => void;
|
|
78
|
+
}
|
|
79
|
+
export declare function createIncidentNotifier(options: CreateNotifierOptions): IncidentNotifier;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long to wait before trying a BLOCKING incident again when no channel
|
|
3
|
+
* managed to deliver it.
|
|
4
|
+
*
|
|
5
|
+
* 🔴 This exists because of a real hole found on the first live sweep: `roms`'
|
|
6
|
+
* overdue backup opened as blocking while the email channel was still
|
|
7
|
+
* misconfigured, both channels failed, and — because `notifiedAt` was stamped —
|
|
8
|
+
* nothing would ever have tried again. The owner would have had a blocking
|
|
9
|
+
* incident on a board he had no reason to open. The dedupe promise is "you are
|
|
10
|
+
* told ONCE", not "we attempt once": an attempt that reached nobody has not told
|
|
11
|
+
* him anything.
|
|
12
|
+
*
|
|
13
|
+
* It retries at most hourly, and it STOPS the moment any channel reports
|
|
14
|
+
* `delivered` — so a permanently broken channel costs one send an hour, and a
|
|
15
|
+
* working one costs exactly one message, which is the guarantee.
|
|
16
|
+
*/
|
|
17
|
+
export const UNDELIVERED_RETRY_MS = 60 * 60 * 1000;
|
|
18
|
+
/** Did any channel actually get it out? `skipped` is not delivery. */
|
|
19
|
+
const wasDelivered = (notifyResult) => Boolean(notifyResult?.includes("=delivered"));
|
|
20
|
+
const factLine = (incident) => {
|
|
21
|
+
const facts = Object.entries(incident.facts ?? {});
|
|
22
|
+
if (facts.length === 0)
|
|
23
|
+
return "";
|
|
24
|
+
return `\n${facts.map(([k, v]) => `${k}: ${v}`).join(" · ")}`;
|
|
25
|
+
};
|
|
26
|
+
/** The message for a newly-open (or newly-blocking) incident. */
|
|
27
|
+
export function composeOpened(incident) {
|
|
28
|
+
const tag = incident.severity === "blocking" ? "BLOCKED" : "Heads up";
|
|
29
|
+
return {
|
|
30
|
+
severity: incident.severity,
|
|
31
|
+
subject: `[${tag}] ${incident.title}`,
|
|
32
|
+
text: [
|
|
33
|
+
`What broke: ${incident.whatBroke}`,
|
|
34
|
+
`What it blocks: ${incident.whatItBlocks}`,
|
|
35
|
+
`What to do: ${incident.whatToDo}`,
|
|
36
|
+
`(${incident.source} · ${incident.kind} · ${incident.subject})${factLine(incident)}`,
|
|
37
|
+
].join("\n\n"),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The message for a resolve. It exists so "is it fixed?" is answered without
|
|
42
|
+
* him having to go and look — the same reason the open message exists.
|
|
43
|
+
*/
|
|
44
|
+
export function composeResolved(incident) {
|
|
45
|
+
const minutes = Math.max(1, Math.round(((incident.resolvedAt ?? 0) - incident.firstSeen) / 60000));
|
|
46
|
+
return {
|
|
47
|
+
severity: incident.severity,
|
|
48
|
+
subject: `[cleared] ${incident.title}`,
|
|
49
|
+
text: [
|
|
50
|
+
`This is no longer happening. It was open for about ${minutes} minute${minutes === 1 ? "" : "s"}.`,
|
|
51
|
+
`It was: ${incident.whatBroke}`,
|
|
52
|
+
`(${incident.source} · ${incident.kind} · ${incident.subject})`,
|
|
53
|
+
].join("\n\n"),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function createIncidentNotifier(options) {
|
|
57
|
+
const { store, channels } = options;
|
|
58
|
+
const dispatch = async (incident, message, transition) => {
|
|
59
|
+
// An incident that IS a channel failure must not be announced down the
|
|
60
|
+
// channel it is about. Without this the first SES outage produces an
|
|
61
|
+
// endless retry loop against SES.
|
|
62
|
+
const avoid = incident.kind === "notification-channel-failing" ? [incident.subject] : undefined;
|
|
63
|
+
const withAvoid = avoid ? { ...message, avoid } : message;
|
|
64
|
+
const results = [];
|
|
65
|
+
for (const channel of channels) {
|
|
66
|
+
// 🔴 The `avoid` guard lives HERE, not in each channel. It was in every
|
|
67
|
+
// channel first and a spy channel in the spec walked straight past it —
|
|
68
|
+
// which is the same hole any channel added later would have had. One
|
|
69
|
+
// enforcement point, and a new channel inherits it by existing.
|
|
70
|
+
if (withAvoid.avoid?.includes(channel.key)) {
|
|
71
|
+
results.push({
|
|
72
|
+
channel: channel.key,
|
|
73
|
+
outcome: "skipped",
|
|
74
|
+
detail: "this message is ABOUT this channel — not sent down it",
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Channels are awaited in series on purpose: there are at most three,
|
|
79
|
+
// they are all fast, and a serial loop means the text channel's
|
|
80
|
+
// delivery-proof read cannot race the email channel's for the same
|
|
81
|
+
// laptop resources.
|
|
82
|
+
const result = await channel.send(withAvoid).catch((err) => ({
|
|
83
|
+
channel: channel.key,
|
|
84
|
+
outcome: "failed",
|
|
85
|
+
detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
|
|
86
|
+
}));
|
|
87
|
+
results.push(result);
|
|
88
|
+
options.onChannelResult?.(incident.fingerprint, result);
|
|
89
|
+
}
|
|
90
|
+
return { fingerprint: incident.fingerprint, transition, results };
|
|
91
|
+
};
|
|
92
|
+
const summarise = (results) => results.map((r) => `${r.channel}=${r.outcome}`).join(" ");
|
|
93
|
+
return {
|
|
94
|
+
channels,
|
|
95
|
+
async announce(t) {
|
|
96
|
+
const out = [];
|
|
97
|
+
for (const incident of [...t.opened, ...t.escalated]) {
|
|
98
|
+
const transition = t.opened.includes(incident) ? "opened" : "escalated";
|
|
99
|
+
const result = await dispatch(incident, composeOpened(incident), transition);
|
|
100
|
+
// 🔴 Only stamp `notifiedAt` when there was actually a channel to try.
|
|
101
|
+
// A host with none (a prod deployment that only stores what the Mac
|
|
102
|
+
// pushes it) has not told him anything, and stamping it there is what
|
|
103
|
+
// made the resolve message below fire for an open he never saw.
|
|
104
|
+
if (result.results.length > 0) {
|
|
105
|
+
store.markNotified([incident.fingerprint], summarise(result.results));
|
|
106
|
+
}
|
|
107
|
+
out.push(result);
|
|
108
|
+
}
|
|
109
|
+
// A blocking incident nobody could be told about is retried, hourly,
|
|
110
|
+
// until one channel lands it. See `UNDELIVERED_RETRY_MS`.
|
|
111
|
+
for (const incident of t.unchanged) {
|
|
112
|
+
if (incident.severity !== "blocking")
|
|
113
|
+
continue;
|
|
114
|
+
if (incident.notifiedAt === null)
|
|
115
|
+
continue;
|
|
116
|
+
if (wasDelivered(incident.notifyResult))
|
|
117
|
+
continue;
|
|
118
|
+
if (Date.now() - incident.notifiedAt < UNDELIVERED_RETRY_MS)
|
|
119
|
+
continue;
|
|
120
|
+
const result = await dispatch(incident, composeOpened(incident), "opened");
|
|
121
|
+
if (result.results.length > 0) {
|
|
122
|
+
store.markNotified([incident.fingerprint], summarise(result.results));
|
|
123
|
+
}
|
|
124
|
+
out.push(result);
|
|
125
|
+
}
|
|
126
|
+
for (const incident of t.resolved) {
|
|
127
|
+
// Never announce a resolve for something whose OPEN was never
|
|
128
|
+
// announced — the owner would get "cleared: X" for an X he has never
|
|
129
|
+
// heard of, which reads as a bug in the alerting rather than as good
|
|
130
|
+
// news. This happens for real: an incident opened, acknowledged and
|
|
131
|
+
// resolved between two sweeps of a host that had no channels.
|
|
132
|
+
if (incident.notifiedAt === null)
|
|
133
|
+
continue;
|
|
134
|
+
const result = await dispatch(incident, composeResolved(incident), "resolved");
|
|
135
|
+
store.markResolvedNotified([incident.fingerprint]);
|
|
136
|
+
out.push(result);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The other side of `POST /incidents/report` — how a service that is NOT the
|
|
3
|
+
* console tells the console what it can see.
|
|
4
|
+
*
|
|
5
|
+
* ── Why a service reports rather than being polled ──────────────────────────
|
|
6
|
+
* The console can measure a service's disk, its port and its HTTP status from
|
|
7
|
+
* outside. It cannot measure the things that actually go wrong inside one: an
|
|
8
|
+
* ingest run where every item failed, a queue whose worker is looping on the
|
|
9
|
+
* same error, a store that is refusing writes for a reason of its own. Those
|
|
10
|
+
* facts exist for a few milliseconds inside the process that has them, and the
|
|
11
|
+
* only place they can be turned into an incident is there.
|
|
12
|
+
*
|
|
13
|
+
* ── 🔴 Reporting is a HEARTBEAT, not an event ───────────────────────────────
|
|
14
|
+
* A reporter sends the complete set of conditions that are true right now — the
|
|
15
|
+
* same contract a local check has — and sends it on a schedule whether or not
|
|
16
|
+
* anything is wrong. That is what lets the console clear an incident when the
|
|
17
|
+
* service recovers. A reporter that only posts on failure can open an incident
|
|
18
|
+
* and can never close one, and the owner ends up with a permanently red board,
|
|
19
|
+
* which is the same uselessness as a permanently green one.
|
|
20
|
+
*
|
|
21
|
+
* The dedupe is entirely the console's: post the same thing every five minutes
|
|
22
|
+
* for a week and he is told once. So a reporter needs no state, no "have I said
|
|
23
|
+
* this?" flag, and no backoff — which is why this file is forty lines and not
|
|
24
|
+
* four hundred.
|
|
25
|
+
*
|
|
26
|
+
* ── It never throws, and never blocks the service ───────────────────────────
|
|
27
|
+
* A service whose real work fails because its ALERTING is unreachable is worse
|
|
28
|
+
* than one with no alerting. Every failure here is logged and swallowed, and the
|
|
29
|
+
* console notices the silence by other means (the service's own health probe).
|
|
30
|
+
*/
|
|
31
|
+
import type { IncidentReport } from "./types";
|
|
32
|
+
export interface IncidentReporterOptions {
|
|
33
|
+
/** e.g. `http://127.0.0.1:3024/api/station/incidents/report`. */
|
|
34
|
+
url: string;
|
|
35
|
+
/** The shared bearer the console's `reporterToken` compares against. */
|
|
36
|
+
token: string;
|
|
37
|
+
/** This service's name — the `source` of everything it reports. */
|
|
38
|
+
source: string;
|
|
39
|
+
/** A stable key for this reporter, owning the incidents it opens. */
|
|
40
|
+
checkKey: string;
|
|
41
|
+
/**
|
|
42
|
+
* Every `kind` this reporter can produce. Required, not optional: without it
|
|
43
|
+
* the console cannot tell "nothing is wrong" from "nothing was said", so a
|
|
44
|
+
* recovered service would stay red for ever. See `ReconcileOptions`.
|
|
45
|
+
*/
|
|
46
|
+
kinds: readonly string[];
|
|
47
|
+
/** Produce the current set of true conditions. Called on every beat. */
|
|
48
|
+
collect: () => Promise<IncidentReport[]> | IncidentReport[];
|
|
49
|
+
/** Default 5 minutes. The console dedupes, so this is chosen for the network. */
|
|
50
|
+
intervalMs?: number;
|
|
51
|
+
fetchImpl?: typeof fetch;
|
|
52
|
+
log?: (message: string) => void;
|
|
53
|
+
}
|
|
54
|
+
export interface IncidentReporter {
|
|
55
|
+
/** Send one beat now. Resolves `false` on any failure — it never throws. */
|
|
56
|
+
beat: () => Promise<boolean>;
|
|
57
|
+
start: () => void;
|
|
58
|
+
stop: () => void;
|
|
59
|
+
}
|
|
60
|
+
export declare function createIncidentReporter(options: IncidentReporterOptions): IncidentReporter;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function createIncidentReporter(options) {
|
|
2
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
3
|
+
const log = options.log ?? ((m) => console.warn(`[incidents] ${m}`));
|
|
4
|
+
const intervalMs = options.intervalMs ?? 5 * 60_000;
|
|
5
|
+
let timer = null;
|
|
6
|
+
const beat = async () => {
|
|
7
|
+
try {
|
|
8
|
+
const reports = await options.collect();
|
|
9
|
+
const res = await doFetch(options.url, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
"content-type": "application/json",
|
|
13
|
+
authorization: `Bearer ${options.token}`,
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify({
|
|
16
|
+
checkKey: options.checkKey,
|
|
17
|
+
source: options.source,
|
|
18
|
+
kinds: options.kinds,
|
|
19
|
+
reports,
|
|
20
|
+
}),
|
|
21
|
+
signal: AbortSignal.timeout(10_000),
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
log(`the console answered ${res.status} — ${reports.length} finding(s) not filed`);
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
log(`could not reach the console: ${err instanceof Error ? err.message : String(err)}`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
beat,
|
|
36
|
+
start() {
|
|
37
|
+
if (timer)
|
|
38
|
+
return;
|
|
39
|
+
timer = setInterval(() => void beat(), intervalMs);
|
|
40
|
+
// Never hold the process open for the sake of its own alerting.
|
|
41
|
+
timer.unref?.();
|
|
42
|
+
},
|
|
43
|
+
stop() {
|
|
44
|
+
if (timer)
|
|
45
|
+
clearInterval(timer);
|
|
46
|
+
timer = null;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mountable half — ordinary Hono routes, so any app can expose its incidents
|
|
3
|
+
* without adopting another app's rpc layer (the same reasoning, and the same
|
|
4
|
+
* shape, as `server/notifications/routes.ts`).
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // BEFORE the session gate — the machine doors carry their own bearer:
|
|
8
|
+
* mountIncidentMachineRoutes(app, { store, notifier, reporterToken });
|
|
9
|
+
* // INSIDE it — the owner's own surface:
|
|
10
|
+
* mountIncidentOwnerRoutes(app, { store, notifier, sweep, authorize: isOwner });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* ── 🔴 Two doors, two MOUNTS, and that is the whole point ───────────────────
|
|
14
|
+
* `authorize` is the OWNER's session — it guards the list, the acks, the manual
|
|
15
|
+
* sweep and the channel test. `reporterToken` is a shared bearer and guards the
|
|
16
|
+
* two routes a DAEMON uses. They answer different questions ("is this the
|
|
17
|
+
* owner's browser?" and "is this one of my own processes?"), so a reporter can
|
|
18
|
+
* never read the incident list and a browser session can never forge an
|
|
19
|
+
* incident.
|
|
20
|
+
*
|
|
21
|
+
* They were one function first, and the console mounted it inside its session
|
|
22
|
+
* gate. Every beat from binary-server and orch came back
|
|
23
|
+
* `{"error":"unauthorized"}` — from the GATE, so nothing in this module logged
|
|
24
|
+
* anything and the console showed a perfectly calm, empty board. A reporter
|
|
25
|
+
* cannot report that it could not reach the reporting door. Two functions make
|
|
26
|
+
* the mount order a thing you have to get right rather than a thing you can
|
|
27
|
+
* forget, and they stop the opposite mistake too: registering the owner's
|
|
28
|
+
* `GET /incidents` outside the gate, where Hono's first-match would shadow the
|
|
29
|
+
* real one for ever.
|
|
30
|
+
*
|
|
31
|
+
* 🔴 `POST /report` is the ONE route that accepts incident text from off-box, so
|
|
32
|
+
* it is also the one that has to be paranoid about it: every field is length-
|
|
33
|
+
* capped and `facts` is re-validated through the store's `assertNoContent`
|
|
34
|
+
* before anything is written. A reporter is trusted to be honest, not to be
|
|
35
|
+
* correct.
|
|
36
|
+
*/
|
|
37
|
+
import type { Hono } from "hono";
|
|
38
|
+
import type { IncidentChannel } from "./channels";
|
|
39
|
+
import type { IncidentNotifier } from "./notifier";
|
|
40
|
+
import { type IncidentStore } from "./store";
|
|
41
|
+
import type { SweepResult } from "./checks";
|
|
42
|
+
export interface IncidentRoutesOptions {
|
|
43
|
+
store: IncidentStore;
|
|
44
|
+
/** Owner-only gate for the read + write surface. Return false to 403. */
|
|
45
|
+
authorize: (c: unknown) => boolean | Promise<boolean>;
|
|
46
|
+
/** Run the checks now. Omitted on a host that runs no detectors (prod). */
|
|
47
|
+
sweep?: () => Promise<SweepResult>;
|
|
48
|
+
/** Used to announce anything a remote report opens, and to list channel health. */
|
|
49
|
+
notifier?: IncidentNotifier;
|
|
50
|
+
/** Shared bearer for `POST /report`. Omitted, the route is not mounted. */
|
|
51
|
+
reporterToken?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Deliver one message as this host. Mounted as `POST /relay-email` and used
|
|
54
|
+
* by the local console, which cannot reach SES — see `channels.ts`.
|
|
55
|
+
*/
|
|
56
|
+
relayEmail?: (msg: {
|
|
57
|
+
subject: string;
|
|
58
|
+
text: string;
|
|
59
|
+
html?: string;
|
|
60
|
+
}) => Promise<{
|
|
61
|
+
sent: boolean;
|
|
62
|
+
reason?: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** The last sweep, for the health route. */
|
|
65
|
+
lastSweep?: () => SweepResult | null;
|
|
66
|
+
/** Send a test message down one channel, so the owner can prove it works. */
|
|
67
|
+
channelsOf?: () => readonly IncidentChannel[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The OWNER's surface: the list, the acks, the health strip, the manual sweep,
|
|
71
|
+
* the channel test. Mount it INSIDE the app's session gate.
|
|
72
|
+
*/
|
|
73
|
+
export declare function mountIncidentOwnerRoutes(app: Hono, options: IncidentRoutesOptions, basePath?: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* The MACHINE doors: `POST /report`, which another service beats its findings
|
|
76
|
+
* into, and `POST /relay-email`, which a host that cannot send mail hands a
|
|
77
|
+
* message to. Both carry a shared bearer.
|
|
78
|
+
*
|
|
79
|
+
* 🔴 **Mount this BEFORE the session gate, and never call it with `authorize`
|
|
80
|
+
* expecting it to matter — it registers no route a browser should reach.**
|
|
81
|
+
* Mounting the two together inside a cookie gate is the trap this split exists
|
|
82
|
+
* to make impossible: every beat from binary-server and orch came back
|
|
83
|
+
* `{"error":"unauthorized"}` from the gate, so no log line in this module said
|
|
84
|
+
* anything was wrong and the console rendered a perfectly calm, empty board.
|
|
85
|
+
* A reporter cannot report the failure to reach the reporting door.
|
|
86
|
+
*
|
|
87
|
+
* Keeping them in one function also risked the opposite mistake — registering
|
|
88
|
+
* the owner's `GET /incidents` outside the gate, where Hono's first-match would
|
|
89
|
+
* have shadowed the real one for ever.
|
|
90
|
+
*/
|
|
91
|
+
export declare function mountIncidentMachineRoutes(app: Hono, options: Pick<IncidentRoutesOptions, "store" | "notifier" | "reporterToken" | "relayEmail">, basePath?: string): void;
|