Files
2026-09-13 00:25:10 +08:00

3527 lines
164 KiB
JavaScript

// First-run supervision bootstrap for the DEFAULT self-update policy.
//
// Default auto self-update degrades to 'off' without a durable supervisor attestation (policy.ts).
// For a standalone release binary, an unsupervised foreground startup may once
// register its own user-level durable launcher (`evolver lifecycle bootstrap`) and hand over to it:
// the generated launcher carries the EVOLVER_SELF_UPDATE_SUPERVISOR attestation, so the next
// supervised startup runs auto self-update with the unchanged signature/health-check/rollback gates.
//
// Bootstrap is a convenience, never an escalation: it is skipped for attested runs, explicit
// non-auto policies, the EVOLVER_SELF_BOOTSTRAP kill switch, CI, containers, and within a
// cooldown window after a safe failed attempt. The npm/JS install shape first attempts the
// signed standalone migration from migration.ts. Foreground startup continues only when no
// registration child was created or a clean rollback and process-tree termination are proven;
// all ambiguous child ownership fails closed.
import { execFileSync, spawn } from 'node:child_process';
import { createHash, randomUUID } from 'node:crypto';
import { createRequire } from 'node:module';
import { closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, realpathSync, renameSync, rmSync, writeFileSync, writeSync, } from 'node:fs';
import { homedir } from 'node:os';
import { basename, dirname, join, parse as parsePath, posix, resolve as resolvePath, win32, } from 'node:path';
import { fileURLToPath } from 'node:url';
import { bootstrap as coreBootstrap, util } from '@evomap/evolver-core';
import { isSelfUpdateExplicit, resolveSelfUpdatePolicy, selfUpdateSupervisorAttested, } from './policy.js';
import { isStandaloneReleaseBinaryName, resolveSelfUpdateTarget, } from './releaseBinary.js';
import { expandHomePath, parseEnvFile } from '../bin/envFile.js';
import { migrateToStandaloneBinary, } from './migration.js';
const requireFromHere = createRequire(import.meta.url);
const BOOTSTRAP_ATTEMPT_FILE = 'bootstrap-attempt.json';
const BOOTSTRAP_REGISTRATION_INTENT_FILE = coreBootstrap.LIFECYCLE_BOOTSTRAP_REGISTRATION_INTENT_FILE;
const BOOTSTRAP_REGISTRATION_INTENT_TERMINAL_FILE = coreBootstrap.LIFECYCLE_BOOTSTRAP_REGISTRATION_INTENT_TERMINAL_FILE;
const BOOTSTRAP_REGISTRATION_INTENT_CLEARING_FILE = coreBootstrap.LIFECYCLE_BOOTSTRAP_REGISTRATION_INTENT_CLEARING_FILE;
const BOOTSTRAP_REGISTRATION_INTENT_PUBLISHING_FILE = 'bootstrap-registration.intent.publishing';
const BOOTSTRAP_REGISTRATION_INTENT_PUBLISHER_PREFIX = 'bootstrap-registration.intent.publisher.';
const BOOTSTRAP_REGISTRATION_INTENT_SCHEMA = coreBootstrap.LIFECYCLE_BOOTSTRAP_REGISTRATION_INTENT_SCHEMA;
const BOOTSTRAP_FAILURE_COOLDOWN_MS = 24 * 60 * 60 * 1000;
// The child owns a shorter absolute transaction deadline. The parent waits beyond that deadline
// so the child can finish its durable rollback, and only then force-terminates the whole process
// tree if the child failed to honor the contract.
const BOOTSTRAP_TRANSACTION_BUDGET_MS = 180_000;
const BOOTSTRAP_PARENT_EXIT_GRACE_MS = 30_000;
const BOOTSTRAP_TIMEOUT_MS = BOOTSTRAP_TRANSACTION_BUDGET_MS + BOOTSTRAP_PARENT_EXIT_GRACE_MS;
const BOOTSTRAP_TREE_TERMINATION_GRACE_MS = 5_000;
const MAX_TIMER_DELAY_MS = 2_147_483_647;
const BOOTSTRAP_ENV_FILE_HANDOFF = 'EVOLVER_INTERNAL_BOOTSTRAP_ENV_FILE';
export const RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV = 'EVOLVER_INTERNAL_RECOVERY_CONTROLLER_LIFECYCLE_OWNER';
const MAX_RECOVERY_CONTROLLER_OWNER_CAPABILITY_BYTES = 1024;
const MAX_BOOTSTRAP_OUTPUT_BYTES = 64 * 1024;
const MAX_BOOTSTRAP_STATE_BYTES = 128 * 1024;
const MAX_BOOTSTRAP_ATTEMPT_BYTES = 4 * 1024;
const MAX_BOOTSTRAP_REGISTRATION_INTENT_BYTES = coreBootstrap.MAX_LIFECYCLE_BOOTSTRAP_REGISTRATION_INTENT_BYTES;
const MAX_BOOTSTRAP_DETAIL_LENGTH = 512;
const HOST_WINDOWS_SYSTEM_ROOT = process.env['SystemRoot']?.trim() || 'C:\\Windows';
/** Lifecycle state dir mirror of evolver-cli lifecyclePaths (kept dependency-free across packages). */
export function resolveBootstrapStateDir(env) {
const explicit = env['EVOLVER_LIFECYCLE_STATE_DIR']?.trim();
const home = env['EVOLVER_HOME'] ?? env['EVOMAP_HOME'] ?? join(homedir(), '.evomap');
return resolvePath(explicit || join(home, 'lifecycle'));
}
export function withRecoveryControllerLifecycleOwnerCapability(env, owner) {
return prepareRecoveryControllerLifecycleOwnerCapability(env, owner).env;
}
export function prepareRecoveryControllerLifecycleOwnerCapability(env, owner) {
const startupAckToken = randomUUID();
return {
env: {
...env,
[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV]: JSON.stringify({
v: 1,
pid: owner.pid,
token: owner.token,
processStartIdentity: owner.processStartIdentity,
startupAckToken,
}),
},
startupAckToken,
};
}
export function clearRecoveryControllerLifecycleOwnerCapability(env) {
delete env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV];
}
export function publishRecoveryControllerLifecycleStartupAttestation(env, descriptor = 3) {
if (env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV] === undefined)
return false;
const capability = recoveryControllerLifecycleOwnerCapability(env);
if (!capability) {
throw new Error('self_update_recovery_controller_lifecycle_capability_invalid');
}
assertRecoveryControllerLifecycleOwnerLease(env, capability.owner);
writeSync(descriptor, `${capability.startupAckToken}\n`, undefined, 'utf8');
closeSync(descriptor);
clearRecoveryControllerLifecycleOwnerCapability(env);
return true;
}
function bootstrapChildEnv(env, transactionDeadlineMs) {
const childEnv = { ...env };
const envFile = env['EVOLVER_ENV_FILE']?.trim();
delete childEnv['EVOLVER_ENV_FILE'];
delete childEnv[BOOTSTRAP_ENV_FILE_HANDOFF];
if (envFile)
childEnv[BOOTSTRAP_ENV_FILE_HANDOFF] = resolvePath(expandHomePath(envFile));
// Resolve while the foreground proxy still owns cwd, then carry that identity through the
// bootstrap child and generated service launcher. Service managers do not share one cwd.
childEnv['EVOLVER_LIFECYCLE_STATE_DIR'] = resolveBootstrapStateDir(env);
childEnv[coreBootstrap.LIFECYCLE_BOOTSTRAP_DEADLINE_ENV] = String(transactionDeadlineMs);
return childEnv;
}
export function lifecycleBootstrapStatePresent(env, exists = bootstrapStateEntryPresent) {
const stateDir = resolveBootstrapStateDir(env);
return [
coreBootstrap.LIFECYCLE_BOOTSTRAP_SUCCESS_FILE,
coreBootstrap.LIFECYCLE_BOOTSTRAP_JOURNAL_FILE,
coreBootstrap.LIFECYCLE_BOOTSTRAP_OWNER_LOCK_FILE,
coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_FILE,
coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_LOCK_FILE,
coreBootstrap.LIFECYCLE_BOOTSTRAP_MANUAL_TRANSITION_FILE,
].some((name) => exists(join(stateDir, name)));
}
function bootstrapStateEntryPresent(path) {
try {
lstatSync(path);
return true;
}
catch (error) {
return !isErrno(error, 'ENOENT');
}
}
const defaultReadTextFile = (path) => readFileSync(path, 'utf8');
const BOOTSTRAP_JOURNAL_SCHEMA = 'evolver.lifecycle-bootstrap-transaction.v1';
const BOOTSTRAP_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const BOOTSTRAP_JOURNAL_STAGES = new Set([
'prepared', 'installing', 'installed', 'activating', 'activated', 'committing',
'committed', 'rollback_pending', 'rolled_back',
]);
function boundedBootstrapDetail(value) {
let normalized = '';
let previousWasSpace = false;
for (const character of value) {
const next = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(character) ? ' ' : character;
if (next === ' ' && previousWasSpace)
continue;
normalized += next;
previousWasSpace = next === ' ';
if (normalized.length >= MAX_BOOTSTRAP_DETAIL_LENGTH)
break;
}
return normalized.slice(0, MAX_BOOTSTRAP_DETAIL_LENGTH);
}
function readBoundedBootstrapStateFile(path, options, maxBytes = MAX_BOOTSTRAP_STATE_BYTES) {
const injected = options.exists !== undefined || options.readFile !== undefined;
try {
if (injected) {
if (!(options.exists ?? existsSync)(path))
return { status: 'absent' };
const raw = (options.readFile ?? defaultReadTextFile)(path);
if (Buffer.byteLength(raw, 'utf8') > maxBytes) {
return { status: 'invalid', detail: `oversized durable state: ${basename(path)}` };
}
return { status: 'present', raw };
}
const before = lstatSync(path, { bigint: true });
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n
|| before.size > BigInt(maxBytes)
|| before.dev <= 0n || before.ino <= 0n) {
return { status: 'invalid', detail: `unsafe durable state: ${basename(path)}` };
}
(options.assertIntentFileTrust ?? assertNativeBootstrapIntentFileTrust)(path);
const descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
try {
const opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino
|| opened.size !== before.size) {
return { status: 'invalid', detail: `changed durable state: ${basename(path)}` };
}
const bytes = Buffer.alloc(maxBytes + 1);
let offset = 0;
while (offset < bytes.length) {
const count = readSync(descriptor, bytes, offset, bytes.length - offset, null);
if (count === 0)
break;
offset += count;
}
if (offset > maxBytes) {
return { status: 'invalid', detail: `oversized durable state: ${basename(path)}` };
}
const raw = bytes.subarray(0, offset).toString('utf8');
const after = fstatSync(descriptor, { bigint: true });
if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size
|| after.mtimeNs !== opened.mtimeNs || after.ctimeNs !== opened.ctimeNs) {
return { status: 'invalid', detail: `changed durable state: ${basename(path)}` };
}
(options.assertIntentFileTrust ?? assertNativeBootstrapIntentFileTrust)(path);
const settled = lstatSync(path, { bigint: true });
if (!settled.isFile() || settled.isSymbolicLink() || settled.nlink !== 1n
|| settled.dev !== opened.dev || settled.ino !== opened.ino || settled.size !== opened.size
|| settled.mtimeNs !== opened.mtimeNs || settled.ctimeNs !== opened.ctimeNs) {
return { status: 'invalid', detail: `changed durable state: ${basename(path)}` };
}
return { status: 'present', raw };
}
finally {
closeSync(descriptor);
}
}
catch (error) {
if (isErrno(error, 'ENOENT'))
return { status: 'absent' };
return {
status: 'invalid',
detail: boundedBootstrapDetail(`unreadable durable state ${basename(path)}: ${error instanceof Error ? error.message : String(error)}`),
};
}
}
function sameBootstrapStateFileSnapshot(left, right) {
if (left.status !== right.status)
return false;
if (left.status === 'present' && right.status === 'present') {
return left.raw === right.raw;
}
return left.status === 'absent' && right.status === 'absent';
}
function legacyBootstrapPathKey(path, target) {
return target === 'windows'
? win32.normalize(path).toLowerCase()
: posix.normalize(path);
}
function assertNativeBootstrapLegacyProofFileTrust(path) {
const info = lstatSync(path);
if (!info.isFile() || info.isSymbolicLink() || info.nlink < 1) {
throw new Error('legacy bootstrap proof file is unsafe');
}
if (process.platform === 'win32') {
assertWindowsBootstrapIntentAclTrusted([{ path, parentOnly: false }]);
return;
}
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
if (uid === undefined
|| (info.uid !== uid && info.uid !== 0)
|| (info.mode & 0o022) !== 0) {
throw new Error('legacy bootstrap proof file is not trusted');
}
}
function readLegacyBootstrapProofFile(receipt, options) {
if (receipt.size < 1 || receipt.size > MAX_BOOTSTRAP_STATE_BYTES) {
throw new Error('legacy bootstrap proof receipt has an invalid size');
}
const injected = options.exists !== undefined || options.readFile !== undefined;
const exists = options.exists ?? existsSync;
const readFile = options.readFile ?? defaultReadTextFile;
if (injected) {
if (!exists(receipt.path))
throw new Error('legacy bootstrap proof file is missing');
const first = readFile(receipt.path);
options.afterLegacyProofRead?.(receipt.path);
const second = readFile(receipt.path);
const bytes = Buffer.from(first, 'utf8');
if (first !== second
|| bytes.length !== receipt.size
|| createHash('sha256').update(bytes).digest('hex') !== receipt.sha256) {
throw new Error('legacy bootstrap proof file changed after adoption');
}
return first;
}
const assertDirectoryTrust = options.assertLegacyProofDirectoryTrust
?? ((directory) => assertNativeBootstrapIntentDirectoryTrust(directory, false));
const assertFileTrust = options.assertLegacyProofFileTrust
?? assertNativeBootstrapLegacyProofFileTrust;
const directory = dirname(receipt.path);
assertDirectoryTrust(directory);
assertFileTrust(receipt.path);
const before = lstatSync(receipt.path, { bigint: true });
if (!before.isFile() || before.isSymbolicLink()
|| before.dev <= 0n || before.ino <= 0n
|| before.size !== BigInt(receipt.size)
|| before.dev !== BigInt(receipt.device ?? '0')
|| before.ino !== BigInt(receipt.inode ?? '0')) {
throw new Error('legacy bootstrap proof identity changed after adoption');
}
const descriptor = openSync(receipt.path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
try {
const opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile()
|| opened.dev !== before.dev || opened.ino !== before.ino
|| opened.size !== before.size
|| opened.mtimeNs !== before.mtimeNs || opened.ctimeNs !== before.ctimeNs) {
throw new Error('legacy bootstrap proof changed while opening');
}
const readPass = () => {
const bytes = Buffer.alloc(receipt.size + 1);
let offset = 0;
while (offset < bytes.length) {
const count = readSync(descriptor, bytes, offset, bytes.length - offset, offset);
if (count === 0)
break;
offset += count;
}
return bytes.subarray(0, offset);
};
const first = readPass();
options.afterLegacyProofRead?.(receipt.path);
const second = readPass();
const after = fstatSync(descriptor, { bigint: true });
if (!first.equals(second)
|| first.length !== receipt.size
|| createHash('sha256').update(first).digest('hex') !== receipt.sha256
|| after.dev !== opened.dev || after.ino !== opened.ino
|| after.size !== opened.size
|| after.mtimeNs !== opened.mtimeNs || after.ctimeNs !== opened.ctimeNs) {
throw new Error('legacy bootstrap proof changed while reading');
}
assertDirectoryTrust(directory);
assertFileTrust(receipt.path);
const settled = lstatSync(receipt.path, { bigint: true });
if (!settled.isFile() || settled.isSymbolicLink()
|| settled.dev !== opened.dev || settled.ino !== opened.ino
|| settled.size !== opened.size
|| settled.mtimeNs !== opened.mtimeNs || settled.ctimeNs !== opened.ctimeNs) {
throw new Error('legacy bootstrap proof path changed while reading');
}
return first.toString('utf8');
}
finally {
closeSync(descriptor);
}
}
function validateLegacyBootstrapStateRootProof(marker, stateDir, env, options) {
const proof = marker.legacyStateRootProof;
if (!proof)
return undefined;
const key = (path) => legacyBootstrapPathKey(path, marker.target);
if (key(proof.stateDir) !== key(stateDir)
|| key(env['EVOLVER_LIFECYCLE_STATE_DIR']?.trim() ?? '') !== key(proof.stateDir)
|| key(env['EVOLVER_ENV_FILE']?.trim() ?? '') !== key(proof.envFilePath)) {
return 'legacy bootstrap state-root binding no longer matches the supervised environment';
}
const receipt = marker.preservedArtifacts?.find((artifact) => key(artifact.path) === key(proof.envFilePath));
if (!receipt)
return 'legacy bootstrap state-root proof has no preserved receipt';
try {
const raw = readLegacyBootstrapProofFile(receipt, options);
const configured = parseEnvFile(raw)['EVOLVER_LIFECYCLE_STATE_DIR'];
if (!configured || key(configured) !== key(proof.stateDir)) {
return 'legacy bootstrap env file no longer pins the adopted state root';
}
}
catch {
return 'legacy bootstrap state-root proof could not be revalidated';
}
return undefined;
}
function parseBootstrapLockOwnerJson(raw) {
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
return undefined;
const record = parsed;
const keys = Object.keys(record).sort();
if (keys.length !== 4
|| keys[0] !== 'pid'
|| keys[1] !== 'processStart'
|| keys[2] !== 'token'
|| keys[3] !== 'v'
|| record['v'] !== 2
|| !Number.isSafeInteger(record['pid']) || record['pid'] <= 0
|| typeof record['token'] !== 'string' || !BOOTSTRAP_UUID_RE.test(record['token'])) {
return undefined;
}
const processStartIdentity = util.parseFileLockProcessStartIdentity(record['processStart']);
if (!processStartIdentity)
return undefined;
return {
pid: record['pid'],
token: record['token'],
processStartIdentity,
};
}
catch {
return undefined;
}
}
function parseBootstrapJournalOwner(value) {
if (!value || typeof value !== 'object' || Array.isArray(value))
return undefined;
const record = value;
const processStartIdentity = util.parseFileLockProcessStartIdentity(record['processStartIdentity']);
if (!Number.isSafeInteger(record['pid']) || record['pid'] <= 0
|| typeof record['token'] !== 'string' || !BOOTSTRAP_UUID_RE.test(record['token'])
|| !processStartIdentity
|| typeof record['acquiredAt'] !== 'string'
|| Number.isNaN(Date.parse(record['acquiredAt']))) {
return undefined;
}
return {
pid: record['pid'],
token: record['token'],
processStartIdentity,
};
}
function recoveryControllerLifecycleOwnerCapability(env, parentPid = process.ppid) {
const raw = env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV];
if (!raw || Buffer.byteLength(raw, 'utf8') > MAX_RECOVERY_CONTROLLER_OWNER_CAPABILITY_BYTES) {
return undefined;
}
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
return undefined;
const record = parsed;
const keys = Object.keys(record).sort();
if (keys.length !== 5
|| keys[0] !== 'pid'
|| keys[1] !== 'processStartIdentity'
|| keys[2] !== 'startupAckToken'
|| keys[3] !== 'token'
|| keys[4] !== 'v'
|| record['v'] !== 1
|| !Number.isSafeInteger(record['pid'])
|| record['pid'] !== parentPid
|| typeof record['token'] !== 'string'
|| !BOOTSTRAP_UUID_RE.test(record['token'])
|| typeof record['startupAckToken'] !== 'string'
|| !BOOTSTRAP_UUID_RE.test(record['startupAckToken'])) {
return undefined;
}
const processStartIdentity = util.parseFileLockProcessStartIdentity(record['processStartIdentity']);
if (!processStartIdentity)
return undefined;
const owner = {
pid: record['pid'],
token: record['token'],
processStartIdentity,
};
if (util.inspectFileLockOwnerProcess(owner) !== 'current')
return undefined;
return { owner, startupAckToken: record['startupAckToken'] };
}
catch {
return undefined;
}
}
function assertRecoveryControllerLifecycleOwnerLease(env, owner, options = {}) {
const durable = inspectLifecycleBootstrapDurableState(env, {
...options,
expectedRecoveryOwner: owner,
});
if (durable.status === 'invalid') {
throw new Error('self_update_recovery_controller_lifecycle_capability_invalid:'
+ boundedBootstrapDetail(durable.detail));
}
}
function sameBootstrapLockOwner(left, right) {
return left.pid === right.pid
&& left.token === right.token
&& util.sameFileLockProcessStartIdentity(left.processStartIdentity, right.processStartIdentity);
}
function bootstrapLockProcessStatus(owner, options) {
try {
return (options.registrationOwnerProcessStatus
?? util.inspectFileLockOwnerProcess)(owner);
}
catch {
return 'unverifiable';
}
}
function inspectBootstrapRecoveryLock(owner, options) {
if (!owner)
return { recoveryStatus: 'active_or_unverifiable' };
const processStatus = bootstrapLockProcessStatus(owner, options);
return {
owner,
processStatus,
recoveryStatus: processStatus === 'dead' || processStatus === 'pid_reused'
? 'stale'
: 'active_or_unverifiable',
};
}
function normalBootstrapActivationContractValid(record, owner, target, deadlineMs) {
const managerBinding = record['managerBinding'];
return owner !== undefined
&& target !== undefined
&& deadlineMs !== undefined
&& record['operation'] === undefined
&& record['terminalAction'] === undefined
&& typeof record['service'] === 'string'
&& record['service'].length > 0
&& record['service'].length <= 128
&& record['managerBefore'] === 'absent'
&& managerBinding !== null
&& typeof managerBinding === 'object'
&& !Array.isArray(managerBinding)
&& managerBinding['kind'] === 'transaction'
&& typeof managerBinding['artifactPath'] === 'string'
&& managerBinding['artifactPath'].length > 0
&& managerBinding['artifactPath'].length <= 4_096
&& Array.isArray(record['artifacts'])
&& record['artifacts'].length > 0
&& record['artifacts'].length <= 32
&& typeof record['updatedAt'] === 'string'
&& !Number.isNaN(Date.parse(record['updatedAt']));
}
function parseBootstrapJournalReceipt(raw) {
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
return undefined;
const record = parsed;
if (record['schema'] !== BOOTSTRAP_JOURNAL_SCHEMA
|| typeof record['transactionId'] !== 'string' || !BOOTSTRAP_UUID_RE.test(record['transactionId'])
|| typeof record['stage'] !== 'string' || !BOOTSTRAP_JOURNAL_STAGES.has(record['stage'])) {
return undefined;
}
if (record['terminalAction'] === undefined) {
if (record['operation'] !== undefined
|| (record['target'] !== undefined
&& (typeof record['target'] !== 'string'
|| !['launchd', 'systemd', 'windows'].includes(record['target'])))
|| (record['owner'] !== undefined && !parseBootstrapJournalOwner(record['owner']))
|| (record['deadlineMs'] !== undefined
&& (!Number.isSafeInteger(record['deadlineMs'])
|| record['deadlineMs'] <= 0))
|| (record['activationStarted'] !== undefined
&& typeof record['activationStarted'] !== 'boolean')) {
return undefined;
}
const target = record['target'];
const owner = record['owner'] === undefined
? undefined
: parseBootstrapJournalOwner(record['owner']);
const deadlineMs = record['deadlineMs'];
return {
transactionId: record['transactionId'],
stage: record['stage'],
...(target ? { target } : {}),
...(owner ? { owner } : {}),
...(deadlineMs !== undefined ? { deadlineMs } : {}),
...(record['activationStarted'] !== undefined
? { activationStarted: record['activationStarted'] }
: {}),
...(normalBootstrapActivationContractValid(record, owner, target, deadlineMs)
? { activationContractValid: true }
: {}),
};
}
if (record['operation'] === 'legacy-v907-remove'
&& record['terminalAction'] === 'remove_committed'
&& ['prepared', 'rollback_pending', 'rolled_back'].includes(record['stage'])
&& typeof record['target'] === 'string'
&& ['launchd', 'systemd', 'windows'].includes(record['target'])) {
return {
transactionId: record['transactionId'],
stage: record['stage'],
target: record['target'],
terminalAction: 'remove_committed',
operation: 'legacy-v907-remove',
};
}
if (record['terminalAction'] !== 'remove_committed'
|| !['rollback_pending', 'rolled_back'].includes(record['stage'])
|| typeof record['target'] !== 'string'
|| !['launchd', 'systemd', 'windows'].includes(record['target'])) {
return undefined;
}
return {
transactionId: record['transactionId'],
stage: record['stage'],
target: record['target'],
terminalAction: 'remove_committed',
};
}
catch {
return undefined;
}
}
function inspectLifecycleBootstrapDurableState(env, options) {
const stateDir = resolveBootstrapStateDir(env);
if (options.exists === undefined && options.readFile === undefined) {
try {
const state = lstatSync(stateDir);
if (!state.isDirectory() || state.isSymbolicLink()) {
return { status: 'invalid', detail: 'lifecycle state directory is not trusted' };
}
if (process.platform !== 'win32') {
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
if ((uid !== undefined && state.uid !== uid) || (state.mode & 0o077) !== 0) {
return { status: 'invalid', detail: 'lifecycle state directory is not owner-only' };
}
}
}
catch (error) {
if (isErrno(error, 'ENOENT'))
return { status: 'clean' };
return { status: 'invalid', detail: 'lifecycle state directory is unreadable' };
}
}
const markerFile = readBoundedBootstrapStateFile(join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_SUCCESS_FILE), options);
const journalFile = readBoundedBootstrapStateFile(join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_JOURNAL_FILE), options);
const ownerLockFile = readBoundedBootstrapStateFile(join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_OWNER_LOCK_FILE), options, util.MAX_LOCK_OWNER_BYTES);
const readinessFile = readBoundedBootstrapStateFile(join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_FILE), options);
const readinessLockFile = readBoundedBootstrapStateFile(join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_LOCK_FILE), options, util.MAX_LOCK_OWNER_BYTES);
const manualTransitionFile = readBoundedBootstrapStateFile(join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_MANUAL_TRANSITION_FILE), options, coreBootstrap.MAX_LIFECYCLE_BOOTSTRAP_MANUAL_TRANSITION_BYTES);
for (const state of [
markerFile,
journalFile,
ownerLockFile,
readinessFile,
readinessLockFile,
manualTransitionFile,
]) {
if (state.status === 'invalid')
return { status: 'invalid', detail: state.detail };
}
const marker = markerFile.status === 'present'
? coreBootstrap.parseLifecycleBootstrapMarkerJson(markerFile.raw)
: undefined;
const legacyMarker = markerFile.status === 'present' && !marker
? coreBootstrap.parseLegacyLifecycleBootstrapMarkerJson(markerFile.raw)
: undefined;
if (markerFile.status === 'present' && !marker && !legacyMarker) {
return { status: 'invalid', detail: 'bootstrap success marker is corrupt' };
}
const readiness = readinessFile.status === 'present'
? coreBootstrap.parseLifecycleBootstrapReadinessJson(readinessFile.raw)
: undefined;
if (readinessFile.status === 'present' && !readiness) {
return { status: 'invalid', detail: 'bootstrap readiness receipt is corrupt' };
}
const journal = journalFile.status === 'present'
? parseBootstrapJournalReceipt(journalFile.raw)
: undefined;
if (journalFile.status === 'present' && !journal) {
return { status: 'invalid', detail: 'bootstrap recovery journal is corrupt' };
}
const manualTransition = manualTransitionFile.status === 'present'
? coreBootstrap.parseLifecycleBootstrapManualTransitionJson(manualTransitionFile.raw)
: undefined;
if (manualTransitionFile.status === 'present' && !manualTransition) {
return { status: 'invalid', detail: 'bootstrap manual-transition tombstone is corrupt' };
}
const ownerLockOwner = ownerLockFile.status === 'present'
? parseBootstrapLockOwnerJson(ownerLockFile.raw)
: undefined;
const expectedRecoveryOwner = options.expectedRecoveryOwner;
if (expectedRecoveryOwner !== undefined
&& (ownerLockFile.status !== 'present'
|| ownerLockOwner === undefined
|| !sameBootstrapLockOwner(ownerLockOwner, expectedRecoveryOwner))) {
return {
status: 'invalid',
detail: 'lifecycle bootstrap owner lease is no longer held by the expected owner',
};
}
const ownerLockIgnored = ownerLockOwner !== undefined
&& expectedRecoveryOwner !== undefined
&& sameBootstrapLockOwner(ownerLockOwner, expectedRecoveryOwner);
const ownerLockPresent = ownerLockFile.status === 'present' && !ownerLockIgnored;
const readinessLockPresent = readinessLockFile.status === 'present';
const ownerLock = ownerLockPresent
? inspectBootstrapRecoveryLock(ownerLockOwner, options)
: undefined;
const readinessLockOwner = readinessLockFile.status === 'present'
? parseBootstrapLockOwnerJson(readinessLockFile.raw)
: undefined;
const readinessLock = readinessLockPresent
? inspectBootstrapRecoveryLock(readinessLockOwner, options)
: undefined;
const changedAfterLockProbe = (prior, path, maxBytes) => {
const current = readBoundedBootstrapStateFile(path, options, maxBytes);
if (current.status === 'invalid')
return current.detail;
return sameBootstrapStateFileSnapshot(prior, current)
? undefined
: `durable lifecycle state changed during lock owner inspection: ${basename(path)}`;
};
const snapshotFailure = changedAfterLockProbe(ownerLockFile, join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_OWNER_LOCK_FILE), util.MAX_LOCK_OWNER_BYTES) ?? changedAfterLockProbe(readinessLockFile, join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_READINESS_LOCK_FILE), util.MAX_LOCK_OWNER_BYTES) ?? changedAfterLockProbe(journalFile, join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_JOURNAL_FILE), MAX_BOOTSTRAP_STATE_BYTES);
if (snapshotFailure)
return { status: 'invalid', detail: snapshotFailure };
const recoveryLocks = {
...(ownerLock ? { owner: ownerLock.recoveryStatus } : {}),
...(readinessLock ? { readiness: readinessLock.recoveryStatus } : {}),
};
const recoveryLockState = Object.keys(recoveryLocks).length > 0
? recoveryLocks
: undefined;
const activeRecoveryLock = Object.values(recoveryLocks)
.some((status) => status === 'active_or_unverifiable');
const withRecoveryLocks = recoveryLockState ? { recoveryLocks: recoveryLockState } : {};
if (journal && readiness && journal.transactionId !== readiness.transactionId) {
return { status: 'invalid', detail: 'bootstrap journal and readiness owners differ' };
}
if (journal?.target && marker && journal.target !== marker.target) {
return { status: 'invalid', detail: 'bootstrap journal and marker targets differ' };
}
if (legacyMarker) {
if (readiness) {
return {
status: 'invalid',
detail: 'legacy bootstrap marker conflicts with transaction durable state',
};
}
if (journal) {
if (manualTransition && (journal.terminalAction !== 'remove_committed'
|| journal.transactionId !== manualTransition.removedTransactionId
|| journal.target !== manualTransition.target)) {
return {
status: 'invalid',
detail: 'legacy bootstrap removal journal conflicts with manual-transition state',
};
}
return {
status: 'pending',
detail: 'legacy bootstrap recovery journal requires CLI recovery',
...withRecoveryLocks,
...(manualTransition ? {
manualTransition: {
transitionId: manualTransition.transitionId,
target: manualTransition.target,
},
} : {}),
};
}
if (manualTransition) {
return {
status: 'invalid',
detail: 'legacy bootstrap marker conflicts with manual-transition state',
};
}
return {
status: 'pending',
detail: 'legacy bootstrap marker requires exact CLI adoption',
...withRecoveryLocks,
};
}
if (manualTransition) {
if (journal) {
if (journal.terminalAction !== 'remove_committed'
|| journal.transactionId !== manualTransition.removedTransactionId
|| journal.target !== manualTransition.target) {
return {
status: 'invalid',
detail: 'bootstrap manual-transition tombstone conflicts with recovery journal',
};
}
if ((marker && (marker.transactionId !== manualTransition.removedTransactionId
|| marker.target !== manualTransition.target))
|| (readiness && readiness.transactionId !== manualTransition.removedTransactionId)) {
return {
status: 'invalid',
detail: 'bootstrap manual-transition tombstone conflicts with committed state',
};
}
return {
status: 'pending',
detail: 'committed service removal recovery is pending',
...withRecoveryLocks,
manualTransition: {
transitionId: manualTransition.transitionId,
target: manualTransition.target,
},
};
}
if ((marker && (marker.transactionId !== manualTransition.removedTransactionId
|| marker.target !== manualTransition.target))
|| (readiness && (!marker
|| readiness.transactionId !== manualTransition.removedTransactionId))) {
return {
status: 'invalid',
detail: 'bootstrap manual-transition tombstone conflicts with committed state',
};
}
if (marker) {
return {
status: 'pending',
detail: 'manual service removal has not retired the committed marker; rerun remove-service',
...withRecoveryLocks,
manualTransition: {
transitionId: manualTransition.transitionId,
target: manualTransition.target,
},
};
}
if (activeRecoveryLock) {
return {
status: 'pending',
detail: 'manual service transition lifecycle lock ownership is active or unverifiable',
...withRecoveryLocks,
manualTransition: {
transitionId: manualTransition.transitionId,
target: manualTransition.target,
},
};
}
return {
status: 'manual_transition',
transitionId: manualTransition.transitionId,
target: manualTransition.target,
};
}
if (!marker && !journal && !ownerLockPresent && !readiness && !readinessLockPresent) {
return { status: 'clean' };
}
if (marker?.managerBindingKind === coreBootstrap.LIFECYCLE_BOOTSTRAP_LEGACY_BINDING) {
const proofFailure = validateLegacyBootstrapStateRootProof(marker, stateDir, env, options);
if (proofFailure) {
return { status: 'invalid', detail: proofFailure };
}
if (readiness) {
return {
status: 'invalid',
detail: 'legacy bootstrap binding cannot own a transaction readiness receipt',
};
}
if (journal) {
return {
status: 'pending',
detail: 'legacy bootstrap adoption journal requires CLI recovery',
...withRecoveryLocks,
};
}
if (activeRecoveryLock) {
return {
status: 'pending',
detail: 'legacy bootstrap adoption owner lock remains active or readiness lock is unverifiable',
...withRecoveryLocks,
};
}
return {
status: 'committed',
transactionId: marker.transactionId,
target: marker.target,
};
}
if (marker && readiness) {
if (marker.transactionId !== readiness.transactionId) {
return { status: 'invalid', detail: 'bootstrap marker and readiness owners differ' };
}
if (journal && (journal.transactionId !== marker.transactionId
|| !['committing', 'committed'].includes(journal.stage))) {
return { status: 'invalid', detail: 'bootstrap journal conflicts with committed receipt' };
}
if (activeRecoveryLock) {
return {
status: 'pending',
detail: 'committed lifecycle state still has active or unverifiable lock ownership',
...withRecoveryLocks,
};
}
return {
status: 'committed',
transactionId: marker.transactionId,
target: marker.target,
};
}
const now = options.now ?? Date.now();
const supervisedActivation = !marker
&& journal?.stage === 'activating'
&& journal.activationStarted === true
&& journal.activationContractValid === true
&& journal.target !== undefined
&& journal.owner !== undefined
&& journal.deadlineMs !== undefined
&& Number.isSafeInteger(now)
&& now >= 0
&& journal.deadlineMs > now
&& ownerLock?.processStatus === 'current'
&& ownerLock.owner !== undefined
&& sameBootstrapLockOwner(ownerLock.owner, journal.owner)
&& readinessLock?.recoveryStatus !== 'active_or_unverifiable'
&& (!readiness || readiness.transactionId === journal.transactionId)
? {
transactionId: journal.transactionId,
target: journal.target,
}
: undefined;
const present = [
marker ? 'marker' : undefined,
journal ? 'journal' : undefined,
ownerLockPresent ? 'owner lock' : undefined,
readiness ? 'readiness' : undefined,
readinessLockPresent ? 'readiness lock' : undefined,
].filter((value) => value !== undefined);
return {
status: 'pending',
detail: `partial durable bootstrap state: ${present.join(',')}`,
...withRecoveryLocks,
...(supervisedActivation ? { supervisedActivation } : {}),
};
}
export function assertSupervisedLifecycleBootstrapState(env, options = {}) {
const capabilityRaw = env[RECOVERY_CONTROLLER_LIFECYCLE_OWNER_CAPABILITY_ENV];
const controllerCapability = capabilityRaw === undefined
? undefined
: recoveryControllerLifecycleOwnerCapability(env, options.recoveryControllerParentPid ?? process.ppid);
if (capabilityRaw !== undefined && !controllerCapability) {
throw new Error('self_update_recovery_controller_lifecycle_capability_invalid');
}
if (!selfUpdateSupervisorAttested(env)) {
if (controllerCapability) {
assertRecoveryControllerLifecycleOwnerLease(env, controllerCapability.owner, options);
}
return;
}
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
const supervisorTarget = supervisor === 'systemd'
? 'systemd'
: supervisor === 'launchd'
? 'launchd'
: supervisor === 'windows-scheduled-task'
? 'windows'
: undefined;
const transactionValue = env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV];
const transactionId = transactionValue?.trim();
if (!lifecycleBootstrapStatePresent(env, options.exists ?? bootstrapStateEntryPresent)) {
if (transactionValue !== undefined || options.requireLifecycleState) {
throw new Error('self_update_supervisor_bootstrap_state_invalid:'
+ (transactionValue !== undefined
? 'transaction-bound supervisor has no durable lifecycle state'
: 'unpinned legacy supervisor has no durable lifecycle state'));
}
return;
}
if (controllerCapability && options.expectedRecoveryOwner
&& !sameBootstrapLockOwner(controllerCapability.owner, options.expectedRecoveryOwner)) {
throw new Error('self_update_recovery_controller_lifecycle_capability_invalid:expected owner differs');
}
const durable = inspectLifecycleBootstrapDurableState(env, {
...options,
expectedRecoveryOwner: options.expectedRecoveryOwner ?? controllerCapability?.owner,
});
if (durable.status === 'committed') {
if (durable.target !== supervisorTarget) {
throw new Error('self_update_supervisor_bootstrap_state_invalid:'
+ 'committed lifecycle target does not match supervisor attestation');
}
if (transactionValue !== undefined
&& (!transactionId
|| !BOOTSTRAP_UUID_RE.test(transactionId)
|| transactionId !== durable.transactionId)) {
throw new Error('self_update_supervisor_bootstrap_state_invalid:'
+ 'committed lifecycle transaction does not match supervisor attestation');
}
return;
}
if (durable.status === 'pending' && durable.supervisedActivation) {
if (!transactionId
|| !BOOTSTRAP_UUID_RE.test(transactionId)
|| transactionId !== durable.supervisedActivation.transactionId
|| durable.supervisedActivation.target !== supervisorTarget) {
throw new Error('self_update_supervisor_bootstrap_state_invalid:'
+ 'activating lifecycle transaction does not match supervisor attestation');
}
return;
}
const detail = durable.status === 'pending' || durable.status === 'invalid'
? durable.detail
: 'supervised lifecycle state is not committed';
throw new Error('self_update_supervisor_bootstrap_state_invalid:' + boundedBootstrapDetail(detail));
}
/**
* Revalidate the narrow parent-owned activation window used by a newly launched recovery
* controller. This deliberately rejects committed supervision: callers use it only as delegated
* authority while the lifecycle parent still owns the mutation lock and is waiting for readiness.
*/
export function assertActiveSupervisedLifecycleBootstrapDelegation(env, options = {}) {
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
const supervisorTarget = supervisor === 'systemd'
? 'systemd'
: supervisor === 'launchd'
? 'launchd'
: supervisor === 'windows-scheduled-task'
? 'windows'
: undefined;
const transactionId = env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV]?.trim();
if (!selfUpdateSupervisorAttested(env)
|| !transactionId
|| !BOOTSTRAP_UUID_RE.test(transactionId)
|| !lifecycleBootstrapStatePresent(env, options.exists ?? bootstrapStateEntryPresent)) {
throw new Error('self_update_supervisor_bootstrap_delegation_invalid:'
+ 'controller has no valid activating lifecycle attestation');
}
const durable = inspectLifecycleBootstrapDurableState(env, options);
if (durable.status === 'pending'
&& durable.supervisedActivation?.transactionId === transactionId
&& durable.supervisedActivation.target === supervisorTarget) {
return;
}
const detail = durable.status === 'pending' || durable.status === 'invalid'
? durable.detail
: 'lifecycle state is not an active delegated activation';
throw new Error('self_update_supervisor_bootstrap_delegation_invalid:'
+ boundedBootstrapDetail(detail));
}
/**
* Revalidate a transaction-bound launcher before any self-update operation. Unlike the startup
* assertion above, this never accepts the narrow activating window used to publish readiness.
*/
export function assertCommittedLifecycleBootstrapState(env, options = {}) {
const transactionValue = env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV];
if (transactionValue === undefined)
return;
const transactionId = transactionValue.trim();
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
const supervisorTarget = supervisor === 'systemd'
? 'systemd'
: supervisor === 'launchd'
? 'launchd'
: supervisor === 'windows-scheduled-task'
? 'windows'
: undefined;
if (!selfUpdateSupervisorAttested(env)
|| !transactionId
|| !BOOTSTRAP_UUID_RE.test(transactionId)
|| !lifecycleBootstrapStatePresent(env, options.exists ?? bootstrapStateEntryPresent)) {
throw new Error('self_update_supervisor_bootstrap_state_invalid:'
+ 'transaction-bound self-update has no valid committed lifecycle attestation');
}
const durable = inspectLifecycleBootstrapDurableState(env, options);
if (durable.status === 'committed'
&& durable.transactionId === transactionId
&& durable.target === supervisorTarget) {
return;
}
const detail = durable.status === 'pending' || durable.status === 'invalid'
? durable.detail
: 'transaction-bound self-update lifecycle state is not committed';
throw new Error('self_update_supervisor_bootstrap_state_invalid:' + boundedBootstrapDetail(detail));
}
/**
* Serialize self-update with every lifecycle bootstrap, recovery, and manual-transition writer.
* Acquisition is deliberately fail-fast: a heartbeat must not block the daemon while another
* lifecycle owner is active. Transaction-bound launchers revalidate the committed receipt under
* the exact acquired generation; legacy launchers still hold and recheck the shared owner lock.
*/
export function acquireLifecycleBootstrapOwnerLease(env, lockOptions = { maxTries: 2, waitMs: 0 }) {
const transactionBound = env[coreBootstrap.LIFECYCLE_BOOTSTRAP_TRANSACTION_ENV] !== undefined;
if (transactionBound)
assertCommittedLifecycleBootstrapState(env);
const stateDir = resolveBootstrapStateDir(env);
assertNativeBootstrapIntentDirectoryTrust(stateDir, true);
mkdirSync(stateDir, { recursive: true, mode: 0o700 });
assertNativeBootstrapIntentDirectoryTrust(stateDir, true);
const path = join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_OWNER_LOCK_FILE);
const owner = util.acquireLock(path, lockOptions);
let state = 'active';
let guardian;
const assertOwned = () => {
if (state !== 'active') {
throw new Error('self_update_lifecycle_owner_lease_not_active');
}
const lock = readBoundedBootstrapStateFile(path, { assertIntentFileTrust: () => undefined }, util.MAX_LOCK_OWNER_BYTES);
const current = lock.status === 'present'
? parseBootstrapLockOwnerJson(lock.raw)
: undefined;
if (!current || !sameBootstrapLockOwner(current, owner)) {
throw new Error('self_update_lifecycle_owner_lease_ownership_changed');
}
};
const release = () => {
if (state === 'transferred')
return;
if (state === 'released')
return;
if (state === 'release_failed') {
throw new Error('self_update_lifecycle_owner_lease_release_failed');
}
const result = util.releaseLock(path);
if (result.released
&& (result.reason === 'released' || result.reason === 'released_with_cleanup_error')) {
state = 'released';
return;
}
state = 'release_failed';
throw new Error(`self_update_lifecycle_owner_lease_release_failed:${result.reason}`);
};
const armProcess = (pid) => {
assertOwned();
if (guardian !== undefined) {
throw new Error('self_update_lifecycle_owner_lease_guardian_already_armed');
}
const result = util.attachLockGuardianToProcess(path, owner, pid);
if (!result.attached) {
throw new Error(`self_update_lifecycle_owner_lease_guardian_arm_failed:${result.reason}`);
}
guardian = result.guardian;
return guardian;
};
const disarmProcess = () => {
assertOwned();
if (guardian === undefined)
return;
const result = util.clearLockGuardianForProcess(path, owner, guardian);
if (!result.cleared) {
throw new Error(`self_update_lifecycle_owner_lease_guardian_disarm_failed:${result.reason}`);
}
guardian = undefined;
};
const retainProcess = () => {
assertOwned();
if (guardian === undefined) {
throw new Error('self_update_lifecycle_owner_lease_guardian_not_armed');
}
const result = util.retainLockGuardianForProcess(path, owner, guardian);
if (!result.retained) {
throw new Error(`self_update_lifecycle_owner_lease_guardian_retain_failed:${result.reason}`);
}
state = 'transferred';
};
const transferToProcess = (pid) => {
const attached = armProcess(pid);
retainProcess();
return attached;
};
try {
assertOwned();
const options = { expectedRecoveryOwner: owner };
if (transactionBound) {
assertCommittedLifecycleBootstrapState(env, options);
}
else {
const durable = inspectLifecycleBootstrapDurableState(env, options);
if (durable.status === 'invalid') {
throw new Error('self_update_lifecycle_owner_lease_invalid:'
+ boundedBootstrapDetail(durable.detail));
}
if (durable.status === 'pending' || durable.status === 'manual_transition') {
const detail = durable.status === 'pending'
? durable.detail
: 'manual_transition';
throw new Error('self_update_lifecycle_owner_lease_state_blocked:'
+ boundedBootstrapDetail(detail));
}
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
const supervisorTarget = supervisor === 'systemd'
? 'systemd'
: supervisor === 'launchd'
? 'launchd'
: supervisor === 'windows-scheduled-task'
? 'windows'
: undefined;
if (durable.status === 'committed'
&& supervisorTarget !== undefined
&& durable.target !== supervisorTarget) {
throw new Error('self_update_lifecycle_owner_lease_state_blocked:'
+ 'committed lifecycle target does not match supervisor attestation');
}
}
}
catch (error) {
try {
release();
}
catch (releaseError) {
throw new AggregateError([error, releaseError], 'self_update_lifecycle_owner_lease_validation_and_release_failed');
}
throw error;
}
return {
path,
owner,
assertOwned,
armProcess,
disarmProcess,
retainProcess,
transferToProcess,
release,
};
}
export function looksLikeContainer(exists, readFile) {
if (exists('/.dockerenv'))
return true;
try {
return /docker|containerd|kubepods|podman|lxc/.test(readFile('/proc/1/cgroup'));
}
catch {
return false;
}
}
const BOOTSTRAP_ATTEMPT_OUTCOMES = new Set([
'already_supervised',
'already_bootstrapped',
'unsupported_install_shape',
'policy_not_auto',
'bootstrap_disabled',
'ci_environment',
'container_environment',
'recent_failure',
'failed',
'cli_not_found',
'rolled_back',
'bootstrapped',
'bootstrapped_lock_release_unconfirmed',
'blocked',
'ambiguous',
'termination_unconfirmed',
'timeout',
'signal',
'migrated',
'migration_failed',
'migration_timeout',
'migration_ambiguous',
]);
const BOOTSTRAP_ATTEMPT_PENDING_OUTCOMES = new Set([
'blocked',
'ambiguous',
'termination_unconfirmed',
'timeout',
'signal',
'migration_ambiguous',
]);
function readBootstrapAttempt(env, options) {
try {
const state = readBoundedBootstrapStateFile(join(resolveBootstrapStateDir(env), BOOTSTRAP_ATTEMPT_FILE), options, MAX_BOOTSTRAP_ATTEMPT_BYTES);
if (state.status === 'absent')
return { status: 'absent' };
if (state.status === 'invalid')
return { status: 'invalid' };
const parsed = JSON.parse(state.raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return { status: 'invalid' };
}
const record = parsed;
if (typeof record['outcome'] !== 'string'
|| !BOOTSTRAP_ATTEMPT_OUTCOMES.has(record['outcome'])
|| typeof record['attemptedAt'] !== 'string') {
return { status: 'invalid' };
}
const attemptedAt = Date.parse(record['attemptedAt']);
if (!Number.isFinite(attemptedAt)
|| new Date(attemptedAt).toISOString() !== record['attemptedAt']
|| (record['detail'] !== undefined
&& typeof record['detail'] !== 'string')) {
return { status: 'invalid' };
}
return BOOTSTRAP_ATTEMPT_PENDING_OUTCOMES.has(record['outcome'])
? { status: 'ambiguous', attemptedAt }
: { status: 'safe_non_ambiguous', attemptedAt, outcome: record['outcome'] };
}
catch {
return { status: 'invalid' };
}
}
function bootstrapRegistrationIntentPresent(env) {
const stateDir = resolveBootstrapStateDir(env);
return bootstrapStateEntryPresent(join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_FILE))
|| bootstrapStateEntryPresent(join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_PUBLISHING_FILE))
|| bootstrapStateEntryPresent(join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_TERMINAL_FILE))
|| bootstrapStateEntryPresent(join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_CLEARING_FILE))
|| bootstrapRegistrationPublisherEntryPresent(stateDir);
}
function bootstrapRegistrationPublisherEntryPresent(stateDir) {
try {
return readdirSync(stateDir).some((name) => name.startsWith(BOOTSTRAP_REGISTRATION_INTENT_PUBLISHER_PREFIX));
}
catch (error) {
return !isErrno(error, 'ENOENT');
}
}
// Only outcomes that prove no competing child remains may suppress a retry. Ambiguous bootstrap
// outcomes deliberately stay out of this set so recovery is never bypassed by the cooldown.
const BOOTSTRAP_COOLDOWN_OUTCOMES = new Set([
'failed',
'cli_not_found',
'rolled_back',
'migration_failed',
'migration_timeout',
]);
/** True when a recent bootstrap/migration attempt failed within the cooldown window. */
export function recentBootstrapFailure(env, optionsOrReadFile, now) {
const options = typeof optionsOrReadFile === 'function'
? { readFile: optionsOrReadFile }
: optionsOrReadFile;
const attempt = readBootstrapAttempt(env, options);
if (attempt.status !== 'safe_non_ambiguous'
|| !BOOTSTRAP_COOLDOWN_OUTCOMES.has(attempt.outcome))
return false;
const ageMs = now - attempt.attemptedAt;
return ageMs >= 0 && ageMs < BOOTSTRAP_FAILURE_COOLDOWN_MS;
}
/**
* Decide whether an unsupervised (degraded) startup should attempt first-run bootstrap.
* Pure — filesystem access is injectable for tests.
*/
export function shouldBootstrap(env, platform = process.platform, options = {}) {
if (selfUpdateSupervisorAttested(env)) {
try {
resolveSelfUpdateTarget({ env, processExecPath: options.execPath });
}
catch {
return { proceed: false, reason: 'unsupported_install_shape' };
}
return { proceed: false, reason: 'already_supervised' };
}
const exists = options.exists ?? existsSync;
// The no-replace intent predates child creation and therefore outranks every lifecycle
// state fragment. Recovery must classify it without creating another child.
if (bootstrapRegistrationIntentPresent(env)) {
return { proceed: false, reason: 'bootstrap_intent_pending' };
}
if (lifecycleBootstrapStatePresent(env, options.exists ?? bootstrapStateEntryPresent)) {
try {
resolveSelfUpdateTarget({ env, processExecPath: options.execPath });
return { proceed: true };
}
catch {
// Durable lifecycle state must be reconciled, but an npm/JS foreground cannot safely
// perform that reconciliation through ordinary lifecycle bootstrap: it would bind the
// service manager back to the same unreplaceable Node launcher. Route recovery through
// the signed standalone migration and keep every non-migrated outcome fail-closed.
return { proceed: false, reason: 'bootstrap_attempt_pending' };
}
}
const attempt = readBootstrapAttempt(env, options);
if (attempt.status === 'invalid') {
return { proceed: false, reason: 'bootstrap_attempt_invalid' };
}
if (attempt.status === 'ambiguous') {
try {
resolveSelfUpdateTarget({ env, processExecPath: options.execPath });
return { proceed: true, reason: 'bootstrap_attempt_pending' };
}
catch {
// An npm/JS foreground cannot safely run ordinary lifecycle bootstrap. Retry the
// signed standalone migration, whose reconciler keeps this ambiguity fail-closed.
return { proceed: false, reason: 'bootstrap_attempt_pending' };
}
}
const bootstrapSwitch = env['EVOLVER_SELF_BOOTSTRAP']?.trim();
if (bootstrapSwitch === '0' || bootstrapSwitch === 'off')
return { proceed: false, reason: 'bootstrap_disabled' };
if (isSelfUpdateExplicit(env) && resolveSelfUpdatePolicy(env) !== 'auto') {
return { proceed: false, reason: 'policy_not_auto' };
}
// The npm/JS install shape has no replaceable standalone binary target, so the launcher
// bootstrap would register a supervised instance that crashes at self-update target
// resolution on every startup (crash-loop under the service manager). Skip it; an explicit
// EVOLVER_SELF_UPDATE_TARGET_PATH keeps the target bindable and bypasses this guard.
try {
resolveSelfUpdateTarget({ env, processExecPath: options.execPath });
}
catch {
return { proceed: false, reason: 'unsupported_install_shape' };
}
const ci = env['CI']?.trim();
if (ci && ci.toLowerCase() !== 'false' && ci !== '0')
return { proceed: false, reason: 'ci_environment' };
if (platform === 'linux' && looksLikeContainer(exists, options.readFile ?? defaultReadTextFile)) {
return { proceed: false, reason: 'container_environment' };
}
if (recentBootstrapFailure(env, options, options.now ?? Date.now())) {
return { proceed: false, reason: 'recent_failure' };
}
return { proceed: true };
}
/**
* Resolve the `lifecycle bootstrap` invocation for the current install shape: standalone binary,
* CLI entry through node, or the npm-installed @evomap/evolver-cli sibling. Returns undefined when
* no CLI can be located. The caller may continue only after confirming durable state is clean.
*/
export function resolveBootstrapCliInvocation(options = {}) {
const execPath = options.execPath ?? process.execPath;
const argv1 = options.argv1 ?? process.argv[1];
const exists = options.exists ?? existsSync;
const executableName = basename(execPath).toLowerCase();
if (isStandaloneReleaseBinaryName(executableName)) {
return { command: execPath, args: ['lifecycle', 'bootstrap'] };
}
if (argv1 && basename(argv1).toLowerCase() === 'cli.js') {
return { command: execPath, args: [argv1, 'lifecycle', 'bootstrap'] };
}
try {
const entry = requireFromHere.resolve('@evomap/evolver-cli');
const cliPath = join(dirname(entry), 'cli.js');
if (exists(cliPath))
return { command: execPath, args: [cliPath, 'lifecycle', 'bootstrap'] };
}
catch {
// Not resolvable from the installed proxy package — fall through to the monorepo layout.
}
const local = fileURLToPath(new URL('../../../evolver-cli/dist/cli.js', import.meta.url));
if (exists(local))
return { command: execPath, args: [local, 'lifecycle', 'bootstrap'] };
return undefined;
}
/** Best-effort attempt marker; never throws — bootstrap bookkeeping must not break startup. */
export function recordBootstrapAttempt(env, outcome, options = {}) {
const path = join(resolveBootstrapStateDir(env), BOOTSTRAP_ATTEMPT_FILE);
const record = {
attemptedAt: new Date(options.now ?? Date.now()).toISOString(),
outcome: outcome.reason,
...(outcome.detail ? { detail: boundedBootstrapDetail(outcome.detail) } : {}),
};
try {
const writeFile = options.writeFile ?? ((target, content) => {
mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
const temporary = join(dirname(target), `.${basename(target)}.${process.pid}.${randomUUID()}.tmp`);
let descriptor;
try {
descriptor = openSync(temporary, 'wx', 0o600);
writeFileSync(descriptor, content, { encoding: 'utf8' });
fsyncSync(descriptor);
closeSync(descriptor);
descriptor = undefined;
renameSync(temporary, target);
}
finally {
if (descriptor !== undefined)
closeSync(descriptor);
rmSync(temporary, { force: true });
}
});
writeFile(path, `${JSON.stringify(record)}\n`);
}
catch {
// Marker is advisory; startup continues regardless.
}
}
function captureBootstrapOutput(stream) {
const chunks = [];
let capturedBytes = 0;
let truncated = false;
stream?.on('data', (chunk) => {
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
const available = Math.max(0, MAX_BOOTSTRAP_OUTPUT_BYTES - capturedBytes);
if (bytes.byteLength > available)
truncated = true;
if (available > 0) {
const captured = bytes.subarray(0, available);
chunks.push(captured);
capturedBytes += captured.byteLength;
}
});
return {
get truncated() { return truncated; },
text: () => Buffer.concat(chunks, capturedBytes).toString('utf8'),
};
}
function parseBootstrapCliResult(output) {
if (output.truncated)
return undefined;
const raw = output.text().trim();
if (!raw)
return undefined;
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
return undefined;
const record = parsed;
if (record['status'] === 'bootstrap-committed-lock-release-unconfirmed') {
const result = record['result'];
const resultRecord = result && typeof result === 'object' && !Array.isArray(result)
? result
: undefined;
const transactionId = resultRecord?.['transactionId'];
if (record['outcome'] !== 'committed'
|| !resultRecord
|| !['bootstrapped', 'already-bootstrapped']
.includes(resultRecord['status'])
|| typeof transactionId !== 'string'
|| !BOOTSTRAP_UUID_RE.test(transactionId)
|| record['transactionId'] !== transactionId) {
return undefined;
}
const detail = typeof record['detail'] === 'string' && record['detail'].trim()
? boundedBootstrapDetail(record['detail'])
: undefined;
if (!detail)
return undefined;
return { outcome: 'committed_lock_release_unconfirmed', transactionId, detail };
}
if (record['status'] === 'bootstrapped' || record['status'] === 'already-bootstrapped') {
const transactionId = record['transactionId'];
if (typeof transactionId !== 'string' || !BOOTSTRAP_UUID_RE.test(transactionId)) {
return undefined;
}
return { outcome: 'bootstrapped', transactionId };
}
if (record['status'] !== 'bootstrap-failed')
return undefined;
const detail = typeof record['detail'] === 'string'
? boundedBootstrapDetail(record['detail'])
: undefined;
if (record['outcome'] === 'rolled_back') {
return { outcome: 'rolled_back', ...(detail ? { detail } : {}) };
}
if (record['outcome'] === 'blocked') {
return { outcome: 'blocked', ...(detail ? { detail } : {}) };
}
return undefined;
}
catch {
return undefined;
}
}
function observationDetail(observation, stdout, stderr) {
const parts = [];
if ('detail' in observation && observation.detail)
parts.push(observation.detail);
if (stdout.truncated)
parts.push('stdout_truncated');
if (stderr.truncated)
parts.push('stderr_truncated');
const stderrText = boundedBootstrapDetail(stderr.text().trim());
if (stderrText)
parts.push(stderrText);
return parts.length > 0 ? boundedBootstrapDetail(parts.join('; ')) : undefined;
}
function reconcileBootstrapOutcome(observation, state, stdout, stderr) {
const detail = observationDetail(observation, stdout, stderr);
const failClosed = (reason, failureDetail) => ({
ok: false,
reason,
...(failureDetail ? { detail: boundedBootstrapDetail(failureDetail) } : {}),
requiresForegroundExit: true,
});
const stateDetail = state.status === 'pending' || state.status === 'invalid' ? state.detail : undefined;
const combinedDetail = [detail, stateDetail].filter((value) => Boolean(value)).join('; ');
if (observation.kind === 'termination_unconfirmed') {
return failClosed('termination_unconfirmed', combinedDetail || observation.detail);
}
if (observation.kind === 'timeout')
return failClosed('timeout', combinedDetail || undefined);
if (observation.kind === 'signal') {
return failClosed('signal', combinedDetail || `signal ${observation.signal}`);
}
if (observation.kind === 'child_ambiguous') {
return failClosed('ambiguous', combinedDetail || observation.detail);
}
if (state.status === 'invalid')
return failClosed('ambiguous', combinedDetail);
if (state.status === 'pending')
return failClosed('blocked', combinedDetail);
if (state.status === 'committed') {
if (observation.kind === 'exit' && observation.code === 0
&& observation.cli?.outcome === 'bootstrapped'
&& observation.cli.transactionId === state.transactionId
&& !stdout.truncated) {
return { ok: true, reason: 'bootstrapped' };
}
if (observation.kind === 'exit' && observation.code === 1
&& observation.cli?.outcome === 'committed_lock_release_unconfirmed'
&& observation.cli.transactionId === state.transactionId
&& !stdout.truncated) {
return {
ok: true,
reason: 'bootstrapped_lock_release_unconfirmed',
...(observation.cli.detail ? { detail: observation.cli.detail } : {}),
};
}
return failClosed('ambiguous', combinedDetail || 'durable bootstrap committed without a matching transaction-bound CLI success result');
}
if (observation.kind === 'no_child') {
return {
ok: false,
reason: observation.reason,
...(detail ? { detail } : {}),
};
}
if (observation.cli?.outcome === 'rolled_back' && observation.code !== 0 && !stdout.truncated) {
return {
ok: false,
reason: 'rolled_back',
...(observation.cli.detail ? { detail: observation.cli.detail } : {}),
};
}
if (observation.cli?.outcome === 'blocked') {
return failClosed('blocked', combinedDetail || observation.cli.detail);
}
return failClosed('ambiguous', combinedDetail || `CLI result did not prove commit or rollback (exit ${observation.code ?? 'null'})`);
}
function isErrno(error, code) {
return typeof error === 'object' && error !== null && error.code === code;
}
function trustedWindowsSystemExecutable(name) {
if (!win32.isAbsolute(HOST_WINDOWS_SYSTEM_ROOT) || /[\r\n\0]/.test(HOST_WINDOWS_SYSTEM_ROOT)) {
throw new Error('Windows SystemRoot is not an absolute trusted path');
}
return win32.join(HOST_WINDOWS_SYSTEM_ROOT, 'System32', name);
}
function remainingMs(deadlineMs) {
return Math.max(0, deadlineMs - Date.now());
}
async function waitForCondition(predicate, deadlineMs) {
while (!predicate()) {
const remaining = remainingMs(deadlineMs);
if (remaining === 0)
return false;
await new Promise((resolvePromise) => {
setTimeout(resolvePromise, Math.min(10, remaining));
});
}
return true;
}
function requestWindowsProcessTreeTermination(pid, spawnFn, deadlineMs) {
return new Promise((resolvePromise) => {
let settled = false;
let killer;
let timer;
const settle = (accepted) => {
if (settled)
return;
settled = true;
if (timer)
clearTimeout(timer);
resolvePromise(accepted);
};
try {
killer = spawnFn(trustedWindowsSystemExecutable('taskkill.exe'), ['/PID', String(pid), '/T', '/F'], {
shell: false,
windowsHide: true,
stdio: 'ignore',
});
killer.once('error', () => settle(false));
killer.once('close', (code) => settle(code === 0));
const remaining = remainingMs(deadlineMs);
if (remaining === 0) {
try {
killer.kill('SIGKILL');
}
catch { /* best-effort watchdog cleanup */ }
settle(false);
return;
}
timer = setTimeout(() => {
try {
killer?.kill('SIGKILL');
}
catch { /* best-effort watchdog cleanup */ }
settle(false);
}, remaining);
}
catch {
settle(false);
}
});
}
async function terminateBootstrapProcessTree(child, platform, childClosed, deadlineMs, processKill, treeKillSpawnFn) {
const pid = child.pid;
if (!Number.isSafeInteger(pid) || typeof pid !== 'number' || pid <= 0) {
try {
child.kill('SIGKILL');
}
catch { /* direct-child fallback only */ }
await waitForCondition(childClosed, deadlineMs);
return { confirmed: false, detail: 'process_tree_termination_unconfirmed' };
}
if (platform === 'win32') {
const treeKillAccepted = await requestWindowsProcessTreeTermination(pid, treeKillSpawnFn, deadlineMs);
if (!treeKillAccepted) {
try {
child.kill('SIGKILL');
}
catch { /* direct-child fallback only */ }
}
const directChildClosed = await waitForCondition(childClosed, deadlineMs);
return treeKillAccepted && directChildClosed
? { confirmed: true }
: { confirmed: false, detail: 'process_tree_termination_unconfirmed' };
}
let groupKillAccepted = false;
try {
processKill(-pid, 'SIGKILL');
groupKillAccepted = true;
}
catch (error) {
groupKillAccepted = isErrno(error, 'ESRCH');
if (!groupKillAccepted) {
try {
child.kill('SIGKILL');
}
catch { /* direct-child fallback only */ }
}
}
const processGroupGone = () => {
try {
processKill(-pid, 0);
return false;
}
catch (error) {
return isErrno(error, 'ESRCH');
}
};
const confirmed = groupKillAccepted
&& await waitForCondition(() => childClosed() && processGroupGone(), deadlineMs);
return confirmed
? { confirmed: true }
: { confirmed: false, detail: 'process_tree_termination_unconfirmed' };
}
/** Spawn `evolver lifecycle bootstrap` and await its result within a bounded timeout. */
export async function runBootstrap(options) {
const emptyOutput = () => captureBootstrapOutput(undefined);
const reconcileNoChild = (reason, detail) => reconcileBootstrapOutcome({ kind: 'no_child', reason, ...(detail ? { detail } : {}) }, inspectLifecycleBootstrapDurableState(options.env, options), emptyOutput(), emptyOutput());
const invocation = resolveBootstrapCliInvocation(options);
if (!invocation)
return reconcileNoChild('cli_not_found');
const spawnFn = options.spawnFn ?? spawn;
const timeoutMs = options.timeoutMs ?? BOOTSTRAP_TIMEOUT_MS;
const transactionBudgetMs = options.transactionBudgetMs ?? BOOTSTRAP_TRANSACTION_BUDGET_MS;
const terminationGraceMs = options.terminationGraceMs ?? BOOTSTRAP_TREE_TERMINATION_GRACE_MS;
const startedAt = options.now ?? Date.now();
if (!Number.isSafeInteger(timeoutMs)
|| !Number.isSafeInteger(transactionBudgetMs)
|| !Number.isSafeInteger(terminationGraceMs)
|| !Number.isSafeInteger(startedAt)
|| !Number.isSafeInteger(startedAt + transactionBudgetMs)
|| timeoutMs <= transactionBudgetMs
|| timeoutMs > MAX_TIMER_DELAY_MS
|| transactionBudgetMs <= 0
|| terminationGraceMs <= 0
|| terminationGraceMs > MAX_TIMER_DELAY_MS
|| startedAt < 0) {
return reconcileNoChild('failed', 'invalid_bootstrap_timeout_contract');
}
const platform = options.platform ?? process.platform;
return new Promise((resolvePromise) => {
let child;
try {
child = spawnFn(invocation.command, invocation.args, {
detached: platform !== 'win32',
shell: false,
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
env: bootstrapChildEnv(options.env, startedAt + transactionBudgetMs),
});
}
catch (error) {
resolvePromise(reconcileNoChild('failed', error instanceof Error ? error.message : String(error)));
return;
}
const stdout = captureBootstrapOutput(child.stdout);
const stderr = captureBootstrapOutput(child.stderr);
let settled = false;
let containmentStarted = false;
let childClosed = false;
let childStarted = Number.isSafeInteger(child.pid) && typeof child.pid === 'number' && child.pid > 0;
const timerRef = {};
const settle = (observation) => {
if (settled)
return;
settled = true;
if (timerRef.value)
clearTimeout(timerRef.value);
resolvePromise(reconcileBootstrapOutcome(observation, inspectLifecycleBootstrapDurableState(options.env, options), stdout, stderr));
};
const containAndSettle = (confirmedObservation) => {
if (settled || containmentStarted)
return;
containmentStarted = true;
const confirmationDeadlineMs = Date.now() + terminationGraceMs;
void terminateBootstrapProcessTree(child, platform, () => childClosed, confirmationDeadlineMs, options.processKill ?? process.kill.bind(process), options.treeKillSpawnFn ?? spawn).then((termination) => {
if (!termination.confirmed) {
settle({ kind: 'termination_unconfirmed', detail: termination.detail ?? 'process_tree_termination_unconfirmed' });
return;
}
settle(confirmedObservation);
}).catch(() => {
settle({ kind: 'termination_unconfirmed', detail: 'process_tree_termination_unconfirmed' });
});
};
child.once('spawn', () => {
childStarted = true;
});
child.once('close', (code, signal) => {
childClosed = true;
if (settled || containmentStarted)
return;
if (signal) {
containAndSettle({ kind: 'signal', signal });
return;
}
const cli = parseBootstrapCliResult(stdout);
settle({
kind: 'exit',
code,
cli,
...((stdout.truncated || !cli)
? { detail: stdout.truncated ? 'bootstrap_cli_stdout_truncated' : 'bootstrap_cli_result_invalid' }
: {}),
});
});
timerRef.value = setTimeout(() => {
containAndSettle({ kind: 'timeout' });
}, timeoutMs);
child.once('error', (error) => {
if (settled || containmentStarted)
return;
if (!childStarted && (!Number.isSafeInteger(child.pid) || !child.pid || child.pid <= 0)) {
settle({ kind: 'no_child', reason: 'failed', detail: error.message });
return;
}
containAndSettle({ kind: 'child_ambiguous', detail: error.message });
});
});
}
export function windowsBootstrapIntentAclScript(checks) {
const encodedChecks = Buffer.from(JSON.stringify(checks), 'utf8').toString('base64');
const d = String.fromCharCode(36);
return [
`${d}ErrorActionPreference = 'Stop'`,
'try {',
` ${d}windowsRoot = [Environment]::GetFolderPath([Environment+SpecialFolder]::Windows)`,
` if ([string]::IsNullOrWhiteSpace(${d}windowsRoot) -or -not [System.IO.Path]::IsPathRooted(${d}windowsRoot)) { exit 24 }`,
` Import-Module (Join-Path ${d}windowsRoot 'System32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Security\\Microsoft.PowerShell.Security.psd1') -ErrorAction Stop`,
` ${d}json = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedChecks}'))`,
` ${d}checks = ${d}json | ConvertFrom-Json`,
` ${d}userSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value`,
` ${d}trustedInstaller = 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464'`,
` ${d}trustedOwners = @(${d}userSid, 'S-1-5-18', 'S-1-5-32-544', ${d}trustedInstaller)`,
` ${d}trustedWriters = @(${d}userSid, 'S-1-5-18', 'S-1-5-32-544', ${d}trustedInstaller, 'S-1-3-0', 'S-1-3-4')`,
` ${d}parentDanger = [System.Security.AccessControl.FileSystemRights]::Delete -bor [System.Security.AccessControl.FileSystemRights]::DeleteSubdirectoriesAndFiles -bor [System.Security.AccessControl.FileSystemRights]::ChangePermissions -bor [System.Security.AccessControl.FileSystemRights]::TakeOwnership`,
` ${d}contentDanger = [System.Security.AccessControl.FileSystemRights]::WriteData -bor [System.Security.AccessControl.FileSystemRights]::AppendData -bor [System.Security.AccessControl.FileSystemRights]::CreateFiles -bor [System.Security.AccessControl.FileSystemRights]::CreateDirectories`,
` foreach (${d}check in @(${d}checks)) {`,
` ${d}acl = Get-Acl -LiteralPath ${d}check.path`,
` ${d}owner = ${d}acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value`,
` if (${d}trustedOwners -notcontains ${d}owner) { exit 21 }`,
` ${d}danger = if (${d}check.parentOnly) { ${d}parentDanger } else { ${d}parentDanger -bor ${d}contentDanger }`,
` foreach (${d}rule in @(${d}acl.Access)) {`,
` if (${d}rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow) { continue }`,
` if ((${d}rule.PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::InheritOnly) -ne 0) { continue }`,
` if ((${d}rule.FileSystemRights -band ${d}danger) -eq 0) { continue }`,
` try { ${d}sid = ${d}rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { exit 22 }`,
` if (${d}trustedWriters -notcontains ${d}sid) { exit 23 }`,
' }',
' }',
'} catch { exit 24 }',
].join('; ');
}
function assertWindowsBootstrapIntentAclTrusted(checks) {
if (process.platform !== 'win32' || checks.length === 0)
return;
try {
execFileSync(trustedWindowsSystemExecutable('WindowsPowerShell\\v1.0\\powershell.exe'), ['-NoProfile', '-NonInteractive', '-Command', windowsBootstrapIntentAclScript(checks)], { stdio: 'ignore', timeout: 10_000, windowsHide: true });
}
catch (error) {
throw new Error('bootstrap registration intent Windows ACL chain is not trusted', { cause: error });
}
}
function bootstrapPathKey(path) {
const resolved = resolvePath(path);
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
}
function assertNativeBootstrapIntentDirectoryTrust(directory, requireOwnerOnlyLeaf) {
const absoluteDirectory = resolvePath(directory);
let current = absoluteDirectory;
const root = parsePath(current).root;
const windowsChecks = [];
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
let nearestExisting = true;
let privateUserAnchor = false;
let directoryExists = false;
for (;;) {
let info;
try {
info = lstatSync(current);
}
catch (error) {
if (!isErrno(error, 'ENOENT'))
throw error;
if (current === root)
break;
current = dirname(current);
continue;
}
if (info.isSymbolicLink() || !info.isDirectory()) {
throw new Error('bootstrap registration intent directory chain is unsafe');
}
if (bootstrapPathKey(current) === bootstrapPathKey(absoluteDirectory))
directoryExists = true;
if (process.platform === 'win32') {
windowsChecks.push({ path: current, parentOnly: bootstrapPathKey(current) !== bootstrapPathKey(absoluteDirectory) });
}
else {
if (uid !== undefined && info.uid !== uid && (nearestExisting || info.uid !== 0)) {
throw new Error('bootstrap registration intent directory owner is untrusted');
}
if (requireOwnerOnlyLeaf && bootstrapPathKey(current) === bootstrapPathKey(absoluteDirectory)
&& (info.mode & 0o077) !== 0) {
throw new Error('bootstrap registration intent directory is not owner-only');
}
const writableByOthers = (info.mode & 0o022) !== 0;
const trustedStickyAncestor = !nearestExisting
&& privateUserAnchor
&& info.uid === 0
&& (info.mode & 0o1000) !== 0;
if (writableByOthers && !trustedStickyAncestor) {
throw new Error('bootstrap registration intent directory chain is writable by another principal');
}
if (info.uid === uid && (info.mode & 0o077) === 0)
privateUserAnchor = true;
}
nearestExisting = false;
if (current === root)
break;
current = dirname(current);
}
if (directoryExists
&& bootstrapPathKey(realpathSync(absoluteDirectory)) !== bootstrapPathKey(absoluteDirectory)) {
throw new Error('bootstrap registration intent directory is not canonical');
}
assertWindowsBootstrapIntentAclTrusted(windowsChecks);
}
function assertNativeBootstrapIntentFileTrust(path, expectedLinkCount = 1) {
const info = lstatSync(path);
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== expectedLinkCount) {
throw new Error('bootstrap registration intent file is unsafe');
}
if (process.platform === 'win32') {
assertWindowsBootstrapIntentAclTrusted([{ path, parentOnly: false }]);
return;
}
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
if ((uid !== undefined && info.uid !== uid) || (info.mode & 0o077) !== 0) {
throw new Error('bootstrap registration intent file is not owner-only');
}
}
function assertBootstrapIntentDirectoryTrust(options, directory, requireOwnerOnlyLeaf) {
if (options.assertIntentDirectoryTrust) {
options.assertIntentDirectoryTrust(directory);
return;
}
assertNativeBootstrapIntentDirectoryTrust(directory, requireOwnerOnlyLeaf);
}
function assertBootstrapIntentFileTrust(options, path, expectedLinkCount = 1) {
if (options.assertIntentFileTrust) {
options.assertIntentFileTrust(path);
return;
}
assertNativeBootstrapIntentFileTrust(path, expectedLinkCount);
}
function readBootstrapRegistrationProcessStartIdentity(options, pid) {
return (options.readRegistrationProcessStartIdentity
?? util.readFileLockProcessStartIdentity)(pid);
}
function bootstrapRegistrationProcessIdentityDigest(identity) {
const canonical = identity.source === 'linux-proc'
? ['linux-proc', identity.bootId, identity.startTicks]
: identity.source === 'windows-powershell'
? ['windows-powershell', identity.startTimeTicks]
: ['darwin-ps', identity.startTime];
return createHash('sha256').update(JSON.stringify(canonical), 'utf8').digest('hex');
}
function bootstrapRegistrationPublisherProcessStatus(options, publisher) {
if (options.registrationPublisherProcessStatus) {
return options.registrationPublisherProcessStatus({
pid: publisher.pid,
token: publisher.token,
processIdentityDigest: publisher.processIdentityDigest,
});
}
try {
process.kill(publisher.pid, 0);
}
catch (error) {
if (isErrno(error, 'ESRCH'))
return 'dead';
if (!isErrno(error, 'EPERM'))
return 'unverifiable';
}
const current = readBootstrapRegistrationProcessStartIdentity(options, publisher.pid);
if (current === null)
return 'unverifiable';
return bootstrapRegistrationProcessIdentityDigest(current) === publisher.processIdentityDigest
? 'current'
: 'pid_reused';
}
function assertBootstrapRegistrationOwnerCurrent(options, owner) {
const current = readBootstrapRegistrationProcessStartIdentity(options, owner.pid);
if (current === null
|| !util.sameFileLockProcessStartIdentity(owner.processStartIdentity, current)) {
throw new Error('bootstrap registration parent process identity is no longer current');
}
}
function bootstrapRegistrationOwnerProcessStatus(options, owner) {
return (options.registrationOwnerProcessStatus
?? util.inspectFileLockOwnerProcess)(owner);
}
function acquireBootstrapRegistrationRecoveryLock(options) {
const stateDir = resolveBootstrapStateDir(options.env);
assertBootstrapIntentDirectoryTrust(options, stateDir, true);
const path = join(stateDir, coreBootstrap.LIFECYCLE_BOOTSTRAP_OWNER_LOCK_FILE);
const owner = util.acquireLock(path, { maxTries: 2, waitMs: 0 });
let released = false;
return {
path,
owner,
release: () => {
if (released)
return;
const result = util.releaseLock(path);
if (!result.released
|| (result.reason !== 'released' && result.reason !== 'released_with_cleanup_error')) {
throw new Error(`bootstrap registration recovery lock release failed: ${result.reason}`);
}
released = true;
},
};
}
function parseBootstrapRegistrationIntent(raw) {
return coreBootstrap.parseLifecycleBootstrapRegistrationIntentJson(raw);
}
function syncBootstrapDirectory(path) {
let descriptor;
try {
descriptor = openSync(path, constants.O_RDONLY);
fsyncSync(descriptor);
}
catch (error) {
if (isErrno(error, 'EINVAL'))
return;
if (process.platform === 'win32' && (isErrno(error, 'EPERM') || isErrno(error, 'EACCES')))
return;
throw error;
}
finally {
if (descriptor !== undefined)
closeSync(descriptor);
}
}
function readBootstrapRegistrationIntentFile(options, path, registrationPath, terminalPath, clearingPath, expectedLinkCount = 1n) {
try {
assertBootstrapIntentFileTrust(options, path, Number(expectedLinkCount));
const before = lstatSync(path, { bigint: true });
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== expectedLinkCount
|| before.dev <= 0n || before.ino <= 0n
|| before.size > BigInt(MAX_BOOTSTRAP_REGISTRATION_INTENT_BYTES)
|| (process.platform !== 'win32' && (before.mode & 63n) !== 0n)) {
return { status: 'invalid', detail: 'registration intent file is unsafe' };
}
const trustedFile = {
path,
device: before.dev,
inode: before.ino,
linkCount: before.nlink,
size: before.size,
mtimeNs: before.mtimeNs,
ctimeNs: before.ctimeNs,
};
const descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
let raw;
try {
const opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile() || opened.nlink !== expectedLinkCount
|| opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size) {
return { status: 'invalid', detail: 'registration intent changed before read' };
}
const bytes = Buffer.alloc(MAX_BOOTSTRAP_REGISTRATION_INTENT_BYTES + 1);
let offset = 0;
while (offset < bytes.length) {
const count = readSync(descriptor, bytes, offset, bytes.length - offset, null);
if (count === 0)
break;
offset += count;
}
if (offset > MAX_BOOTSTRAP_REGISTRATION_INTENT_BYTES) {
return { status: 'invalid', detail: 'registration intent is oversized', trustedFile };
}
raw = bytes.subarray(0, offset).toString('utf8');
const settled = fstatSync(descriptor, { bigint: true });
if (settled.dev !== opened.dev || settled.ino !== opened.ino
|| settled.size !== opened.size || settled.mtimeNs !== opened.mtimeNs
|| settled.ctimeNs !== opened.ctimeNs) {
return { status: 'invalid', detail: 'registration intent changed during read' };
}
}
finally {
closeSync(descriptor);
}
const after = lstatSync(path, { bigint: true });
if (!after.isFile() || after.isSymbolicLink() || after.nlink !== expectedLinkCount
|| after.dev !== before.dev || after.ino !== before.ino || after.size !== before.size
|| after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
return { status: 'invalid', detail: 'registration intent changed after read' };
}
assertBootstrapIntentFileTrust(options, path, Number(expectedLinkCount));
const record = parseBootstrapRegistrationIntent(raw);
if (!record) {
return { status: 'invalid', detail: 'registration intent payload is invalid', trustedFile };
}
return {
status: 'present',
lease: {
path: registrationPath,
terminalPath,
clearingPath,
currentPath: path,
device: before.dev,
inode: before.ino,
size: before.size,
mtimeNs: before.mtimeNs,
ctimeNs: before.ctimeNs,
record,
},
};
}
catch (error) {
return {
status: 'invalid',
detail: boundedBootstrapDetail(`registration intent is unreadable: ${error instanceof Error ? error.message : String(error)}`),
};
}
}
function sameBootstrapRegistrationIntentOwner(left, right) {
return left.createdAt === right.createdAt
&& left.owner.pid === right.owner.pid
&& left.owner.token === right.owner.token
&& util.sameFileLockProcessStartIdentity(left.owner.processStartIdentity, right.owner.processStartIdentity);
}
function bootstrapRegistrationIntentIdentity(lease, linkCount) {
return {
path: lease.currentPath,
device: lease.device,
inode: lease.inode,
linkCount,
size: lease.size,
mtimeNs: lease.mtimeNs,
ctimeNs: lease.ctimeNs,
};
}
function sameBootstrapRegistrationIntentFileIdentity(left, right) {
return left.path === right.path
&& left.device === right.device
&& left.inode === right.inode
&& left.linkCount === right.linkCount
&& left.size === right.size
&& left.mtimeNs === right.mtimeNs
&& left.ctimeNs === right.ctimeNs;
}
const BOOTSTRAP_REGISTRATION_PUBLISHER_RE = /^bootstrap-registration\.intent\.publisher\.([1-9][0-9]{0,15})\.([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.([0-9a-f]{64})$/;
function readBootstrapRegistrationPublisherClaim(options, stateDir) {
let names;
try {
names = readdirSync(stateDir).filter((name) => name.startsWith(BOOTSTRAP_REGISTRATION_INTENT_PUBLISHER_PREFIX));
}
catch (error) {
if (isErrno(error, 'ENOENT'))
return { status: 'absent' };
return {
status: 'invalid',
detail: boundedBootstrapDetail(`registration publisher claim directory is unreadable: ${error instanceof Error ? error.message : String(error)}`),
};
}
if (names.length === 0)
return { status: 'absent' };
if (names.length !== 1) {
return { status: 'invalid', detail: 'registration publisher claim count is invalid' };
}
const name = names[0];
const match = BOOTSTRAP_REGISTRATION_PUBLISHER_RE.exec(name);
if (!match)
return { status: 'invalid', detail: 'registration publisher claim name is invalid' };
const pid = Number(match[1]);
if (!Number.isSafeInteger(pid) || pid <= 0) {
return { status: 'invalid', detail: 'registration publisher claim pid is invalid' };
}
const path = join(stateDir, name);
try {
assertBootstrapIntentFileTrust(options, path);
const before = lstatSync(path, { bigint: true });
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1n
|| before.size !== 0n || before.dev <= 0n || before.ino <= 0n) {
return { status: 'invalid', detail: 'registration publisher claim is unsafe' };
}
let descriptor;
try {
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
const opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile() || opened.nlink !== 1n || opened.size !== 0n
|| opened.dev !== before.dev || opened.ino !== before.ino
|| opened.mtimeNs !== before.mtimeNs || opened.ctimeNs !== before.ctimeNs) {
return { status: 'invalid', detail: 'registration publisher claim changed before read' };
}
}
finally {
if (descriptor !== undefined)
closeSync(descriptor);
}
const after = lstatSync(path, { bigint: true });
if (!after.isFile() || after.isSymbolicLink() || after.nlink !== 1n || after.size !== 0n
|| after.dev !== before.dev || after.ino !== before.ino
|| after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs) {
return { status: 'invalid', detail: 'registration publisher claim changed after read' };
}
assertBootstrapIntentFileTrust(options, path);
return {
status: 'present',
claim: {
path,
pid,
token: match[2],
processIdentityDigest: match[3],
identity: {
path,
device: before.dev,
inode: before.ino,
linkCount: 1n,
size: 0n,
mtimeNs: before.mtimeNs,
ctimeNs: before.ctimeNs,
},
},
};
}
catch (error) {
return {
status: 'invalid',
detail: boundedBootstrapDetail(`registration publisher claim is unreadable: ${error instanceof Error ? error.message : String(error)}`),
};
}
}
function bootstrapRegistrationPublisherOwnsIntent(publisher, record) {
return record.owner.pid === publisher.pid
&& record.owner.token === publisher.token
&& bootstrapRegistrationProcessIdentityDigest(record.owner.processStartIdentity)
=== publisher.processIdentityDigest;
}
function readBootstrapRegistrationIntent(options) {
const stateDir = resolveBootstrapStateDir(options.env);
const path = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_FILE);
const publishingPath = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_PUBLISHING_FILE);
const terminalPath = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_TERMINAL_FILE);
const clearingPath = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_CLEARING_FILE);
try {
const pathPresent = bootstrapStateEntryPresent(path);
const publishingPresent = bootstrapStateEntryPresent(publishingPath);
const terminalPresent = bootstrapStateEntryPresent(terminalPath);
const clearingPresent = bootstrapStateEntryPresent(clearingPath);
const publisherEntryPresent = bootstrapRegistrationPublisherEntryPresent(stateDir);
if (!pathPresent && !publishingPresent && !terminalPresent && !clearingPresent
&& !publisherEntryPresent) {
return { status: 'absent' };
}
assertBootstrapIntentDirectoryTrust(options, stateDir, true);
const publisher = readBootstrapRegistrationPublisherClaim(options, stateDir);
if (publisher.status === 'invalid') {
return { status: 'invalid', detail: publisher.detail };
}
if (publishingPresent || publisher.status === 'present') {
if (terminalPresent || clearingPresent) {
return {
status: 'invalid',
detail: 'registration intent publication conflicts with a terminal transition',
};
}
if (publisher.status !== 'present') {
return {
status: 'invalid',
detail: 'registration intent publication has no authenticated publisher claim',
};
}
if (pathPresent && publishingPresent) {
const registration = readBootstrapRegistrationIntentFile(options, path, path, terminalPath, clearingPath, 2n);
const publishing = readBootstrapRegistrationIntentFile(options, publishingPath, path, terminalPath, clearingPath, 2n);
if (registration.status !== 'present'
|| publishing.status !== 'present'
|| registration.lease.record.state !== 'registering'
|| publishing.lease.record.state !== 'registering'
|| !bootstrapRegistrationPublisherOwnsIntent(publisher.claim, registration.lease.record)
|| !bootstrapRegistrationPublisherOwnsIntent(publisher.claim, publishing.lease.record)
|| !sameBootstrapRegistrationIntentOwner(registration.lease.record, publishing.lease.record)) {
return {
status: 'invalid',
detail: 'registration intent publication identity is invalid',
};
}
const registrationIdentity = bootstrapRegistrationIntentIdentity(registration.lease, 2n);
const publishingIdentity = bootstrapRegistrationIntentIdentity(publishing.lease, 2n);
if (registrationIdentity.device !== publishingIdentity.device
|| registrationIdentity.inode !== publishingIdentity.inode
|| registrationIdentity.size !== publishingIdentity.size
|| registrationIdentity.mtimeNs !== publishingIdentity.mtimeNs
|| registrationIdentity.ctimeNs !== publishingIdentity.ctimeNs) {
return {
status: 'invalid',
detail: 'registration intent publication does not own the canonical claim',
};
}
return {
status: 'incomplete_publication',
publication: {
publisher: publisher.claim,
publishing: publishingIdentity,
registration: {
lease: registration.lease,
identity: registrationIdentity,
},
},
};
}
if (pathPresent) {
const registration = readBootstrapRegistrationIntentFile(options, path, path, terminalPath, clearingPath);
if (registration.status !== 'present'
|| registration.lease.record.state !== 'registering'
|| !bootstrapRegistrationPublisherOwnsIntent(publisher.claim, registration.lease.record)) {
return {
status: 'invalid',
detail: 'registration intent canonical publisher claim is invalid',
};
}
return {
status: 'incomplete_publication',
publication: {
publisher: publisher.claim,
registration: {
lease: registration.lease,
identity: bootstrapRegistrationIntentIdentity(registration.lease, 1n),
},
},
};
}
if (publishingPresent) {
const publishing = readBootstrapRegistrationIntentFile(options, publishingPath, path, terminalPath, clearingPath);
if (publishing.status === 'present') {
if (publishing.lease.record.state !== 'registering'
|| !bootstrapRegistrationPublisherOwnsIntent(publisher.claim, publishing.lease.record)) {
return {
status: 'invalid',
detail: 'registration intent publication payload is invalid',
};
}
return {
status: 'incomplete_publication',
publication: {
publisher: publisher.claim,
publishing: bootstrapRegistrationIntentIdentity(publishing.lease, 1n),
},
};
}
if (publishing.detail === 'registration intent payload is invalid'
&& publishing.trustedFile !== undefined) {
return {
status: 'incomplete_publication',
publication: {
publisher: publisher.claim,
publishing: publishing.trustedFile,
},
};
}
return { status: 'invalid', detail: publishing.detail };
}
return {
status: 'incomplete_publication',
publication: { publisher: publisher.claim },
};
}
if (clearingPresent && (pathPresent || terminalPresent)) {
return { status: 'invalid', detail: 'registration intent clearing conflicts with another state' };
}
if (clearingPresent) {
const clearing = readBootstrapRegistrationIntentFile(options, clearingPath, path, terminalPath, clearingPath);
if (clearing.status !== 'present')
return { status: 'invalid', detail: clearing.detail };
if (clearing.lease.record.state !== 'terminal') {
return { status: 'invalid', detail: 'registration clearing receipt is invalid' };
}
return { status: 'present', lease: clearing.lease };
}
const registration = pathPresent
? readBootstrapRegistrationIntentFile(options, path, path, terminalPath, clearingPath)
: undefined;
const terminal = terminalPresent
? readBootstrapRegistrationIntentFile(options, terminalPath, path, terminalPath, clearingPath)
: undefined;
if (registration && terminal) {
if (registration.status !== 'present' || registration.lease.record.state !== 'registering') {
return { status: 'invalid', detail: 'registration intent source is invalid during terminal publication' };
}
if (terminal.status === 'invalid') {
if (!terminal.trustedFile)
return { status: 'invalid', detail: terminal.detail };
return {
status: 'incomplete_terminal',
registering: registration.lease,
terminalFile: terminal.trustedFile,
};
}
if (terminal.lease.record.state !== 'terminal'
|| !sameBootstrapRegistrationIntentOwner(registration.lease.record, terminal.lease.record)) {
return { status: 'invalid', detail: 'registration terminal receipt has a different owner' };
}
return {
status: 'present',
lease: {
...terminal.lease,
staleRegistering: {
path,
device: registration.lease.device,
inode: registration.lease.inode,
size: registration.lease.size,
mtimeNs: registration.lease.mtimeNs,
ctimeNs: registration.lease.ctimeNs,
},
},
};
}
if (registration) {
if (registration.status !== 'present')
return { status: 'invalid', detail: registration.detail };
if (registration.lease.record.state !== 'registering') {
return { status: 'invalid', detail: 'registration intent payload is invalid' };
}
return { status: 'present', lease: registration.lease };
}
if (!terminal)
return { status: 'invalid', detail: 'registration terminal receipt is absent' };
if (terminal.status !== 'present')
return { status: 'invalid', detail: terminal.detail };
if (terminal.lease.record.state !== 'terminal') {
return { status: 'invalid', detail: 'registration terminal receipt is invalid' };
}
return { status: 'present', lease: terminal.lease };
}
catch (error) {
return {
status: 'invalid',
detail: boundedBootstrapDetail(`registration intent is unreadable: ${error instanceof Error ? error.message : String(error)}`),
};
}
}
function writeBootstrapRegistrationIntentBytes(descriptor, content, start, end) {
let offset = start;
while (offset < end) {
const written = writeSync(descriptor, content, offset, end - offset, null);
if (written <= 0)
throw new Error('bootstrap registration intent write made no progress');
offset += written;
}
}
function createBootstrapRegistrationPublisherClaim(options, stateDir, owner) {
const processIdentityDigest = bootstrapRegistrationProcessIdentityDigest(owner.processStartIdentity);
const path = join(stateDir, `${BOOTSTRAP_REGISTRATION_INTENT_PUBLISHER_PREFIX}${owner.pid}.${owner.token}.${processIdentityDigest}`);
let descriptor;
let opened;
try {
descriptor = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
fsyncSync(descriptor);
opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile() || opened.nlink !== 1n || opened.size !== 0n
|| opened.dev <= 0n || opened.ino <= 0n
|| (process.platform !== 'win32' && (opened.mode & 63n) !== 0n)) {
throw new Error('unsafe bootstrap registration publisher claim');
}
}
finally {
if (descriptor !== undefined)
closeSync(descriptor);
}
if (!opened)
throw new Error('bootstrap registration publisher claim failed');
syncBootstrapDirectory(stateDir);
assertBootstrapIntentFileTrust(options, path);
const settled = lstatSync(path, { bigint: true });
if (!settled.isFile() || settled.isSymbolicLink() || settled.nlink !== 1n
|| settled.size !== 0n || settled.dev !== opened.dev || settled.ino !== opened.ino
|| settled.mtimeNs !== opened.mtimeNs || settled.ctimeNs !== opened.ctimeNs) {
throw new Error('bootstrap registration publisher claim is unconfirmed');
}
return {
path,
pid: owner.pid,
token: owner.token,
processIdentityDigest,
identity: {
path,
device: settled.dev,
inode: settled.ino,
linkCount: 1n,
size: 0n,
mtimeNs: settled.mtimeNs,
ctimeNs: settled.ctimeNs,
},
};
}
function retireBootstrapRegistrationPublisherClaim(options, publisher) {
const stateDir = dirname(publisher.path);
assertBootstrapRegistrationIntentFileIdentity(options, publisher.identity);
rmSync(publisher.path);
syncBootstrapDirectory(stateDir);
const remaining = readBootstrapRegistrationPublisherClaim(options, stateDir);
if (remaining.status !== 'absent') {
throw new Error('bootstrap registration publisher claim retirement is unconfirmed');
}
}
function publishBootstrapRegistrationIntent(options) {
const stateDir = resolveBootstrapStateDir(options.env);
assertBootstrapIntentDirectoryTrust(options, stateDir, false);
mkdirSync(stateDir, { recursive: true, mode: 0o700 });
assertBootstrapIntentDirectoryTrust(options, stateDir, true);
const path = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_FILE);
const publishingPath = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_PUBLISHING_FILE);
const terminalPath = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_TERMINAL_FILE);
const clearingPath = join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_CLEARING_FILE);
options.beforeIntentPublish?.();
const publicationLock = acquireBootstrapRegistrationRecoveryLock(options);
try {
for (const unavailablePath of [path, publishingPath, terminalPath, clearingPath]) {
try {
lstatSync(unavailablePath);
throw new Error('bootstrap registration intent transition is incomplete');
}
catch (error) {
if (!isErrno(error, 'ENOENT'))
throw error;
}
}
if (readBootstrapRegistrationPublisherClaim(options, stateDir).status !== 'absent') {
throw new Error('bootstrap registration publisher claim already exists');
}
const token = randomUUID();
const createdAtMs = options.now ?? Date.now();
if (!Number.isSafeInteger(createdAtMs) || createdAtMs < 0) {
throw new Error('invalid bootstrap registration intent clock');
}
const processStartIdentity = readBootstrapRegistrationProcessStartIdentity(options, process.pid);
if (processStartIdentity === null) {
throw new Error('bootstrap registration parent process identity is unavailable');
}
const record = {
schema: BOOTSTRAP_REGISTRATION_INTENT_SCHEMA,
state: 'registering',
owner: { pid: process.pid, token, processStartIdentity },
createdAt: new Date(createdAtMs).toISOString(),
};
const content = Buffer.from(`${JSON.stringify(record)}\n`, 'utf8');
if (content.byteLength > MAX_BOOTSTRAP_REGISTRATION_INTENT_BYTES) {
throw new Error('bootstrap registration intent publication is oversized');
}
const publisher = createBootstrapRegistrationPublisherClaim(options, stateDir, record.owner);
let descriptor;
let opened;
try {
descriptor = openSync(publishingPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
options.afterIntentPublicationStep?.('create', publishingPath);
const split = Math.max(1, Math.floor(content.byteLength / 2));
writeBootstrapRegistrationIntentBytes(descriptor, content, 0, split);
options.afterIntentPublicationStep?.('partial_write', publishingPath);
writeBootstrapRegistrationIntentBytes(descriptor, content, split, content.byteLength);
fsyncSync(descriptor);
options.afterIntentPublicationStep?.('file_fsync', publishingPath);
opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile() || opened.nlink !== 1n || opened.dev <= 0n || opened.ino <= 0n
|| opened.size !== BigInt(content.byteLength)
|| (process.platform !== 'win32' && (opened.mode & 63n) !== 0n)) {
throw new Error('unsafe bootstrap registration intent publication');
}
}
finally {
if (descriptor !== undefined)
closeSync(descriptor);
}
if (!opened)
throw new Error('bootstrap registration intent publication failed');
assertBootstrapIntentFileTrust(options, publishingPath);
const prepared = lstatSync(publishingPath, { bigint: true });
if (!prepared.isFile() || prepared.isSymbolicLink() || prepared.nlink !== 1n
|| prepared.dev !== opened.dev || prepared.ino !== opened.ino
|| prepared.size !== opened.size || prepared.mtimeNs !== opened.mtimeNs
|| prepared.ctimeNs !== opened.ctimeNs) {
throw new Error('bootstrap registration intent temp changed before publication');
}
linkSync(publishingPath, path);
options.afterIntentPublicationStep?.('link', path);
syncBootstrapDirectory(stateDir);
options.afterIntentPublicationStep?.('directory_fsync', stateDir);
assertBootstrapIntentFileTrust(options, publishingPath, 2);
assertBootstrapIntentFileTrust(options, path, 2);
const linkedTemp = lstatSync(publishingPath, { bigint: true });
const linkedCanonical = lstatSync(path, { bigint: true });
if (!linkedTemp.isFile() || linkedTemp.isSymbolicLink() || linkedTemp.nlink !== 2n
|| !linkedCanonical.isFile() || linkedCanonical.isSymbolicLink()
|| linkedCanonical.nlink !== 2n
|| linkedTemp.dev !== opened.dev || linkedTemp.ino !== opened.ino
|| linkedCanonical.dev !== opened.dev || linkedCanonical.ino !== opened.ino
|| linkedTemp.size !== opened.size || linkedCanonical.size !== opened.size
|| linkedTemp.mtimeNs !== opened.mtimeNs || linkedCanonical.mtimeNs !== opened.mtimeNs) {
throw new Error('bootstrap registration intent canonical claim is unconfirmed');
}
rmSync(publishingPath);
syncBootstrapDirectory(stateDir);
if (bootstrapStateEntryPresent(publishingPath)) {
throw new Error('bootstrap registration intent temp retirement is unconfirmed');
}
assertBootstrapIntentFileTrust(options, path);
const published = lstatSync(path, { bigint: true });
if (!published.isFile() || published.isSymbolicLink() || published.nlink !== 1n
|| published.dev !== opened.dev || published.ino !== opened.ino
|| published.size !== opened.size || published.mtimeNs !== opened.mtimeNs) {
throw new Error('bootstrap registration intent changed during publication');
}
retireBootstrapRegistrationPublisherClaim(options, publisher);
return {
path,
terminalPath,
clearingPath,
currentPath: path,
device: published.dev,
inode: published.ino,
size: published.size,
mtimeNs: published.mtimeNs,
ctimeNs: published.ctimeNs,
record,
};
}
finally {
publicationLock.release();
}
}
function terminalizeBootstrapRegistrationIntent(options, lease, outcome, transactionId) {
if (outcome === 'committed' && (!transactionId || !BOOTSTRAP_UUID_RE.test(transactionId))) {
throw new Error('bootstrap registration intent commit identity is invalid');
}
if (outcome !== 'committed' && transactionId !== undefined) {
throw new Error('bootstrap registration intent terminal identity is unexpected');
}
if (lease.record.state !== 'registering' || lease.currentPath !== lease.path) {
throw new Error('bootstrap registration intent is not an active registration');
}
assertBootstrapIntentDirectoryTrust(options, dirname(lease.currentPath), true);
assertBootstrapIntentFileTrust(options, lease.currentPath);
const current = lstatSync(lease.currentPath, { bigint: true });
if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1n
|| current.dev !== lease.device || current.ino !== lease.inode || current.size !== lease.size
|| current.mtimeNs !== lease.mtimeNs || current.ctimeNs !== lease.ctimeNs) {
throw new Error('bootstrap registration intent ownership changed');
}
const terminalAtMs = options.now ?? Date.now();
if (!Number.isSafeInteger(terminalAtMs) || terminalAtMs < 0) {
throw new Error('invalid bootstrap registration intent terminal clock');
}
const record = {
schema: BOOTSTRAP_REGISTRATION_INTENT_SCHEMA,
state: 'terminal',
owner: lease.record.owner,
createdAt: lease.record.createdAt,
outcome,
terminalAt: new Date(terminalAtMs).toISOString(),
...(transactionId !== undefined ? { transactionId } : {}),
};
const content = `${JSON.stringify(record)}\n`;
if (Buffer.byteLength(content, 'utf8') > MAX_BOOTSTRAP_REGISTRATION_INTENT_BYTES) {
throw new Error('bootstrap registration intent terminal receipt is oversized');
}
for (const unavailablePath of [lease.terminalPath, lease.clearingPath]) {
try {
lstatSync(unavailablePath);
throw new Error('bootstrap registration intent terminal path exists');
}
catch (error) {
if (!isErrno(error, 'ENOENT'))
throw error;
}
}
let descriptor;
let settled;
try {
descriptor = openSync(lease.terminalPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
writeFileSync(descriptor, content, { encoding: 'utf8' });
options.beforeIntentTerminalFsync?.(lease.terminalPath);
fsyncSync(descriptor);
settled = fstatSync(descriptor, { bigint: true });
if (!settled.isFile() || settled.nlink !== 1n
|| settled.dev <= 0n || settled.ino <= 0n
|| settled.size !== BigInt(Buffer.byteLength(content, 'utf8'))) {
throw new Error('bootstrap registration intent terminalization is unsafe');
}
}
finally {
if (descriptor !== undefined)
closeSync(descriptor);
}
if (!settled)
throw new Error('bootstrap registration intent terminal publication failed');
assertBootstrapIntentFileTrust(options, lease.terminalPath);
const after = lstatSync(lease.terminalPath, { bigint: true });
if (!after.isFile() || after.isSymbolicLink() || after.nlink !== 1n
|| after.dev !== settled.dev || after.ino !== settled.ino
|| after.size !== settled.size || after.mtimeNs !== settled.mtimeNs
|| after.ctimeNs !== settled.ctimeNs) {
throw new Error('bootstrap registration intent changed after terminalization');
}
const stateDir = dirname(lease.currentPath);
syncBootstrapDirectory(stateDir);
options.afterIntentTerminalPublish?.();
const active = lstatSync(lease.path, { bigint: true });
if (!active.isFile() || active.isSymbolicLink() || active.nlink !== 1n
|| active.dev !== lease.device || active.ino !== lease.inode || active.size !== lease.size
|| active.mtimeNs !== lease.mtimeNs || active.ctimeNs !== lease.ctimeNs) {
throw new Error('bootstrap registration intent source changed before retirement');
}
rmSync(lease.path);
syncBootstrapDirectory(stateDir);
if (bootstrapStateEntryPresent(lease.path)) {
throw new Error('bootstrap registration intent source retirement is unconfirmed');
}
return {
...lease,
currentPath: lease.terminalPath,
device: settled.dev,
inode: settled.ino,
size: settled.size,
mtimeNs: settled.mtimeNs,
ctimeNs: settled.ctimeNs,
record,
};
}
function clearBootstrapRegistrationIntent(options, lease) {
if (lease.record.state !== 'terminal') {
throw new Error('bootstrap registration intent is not terminal');
}
const stateDir = dirname(lease.currentPath);
assertBootstrapIntentDirectoryTrust(options, stateDir, true);
if (lease.staleRegistering) {
assertBootstrapIntentFileTrust(options, lease.staleRegistering.path);
const stale = lstatSync(lease.staleRegistering.path, { bigint: true });
if (!stale.isFile() || stale.isSymbolicLink() || stale.nlink !== 1n
|| stale.dev !== lease.staleRegistering.device || stale.ino !== lease.staleRegistering.inode
|| stale.size !== lease.staleRegistering.size
|| stale.mtimeNs !== lease.staleRegistering.mtimeNs
|| stale.ctimeNs !== lease.staleRegistering.ctimeNs) {
throw new Error('bootstrap registration intent source changed before recovery retirement');
}
rmSync(lease.staleRegistering.path);
syncBootstrapDirectory(stateDir);
if (bootstrapStateEntryPresent(lease.staleRegistering.path)) {
throw new Error('bootstrap registration intent source recovery retirement is unconfirmed');
}
}
assertBootstrapIntentFileTrust(options, lease.currentPath);
const current = lstatSync(lease.currentPath, { bigint: true });
if (!current.isFile() || current.isSymbolicLink() || current.nlink !== 1n
|| current.dev !== lease.device || current.ino !== lease.inode || current.size !== lease.size
|| current.mtimeNs !== lease.mtimeNs || current.ctimeNs !== lease.ctimeNs) {
throw new Error('bootstrap registration intent ownership changed');
}
let clearingLease = lease;
if (lease.currentPath === lease.terminalPath) {
try {
lstatSync(lease.clearingPath);
throw new Error('bootstrap registration intent clearing path exists');
}
catch (error) {
if (!isErrno(error, 'ENOENT'))
throw error;
}
renameSync(lease.terminalPath, lease.clearingPath);
syncBootstrapDirectory(stateDir);
const renamed = lstatSync(lease.clearingPath, { bigint: true });
if (!renamed.isFile() || renamed.isSymbolicLink() || renamed.nlink !== 1n
|| renamed.dev !== lease.device || renamed.ino !== lease.inode
|| renamed.size !== lease.size) {
throw new Error('bootstrap registration intent clear rename changed ownership');
}
clearingLease = {
...lease,
currentPath: lease.clearingPath,
size: renamed.size,
mtimeNs: renamed.mtimeNs,
ctimeNs: renamed.ctimeNs,
};
options.afterIntentClearRename?.();
}
else if (lease.currentPath !== lease.clearingPath) {
throw new Error('bootstrap registration intent path is unexpected');
}
assertBootstrapIntentFileTrust(options, clearingLease.currentPath);
const moved = lstatSync(clearingLease.currentPath, { bigint: true });
if (!moved.isFile() || moved.isSymbolicLink() || moved.nlink !== 1n
|| moved.dev !== clearingLease.device || moved.ino !== clearingLease.inode
|| moved.size !== clearingLease.size || moved.mtimeNs !== clearingLease.mtimeNs
|| moved.ctimeNs !== clearingLease.ctimeNs) {
throw new Error('bootstrap registration intent clear changed ownership');
}
rmSync(clearingLease.currentPath);
syncBootstrapDirectory(stateDir);
if (bootstrapStateEntryPresent(lease.path)
|| bootstrapStateEntryPresent(lease.terminalPath)
|| bootstrapStateEntryPresent(lease.clearingPath)) {
throw new Error('bootstrap registration intent clear is unconfirmed');
}
}
function sameBootstrapIncompletePublication(left, right) {
if (left.publisher.pid !== right.publisher.pid
|| left.publisher.token !== right.publisher.token
|| left.publisher.processIdentityDigest !== right.publisher.processIdentityDigest
|| !sameBootstrapRegistrationIntentFileIdentity(left.publisher.identity, right.publisher.identity)) {
return false;
}
if ((left.publishing === undefined) !== (right.publishing === undefined))
return false;
if (left.publishing && right.publishing
&& !sameBootstrapRegistrationIntentFileIdentity(left.publishing, right.publishing)) {
return false;
}
if ((left.registration === undefined) !== (right.registration === undefined))
return false;
if (!left.registration || !right.registration)
return true;
return sameBootstrapRegistrationIntentFileIdentity(left.registration.identity, right.registration.identity) && sameBootstrapRegistrationIntentOwner(left.registration.lease.record, right.registration.lease.record);
}
function assertBootstrapRegistrationIntentFileIdentity(options, expected) {
assertBootstrapIntentFileTrust(options, expected.path, Number(expected.linkCount));
const current = lstatSync(expected.path, { bigint: true });
if (!current.isFile() || current.isSymbolicLink() || current.nlink !== expected.linkCount
|| current.dev !== expected.device || current.ino !== expected.inode
|| current.size !== expected.size || current.mtimeNs !== expected.mtimeNs
|| current.ctimeNs !== expected.ctimeNs) {
throw new Error('incomplete registration publication identity changed');
}
}
function clearBootstrapIncompletePublication(options, publication) {
const stateDir = resolveBootstrapStateDir(options.env);
assertBootstrapIntentDirectoryTrust(options, stateDir, true);
assertBootstrapRegistrationIntentFileIdentity(options, publication.publisher.identity);
if (publication.publishing) {
assertBootstrapRegistrationIntentFileIdentity(options, publication.publishing);
}
if (publication.registration) {
assertBootstrapRegistrationIntentFileIdentity(options, publication.registration.identity);
rmSync(publication.registration.identity.path);
syncBootstrapDirectory(stateDir);
if (bootstrapStateEntryPresent(publication.registration.identity.path)) {
throw new Error('incomplete canonical registration claim retirement is unconfirmed');
}
if (publication.publishing
&& publication.registration.identity.device === publication.publishing.device
&& publication.registration.identity.inode === publication.publishing.inode) {
assertBootstrapIntentFileTrust(options, publication.publishing.path);
const remaining = lstatSync(publication.publishing.path, { bigint: true });
if (!remaining.isFile() || remaining.isSymbolicLink() || remaining.nlink !== 1n
|| remaining.dev !== publication.publishing.device
|| remaining.ino !== publication.publishing.inode
|| remaining.size !== publication.publishing.size
|| remaining.mtimeNs !== publication.publishing.mtimeNs) {
throw new Error('incomplete registration publication changed after claim retirement');
}
}
}
if (publication.publishing) {
if (!publication.registration
|| publication.registration.identity.device !== publication.publishing.device
|| publication.registration.identity.inode !== publication.publishing.inode) {
assertBootstrapRegistrationIntentFileIdentity(options, publication.publishing);
}
rmSync(publication.publishing.path);
syncBootstrapDirectory(stateDir);
if (bootstrapStateEntryPresent(publication.publishing.path)) {
throw new Error('incomplete registration publication retirement is unconfirmed');
}
}
retireBootstrapRegistrationPublisherClaim(options, publication.publisher);
if (bootstrapStateEntryPresent(join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_FILE))
|| bootstrapStateEntryPresent(join(stateDir, BOOTSTRAP_REGISTRATION_INTENT_PUBLISHING_FILE))
|| bootstrapRegistrationPublisherEntryPresent(stateDir)) {
throw new Error('incomplete registration publication retirement is unconfirmed');
}
}
function bootstrapRegistrationOwnerIsStale(status) {
return status === 'dead' || status === 'pid_reused';
}
function recoverBootstrapRegistrationIntent(options) {
const intent = readBootstrapRegistrationIntent(options);
if (intent.status === 'absent' || intent.status === 'invalid') {
return {
status: 'blocked',
detail: intent.status === 'invalid' ? intent.detail : 'registration intent disappeared during recovery',
};
}
try {
if (intent.status === 'incomplete_publication') {
const observedPublisherStatus = bootstrapRegistrationPublisherProcessStatus(options, intent.publication.publisher);
if (!bootstrapRegistrationOwnerIsStale(observedPublisherStatus)) {
return {
status: 'blocked',
detail: `registration publisher owner is ${observedPublisherStatus}`,
};
}
let recoveryLock;
try {
recoveryLock = acquireBootstrapRegistrationRecoveryLock(options);
}
catch {
return {
status: 'blocked',
detail: 'registration publisher or another recovery owns the bootstrap transaction lock',
};
}
try {
const lockedIntent = readBootstrapRegistrationIntent(options);
if (lockedIntent.status !== 'incomplete_publication'
|| !sameBootstrapIncompletePublication(intent.publication, lockedIntent.publication)) {
return {
status: 'blocked',
detail: 'registration publication changed while acquiring recovery ownership',
};
}
const lockedPublisherStatus = bootstrapRegistrationPublisherProcessStatus(options, lockedIntent.publication.publisher);
if (!bootstrapRegistrationOwnerIsStale(lockedPublisherStatus)) {
return {
status: 'blocked',
detail: `registration publisher owner became ${lockedPublisherStatus}`,
};
}
const lockedDurable = inspectLifecycleBootstrapDurableState(options.env, { ...options, expectedRecoveryOwner: recoveryLock.owner });
if (lockedDurable.status !== 'clean'
&& lockedDurable.status !== 'committed'
&& lockedDurable.status !== 'manual_transition') {
return {
status: 'blocked',
detail: lockedDurable.status === 'invalid' || lockedDurable.status === 'pending'
? lockedDurable.detail
: 'incomplete registration publication durable state is unresolved',
};
}
clearBootstrapIncompletePublication(options, lockedIntent.publication);
return lockedDurable.status === 'committed'
? { status: 'committed', transactionId: lockedDurable.transactionId }
: {
status: 'safe_terminal',
outcome: lockedDurable.status === 'manual_transition'
? 'rolled_back'
: 'cancelled',
};
}
finally {
recoveryLock.release();
}
}
if (intent.status === 'present' && intent.lease.record.state === 'registering') {
const observedStatus = bootstrapRegistrationOwnerProcessStatus(options, intent.lease.record.owner);
if (!bootstrapRegistrationOwnerIsStale(observedStatus)) {
return {
status: 'blocked',
detail: `registration intent owner is ${observedStatus}`,
};
}
let recoveryLock;
try {
recoveryLock = acquireBootstrapRegistrationRecoveryLock(options);
}
catch {
return {
status: 'blocked',
detail: 'registration child or another recovery owns the bootstrap transaction lock',
};
}
try {
const lockedIntent = readBootstrapRegistrationIntent(options);
if (lockedIntent.status !== 'present'
|| lockedIntent.lease.record.state !== 'registering'
|| lockedIntent.lease.currentPath !== intent.lease.currentPath
|| lockedIntent.lease.device !== intent.lease.device
|| lockedIntent.lease.inode !== intent.lease.inode
|| lockedIntent.lease.size !== intent.lease.size
|| lockedIntent.lease.mtimeNs !== intent.lease.mtimeNs
|| lockedIntent.lease.ctimeNs !== intent.lease.ctimeNs
|| !sameBootstrapRegistrationIntentOwner(lockedIntent.lease.record, intent.lease.record)) {
return {
status: 'blocked',
detail: 'registration intent changed while acquiring recovery ownership',
};
}
const lockedOwnerStatus = bootstrapRegistrationOwnerProcessStatus(options, lockedIntent.lease.record.owner);
if (!bootstrapRegistrationOwnerIsStale(lockedOwnerStatus)) {
return {
status: 'blocked',
detail: `registration intent owner became ${lockedOwnerStatus}`,
};
}
const lockedDurable = inspectLifecycleBootstrapDurableState(options.env, { ...options, expectedRecoveryOwner: recoveryLock.owner });
if (lockedDurable.status !== 'clean'
&& lockedDurable.status !== 'committed'
&& lockedDurable.status !== 'manual_transition') {
return {
status: 'blocked',
detail: lockedDurable.status === 'invalid' || lockedDurable.status === 'pending'
? lockedDurable.detail
: 'registration intent durable state is unresolved',
};
}
const terminal = terminalizeBootstrapRegistrationIntent(options, lockedIntent.lease, lockedDurable.status === 'committed'
? 'committed'
: lockedDurable.status === 'manual_transition'
? 'rolled_back'
: 'cancelled', lockedDurable.status === 'committed' ? lockedDurable.transactionId : undefined);
clearBootstrapRegistrationIntent(options, terminal);
return lockedDurable.status === 'committed'
? { status: 'committed', transactionId: lockedDurable.transactionId }
: {
status: 'safe_terminal',
outcome: lockedDurable.status === 'manual_transition'
? 'rolled_back'
: 'cancelled',
};
}
finally {
recoveryLock.release();
}
}
const durable = inspectLifecycleBootstrapDurableState(options.env, options);
let lease;
if (intent.status === 'incomplete_terminal') {
if (durable.status !== 'clean'
&& durable.status !== 'committed'
&& durable.status !== 'manual_transition') {
return {
status: 'blocked',
detail: durable.status === 'invalid' || durable.status === 'pending'
? durable.detail
: 'incomplete terminal receipt has no safe durable proof',
};
}
assertBootstrapIntentFileTrust(options, intent.terminalFile.path);
const partial = lstatSync(intent.terminalFile.path, { bigint: true });
if (!partial.isFile() || partial.isSymbolicLink() || partial.nlink !== 1n
|| partial.dev !== intent.terminalFile.device || partial.ino !== intent.terminalFile.inode) {
throw new Error('incomplete terminal receipt ownership changed');
}
rmSync(intent.terminalFile.path);
syncBootstrapDirectory(dirname(intent.terminalFile.path));
if (bootstrapStateEntryPresent(intent.terminalFile.path)) {
throw new Error('incomplete terminal receipt retirement is unconfirmed');
}
lease = terminalizeBootstrapRegistrationIntent(options, intent.registering, durable.status === 'committed'
? 'committed'
: durable.status === 'manual_transition'
? 'rolled_back'
: 'no_child', durable.status === 'committed' ? durable.transactionId : undefined);
}
else {
lease = intent.lease;
}
if (lease.record.state === 'registering') {
if (durable.status === 'manual_transition') {
lease = terminalizeBootstrapRegistrationIntent(options, lease, 'rolled_back');
}
else if (durable.status !== 'committed') {
const detail = durable.status === 'clean'
? 'registration intent has no durable child completion proof'
: durable.detail;
return { status: 'blocked', detail };
}
else {
lease = terminalizeBootstrapRegistrationIntent(options, lease, 'committed', durable.transactionId);
}
}
const terminal = lease.record;
if (terminal.state !== 'terminal') {
return { status: 'blocked', detail: 'registration intent did not reach a terminal state' };
}
if (terminal.outcome === 'committed') {
if (durable.status !== 'committed'
|| durable.transactionId !== terminal.transactionId) {
return { status: 'blocked', detail: 'registration intent commit receipt conflicts with durable state' };
}
clearBootstrapRegistrationIntent(options, lease);
return { status: 'committed', transactionId: durable.transactionId };
}
const safeRolledBackTransition = terminal.outcome === 'rolled_back'
&& durable.status === 'manual_transition';
if (durable.status !== 'clean' && !safeRolledBackTransition) {
return {
status: 'blocked',
detail: durable.status === 'invalid' || durable.status === 'pending'
? durable.detail
: 'registration intent terminal receipt conflicts with committed state',
};
}
const outcome = terminal.outcome;
clearBootstrapRegistrationIntent(options, lease);
return { status: 'safe_terminal', outcome };
}
catch (error) {
return {
status: 'blocked',
detail: boundedBootstrapDetail(`registration intent recovery failed: ${error instanceof Error ? error.message : String(error)}`),
};
}
}
async function runBootstrapWithRegistrationIntent(options, prepareRun) {
let lease;
try {
lease = publishBootstrapRegistrationIntent(options);
}
catch {
return {
ok: false,
reason: 'blocked',
detail: 'bootstrap registration intent publication is unavailable',
requiresForegroundExit: true,
};
}
let runOptions = options;
let outcome;
let parentCurrentAtFinalClock = false;
if (prepareRun) {
try {
runOptions = {
...options,
...await prepareRun(() => {
assertBootstrapRegistrationOwnerCurrent(options, lease.record.owner);
parentCurrentAtFinalClock = true;
}),
};
if (!parentCurrentAtFinalClock) {
throw new Error('bootstrap registration preparation omitted parent identity revalidation');
}
}
catch (error) {
// The preparation hook runs before child creation, so a strict terminal no-child receipt
// can retire this bootstrap intent while the migration transaction handles its own state.
outcome = {
ok: false,
reason: 'failed',
detail: boundedBootstrapDetail(error instanceof Error ? error.message : String(error)),
};
}
}
if (!outcome) {
try {
// Re-read the parent process identity after every asynchronous sealed check, then bind the
// exact unique intent token into only the child environment. runBootstrap reaches spawn
// synchronously, so no await or mutation window exists between this check and child creation.
if (!parentCurrentAtFinalClock) {
assertBootstrapRegistrationOwnerCurrent(runOptions, lease.record.owner);
}
runOptions = {
...runOptions,
env: {
...runOptions.env,
[coreBootstrap.LIFECYCLE_BOOTSTRAP_REGISTRATION_TOKEN_ENV]: lease.record.owner.token,
},
};
}
catch (error) {
outcome = {
ok: false,
reason: 'failed',
detail: boundedBootstrapDetail(error instanceof Error ? error.message : String(error)),
};
}
}
if (!outcome) {
try {
outcome = await runBootstrap(runOptions);
}
catch {
// The no-replace intent remains authoritative until an operator or durable recovery clears it.
return {
ok: false,
reason: 'ambiguous',
detail: 'bootstrap registration runner failed unexpectedly',
requiresForegroundExit: true,
};
}
}
if (outcome.requiresForegroundExit === true)
return outcome;
try {
const durable = inspectLifecycleBootstrapDurableState(runOptions.env, runOptions);
if (outcome.ok) {
if (durable.status !== 'committed') {
throw new Error('bootstrap success lacks a durable committed transaction');
}
lease = terminalizeBootstrapRegistrationIntent(runOptions, lease, 'committed', durable.transactionId);
}
else if (outcome.reason === 'rolled_back') {
if (durable.status !== 'clean' && durable.status !== 'manual_transition') {
throw new Error('bootstrap rollback did not restore a clean durable state');
}
lease = terminalizeBootstrapRegistrationIntent(runOptions, lease, 'rolled_back');
}
else if (['failed', 'cli_not_found'].includes(outcome.reason)) {
if (durable.status !== 'clean') {
throw new Error('bootstrap no-child failure left durable state');
}
lease = terminalizeBootstrapRegistrationIntent(runOptions, lease, 'no_child');
}
else {
throw new Error('bootstrap result does not prove a safe terminal state');
}
clearBootstrapRegistrationIntent(runOptions, lease);
}
catch {
return {
ok: false,
reason: 'blocked',
detail: 'bootstrap registration intent clear is unconfirmed',
requiresForegroundExit: true,
};
}
return outcome;
}
function defaultMigrationRegistrationRunner(options) {
const clock = options.migration?.clock ?? Date.now;
const failed = (detail) => ({
ok: false,
reason: 'failed',
detail: boundedBootstrapDetail(detail),
});
const readClock = () => {
try {
const value = clock();
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
}
catch {
return undefined;
}
};
return async (request) => {
if (request.execPath !== request.targetIdentity.path
|| request.env['EVOLVER_SELF_UPDATE_TARGET_PATH'] !== request.execPath
|| !Number.isSafeInteger(request.startedAtMs)
|| request.startedAtMs < 0
|| !Number.isSafeInteger(request.transactionBudgetMs)
|| request.transactionBudgetMs <= 0
|| !Number.isSafeInteger(request.timeoutMs)
|| request.timeoutMs > MAX_TIMER_DELAY_MS
|| !Number.isSafeInteger(request.transactionDeadlineMs)
|| !Number.isSafeInteger(request.parentDeadlineMs)
|| request.transactionDeadlineMs - request.startedAtMs !== request.transactionBudgetMs
|| request.parentDeadlineMs - request.startedAtMs !== request.timeoutMs
|| request.timeoutMs <= request.transactionBudgetMs) {
return failed('invalid migration registration binding');
}
return runBootstrapWithRegistrationIntent({
env: request.env,
platform: request.platform,
execPath: request.execPath,
...(request.exists !== undefined ? { exists: request.exists } : {}),
...(request.readFile !== undefined ? { readFile: request.readFile } : {}),
...(request.writeFile !== undefined ? { writeFile: request.writeFile } : {}),
...(request.spawnFn !== undefined ? { spawnFn: request.spawnFn } : {}),
...(options.treeKillSpawnFn !== undefined ? { treeKillSpawnFn: options.treeKillSpawnFn } : {}),
...(options.processKill !== undefined ? { processKill: options.processKill } : {}),
...(options.terminationGraceMs !== undefined ? { terminationGraceMs: options.terminationGraceMs } : {}),
...(options.beforeIntentPublish !== undefined ? { beforeIntentPublish: options.beforeIntentPublish } : {}),
...(options.afterIntentPublicationStep !== undefined
? { afterIntentPublicationStep: options.afterIntentPublicationStep }
: {}),
...(options.afterIntentTerminalPublish !== undefined
? { afterIntentTerminalPublish: options.afterIntentTerminalPublish }
: {}),
...(options.beforeIntentTerminalFsync !== undefined
? { beforeIntentTerminalFsync: options.beforeIntentTerminalFsync }
: {}),
...(options.afterIntentClearRename !== undefined
? { afterIntentClearRename: options.afterIntentClearRename }
: {}),
...(options.assertIntentDirectoryTrust !== undefined
? { assertIntentDirectoryTrust: options.assertIntentDirectoryTrust }
: {}),
...(options.assertIntentFileTrust !== undefined
? { assertIntentFileTrust: options.assertIntentFileTrust }
: {}),
...(options.readRegistrationProcessStartIdentity !== undefined
? { readRegistrationProcessStartIdentity: options.readRegistrationProcessStartIdentity }
: {}),
...(options.registrationOwnerProcessStatus !== undefined
? { registrationOwnerProcessStatus: options.registrationOwnerProcessStatus }
: {}),
...(options.registrationPublisherProcessStatus !== undefined
? { registrationPublisherProcessStatus: options.registrationPublisherProcessStatus }
: {}),
}, async (assertParentCurrent) => {
// The bootstrap intent is durable before these sealed checks. No await occurs between
// successful target revalidation, remaining-budget calculation, and runBootstrap's spawn.
const beforeRevalidation = readClock();
if (beforeRevalidation === undefined
|| beforeRevalidation < request.startedAtMs
|| beforeRevalidation >= request.transactionDeadlineMs
|| beforeRevalidation >= request.parentDeadlineMs) {
throw new Error('migration registration deadline expired before target revalidation');
}
try {
await request.assertRegistrationIntentCurrent();
await request.revalidateTarget();
}
catch {
throw new Error('migration registration intent or target revalidation failed');
}
// This native observation can block. Run it before the final absolute-deadline clock read
// so its elapsed time is subtracted instead of silently extending either remaining budget.
assertParentCurrent();
const current = readClock();
if (current === undefined || current < beforeRevalidation) {
throw new Error('migration registration clock regressed');
}
const transactionBudgetMs = request.transactionDeadlineMs - current;
const timeoutMs = request.parentDeadlineMs - current;
if (!Number.isSafeInteger(transactionBudgetMs)
|| !Number.isSafeInteger(timeoutMs)
|| transactionBudgetMs <= 0
|| timeoutMs <= transactionBudgetMs
|| timeoutMs > MAX_TIMER_DELAY_MS) {
throw new Error('migration registration deadline expired before child creation');
}
return { now: current, transactionBudgetMs, timeoutMs };
});
};
}
/**
* Orchestrate bootstrap for a degraded (default-auto, unsupervised) startup: decide, attempt,
* record, and produce the operator message. Never throws.
*/
export async function bootstrapDegradedSelfUpdateStartup(env, platform = process.platform, options = {}) {
const bootstrapEnv = {
...env,
EVOLVER_LIFECYCLE_STATE_DIR: resolveBootstrapStateDir(env),
};
const migrateUnbindableInstall = async (reason) => {
if (reason === 'bootstrap_attempt_pending') {
const durable = inspectLifecycleBootstrapDurableState(bootstrapEnv, options);
if (durable.status === 'manual_transition') {
return {
disposition: 'continue',
handedOver: false,
message: '[evolver-proxy] self-update: automatic bootstrap remains disabled by a durable '
+ 'manual service transition; continuing degraded without downloading, replacing, or '
+ 'registering a standalone binary. Run evolver lifecycle install-service --target='
+ durable.target
+ ' explicitly to resume.',
};
}
if (durable.status === 'pending' && durable.manualTransition) {
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: manual service transition recovery is incomplete; '
+ 'exiting without downloading, replacing, or registering a standalone binary. '
+ `(${boundedBootstrapDetail(durable.detail)}) Run evolver lifecycle remove-service --target=`
+ durable.manualTransition.target
+ ' to finish the interrupted removal before explicitly reinstalling the service.',
};
}
if (durable.status === 'pending'
&& Object.values(durable.recoveryLocks ?? {})
.some((status) => status === 'active_or_unverifiable')) {
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: lifecycle recovery lock ownership is active or '
+ 'unverifiable; exiting before migration download, target mutation, or child creation. ('
+ boundedBootstrapDetail(durable.detail)
+ ')',
};
}
if (durable.status === 'invalid') {
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: lifecycle recovery state is invalid; exiting without '
+ 'migration, target mutation, or child creation. ('
+ boundedBootstrapDetail(durable.detail)
+ ')',
};
}
}
// The npm/JS install shape cannot safely run ordinary lifecycle bootstrap because it would
// bind supervision back to an unreplaceable Node launcher. Only the signed standalone
// migration may register recovery, and pending ownership stays fail-closed on every skip.
const migration = await migrateToStandaloneBinary(bootstrapEnv, platform, {
...options.migration,
registrationRunner: options.migration?.registrationRunner
?? defaultMigrationRegistrationRunner(options),
...(options.execPath !== undefined ? { execPath: options.execPath } : {}),
...(options.exists !== undefined ? { exists: options.exists } : {}),
...(options.readFile !== undefined ? { readFile: options.readFile } : {}),
...(options.writeFile !== undefined ? { writeFile: options.writeFile } : {}),
...(options.spawnFn !== undefined ? { spawnFn: options.spawnFn } : {}),
...(options.now !== undefined ? { now: options.now } : {}),
});
const recoveringPendingOwnership = reason === 'migration_ambiguous'
|| reason === 'bootstrap_attempt_pending';
if (migration.requiresForegroundExit === true
|| (recoveringPendingOwnership && migration.outcome !== 'migrated')) {
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: standalone migration registration did not prove a clean rollback; '
+ 'exiting the foreground proxy to avoid concurrent IPC ownership. '
+ 'Inspect the lifecycle bootstrap transaction state before retrying.',
};
}
if (migration.outcome === 'migrated') {
return {
disposition: 'handoff',
handedOver: true,
exitCode: 0,
message: migration.message,
};
}
if (migration.outcome === 'skipped' && migration.reason !== 'cooldown') {
recordBootstrapAttempt(bootstrapEnv, { ok: false, reason }, options);
}
return {
disposition: 'continue',
handedOver: false,
message: '[evolver-proxy] self-update: running from the npm/JS install shape, which has no standalone '
+ 'binary target for self-update; bootstrap skipped, continuing with self-update off. '
+ 'Install the standalone binary from GitHub Releases and start it to enable self-update. ('
+ migration.message
+ ')',
};
};
const decision = shouldBootstrap(bootstrapEnv, platform, options);
if (!decision.proceed) {
const reason = decision.reason ?? 'skipped';
if (reason === 'bootstrap_attempt_invalid') {
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: bootstrap attempt recovery state is invalid; '
+ 'exiting without replacing it because prior process ownership cannot be classified safely. '
+ 'Inspect the lifecycle state before retrying.',
};
}
if (reason === 'bootstrap_intent_pending') {
const recovery = recoverBootstrapRegistrationIntent({
env: bootstrapEnv,
platform,
...options,
});
if (recovery.status === 'committed') {
try {
resolveSelfUpdateTarget({ env: bootstrapEnv, processExecPath: options.execPath });
return {
disposition: 'handoff',
handedOver: true,
exitCode: 0,
message: '[evolver-proxy] self-update: recovered a committed lifecycle bootstrap registration; '
+ 'handing over to the service manager without creating another child.',
};
}
catch {
// Exact intent recovery retired the old no-replace authority. An npm/JS foreground
// still cannot prove that the committed launcher binds a standalone executable.
return migrateUnbindableInstall('bootstrap_attempt_pending');
}
}
if (recovery.status === 'safe_terminal') {
return {
disposition: 'continue',
handedOver: false,
message: '[evolver-proxy] self-update: recovered and cleared a bootstrap registration '
+ `${recovery.outcome} receipt without creating another child; continuing degraded.`,
};
}
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: prior bootstrap registration ownership remains unresolved; '
+ 'exiting without migration, target mutation, or child creation. '
+ `(${boundedBootstrapDetail(recovery.detail)})`,
};
}
if (reason === 'unsupported_install_shape'
|| reason === 'migration_ambiguous'
|| reason === 'bootstrap_attempt_pending') {
// Do not suggest `evolver lifecycle bootstrap` here: under the npm/JS install shape it
// would register a supervised service that crashes at self-update target resolution on
// every startup (crash-loop). Only a standalone release binary can host self-update —
// so attempt the one-time migration to it; any skip/failure keeps the degraded startup.
return migrateUnbindableInstall(reason);
}
if (reason !== 'recent_failure') {
recordBootstrapAttempt(bootstrapEnv, { ok: false, reason }, options);
}
return {
disposition: 'continue',
handedOver: false,
message: '[evolver-proxy] self-update: default auto requires a durable supervisor attestation; '
+ `running with self-update off (bootstrap skipped: ${reason}). `
+ 'Run `evolver lifecycle bootstrap` or `evolver lifecycle install-service` to enable.',
};
}
const outcome = await runBootstrapWithRegistrationIntent({ env: bootstrapEnv, platform, ...options });
const recoveringPendingOwnership = decision.reason === 'migration_ambiguous'
|| decision.reason === 'bootstrap_attempt_pending';
if (!recoveringPendingOwnership || outcome.ok) {
recordBootstrapAttempt(bootstrapEnv, outcome, options);
}
if (outcome.ok) {
const lockReleaseWarning = outcome.reason === 'bootstrapped_lock_release_unconfirmed'
? ' The lifecycle commit is durable, but lock release could not be confirmed'
+ ` (${boundedBootstrapDetail(outcome.detail ?? 'bootstrap lifecycle lock release is unconfirmed')}).`
+ ' Inspect the lifecycle state before the next lifecycle mutation.'
: '';
return {
disposition: 'handoff',
handedOver: true,
exitCode: 0,
message: '[evolver-proxy] self-update: registered durable service supervision via `evolver lifecycle bootstrap`; '
+ 'handing over to the service manager and exiting so it can take the IPC port.'
+ lockReleaseWarning,
};
}
if (outcome.requiresForegroundExit) {
const termination = outcome.reason === 'termination_unconfirmed'
? 'process-tree termination could not be confirmed'
: `the child result was ${outcome.reason} and did not prove a clean rollback`;
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: `[evolver-proxy] self-update: bootstrap ${termination}; exiting the foreground proxy to avoid concurrent IPC ownership. `
+ 'Inspect the lifecycle bootstrap transaction state before retrying.',
};
}
if (recoveringPendingOwnership) {
return {
disposition: 'fail_closed',
handedOver: false,
exitCode: 1,
message: '[evolver-proxy] self-update: prior standalone migration ownership remains unresolved; '
+ 'exiting the foreground proxy without replacing the ambiguity marker. '
+ 'Inspect the lifecycle bootstrap transaction state before retrying.',
};
}
return {
disposition: 'continue',
handedOver: false,
message: `[evolver-proxy] self-update: first-run bootstrap failed (${outcome.reason}); `
+ 'running with self-update off. Run `evolver lifecycle install-service` manually to enable.',
};
}