import type Database from 'better-sqlite3'; import { stat as fsStat } from 'node:fs/promises'; import { MIGRATIONS } from './infrastructure/database/migrations.js'; import type { ReadinessResult } from './app.js'; export interface ProductionReadinessOptions { readonly db: Database.Database; readonly gatewayToken: string; /** Metadata/availability probe only. This callback must never retrieve a secret. */ readonly keychainMetadataCheck: () => boolean | Promise; /** Release gates are deliberately required: omitting one fails readiness closed. */ readonly assetsCheck?: () => boolean | Promise; readonly recoveryMarkersCheck?: () => number | Promise; readonly upstreamGatewayCheck?: () => boolean | Promise; } interface ExecutableMetadata { isFile(): boolean; readonly mode: number; } export async function defaultKeychainMetadataCheck( platform: NodeJS.Platform = process.platform, stat: (path: string) => Promise = fsStat, ): Promise { if (platform !== 'darwin') return false; try { const metadata = await stat('/usr/bin/security'); return metadata.isFile() && (metadata.mode & 0o111) !== 0; } catch { return false; } } export function createProductionReadiness( options: ProductionReadinessOptions, ): () => Promise { return async () => { const checks: Record = {}; let ready = true; try { options.db.prepare('SELECT 1').get(); checks.database = 'ready'; } catch { checks.database = 'unavailable'; ready = false; } try { const expected = MIGRATIONS.at(-1)?.id ?? 0; const row = options.db.prepare('SELECT MAX(id) AS id FROM schema_migrations').get() as { id: number | null; }; checks.migrations = row.id === expected ? 'ready' : 'pending'; if (row.id !== expected) ready = false; } catch { checks.migrations = 'unavailable'; ready = false; } checks.gatewayAuth = Buffer.byteLength(options.gatewayToken) >= 32 ? 'ready' : 'unconfigured'; if (checks.gatewayAuth !== 'ready') ready = false; try { checks.keychain = (await options.keychainMetadataCheck()) ? 'ready' : 'unavailable'; } catch { checks.keychain = 'unavailable'; } if (checks.keychain !== 'ready') ready = false; try { const row = options.db .prepare( "SELECT value_json FROM app_settings WHERE key = 'legacyImport.pendingCredentials'", ) .get() as { value_json: string } | undefined; const value: unknown = row === undefined ? 0 : JSON.parse(row.value_json); checks.pendingCredentials = Number.isSafeInteger(value) && (value as number) >= 0 ? value === 0 ? 'ready' : `pending:${String(value)}` : 'unavailable'; } catch { checks.pendingCredentials = 'unavailable'; } if (checks.pendingCredentials !== 'ready') ready = false; if (options.recoveryMarkersCheck === undefined) { checks.recoveryMarkers = 'unconfigured'; } else { try { const count = await options.recoveryMarkersCheck(); checks.recoveryMarkers = Number.isSafeInteger(count) && count >= 0 ? count === 0 ? 'ready' : `present:${String(count)}` : 'unavailable'; } catch { checks.recoveryMarkers = 'unavailable'; } } if (checks.recoveryMarkers !== 'ready') ready = false; for (const [name, check] of [ ['assets', options.assetsCheck], ['upstreamGateway', options.upstreamGatewayCheck], ] as const) { if (check === undefined) { checks[name] = 'unconfigured'; } else { try { checks[name] = (await check()) ? 'ready' : 'unavailable'; } catch { checks[name] = 'unavailable'; } } if (checks[name] !== 'ready') ready = false; } return { ready, checks }; }; }