feat(runtime): add production control-plane foundation
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
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<boolean>;
|
||||
}
|
||||
interface ExecutableMetadata {
|
||||
isFile(): boolean;
|
||||
readonly mode: number;
|
||||
}
|
||||
export async function defaultKeychainMetadataCheck(
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
stat: (path: string) => Promise<ExecutableMetadata> = fsStat,
|
||||
): Promise<boolean> {
|
||||
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<ReadinessResult> {
|
||||
return async () => {
|
||||
const checks: Record<string, string> = {};
|
||||
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;
|
||||
return { ready, checks };
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user