feat(release): add reversible production cutover gates
This commit is contained in:
@@ -8,6 +8,10 @@ export interface ProductionReadinessOptions {
|
||||
readonly gatewayToken: string;
|
||||
/** Metadata/availability probe only. This callback must never retrieve a secret. */
|
||||
readonly keychainMetadataCheck: () => boolean | Promise<boolean>;
|
||||
/** Release gates are deliberately required: omitting one fails readiness closed. */
|
||||
readonly assetsCheck?: () => boolean | Promise<boolean>;
|
||||
readonly recoveryMarkersCheck?: () => number | Promise<number>;
|
||||
readonly upstreamGatewayCheck?: () => boolean | Promise<boolean>;
|
||||
}
|
||||
interface ExecutableMetadata {
|
||||
isFile(): boolean;
|
||||
@@ -57,6 +61,57 @@ export function createProductionReadiness(
|
||||
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 };
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user