import * as fs from 'node:fs'; import { basename, dirname, isAbsolute, join, resolve, parse, relative, sep } from 'node:path'; import { randomBytes } from 'node:crypto'; import { atomicWriteSync } from '../fs-atomic'; import { CsoError, RunReportV3, canonical, completeness, fingerprint, renderReport, sha256, } from './contracts'; import { redact, sanitizeForJson, sanitizeHelperForJson } from './process'; const MAX_STATE_FILE = 1024 * 1024; type ExactStats = Pick< fs.BigIntStats, 'dev' | 'ino' | 'mtimeNs' | 'ctimeNs' | 'isFile' | 'isSymbolicLink' | 'isDirectory' > & Pick; function exactStats(stat: fs.BigIntStats): ExactStats { for (const value of [stat.nlink, stat.size, stat.mode, stat.uid]) if (value > BigInt(Number.MAX_SAFE_INTEGER) || value < -BigInt(Number.MAX_SAFE_INTEGER)) throw new CsoError('UNSAFE_PATH', 'Filesystem metadata exceeds safe bounds'); return { dev: stat.dev, ino: stat.ino, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs, nlink: Number(stat.nlink), size: Number(stat.size), mode: Number(stat.mode), uid: Number(stat.uid), isFile: () => stat.isFile(), isSymbolicLink: () => stat.isSymbolicLink(), isDirectory: () => stat.isDirectory(), }; } function exactLstat(path: string): ExactStats { return exactStats(fs.lstatSync(path, { bigint: true })); } function exactFstat(fd: number): ExactStats { return exactStats(fs.fstatSync(fd, { bigint: true })); } type AtomicRecoveryIdentity = { dev: bigint; ino: bigint; nlink: number; size: number; mode: number; uid: number; mtimeNs: bigint; ctimeNs: bigint; }; export interface AtomicNoReplaceRecoveryOptions { label: string; maxBytes: number; validate?: (value: unknown, publisherPid: number) => void; publisherAlive?: (value: unknown, publisherPid: number) => boolean; } class AtomicPublicationTransition extends CsoError { constructor(message: string) { super('SNAPSHOT_RACE', message); this.name = 'AtomicPublicationTransition'; } } function recoveryIdentity(stat: ExactStats): AtomicRecoveryIdentity { return { dev: stat.dev, ino: stat.ino, nlink: stat.nlink, size: stat.size, mode: stat.mode, uid: stat.uid, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs, }; } function sameRecoveryIdentity(left: AtomicRecoveryIdentity, right: AtomicRecoveryIdentity): boolean { return ( left.dev === right.dev && left.ino === right.ino && left.nlink === right.nlink && left.size === right.size && left.mode === right.mode && left.uid === right.uid && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs ); } function recoveryProcessAlive(pid: number): boolean { try { process.kill(pid, 0); return true; } catch (error: any) { return error?.code === 'EPERM'; } } function liveRecognizedPublication( temp: string, target: string, pid: number, options: AtomicNoReplaceRecoveryOptions, ): boolean { if (!recoveryProcessAlive(pid)) return false; try { const temporary = exactLstat(temp); if ( temporary.isSymbolicLink() || !temporary.isFile() || temporary.nlink < 1 || temporary.nlink > 2 || (process.getuid && temporary.uid !== process.getuid()) || (process.platform !== 'win32' && (temporary.mode & 0o077) !== 0) ) return false; if (temporary.size === 0) return temporary.nlink === 1; if (temporary.nlink !== 2 || !privatePublicationFile(temporary, options)) return false; const published = exactLstat(target); return published.nlink === 2 && samePublicationInode(temporary, published, options); } catch { return false; } } function liveEmptyPublication(path: string, pid: number): boolean { if (!recoveryProcessAlive(pid)) return false; try { const stat = exactLstat(path); return ( stat.isFile() && !stat.isSymbolicLink() && stat.size === 0 && stat.nlink === 1 && (!process.getuid || stat.uid === process.getuid()) && (process.platform === 'win32' || (stat.mode & 0o077) === 0) ); } catch { return false; } } function privatePublicationObservation(stat: ExactStats, options: AtomicNoReplaceRecoveryOptions): boolean { return ( stat.isFile() && !stat.isSymbolicLink() && stat.size >= 0 && stat.size <= options.maxBytes && stat.nlink >= 1 && stat.nlink <= 2 && (!process.getuid || stat.uid === process.getuid()) && (process.platform === 'win32' || (stat.mode & 0o077) === 0) ); } function livePublicationAdvanced( temp: string, pid: number, observed: ExactStats | undefined, options: AtomicNoReplaceRecoveryOptions, ): boolean { if (!observed || !privatePublicationObservation(observed, options) || !recoveryProcessAlive(pid)) return false; Atomics.wait(LEASE_ELECTION_WAIT, 0, 0, LEASE_ELECTION_POLL_MS); let current: ExactStats; try { current = exactLstat(temp); } catch (error: any) { return error?.code === 'ENOENT'; } if ( !privatePublicationObservation(current, options) || current.dev !== observed.dev || current.ino !== observed.ino ) return false; if (current.nlink !== observed.nlink || current.size !== observed.size) return true; return false; } function publicationOwnerAlive( value: unknown, publisherPid: number, options: AtomicNoReplaceRecoveryOptions, ): boolean { return options.publisherAlive?.(value, publisherPid) ?? recoveryProcessAlive(publisherPid); } function privatePublicationFile(stat: ExactStats, options: AtomicNoReplaceRecoveryOptions): boolean { return ( stat.isFile() && !stat.isSymbolicLink() && stat.size > 0 && stat.size <= options.maxBytes && (!process.getuid || stat.uid === process.getuid()) && (process.platform === 'win32' || (stat.mode & 0o077) === 0) ); } function samePublicationObject( left: ExactStats, right: ExactStats, options: AtomicNoReplaceRecoveryOptions, ): boolean { return ( privatePublicationFile(left, options) && privatePublicationFile(right, options) && left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mode === right.mode && left.uid === right.uid ); } function samePublicationInode( left: ExactStats, right: ExactStats, options: AtomicNoReplaceRecoveryOptions, ): boolean { return samePublicationObject(left, right, options) && left.mtimeNs === right.mtimeNs; } function publicationLinkTransition( observed: ExactStats, current: ExactStats, links: 1 | 2, options: AtomicNoReplaceRecoveryOptions, ): boolean { const from = links === 1 ? 1 : 2, to = links === 1 ? 2 : 1; return observed.nlink === from && current.nlink === to && samePublicationInode(observed, current, options); } function publicationPathRemoved( observed: ExactStats, current: ExactStats, options: AtomicNoReplaceRecoveryOptions, ): boolean { return ( observed.nlink >= 1 && observed.nlink <= 2 && current.nlink >= 0 && current.nlink < observed.nlink && samePublicationObject(observed, current, options) ); } function publicationProgress( left: ExactStats, right: ExactStats, options: AtomicNoReplaceRecoveryOptions, ): boolean { return ( left.nlink >= 0 && left.nlink <= 2 && right.nlink >= 0 && right.nlink <= 2 && left.nlink !== right.nlink && samePublicationInode(left, right, options) ); } function atomicTempTarget(path: string, publisherPid?: number): { target: string; pid: number } | undefined { const match = basename(path).match(/^(.*)\.tmp\.(\d{1,10})\.[a-f0-9]{8}$/), pid = match ? Number(match[2]) : 0; return match && match[1] && Number.isSafeInteger(pid) && pid > 1 && (publisherPid === undefined || pid === publisherPid) ? { target: join(dirname(path), match[1]), pid } : undefined; } function settledAtomicTemp( path: string, observed: ExactStats, options: AtomicNoReplaceRecoveryOptions, ): boolean { const publication = atomicTempTarget(path); if (!publication) return false; let target: ExactStats; try { target = exactLstat(publication.target); } catch { return false; } return ( observed.nlink >= 1 && observed.nlink <= 2 && target.nlink === 1 && privatePublicationFile(observed, options) && privatePublicationFile(target, options) && observed.dev === target.dev && observed.ino === target.ino && observed.size === target.size && observed.mode === target.mode && observed.uid === target.uid && observed.mtimeNs === target.mtimeNs ); } function readPublicationBytes(fd: number, size: number, label: string): string { const bytes = Buffer.alloc(size); let offset = 0; while (offset < size) { const count = fs.readSync(fd, bytes, offset, size - offset, offset); if (count <= 0) throw new CsoError('SNAPSHOT_RACE', `${label} interrupted publication changed while it was read`); offset += count; } const extra = Buffer.alloc(1); if (fs.readSync(fd, extra, 0, 1, size) !== 0) throw new CsoError('SNAPSHOT_RACE', `${label} interrupted publication changed while it was read`); return bytes.toString('utf8'); } function recoveryJson( path: string, links: 1 | 2, options: AtomicNoReplaceRecoveryOptions, observed?: ExactStats, ): { identity: AtomicRecoveryIdentity; value: unknown } { let fd: number | undefined; try { const before = exactLstat(path); if (before.nlink === 0) { let current: ExactStats; try { current = exactLstat(path); } catch (error: any) { if (error?.code === 'ENOENT') throw new CsoError('INSUFFICIENT_CAPACITY', `${options.label} was removed while it was inspected`); throw error; } if (samePublicationInode(before, current, options) && (current.nlink === 0 || current.nlink === links)) throw new AtomicPublicationTransition(`${options.label} changed link state while it was inspected`); throw new CsoError('UNSAFE_PATH', `${options.label} was replaced while it was inspected`); } if (observed && publicationLinkTransition(observed, before, links, options)) throw new AtomicPublicationTransition(`${options.label} interrupted publication changed link state`); if ( observed && publicationProgress(observed, before, options) && !sameRecoveryIdentity(recoveryIdentity(observed), recoveryIdentity(before)) ) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} changed phase during concurrent recovery`, ); if ( !before.isFile() || before.isSymbolicLink() || before.nlink !== links || before.size <= 0 || before.size > options.maxBytes || (process.getuid && before.uid !== process.getuid()) || (process.platform !== 'win32' && (before.mode & 0o077) !== 0) ) throw new CsoError( 'UNSAFE_PATH', `${options.label} interrupted publication is not one private regular file`, ); fd = fs.openSync(path, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0)); const opened = exactFstat(fd); if (!sameRecoveryIdentity(recoveryIdentity(before), recoveryIdentity(opened))) { if (publicationLinkTransition(before, opened, links, options)) throw new AtomicPublicationTransition( `${options.label} interrupted publication changed link state while it was opened`, ); if (publicationProgress(before, opened, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} changed phase during concurrent recovery while it was opened`, ); throw new CsoError( 'SNAPSHOT_RACE', `${options.label} interrupted publication changed while it was opened`, ); } const serialized = readPublicationBytes(fd, opened.size, options.label); let value: unknown; try { value = JSON.parse(serialized); } catch { throw new CsoError('UNSAFE_PATH', `${options.label} interrupted publication is not valid JSON`); } const final = exactFstat(fd); if (readPublicationBytes(fd, opened.size, options.label) !== serialized) throw new CsoError( 'SNAPSHOT_RACE', `${options.label} interrupted publication changed while it was read`, ); let after: ExactStats; try { after = exactLstat(path); } catch (error: any) { if (error?.code === 'ENOENT' && publicationPathRemoved(opened, final, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} was removed by another recovery helper while it was read`, ); throw error; } const openedIdentity = recoveryIdentity(opened), finalIdentity = recoveryIdentity(final), afterIdentity = recoveryIdentity(after); if ( !sameRecoveryIdentity(openedIdentity, finalIdentity) || !sameRecoveryIdentity(openedIdentity, afterIdentity) ) { const coherentTransition = (sameRecoveryIdentity(openedIdentity, finalIdentity) && publicationLinkTransition(opened, after, links, options)) || (publicationLinkTransition(opened, final, links, options) && sameRecoveryIdentity(finalIdentity, afterIdentity)); if (coherentTransition) throw new AtomicPublicationTransition( `${options.label} interrupted publication changed link state while it was read`, ); if (publicationProgress(opened, final, options) && publicationProgress(final, after, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} changed phase during concurrent recovery while it was read`, ); throw new CsoError( 'SNAPSHOT_RACE', `${options.label} interrupted publication changed while it was read`, ); } return { identity: recoveryIdentity(opened), value }; } catch (error: any) { if (error instanceof CsoError) throw error; if (error?.code === 'ENOENT') { if (observed && settledAtomicTemp(path, observed, options)) throw new AtomicPublicationTransition( `${options.label} interrupted publication settled while it was observed`, ); if (observed && !fs.existsSync(path)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} was removed by another recovery helper`, ); throw new CsoError('SNAPSHOT_RACE', `${options.label} interrupted publication disappeared`); } throw new CsoError('UNSAFE_PATH', `${options.label} interrupted publication could not be validated`); } finally { if (fd !== undefined) try { fs.closeSync(fd); } catch {} } } function atomicTempCandidates(target: string): Array<{ path: string; pid: number }> { const directory = dirname(target), name = basename(target), escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), pattern = new RegExp(`^${escaped}\\.tmp\\.(\\d{1,10})\\.([a-f0-9]{8})$`); return fs.readdirSync(directory).flatMap((entry) => { const match = entry.match(pattern), pid = match ? Number(match[1]) : 0; return match && Number.isSafeInteger(pid) && pid > 1 ? [{ path: join(directory, entry), pid }] : []; }); } function matchesRecoveryInode( stat: ExactStats, identity: AtomicRecoveryIdentity, options: AtomicNoReplaceRecoveryOptions, ): boolean { return ( privatePublicationFile(stat, options) && stat.dev === identity.dev && stat.ino === identity.ino && stat.size === identity.size && stat.mode === identity.mode && stat.uid === identity.uid && stat.mtimeNs === identity.mtimeNs ); } /** Recover only the hard-link publication window of atomicWriteSync(noReplace). */ export function recoverAtomicNoReplaceJson(target: string, options: AtomicNoReplaceRecoveryOptions): void { let targetStat: ExactStats; try { targetStat = exactLstat(target); } catch (error: any) { if (error?.code === 'ENOENT') return; throw new CsoError('UNSAFE_PATH', `${options.label} could not be inspected`); } // Callers own legacy-directory and special-file handling. Only a regular // file can be the no-replace hard-link publication this helper recognizes. if (!targetStat.isFile() || targetStat.isSymbolicLink()) return; if (targetStat.nlink === 1) return; if (targetStat.nlink === 0) { let current: ExactStats; try { current = exactLstat(target); } catch (error: any) { if (error?.code === 'ENOENT') throw new AtomicPublicationTransition(`${options.label} was removed while it was inspected`); throw new CsoError('UNSAFE_PATH', `${options.label} could not be reinspected`); } if (samePublicationInode(targetStat, current, options) && current.nlink >= 0 && current.nlink <= 2) throw new AtomicPublicationTransition(`${options.label} changed link state while it was inspected`); throw new CsoError('UNSAFE_PATH', `${options.label} was replaced while it was inspected`); } if (targetStat.nlink !== 2) throw new CsoError('UNSAFE_PATH', `${options.label} has an unrecognized hard-link count`); const canonical = recoveryJson(target, 2, options, targetStat), matches = atomicTempCandidates(target).flatMap((candidate) => { try { const observed = exactLstat(candidate.path); return observed.dev === canonical.identity.dev && observed.ino === canonical.identity.ino ? [{ ...candidate, observed }] : []; } catch { return []; } }); if (matches.length !== 1) { let settled: ExactStats | undefined; try { settled = exactLstat(target); } catch (error: any) { if (matches.length === 0 && error?.code === 'ENOENT') throw new AtomicPublicationTransition(`${options.label} was removed during candidate enumeration`); } if (settled && publicationLinkTransition(targetStat, settled, 2, options)) throw new AtomicPublicationTransition( `${options.label} interrupted publication settled during candidate enumeration`, ); throw new CsoError( 'UNSAFE_PATH', `${options.label} hard link does not match one recognized interrupted publication`, ); } const candidate = matches[0], temporary = recoveryJson(candidate.path, 2, options, candidate.observed); if (!sameRecoveryIdentity(canonical.identity, temporary.identity)) throw new CsoError('UNSAFE_PATH', `${options.label} hard link changed identity`); options.validate?.(canonical.value, candidate.pid); options.validate?.(temporary.value, candidate.pid); if (publicationOwnerAlive(canonical.value, candidate.pid, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} publication is still owned by a live helper`, ); let finalTarget: ExactStats, finalTemp: ExactStats; try { finalTarget = exactLstat(target); finalTemp = exactLstat(candidate.path); } catch (error: any) { if (error?.code !== 'ENOENT') throw error; for (const path of [target, candidate.path]) { try { const stat = exactLstat(path); if (!matchesRecoveryInode(stat, canonical.identity, options)) throw new CsoError('UNSAFE_PATH', `${options.label} was replaced during concurrent recovery`); } catch (recoveryError: any) { if (recoveryError instanceof CsoError) throw recoveryError; if (recoveryError?.code !== 'ENOENT') throw recoveryError; } } throw new AtomicPublicationTransition(`${options.label} was settled by another recovery helper`); } if ( !sameRecoveryIdentity(canonical.identity, recoveryIdentity(finalTarget)) || !sameRecoveryIdentity(canonical.identity, recoveryIdentity(finalTemp)) ) { if ( matchesRecoveryInode(finalTarget, canonical.identity, options) && matchesRecoveryInode(finalTemp, canonical.identity, options) && finalTarget.nlink <= 2 && finalTemp.nlink <= 2 ) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} hard link changed during concurrent recovery`, ); throw new CsoError('SNAPSHOT_RACE', `${options.label} hard link changed before recovery`); } try { fs.unlinkSync(candidate.path); } catch (error: any) { if (error?.code !== 'ENOENT') throw new CsoError( 'PERSISTENCE_FAILED', `${options.label} interrupted publication could not be recovered`, ); } let recovered: { identity: AtomicRecoveryIdentity; value: unknown }; try { recovered = recoveryJson(target, 1, options); } catch (error) { if (error instanceof CsoError && error.code === 'SNAPSHOT_RACE' && !fs.existsSync(target)) throw new AtomicPublicationTransition(`${options.label} was removed by another recovery helper`); throw error; } options.validate?.(recovered.value, candidate.pid); if (recovered.identity.dev !== canonical.identity.dev || recovered.identity.ino !== canonical.identity.ino) throw new CsoError('UNSAFE_PATH', `${options.label} changed identity during recovery`); } /** Remove a never-published temp, or validate a temp that became published while observed. */ export function discardAtomicNoReplaceTemp( path: string, publisherPid: number, options: AtomicNoReplaceRecoveryOptions, ): void { let observed: ExactStats; try { observed = exactLstat(path); } catch (error: any) { if (error?.code === 'ENOENT') { const publication = atomicTempTarget(path, publisherPid); if (publication) { try { const settled = recoveryJson(publication.target, 1, options); options.validate?.(settled.value, publisherPid); if (publicationOwnerAlive(settled.value, publisherPid, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} publication is still owned by a live helper`, ); return; } catch (settledError) { if ( settledError instanceof CsoError && settledError.code === 'SNAPSHOT_RACE' && !fs.existsSync(publication.target) ) return; if (settledError instanceof CsoError) throw settledError; } } throw new CsoError('SNAPSHOT_RACE', `${options.label} interrupted publication disappeared`); } throw new CsoError('UNSAFE_PATH', `${options.label} interrupted publication could not be inspected`); } if (observed.nlink === 2 && privatePublicationFile(observed, options)) { const target = atomicTempTarget(path, publisherPid)?.target; let published: ExactStats | undefined; try { if (target) published = exactLstat(target); } catch {} if ( target && published && published.dev === observed.dev && published.ino === observed.ino && published.nlink === 2 && privatePublicationFile(published, options) ) { recoverAtomicNoReplaceJson(target, options); const settled = recoveryJson(target, 1, options); if (settled.identity.dev !== observed.dev || settled.identity.ino !== observed.ino) throw new CsoError( 'UNSAFE_PATH', `${options.label} published target changed identity while it settled`, ); options.validate?.(settled.value, publisherPid); if (publicationOwnerAlive(settled.value, publisherPid, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} publication is still owned by a live helper`, ); return; } } const temporary = recoveryJson(path, 1, options, observed); options.validate?.(temporary.value, publisherPid); if (publicationOwnerAlive(temporary.value, publisherPid, options)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} publication is still owned by a live helper`, ); let final: ExactStats; try { final = exactLstat(path); } catch (error: any) { if (error?.code === 'ENOENT') throw new AtomicPublicationTransition(`${options.label} temp was removed by another recovery helper`); throw error; } if (!sameRecoveryIdentity(temporary.identity, recoveryIdentity(final))) throw new CsoError('SNAPSHOT_RACE', `${options.label} temp changed before recovery`); try { fs.unlinkSync(path); } catch (error: any) { if (error?.code === 'ENOENT') throw new AtomicPublicationTransition(`${options.label} temp was removed by another recovery helper`); throw new CsoError('PERSISTENCE_FAILED', `${options.label} unpublished temp could not be removed`); } } export function stateRoot(env: Record = process.env): string { // Mirrors bin/gstack-paths. Security artifacts are intentionally outside every sync allowlist. const userHome = env.HOME || (process.platform === 'win32' ? env.USERPROFILE : ''); return resolve( env.GSTACK_HOME || (env.CLAUDE_PLUGIN_ROOT?.toLowerCase().includes('gstack') ? env.CLAUDE_PLUGIN_DATA : '') || join(userHome || '.', '.gstack'), ); } function ensureDirectory(path: string, hardenExistingLeaf: boolean): string { const p = resolve(path), root = parse(p).root; if (p === root) throw new CsoError('UNSAFE_PATH', 'Private state cannot use a filesystem root'); let cursor = root, leafCreated = false; for (const part of relative(root, p).split(sep).filter(Boolean)) { cursor = join(cursor, part); let created = false; try { fs.mkdirSync(cursor, { mode: 0o700 }); created = true; } catch (error: any) { if (error?.code !== 'EEXIST') throw error; } if (cursor === p) leafCreated = created; const s = fs.lstatSync(cursor); if (s.isSymbolicLink() || !s.isDirectory()) throw new CsoError('UNSAFE_PATH', 'Private state has a symlink or non-directory ancestor'); // Root-owned system ancestors are normal; a writable ancestor owned by anyone else is not. if (s.uid !== process.getuid?.() && s.uid !== 0) throw new CsoError('UNSAFE_PATH', 'Private state ancestor has an unexpected owner'); if (process.platform !== 'win32' && s.mode & 0o022 && !(s.mode & 0o1000)) throw new CsoError('UNSAFE_PATH', 'Private state has a group- or world-writable ancestor'); } const s = fs.statSync(p); if (process.getuid && s.uid !== process.getuid()) throw new CsoError('UNSAFE_PATH', 'Private directory must be owned by the current user'); if (hardenExistingLeaf || leafCreated) fs.chmodSync(p, 0o700); return p; } /** The supplied leaf is CSO-owned. Existing ancestors are validated, never mutated. */ export function secureDirectory(path: string): string { return ensureDirectory(path, true); } export function privateRoot(): string { const container = ensureDirectory(stateRoot(), false); return secureDirectory(join(container, 'security', 'cso')); } export function assertStateOutside(repo: string): void { const source = fs.realpathSync(repo), candidate = resolve(stateRoot(), 'security', 'cso'); const relation = relative(source, candidate); if (relation === '' || (!relation.startsWith(`..${sep}`) && relation !== '..' && !isAbsolute(relation))) throw new CsoError('UNSAFE_PATH', 'CSO private state must be outside the audited repository'); } export function repoId(repo: string): string { return sha256(fs.realpathSync(repo)).slice(0, 24); } export function newRun(repo: string): { runId: string; dir: string; repoId: string } { assertStateOutside(repo); const id = repoId(repo), runId = `${Date.now()}-${randomBytes(8).toString('hex')}`; return { runId, repoId: id, dir: secureDirectory(join(privateRoot(), id, runId)) }; } export function runDirectory(id: string): string { if (!/^\d{13}-[a-f0-9]{16}$/.test(id)) throw new CsoError('INVALID_ARGUMENT', 'Run identifier must be the ID returned by start'); const root = privateRoot(); for (const item of fs.readdirSync(root)) { if (!/^[a-f0-9]{24}$/.test(item)) continue; const dir = join(root, item, id); if (fs.existsSync(dir)) return secureDirectory(dir); } throw new CsoError('MISSING_INPUT', 'Run was not found or has expired'); } export function writeJson(path: string, value: unknown): void { try { secureDirectory(dirname(path)); if (fs.existsSync(path) && fs.lstatSync(path).isSymbolicLink()) throw new CsoError('UNSAFE_PATH', 'State file cannot be a symlink'); const sanitized = JSON.stringify(sanitizeForJson(value), null, 2); if (Buffer.byteLength(sanitized) + 1 > MAX_STATE_FILE) throw new CsoError( 'PERSISTENCE_FAILED', 'Private state exceeds the 1 MiB persistence limit; the previous artifact was preserved', ); JSON.parse(sanitized); atomicWriteSync(path, sanitized + '\n', { mode: 0o600 }); } catch (e) { if (e instanceof CsoError) throw e; throw new CsoError( 'PERSISTENCE_FAILED', 'Private report could not be written; no saved report is claimed', ); } } export function writeHelperJson(path: string, value: unknown): void { try { secureDirectory(dirname(path)); if (fs.existsSync(path) && fs.lstatSync(path).isSymbolicLink()) throw new CsoError('UNSAFE_PATH', 'State file cannot be a symlink'); const serialized = JSON.stringify(sanitizeHelperForJson(value), null, 2); if (Buffer.byteLength(serialized) + 1 > MAX_STATE_FILE) throw new CsoError( 'PERSISTENCE_FAILED', 'Private helper state exceeds the 1 MiB persistence limit; the previous artifact was preserved', ); JSON.parse(serialized); atomicWriteSync(path, serialized + '\n', { mode: 0o600 }); } catch (error) { if (error instanceof CsoError) throw error; throw new CsoError( 'PERSISTENCE_FAILED', 'Private helper state could not be written; no saved artifact is claimed', ); } } export function writeJsonExclusive(path: string, value: unknown): void { try { secureDirectory(dirname(path)); if (fs.existsSync(path) && fs.lstatSync(path).isSymbolicLink()) throw new CsoError('UNSAFE_PATH', 'State file cannot be a symlink'); const sanitized = JSON.stringify(sanitizeHelperForJson(value), null, 2); JSON.parse(sanitized); if (Buffer.byteLength(sanitized) + 1 > MAX_STATE_FILE) throw new CsoError( 'PERSISTENCE_FAILED', 'Private immutable artifact exceeds the 1 MiB persistence limit', ); atomicWriteSync(path, sanitized + '\n', { mode: 0o600, noReplace: true }); } catch (error: any) { if (error instanceof CsoError) throw error; if (error?.code === 'EEXIST') throw new CsoError('PERSISTENCE_FAILED', 'Immutable artifact already exists; it was not replaced'); throw new CsoError('PERSISTENCE_FAILED', 'Private immutable artifact could not be written'); } } function readPrivateJson(path: string): unknown { let fd: number | undefined; try { const before = exactLstat(path); if ( !before.isFile() || before.isSymbolicLink() || before.nlink !== 1 || before.size <= 0 || before.size > MAX_STATE_FILE || (process.getuid && before.uid !== process.getuid()) || (process.platform !== 'win32' && (before.mode & 0o077) !== 0) ) throw new CsoError('UNSAFE_PATH', 'Invalid private state file'); fd = fs.openSync(path, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0)); const opened = exactFstat(fd); if (!sameRecoveryIdentity(recoveryIdentity(before), recoveryIdentity(opened))) throw new CsoError('SNAPSHOT_RACE', 'Private state file changed while it was opened'); const raw = fs.readFileSync(fd, 'utf8'); const final = exactFstat(fd), after = exactLstat(path); if ( !sameRecoveryIdentity(recoveryIdentity(opened), recoveryIdentity(final)) || !sameRecoveryIdentity(recoveryIdentity(opened), recoveryIdentity(after)) ) throw new CsoError('SNAPSHOT_RACE', 'Private state file changed while it was read'); try { return JSON.parse(raw); } catch { throw new CsoError('MISSING_INPUT', 'Private state file is missing or invalid'); } } catch (error: any) { if (error instanceof CsoError) throw error; if (error?.code === 'ENOENT' || error?.code === 'ELOOP') throw new CsoError('SNAPSHOT_RACE', 'Private state file changed while it was opened'); throw new CsoError('MISSING_INPUT', 'Private state file is missing or invalid'); } finally { if (fd !== undefined) try { fs.closeSync(fd); } catch {} } } export function readJson(path: string): any { try { secureDirectory(dirname(path)); recoverAtomicNoReplaceJson(path, { label: 'Private immutable artifact', maxBytes: MAX_STATE_FILE }); return readPrivateJson(path); } catch (e) { if (e instanceof CsoError) throw e; throw new CsoError('MISSING_INPUT', 'Private state file is missing or invalid'); } } export const PUBLIC_SOURCE_ROOT = ''; /** A report is public evidence; the real root remains in the private snapshot. */ export function publicReport(report: RunReportV3): RunReportV3 { return { ...report, source: { ...report.source, root: PUBLIC_SOURCE_ROOT } }; } export function saveReport(dir: string, report: RunReportV3): void { report.completeness = completeness(report); const safe = sanitizeHelperForJson(publicReport(report)) as RunReportV3; for (const finding of safe.findings) { const expected = fingerprint(finding); if (finding.id !== expected || finding.fingerprint !== expected) throw new CsoError( 'PERSISTENCE_FAILED', 'Finding identity changed during redaction; the previous report was preserved', ); } try { secureDirectory(dir); const serialized = JSON.stringify(safe, null, 2); if (Buffer.byteLength(serialized) + 1 > MAX_STATE_FILE) throw new CsoError( 'PERSISTENCE_FAILED', 'Private state exceeds the 1 MiB persistence limit; the previous artifact was preserved', ); atomicWriteSync(join(dir, 'report.json'), serialized + '\n', { mode: 0o600 }); } catch (error) { if (error instanceof CsoError) throw error; throw new CsoError( 'PERSISTENCE_FAILED', 'Private report could not be written; no saved report is claimed', ); } try { atomicWriteSync(join(dir, 'report.md'), renderReport(safe), { mode: 0o600 }); } catch { throw new CsoError('PERSISTENCE_FAILED', 'JSON was saved but the readable report could not be written'); } } export function loadReport(dir: string): RunReportV3 { const v = readJson(join(dir, 'report.json')); if (v.schemaVersion !== 3 || !Array.isArray(v.coverage) || !Array.isArray(v.findings)) throw new CsoError('INCOMPATIBLE_INPUT', 'Expected a v3 run report'); return publicReport(v); } export function event(report: RunReportV3, kind: string, message: string): void { report.events.push({ at: new Date().toISOString(), kind, message: redact(message) }); } export function executionDeadline(report: RunReportV3): number { return Date.parse(report.deadline) - 60_000; } export function requireTime(report: RunReportV3): void { if (Date.now() >= executionDeadline(report)) throw new CsoError( 'DEADLINE', 'Investigation deadline reached; the final minute is reserved for reporting', ); } const LOCK_PROTOCOL = 'immutable-lease-set-v3'; const LOCK_OWNER_MAX_BYTES = 4096; const LOCK_TOKEN = /^[a-f0-9]{32}$/; const PROCESS_IDENTITY = /^linux:\d+$/; const LEASE_PUBLICATION_TEMP = /^([a-f0-9]{32})\.(json|decision)\.tmp\.(\d{1,10})\.[a-f0-9]{8}$/; const LEASE_BLOCKED_WAIT_MS = 250; const LEASE_ELECTION_POLL_MS = 1; const LEASE_ELECTION_WAIT = new Int32Array(new SharedArrayBuffer(4)); const LEASE_CANDIDATE = /^([a-f0-9]{32})\.json$/; const LEASE_DECISION = /^([a-f0-9]{32})\.decision$/; const LEASE_ACTIVE = /^([a-f0-9]{32})\.active\.([a-f0-9]{16})$/; type LockOwner = { pid: number; processIdentity?: string; token: string; createdAt: number }; type LockIdentity = { dev: bigint; ino: bigint }; type LeaseLinks = 1 | 2; type LeaseDecision = { schemaVersion: 1; token: string; kind: 'ticket' | 'withdraw'; ticket?: string; candidateDev: string; candidateIno: string; ownerPid: number; ownerProcessIdentity?: string; ownerCreatedAt: number; publisherPid: number; publisherProcessIdentity?: string; createdAt: number; }; function processAlive(pid: number): boolean { if (!Number.isInteger(pid) || pid <= 1) return false; try { process.kill(pid, 0); return true; } catch (error: any) { return error?.code === 'EPERM'; } } function processIdentity(pid: number): string | undefined { if (process.platform !== 'linux') return; try { const raw = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'), tail = raw .slice(raw.lastIndexOf(')') + 2) .trim() .split(/\s+/); return /^\d+$/.test(tail[19] ?? '') ? `linux:${tail[19]}` : undefined; } catch { return; } } function validateOwner(value: unknown, expectedToken?: string): LockOwner { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new CsoError('UNSAFE_PATH', 'Run mutation lease owner is malformed'); const owner = value as Record; if ( !Number.isInteger(owner.pid) || Number(owner.pid) <= 1 || typeof owner.token !== 'string' || !LOCK_TOKEN.test(owner.token) || (expectedToken !== undefined && owner.token !== expectedToken) || !Number.isFinite(owner.createdAt) || Number(owner.createdAt) < 0 || (owner.processIdentity !== undefined && (typeof owner.processIdentity !== 'string' || !PROCESS_IDENTITY.test(owner.processIdentity))) ) throw new CsoError('UNSAFE_PATH', 'Run mutation lease owner is malformed'); return { pid: Number(owner.pid), token: owner.token, createdAt: Number(owner.createdAt), ...(owner.processIdentity === undefined ? {} : { processIdentity: owner.processIdentity as string }), }; } function validateLeaseDecision(value: unknown, expectedToken?: string): LeaseDecision { if (!value || typeof value !== 'object' || Array.isArray(value)) throw new CsoError('UNSAFE_PATH', 'Run mutation lease decision is malformed'); const decision = value as Record, kind = decision.kind, ticket = decision.ticket; if ( decision.schemaVersion !== 1 || typeof decision.token !== 'string' || !LOCK_TOKEN.test(decision.token) || (expectedToken !== undefined && decision.token !== expectedToken) || (kind !== 'ticket' && kind !== 'withdraw') || (kind === 'ticket' && (typeof ticket !== 'string' || !/^[a-f0-9]{16}$/.test(ticket) || ticket === '0000000000000000')) || (kind === 'withdraw' && ticket !== undefined) || typeof decision.candidateDev !== 'string' || !/^(0|[1-9]\d*)$/.test(decision.candidateDev) || BigInt(decision.candidateDev) > 0xffffffffffffffffn || typeof decision.candidateIno !== 'string' || !/^(0|[1-9]\d*)$/.test(decision.candidateIno) || BigInt(decision.candidateIno) > 0xffffffffffffffffn || !Number.isInteger(decision.ownerPid) || Number(decision.ownerPid) <= 1 || !Number.isFinite(decision.ownerCreatedAt) || Number(decision.ownerCreatedAt) < 0 || !Number.isInteger(decision.publisherPid) || Number(decision.publisherPid) <= 1 || !Number.isFinite(decision.createdAt) || Number(decision.createdAt) < 0 || (decision.ownerProcessIdentity !== undefined && (typeof decision.ownerProcessIdentity !== 'string' || !PROCESS_IDENTITY.test(decision.ownerProcessIdentity))) || (decision.publisherProcessIdentity !== undefined && (typeof decision.publisherProcessIdentity !== 'string' || !PROCESS_IDENTITY.test(decision.publisherProcessIdentity))) ) throw new CsoError('UNSAFE_PATH', 'Run mutation lease decision is malformed'); return decision as LeaseDecision; } function decisionOwner(decision: LeaseDecision): LockOwner { return { pid: decision.ownerPid, token: decision.token, createdAt: decision.ownerCreatedAt, ...(decision.ownerProcessIdentity ? { processIdentity: decision.ownerProcessIdentity } : {}), }; } function decisionPublisher(decision: LeaseDecision): LockOwner { return { pid: decision.publisherPid, token: decision.token, createdAt: decision.createdAt, ...(decision.publisherProcessIdentity ? { processIdentity: decision.publisherProcessIdentity } : {}), }; } function leaseDecisionRecoveryOptions(token: string): AtomicNoReplaceRecoveryOptions { return { label: 'Run mutation lease decision', maxBytes: LOCK_OWNER_MAX_BYTES, validate: (value, pid) => { const decision = validateLeaseDecision(value, token); if (decision.publisherPid !== pid) throw new CsoError('UNSAFE_PATH', 'Run mutation lease decision temp does not match its publisher'); }, publisherAlive: (value, pid) => { const decision = validateLeaseDecision(value, token); if (decision.publisherPid !== pid) throw new CsoError('UNSAFE_PATH', 'Run mutation lease decision temp does not match its publisher'); return ownerIsAlive(decisionPublisher(decision)); }, }; } function ownerLinkTransition(left: ExactStats, right: ExactStats): boolean { return ( left.isFile() && right.isFile() && left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mode === right.mode && left.uid === right.uid && left.nlink >= 0 && left.nlink <= 2 && right.nlink >= 0 && right.nlink <= 2 && left.nlink !== right.nlink ); } function readOwner( path: string, expectedToken?: string, expectedLinks: LeaseLinks = 1, observed?: ExactStats, ): { owner: LockOwner; identity: LockIdentity } { let fd: number | undefined; try { const before = exactLstat(path); if (observed && ownerLinkTransition(observed, before)) throw new CsoError('INSUFFICIENT_CAPACITY', 'Run mutation lease changed phase while it was read'); if ( before.isSymbolicLink() || !before.isFile() || before.nlink !== expectedLinks || before.size <= 0 || before.size > LOCK_OWNER_MAX_BYTES || (process.getuid && before.uid !== process.getuid()) || (process.platform !== 'win32' && (before.mode & 0o077) !== 0) ) throw new CsoError('UNSAFE_PATH', 'Run mutation lease is invalid'); fd = fs.openSync(path, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0)); const opened = exactFstat(fd); if (ownerLinkTransition(before, opened)) throw new CsoError('INSUFFICIENT_CAPACITY', 'Run mutation lease changed phase while it was read'); if ( !opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.nlink !== expectedLinks || opened.size !== before.size ) throw new CsoError('UNSAFE_PATH', 'Run mutation lease changed while it was read'); let parsed: unknown; try { parsed = JSON.parse(fs.readFileSync(fd, 'utf8')); } catch { throw new CsoError('UNSAFE_PATH', 'Run mutation lease is malformed'); } const final = exactFstat(fd), after = exactLstat(path); const coherentTransition = (ownerLinkTransition(opened, final) && final.dev === after.dev && final.ino === after.ino && final.nlink === after.nlink) || (opened.dev === final.dev && opened.ino === final.ino && opened.nlink === final.nlink && ownerLinkTransition(opened, after)); if (coherentTransition) throw new CsoError('INSUFFICIENT_CAPACITY', 'Run mutation lease changed phase while it was read'); if ( after.isSymbolicLink() || after.dev !== opened.dev || after.ino !== opened.ino || after.nlink !== expectedLinks || final.dev !== opened.dev || final.ino !== opened.ino || final.nlink !== expectedLinks || final.size !== opened.size ) throw new CsoError('UNSAFE_PATH', 'Run mutation lease changed while it was read'); return { owner: validateOwner(parsed, expectedToken), identity: { dev: opened.dev, ino: opened.ino } }; } catch (error: any) { if (error instanceof CsoError) throw error; if (error?.code === 'ENOENT') throw new CsoError('INSUFFICIENT_CAPACITY', 'Run mutation lease changed during recovery'); throw new CsoError('UNSAFE_PATH', 'Run mutation lease could not be validated'); } finally { if (fd !== undefined) try { fs.closeSync(fd); } catch {} } } function ownerIsAlive(owner: LockOwner): boolean { const pid = owner.pid; if (!processAlive(pid)) return false; const current = processIdentity(pid); return !( typeof owner.processIdentity === 'string' && current !== undefined && owner.processIdentity !== current ); } function recoverLeasePublications(leases: string): void { for (let attempt = 0; attempt < 4; attempt++) { try { for (const name of fs.readdirSync(leases)) { const match = name.match(LEASE_PUBLICATION_TEMP); if (!match) continue; const token = match[1], kind = match[2] as 'json' | 'decision', publisherPid = Number(match[3]), temp = join(leases, name), target = join(leases, `${token}.${kind}`), options: AtomicNoReplaceRecoveryOptions = kind === 'json' ? { label: 'Run mutation lease', maxBytes: LOCK_OWNER_MAX_BYTES, validate: (value, pid) => { const owner = validateOwner(value, token); if (owner.pid !== pid) throw new CsoError( 'UNSAFE_PATH', 'Run mutation lease temp does not match its publisher', ); }, publisherAlive: (value, pid) => { const owner = validateOwner(value, token); if (owner.pid !== pid) throw new CsoError( 'UNSAFE_PATH', 'Run mutation lease temp does not match its publisher', ); return ownerIsAlive(owner); }, } : leaseDecisionRecoveryOptions(token); let publicationObserved: ExactStats | undefined; try { publicationObserved = exactLstat(temp); } catch {} if (liveEmptyPublication(temp, publisherPid)) throw new CsoError( 'INSUFFICIENT_CAPACITY', `${options.label} publication is still changing under a live helper`, ); try { if (fs.existsSync(target)) recoverAtomicNoReplaceJson(target, options); if (fs.existsSync(temp)) discardAtomicNoReplaceTemp(temp, publisherPid, options); } catch (error) { // A live cooperating publisher may still be writing its private temp. // Do not accept or remove unstable bytes; report ordinary contention. const transientShape = error instanceof CsoError && error.code === 'UNSAFE_PATH' && error.message === `${options.label} interrupted publication is not one private regular file`; if ( error instanceof CsoError && ((error.code === 'SNAPSHOT_RACE' && livePublicationAdvanced(temp, publisherPid, publicationObserved, options)) || (transientShape && (liveRecognizedPublication(temp, target, publisherPid, options) || livePublicationAdvanced(temp, publisherPid, publicationObserved, options)))) ) throw new AtomicPublicationTransition( `${options.label} publication advanced under its live helper`, ); throw error; } } return; } catch (error) { // Retry only a proven same-inode no-replace transition. Foreign inode, // content, permission, and pathname races remain visible failures. if (!(error instanceof AtomicPublicationTransition)) throw error; } } throw new CsoError('INSUFFICIENT_CAPACITY', 'Another helper is publishing a run mutation lease'); } function readLegacyOwner(path: string): { pid: number; processIdentity?: string; token: string; createdAt: number; } { let fd: number | undefined; try { const before = exactLstat(path); if ( before.isSymbolicLink() || !before.isFile() || before.nlink !== 1 || before.size <= 0 || before.size > LOCK_OWNER_MAX_BYTES || (process.getuid && before.uid !== process.getuid()) ) throw new CsoError('UNSAFE_PATH', 'Legacy run mutation lock owner is invalid'); fd = fs.openSync(path, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0)); const opened = exactFstat(fd); if (opened.dev !== before.dev || opened.ino !== before.ino || opened.nlink !== 1) throw new CsoError('UNSAFE_PATH', 'Legacy run mutation lock owner changed while it was read'); let value: unknown; try { value = JSON.parse(fs.readFileSync(fd, 'utf8')); } catch { throw new CsoError('UNSAFE_PATH', 'Legacy run mutation lock owner is malformed'); } const after = exactLstat(path), record = value as Record; if ( after.dev !== opened.dev || after.ino !== opened.ino || !record || typeof record !== 'object' || Array.isArray(record) || !Number.isInteger(record.pid) || Number(record.pid) <= 1 || typeof record.token !== 'string' || record.token.length < 1 || record.token.length > 256 || (record.processIdentity !== undefined && (typeof record.processIdentity !== 'string' || !PROCESS_IDENTITY.test(record.processIdentity))) ) throw new CsoError('UNSAFE_PATH', 'Legacy run mutation lock owner is malformed'); return { pid: Number(record.pid), token: record.token, createdAt: typeof record.createdAt === 'number' && Number.isFinite(record.createdAt) ? record.createdAt : 0, ...(record.processIdentity === undefined ? {} : { processIdentity: record.processIdentity as string }), }; } catch (error: any) { if (error instanceof CsoError) throw error; if (error?.code === 'ENOENT') throw new CsoError( 'INSUFFICIENT_CAPACITY', 'A legacy helper may still be initializing this run; its incomplete lock was left intact', ); throw new CsoError('UNSAFE_PATH', 'Legacy run mutation lock owner could not be validated'); } finally { if (fd !== undefined) try { fs.closeSync(fd); } catch {} } } function exactUnlink(path: string, token: string, identity: LockIdentity, links: LeaseLinks = 1): void { let current: { owner: LockOwner; identity: LockIdentity }; try { current = readOwner(path, token, links); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); } if (current.identity.dev !== identity.dev || current.identity.ino !== identity.ino) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); // One final pathname check narrows lstat/read/unlink replacement races. Lease // names are immutable and never reused by cooperating helpers. let final: ExactStats; try { final = exactLstat(path); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); } if ( final.isSymbolicLink() || final.dev !== identity.dev || final.ino !== identity.ino || final.nlink !== links ) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); try { fs.unlinkSync(path); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); } } function acquireMigrationClaim(path: string): { owner: LockOwner; identity: LockIdentity } { for (let attempt = 0; attempt < 4; attempt++) { recoverAtomicNoReplaceJson(path, { label: 'Legacy run mutation recovery claim', maxBytes: LOCK_OWNER_MAX_BYTES, validate: (value, pid) => { const owner = validateOwner(value); if (owner.pid !== pid) throw new CsoError('UNSAFE_PATH', 'Legacy recovery temp does not match its publisher'); }, }); const owner: LockOwner = { pid: process.pid, processIdentity: processIdentity(process.pid), token: randomBytes(16).toString('hex'), createdAt: Date.now(), }; try { atomicWriteSync(path, JSON.stringify(owner) + '\n', { mode: 0o600, noReplace: true }); return readOwner(path, owner.token); } catch (error: any) { if (error instanceof CsoError) throw error; if (error?.code !== 'EEXIST') throw new CsoError('PERSISTENCE_FAILED', 'Legacy run mutation recovery claim could not be published'); const stale = readOwner(path); if (ownerIsAlive(stale.owner)) throw new CsoError('INSUFFICIENT_CAPACITY', 'Another helper is recovering this run'); try { exactUnlink(path, stale.owner.token, stale.identity); } catch (recoveryError) { if (attempt === 3) throw recoveryError; } } } throw new CsoError('INSUFFICIENT_CAPACITY', 'Another helper is recovering this run'); } function ensureLockProtocol(dir: string): string { const lock = join(dir, '.mutation-lock'), marker = JSON.stringify({ protocol: LOCK_PROTOCOL }) + '\n'; try { atomicWriteSync(lock, marker, { mode: 0o600, noReplace: true }); } catch (error: any) { if (error?.code !== 'EEXIST') throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lock protocol could not be initialized'); recoverAtomicNoReplaceJson(lock, { label: 'Run mutation lock protocol', maxBytes: LOCK_OWNER_MAX_BYTES, validate: (value) => { if ( !value || typeof value !== 'object' || Array.isArray(value) || (value as any).protocol !== LOCK_PROTOCOL ) throw new CsoError('UNSAFE_PATH', 'Run mutation lock protocol is invalid'); }, }); const stat = exactLstat(lock); if (stat.isSymbolicLink()) throw new CsoError('UNSAFE_PATH', 'Run mutation lock is a symlink'); if (stat.isFile()) { let protocol = ''; try { if ( stat.nlink !== 1 || stat.size <= 0 || stat.size > LOCK_OWNER_MAX_BYTES || (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) ) throw new Error('invalid'); protocol = JSON.parse(fs.readFileSync(lock, 'utf8')).protocol; } catch {} if (protocol !== LOCK_PROTOCOL || stat.nlink !== 1 || (process.getuid && stat.uid !== process.getuid())) throw new CsoError('UNSAFE_PATH', 'Run mutation lock protocol is invalid'); } else if (stat.isDirectory()) { // v2 created the canonical directory before publishing owner.json. A // missing/malformed owner can still belong to a paused live initializer, // so it is never age-reclaimed. Fully published dead owners can migrate. const owner = readLegacyOwner(join(lock, 'owner.json')); if (ownerIsAlive(owner as LockOwner)) throw new CsoError('INSUFFICIENT_CAPACITY', 'Another helper is updating this run'); const migration = join(lock, '.v3-migration'), claim = acquireMigrationClaim(migration), current = exactLstat(lock); if (current.dev !== stat.dev || current.ino !== stat.ino) { try { exactUnlink(migration, claim.owner.token, claim.identity); } catch {} throw new CsoError('INSUFFICIENT_CAPACITY', 'Another helper changed this run during recovery'); } const tomb = join(dir, `.mutation-lock.legacy-${process.pid}-${randomBytes(4).toString('hex')}`); try { fs.renameSync(lock, tomb); atomicWriteSync(lock, marker, { mode: 0o600, noReplace: true }); fs.rmSync(tomb, { recursive: true, force: true }); } catch { try { if (!fs.existsSync(lock) && fs.existsSync(tomb)) fs.renameSync(tomb, lock); } catch {} try { if (fs.existsSync(migration)) exactUnlink(migration, claim.owner.token, claim.identity); } catch {} throw new CsoError('PERSISTENCE_FAILED', 'Legacy run mutation lock could not be migrated safely'); } } else throw new CsoError('UNSAFE_PATH', 'Run mutation lock has an invalid file type'); } const leases = join(dir, '.mutation-lock-leases'); if (!fs.existsSync(leases)) try { fs.mkdirSync(leases, { mode: 0o700 }); } catch (error: any) { if (error?.code !== 'EEXIST') throw error; } const stat = exactLstat(leases); if (stat.isSymbolicLink() || !stat.isDirectory() || (process.getuid && stat.uid !== process.getuid())) throw new CsoError('UNSAFE_PATH', 'Run mutation lease directory is invalid'); if (process.platform !== 'win32') fs.chmodSync(leases, 0o700); return leases; } type LeaseState = { token: string; owner: LockOwner; identity: LockIdentity; candidate?: string; decisionPath?: string; decision?: LeaseDecision; decisionIdentity?: LockIdentity; active?: string; number?: bigint; }; type HeldRunLease = { path: string; decision: string; decisionIdentity: LockIdentity; active: string; token: string; identity: LockIdentity; }; function privateLeaseArtifact(stat: ExactStats): boolean { return ( stat.isFile() && !stat.isSymbolicLink() && stat.size > 0 && stat.size <= LOCK_OWNER_MAX_BYTES && (!process.getuid || stat.uid === process.getuid()) && (process.platform === 'win32' || (stat.mode & 0o077) === 0) ); } function readLeaseDecision(path: string, token: string): { decision: LeaseDecision; identity: LockIdentity } { const options = leaseDecisionRecoveryOptions(token); recoverAtomicNoReplaceJson(path, options); const recovered = recoveryJson(path, 1, options); return { decision: validateLeaseDecision(recovered.value, token), identity: { dev: recovered.identity.dev, ino: recovered.identity.ino }, }; } function decisionMatchesIdentity(decision: LeaseDecision, identity: LockIdentity): boolean { return decision.candidateDev === String(identity.dev) && decision.candidateIno === String(identity.ino); } function decisionMatchesOwner(decision: LeaseDecision, owner: LockOwner): boolean { return ( decision.ownerPid === owner.pid && decision.ownerCreatedAt === owner.createdAt && decision.ownerProcessIdentity === owner.processIdentity ); } function scanRunLeases(leases: string): LeaseState[] { const deadline = Date.now() + LEASE_BLOCKED_WAIT_MS; let contention: CsoError | undefined; for (let attempt = 0; attempt < LEASE_BLOCKED_WAIT_MS / LEASE_ELECTION_POLL_MS + 16; attempt++) { contention = undefined; const names = fs.readdirSync(leases).sort(); if (names.some((name) => LEASE_PUBLICATION_TEMP.test(name))) { try { recoverLeasePublications(leases); contention = new CsoError( 'INSUFFICIENT_CAPACITY', 'Run mutation lease publication changed during the lease scan', ); } catch (error) { if (!(error instanceof CsoError) || error.code !== 'INSUFFICIENT_CAPACITY' || Date.now() >= deadline) throw error; contention = error; Atomics.wait(LEASE_ELECTION_WAIT, 0, 0, LEASE_ELECTION_POLL_MS); } continue; } const grouped = new Map< string, { candidate?: string; decision?: string; actives: Array<{ path: string; encoded: string }> } >(); for (const name of names) { const candidate = name.match(LEASE_CANDIDATE), decision = name.match(LEASE_DECISION), active = name.match(LEASE_ACTIVE), token = candidate?.[1] ?? decision?.[1] ?? active?.[1]; if (!token) throw new CsoError('UNSAFE_PATH', 'Run mutation lease directory contains an invalid artifact'); const group = grouped.get(token) ?? { actives: [] }; if (candidate) { if (group.candidate) throw new CsoError('UNSAFE_PATH', 'Run mutation lease has duplicate candidate state'); group.candidate = join(leases, name); } else if (decision) { if (group.decision) throw new CsoError('UNSAFE_PATH', 'Run mutation lease has duplicate decision state'); group.decision = join(leases, name); } else if (active) group.actives.push({ path: join(leases, name), encoded: active[2] }); grouped.set(token, group); } let retry = false; const states: LeaseState[] = []; for (const [token, group] of grouped) { if ( group.actives.length > 1 || (group.actives.length === 1 && !group.decision) || (!group.candidate && !group.decision) ) { retry = true; break; } let decisionRecord: { decision: LeaseDecision; identity: LockIdentity } | undefined; if (group.decision) try { decisionRecord = readLeaseDecision(group.decision, token); } catch (error) { if ( error instanceof CsoError && (error.code === 'SNAPSHOT_RACE' || error.code === 'INSUFFICIENT_CAPACITY') ) { if (error.code === 'INSUFFICIENT_CAPACITY') { if (Date.now() >= deadline) throw error; contention = error; } retry = true; break; } throw error; } if ( group.actives[0] && (!decisionRecord || decisionRecord.decision.kind !== 'ticket' || decisionRecord.decision.ticket !== group.actives[0].encoded) ) throw new CsoError( 'UNSAFE_PATH', 'Run mutation lease active phase does not match its ticket decision', ); const ownerPath = group.candidate ?? group.actives[0]?.path; let inspected: { owner: LockOwner; identity: LockIdentity } | undefined; if (ownerPath) { const expected = (group.candidate && group.actives[0] ? 2 : 1) as LeaseLinks; let observed: ExactStats; try { observed = exactLstat(ownerPath); } catch (error: any) { if (error?.code === 'ENOENT') { retry = true; break; } throw error; } if (!privateLeaseArtifact(observed)) { throw new CsoError('UNSAFE_PATH', 'Run mutation lease owner phase is not one private regular file'); } if (observed.nlink !== expected) { retry = true; break; } try { inspected = readOwner(ownerPath, token, expected, observed); } catch (error) { if (error instanceof CsoError && error.code === 'INSUFFICIENT_CAPACITY') { if (Date.now() >= deadline) throw error; contention = error; retry = true; break; } throw error; } } if (group.candidate && group.actives[0]) { let activeStat: ExactStats; try { activeStat = exactLstat(group.actives[0].path); } catch (error: any) { if (error?.code === 'ENOENT') { retry = true; break; } throw error; } if ( !privateLeaseArtifact(activeStat) || activeStat.dev !== inspected!.identity.dev || activeStat.ino !== inspected!.identity.ino ) throw new CsoError( 'UNSAFE_PATH', 'Run mutation lease active phase does not match its candidate inode', ); if (activeStat.nlink !== 2) { retry = true; break; } } const identity = inspected?.identity ?? { dev: BigInt(decisionRecord!.decision.candidateDev), ino: BigInt(decisionRecord!.decision.candidateIno), }, owner = inspected?.owner ?? decisionOwner(decisionRecord!.decision); if ( decisionRecord && (!decisionMatchesIdentity(decisionRecord.decision, identity) || !decisionMatchesOwner(decisionRecord.decision, owner)) ) throw new CsoError('UNSAFE_PATH', 'Run mutation lease decision does not match its candidate owner'); const number = decisionRecord?.decision.kind === 'ticket' ? BigInt(`0x${decisionRecord.decision.ticket}`) : undefined; states.push({ token, owner, identity, ...(group.candidate ? { candidate: group.candidate } : {}), ...(group.decision && decisionRecord ? { decisionPath: group.decision, decision: decisionRecord.decision, decisionIdentity: decisionRecord.identity, } : {}), ...(group.actives[0] ? { active: group.actives[0].path } : {}), ...(number !== undefined ? { number } : {}), }); } if (!retry && fs.readdirSync(leases).sort().join('\0') === names.join('\0')) return states; Atomics.wait(LEASE_ELECTION_WAIT, 0, 0, LEASE_ELECTION_POLL_MS); } if (contention) throw contention; throw new CsoError('UNSAFE_PATH', 'Run mutation lease phases could not be validated as one coherent set'); } function releaseLeaseState(state: LeaseState): void { if (state.candidate) exactUnlink(state.candidate, state.token, state.identity, state.active ? 2 : 1); if (state.active) exactUnlink(state.active, state.token, state.identity, 1); if (state.decisionPath && state.decisionIdentity) exactDecisionUnlink(state.decisionPath, state.token, state.decisionIdentity); } function exactDecisionUnlink(path: string, token: string, identity: LockIdentity): void { let current: { decision: LeaseDecision; identity: LockIdentity }; try { current = readLeaseDecision(path, token); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before exact release'); } if (current.identity.dev !== identity.dev || current.identity.ino !== identity.ino) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before exact release'); let final: ExactStats; try { final = exactLstat(path); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before exact release'); } if ( !privateLeaseArtifact(final) || final.nlink !== 1 || final.dev !== identity.dev || final.ino !== identity.ino ) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before exact release'); try { fs.unlinkSync(path); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before exact release'); } } function publishLeasePhase(candidate: string, target: string, token: string, identity: LockIdentity): void { const before = readOwner(candidate, token, 1); if (before.identity.dev !== identity.dev || before.identity.ino !== identity.ino) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease changed before phase publication'); try { fs.linkSync(candidate, target); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease phase could not be published'); } const source = exactLstat(candidate), phase = exactLstat(target); if ( source.dev !== identity.dev || source.ino !== identity.ino || phase.dev !== identity.dev || phase.ino !== identity.ino || source.nlink !== 2 || phase.nlink !== 2 ) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease phase changed during publication'); } function makeLeaseDecision( owner: LockOwner, identity: LockIdentity, kind: 'ticket' | 'withdraw', ticket?: string, ): LeaseDecision { const publisherIdentity = processIdentity(process.pid); return { schemaVersion: 1, token: owner.token, kind, ...(ticket ? { ticket } : {}), candidateDev: String(identity.dev), candidateIno: String(identity.ino), ownerPid: owner.pid, ...(owner.processIdentity ? { ownerProcessIdentity: owner.processIdentity } : {}), ownerCreatedAt: owner.createdAt, publisherPid: process.pid, ...(publisherIdentity ? { publisherProcessIdentity: publisherIdentity } : {}), createdAt: Date.now(), }; } function publishLeaseDecision(path: string, decision: LeaseDecision): void { try { atomicWriteSync(path, JSON.stringify(decision) + '\n', { mode: 0o600, noReplace: true }); } catch (error: any) { if (error?.code !== 'EEXIST') throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision could not be published'); } } function releaseKnownLease( candidate: string, decisionPath: string, active: string | undefined, token: string, identity: LockIdentity, expectedDecisionIdentity?: LockIdentity, requireActive = false, ): void { let candidateStat: ExactStats; try { candidateStat = exactLstat(candidate); } catch { throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); } if ( !privateLeaseArtifact(candidateStat) || candidateStat.dev !== identity.dev || candidateStat.ino !== identity.ino ) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before exact release'); let activeStat: ExactStats | undefined; try { if (active) activeStat = exactLstat(active); } catch (error: any) { if (error?.code !== 'ENOENT') throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease active phase changed before cleanup'); } if (requireActive && !activeStat) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease active phase changed before exact release'); if ( activeStat && (!privateLeaseArtifact(activeStat) || activeStat.dev !== identity.dev || activeStat.ino !== identity.ino) ) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease active phase changed before cleanup'); const expected = activeStat ? 2 : 1; if (candidateStat.nlink !== expected || (activeStat && activeStat.nlink !== 2)) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease link state changed before cleanup'); let decisionRecord: { decision: LeaseDecision; identity: LockIdentity } | undefined; try { decisionRecord = readLeaseDecision(decisionPath, token); } catch (error: any) { if (!(error instanceof CsoError) || error.code !== 'SNAPSHOT_RACE') throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before cleanup'); } if (expectedDecisionIdentity && !decisionRecord) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before exact release'); if ( decisionRecord && (!decisionMatchesIdentity(decisionRecord.decision, identity) || decisionRecord.decision.ownerPid !== process.pid || (expectedDecisionIdentity && (decisionRecord.identity.dev !== expectedDecisionIdentity.dev || decisionRecord.identity.ino !== expectedDecisionIdentity.ino))) ) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease decision changed before cleanup'); exactUnlink(candidate, token, identity, expected); if (activeStat) exactUnlink(active!, token, identity, 1); if (decisionRecord) exactDecisionUnlink(decisionPath, token, decisionRecord.identity); } function compareLeaseOrder(left: LeaseState, rightNumber: bigint, rightToken: string): number { return left.number! < rightNumber ? -1 : left.number! > rightNumber ? 1 : left.token < rightToken ? -1 : left.token > rightToken ? 1 : 0; } function recoverDeadLease(state: LeaseState): boolean { if (ownerIsAlive(state.owner)) return false; try { releaseLeaseState(state); } catch (error) { if (!(error instanceof CsoError) || error.code !== 'PERSISTENCE_FAILED') throw error; for (const path of [state.candidate, state.active].filter((value): value is string => Boolean(value))) { try { const stat = exactLstat(path); if (!privateLeaseArtifact(stat) || stat.dev !== state.identity.dev || stat.ino !== state.identity.ino) throw new CsoError('UNSAFE_PATH', 'Dead run mutation lease was replaced during recovery'); } catch (recoveryError: any) { if (recoveryError instanceof CsoError) throw recoveryError; if (recoveryError?.code !== 'ENOENT') throw recoveryError; } } if (state.decisionPath && state.decisionIdentity) try { const stat = exactLstat(state.decisionPath); if ( !privateLeaseArtifact(stat) || stat.dev !== state.decisionIdentity.dev || stat.ino !== state.decisionIdentity.ino ) throw new CsoError('UNSAFE_PATH', 'Dead run mutation lease decision was replaced during recovery'); } catch (recoveryError: any) { if (recoveryError instanceof CsoError) throw recoveryError; if (recoveryError?.code !== 'ENOENT') throw recoveryError; } Atomics.wait(LEASE_ELECTION_WAIT, 0, 0, LEASE_ELECTION_POLL_MS); } return true; } function chooseRunLeaseTicket( leases: string, token: string, owner: LockOwner, identity: LockIdentity, ): { path: string; number: bigint; identity: LockIdentity } { for (;;) { const states = scanRunLeases(leases); let recovered = false, max = 0n; const own = states.find((state) => state.token === token); if (!own?.candidate || own.identity.dev !== identity.dev || own.identity.ino !== identity.ino) throw new CsoError( 'PERSISTENCE_FAILED', 'Run mutation lease candidate changed before ticket selection', ); if (own.decision) { if (own.decision.kind === 'withdraw') throw new CsoError( 'INSUFFICIENT_CAPACITY', 'This run mutation lease was withdrawn before ticket selection', ); if (own.number === undefined || !own.decisionPath || !own.decisionIdentity) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ticket decision is incomplete'); return { path: own.decisionPath, number: own.number, identity: own.decisionIdentity }; } for (const state of states) { if (state.token === token) continue; if (recoverDeadLease(state)) { recovered = true; break; } if (state.active) throw new CsoError( 'INSUFFICIENT_CAPACITY', state.owner.pid === process.pid ? 'Another operation in this helper is updating this run' : 'Another helper is updating this run', ); if (!state.candidate || state.decision?.kind === 'withdraw') continue; if (state.owner.pid === process.pid) throw new CsoError('INSUFFICIENT_CAPACITY', 'Another operation in this helper is updating this run'); if (state.number !== undefined && state.number > max) max = state.number; } if (recovered) continue; const number = max + 1n; if (number > 0xffffffffffffffffn) throw new CsoError('INSUFFICIENT_CAPACITY', 'Run mutation lease ticket space is exhausted'); const encoded = number.toString(16).padStart(16, '0'), path = join(leases, `${token}.decision`); publishLeaseDecision(path, makeLeaseDecision(owner, identity, 'ticket', encoded)); } } function fenceRunLeaseCandidate(leases: string, state: LeaseState): void { if (!state.candidate || state.decision) return; const observed = readOwner(state.candidate, state.token, 1); if (observed.identity.dev !== state.identity.dev || observed.identity.ino !== state.identity.ino) throw new CsoError('UNSAFE_PATH', 'Run mutation lease candidate changed before withdrawal'); publishLeaseDecision( join(leases, `${state.token}.decision`), makeLeaseDecision(state.owner, state.identity, 'withdraw'), ); } function activateRunLease( leases: string, token: string, candidate: string, decisionPath: string, active: string, number: bigint, identity: LockIdentity, ): void { const blockedDeadline = Date.now() + LEASE_BLOCKED_WAIT_MS; for (;;) { const states = scanRunLeases(leases), own = states.find((state) => state.token === token); if ( !own || own.candidate !== candidate || own.decisionPath !== decisionPath || own.decision?.kind !== 'ticket' || own.number !== number || own.active || own.identity.dev !== identity.dev || own.identity.ino !== identity.ino ) throw new CsoError( own?.decision?.kind === 'withdraw' ? 'INSUFFICIENT_CAPACITY' : 'PERSISTENCE_FAILED', own?.decision?.kind === 'withdraw' ? 'This run mutation lease was withdrawn before activation' : 'Run mutation lease ticket changed before activation', ); let retry = false, lost = false; const pending: LeaseState[] = []; for (const state of states) { if (state.token === token) continue; if (recoverDeadLease(state)) { retry = true; break; } if (state.active) throw new CsoError( 'INSUFFICIENT_CAPACITY', state.owner.pid === process.pid ? 'Another operation in this helper is updating this run' : 'Another helper is updating this run', ); if (!state.candidate || state.decision?.kind === 'withdraw') continue; if (state.owner.pid === process.pid) throw new CsoError('INSUFFICIENT_CAPACITY', 'Another operation in this helper is updating this run'); if (state.number === undefined) pending.push(state); else if (compareLeaseOrder(state, number, token) < 0) lost = true; } if (retry) continue; if (lost) throw new CsoError('INSUFFICIENT_CAPACITY', 'An earlier run mutation lease ticket won the election'); if (pending.length) { if (Date.now() < blockedDeadline) { Atomics.wait(LEASE_ELECTION_WAIT, 0, 0, LEASE_ELECTION_POLL_MS); continue; } for (const state of pending) fenceRunLeaseCandidate(leases, state); continue; } publishLeasePhase(candidate, active, token, identity); const verified = scanRunLeases(leases), current = verified.find((state) => state.token === token); if ( !current || current.candidate !== candidate || current.decisionPath !== decisionPath || current.decision?.kind !== 'ticket' || current.number !== number || current.active !== active || current.identity.dev !== identity.dev || current.identity.ino !== identity.ino || verified.some((state) => state.token !== token && state.active) ) throw new CsoError( 'PERSISTENCE_FAILED', 'Run mutation lease activation could not be verified exclusively', ); return; } } function acquireRunLease(dir: string): HeldRunLease { secureDirectory(dir); const leases = ensureLockProtocol(dir); recoverLeasePublications(leases); const token = randomBytes(16).toString('hex'), lease = join(leases, `${token}.json`), owner: LockOwner = { pid: process.pid, processIdentity: processIdentity(process.pid), token, createdAt: Date.now(), }; atomicWriteSync(lease, JSON.stringify(owner) + '\n', { mode: 0o600, noReplace: true }); const ownStat = exactLstat(lease), ownIdentity = { dev: ownStat.dev, ino: ownStat.ino }; const decision = join(leases, `${token}.decision`); let decisionIdentity: LockIdentity | undefined, active: string | undefined; try { const chosen = chooseRunLeaseTicket(leases, token, owner, ownIdentity); decisionIdentity = chosen.identity; active = join(leases, `${token}.active.${chosen.number.toString(16).padStart(16, '0')}`); activateRunLease(leases, token, lease, decision, active, chosen.number, ownIdentity); const published = readOwner(active, token, 2); if (published.identity.dev !== ownIdentity.dev || published.identity.ino !== ownIdentity.ino) throw new CsoError('PERSISTENCE_FAILED', 'Run mutation lease ownership changed before work began'); } catch (error) { try { releaseKnownLease(lease, decision, active, token, ownIdentity, decisionIdentity); } catch (releaseError) { throw releaseError; } throw error; } return { path: lease, decision, decisionIdentity: decisionIdentity!, active, token, identity: ownIdentity }; } function releaseRunLease(lease: HeldRunLease, path = lease.path): void { const directory = dirname(path); releaseKnownLease( path, join(directory, basename(lease.decision)), join(directory, basename(lease.active)), lease.token, lease.identity, lease.decisionIdentity, true, ); } export function withLock(dir: string, fn: () => T): T | Promise> { const lease = acquireRunLease(dir), unlock = () => releaseRunLease(lease); let value: T; try { value = fn(); } catch (error) { try { unlock(); } catch (releaseError) { throw releaseError; } throw error; } if (value && typeof (value as any).then === 'function') return Promise.resolve(value).finally(unlock) as Promise>; // Keep release errors outside the callback catch path. Retrying an exact // release after it partially succeeds can only obscure which lease phase // changed and attempts the same fail-closed cleanup twice. unlock(); return value as any; } function boundedMarker(path: string, admit: () => void = () => {}): string { admit(); try { const stat = fs.lstatSync(path); if (stat.isSymbolicLink() || !stat.isFile() || stat.size > 8192) return ''; return fs.readFileSync(path, 'utf8'); } catch { return ''; } } /** A detached watchdog owns these paths until it records exact cleanup or an acknowledgement. */ export function hasPendingWatchdogCleanup(dir: string, admit: () => void = () => {}): boolean { let visited = 0, pending = false; const walk = (at: string, depth: number) => { if (pending || depth > 6 || visited++ > 4000) return; const entries: fs.Dirent[] = []; let directory: fs.Dir; admit(); try { directory = fs.opendirSync(at); } catch { return; } try { for (;;) { admit(); const entry = directory.readSync(); if (!entry) break; entries.push(entry); } } finally { directory.closeSync(); } const names = new Set(entries.map((entry) => entry.name)); if ( names.has('attempt.ready') && !names.has('attempt.stopped') && !boundedMarker(join(at, 'attempt.event'), admit).includes('execution-copy cleanup complete') ) { pending = true; return; } if ( names.has('watchdog.ready') && !names.has('watchdog.stopped') && !boundedMarker(join(at, 'watchdog.event'), admit).includes('cleanup complete') ) { pending = true; return; } for (const entry of entries) { if (!/^[A-Za-z0-9._-]{1,120}$/.test(entry.name) || !entry.isDirectory()) continue; walk(join(at, entry.name), depth + 1); if (pending) return; } }; for (const name of ['supervision', 'preparation-execution']) { admit(); const root = join(dir, name); if (!fs.existsSync(root)) continue; admit(); const rootStat = fs.lstatSync(root); if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) throw new CsoError('UNSAFE_PATH', 'Watchdog supervision state is not a private directory'); walk(root, 0); if (pending) return true; } return false; } const EPHEMERAL_REPLAY = '.ephemeral-replay.json'; /** Delete a replay-only snapshot unless detached cleanup still owns its control tree. */ export function finalizeReplayTemporary(dir: string): void { if (hasPendingWatchdogCleanup(dir)) { writeJsonExclusive(join(dir, EPHEMERAL_REPLAY), { schemaVersion: 1, kind: 'replay-temporary', retainedAt: new Date().toISOString(), }); return; } fs.rmSync(dir, { recursive: true, force: true }); } /** Remove one private tree cooperatively without following links or holding directory handles across checks. */ function boundedRemoveTree(root: string, admit: () => void, preserveRootName?: string): void { type Frame = { path: string; root: boolean; names?: string[]; index: number }; const stack: Frame[] = [{ path: root, root: true, index: 0 }]; while (stack.length) { const frame = stack[stack.length - 1]; if (!frame.names) { admit(); let stat: fs.Stats; try { stat = fs.lstatSync(frame.path); } catch (error: any) { if (error?.code === 'ENOENT') { stack.pop(); continue; } throw error; } if (stat.isSymbolicLink() || !stat.isDirectory()) { admit(); fs.unlinkSync(frame.path); stack.pop(); continue; } const names: string[] = []; admit(); const directory = fs.opendirSync(frame.path); try { for (;;) { admit(); const entry = directory.readSync(); if (!entry) break; if (!(frame.root && entry.name === preserveRootName)) names.push(entry.name); } } finally { directory.closeSync(); } frame.names = names; frame.index = 0; } if (frame.index < frame.names.length) { const name = frame.names[frame.index++]; stack.push({ path: join(frame.path, name), root: false, index: 0 }); continue; } if (frame.root && preserveRootName) return; admit(); fs.rmdirSync(frame.path); stack.pop(); } } function consumeLeasedTree(root: string, admit: () => void): void { boundedRemoveTree(root, admit, '.mutation-lock-leases'); admit(); const directory = fs.opendirSync(root); try { for (;;) { admit(); const entry = directory.readSync(); if (!entry) break; if (entry.name !== '.mutation-lock-leases') throw new CsoError('SNAPSHOT_RACE', 'Private retention tree changed during bounded cleanup'); } } finally { directory.closeSync(); } // Only the helper's fixed-size lease protocol remains. Consuming it with the // directory preserves the exact-release invariant without an unbounded walk. admit(); fs.rmSync(root, { recursive: true, force: true }); } function repairBundleExpiry( dir: string, run: string, now: number, runExpired: boolean, admit: () => void, ): boolean { admit(); const bundles = join(dir, 'bundles'); if (!fs.existsSync(bundles)) return false; admit(); const stat = fs.lstatSync(bundles); if (stat.isSymbolicLink() || !stat.isDirectory()) throw new CsoError('UNSAFE_PATH', 'Repair bundle archive is not a private directory'); let retained = false, remaining = 0; admit(); const directory = fs.opendirSync(bundles); try { for (;;) { admit(); const entry = directory.readSync(); if (!entry) break; const name = entry.name; const match = name.match(/^([a-f0-9]{32})\.json$/); if (!match) { if (runExpired) boundedRemoveTree(join(bundles, name), admit); else remaining++; continue; } admit(); const value = readJson(join(bundles, name)) as Record, id = match[1], created = Date.parse(value?.createdAt), expires = Date.parse(value?.expiresAt); if ( value?.schemaVersion !== 3 || value?.id !== id || value?.runId !== run || value?.verification?.id !== id || value?.verification?.runId !== run || value?.verification?.createdAt !== value.createdAt || !Number.isFinite(created) || new Date(created).toISOString() !== value.createdAt || !Number.isFinite(expires) || value.expiresAt !== new Date(created + 30 * 86400_000).toISOString() ) throw new CsoError('INCOMPATIBLE_INPUT', 'Repair bundle retention identity is invalid'); if (expires <= now) { admit(); fs.unlinkSync(join(bundles, name)); } else { retained = true; remaining++; } } } finally { directory.closeSync(); } if (!remaining) { admit(); fs.rmdirSync(bundles); } return retained; } function cleanupRun(dir: string, run: string, now: number, pinned: boolean, admit: () => void): void { admit(); let lease: HeldRunLease; try { lease = acquireRunLease(dir); } catch (error) { if (error instanceof CsoError && error.code === 'INSUFFICIENT_CAPACITY') return; throw error; } let releasePath = lease.path, consumed = false; try { if (hasPendingWatchdogCleanup(dir, admit)) return; const created = Number(run.split('-')[0]), runExpired = now - created > 30 * 86400_000, retainedBundle = repairBundleExpiry(dir, run, now, runExpired, admit); admit(); const ephemeral = fs.existsSync(join(dir, EPHEMERAL_REPLAY)); if (ephemeral || (runExpired && !pinned && !retainedBundle)) { admit(); const before = exactLstat(dir), tomb = join(dirname(dir), `.retired-${run}-${randomBytes(16).toString('hex')}`); fs.renameSync(dir, tomb); releasePath = join(tomb, '.mutation-lock-leases', basename(lease.path)); admit(); const after = exactLstat(tomb); if (before.dev !== after.dev || before.ino !== after.ino) throw new CsoError('SNAPSHOT_RACE', 'Expired run changed while it was retired'); // The retired name is outside the public run namespace. Consume the // exclusive lease with the tree so no release/delete gap can admit a // second helper against the same directory. consumeLeasedTree(tomb, admit); consumed = true; return; } if (now - created > 7 * 86400_000) for (const p of ['snapshot', 'readable']) boundedRemoveTree(join(dir, p), admit); if (runExpired) for (const p of [ 'reviews', 'replays', 'dependency-closures', 'scanner-outcomes', 'verification-attempts', ]) boundedRemoveTree(join(dir, p), admit); } finally { if (!consumed) releaseRunLease(lease, releasePath); } } function cleanupRetiredRun(dir: string, admit: () => void): void { admit(); let lease: HeldRunLease; try { lease = acquireRunLease(dir); } catch (error) { if (error instanceof CsoError && error.code === 'INSUFFICIENT_CAPACITY') return; throw error; } let consumed = false; try { if (hasPendingWatchdogCleanup(dir, admit)) return; consumeLeasedTree(dir, admit); consumed = true; } finally { if (!consumed) releaseRunLease(lease); } } export interface RetentionOptions { deadlineMs?: number; maxEntries?: number; } export interface RetentionResult { complete: boolean; visited: number; } class RetentionBudgetExhausted extends Error {} export function retention(now = Date.now(), options: RetentionOptions = {}): RetentionResult { const deadlineMs = options.deadlineMs ?? Number.MAX_SAFE_INTEGER, maxEntries = options.maxEntries ?? Number.MAX_SAFE_INTEGER; if ( !Number.isSafeInteger(deadlineMs) || deadlineMs < 0 || !Number.isSafeInteger(maxEntries) || maxEntries < 1 ) throw new CsoError('INVALID_ARGUMENT', 'Invalid retention maintenance budget'); let visited = 0; const admit = () => { if (Date.now() >= deadlineMs || visited >= maxEntries) throw new RetentionBudgetExhausted(); visited++; }; const names = (dir: string, pattern: RegExp): string[] => { const found: string[] = []; admit(); const directory = fs.opendirSync(dir); try { for (;;) { admit(); const entry = directory.readSync(); if (!entry) break; if (pattern.test(entry.name)) found.push(entry.name); } } finally { directory.closeSync(); } return found; }; const root = privateRoot(); const retainedParents = new Set(), repositories: { repo: string; repoDir: string; runs: string[]; retired: string[] }[] = []; const retainedReport = (repoDir: string, repo: string, run: string): RunReportV3 | undefined => { const file = join(repoDir, run, 'report.json'); try { admit(); const stat = fs.lstatSync(file); if ( !stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size <= 0 || stat.size > MAX_STATE_FILE || (process.getuid && stat.uid !== process.getuid()) || (process.platform !== 'win32' && (stat.mode & 0o077) !== 0) ) return; admit(); const report = JSON.parse(fs.readFileSync(file, 'utf8')); if ( report?.schemaVersion !== 3 || report.runId !== run || report.repoId !== repo || !Array.isArray(report.coverage) || !Array.isArray(report.findings) || !['running', 'finished', 'interrupted'].includes(report.status) ) return; return report as RunReportV3; } catch (error) { if (error instanceof RetentionBudgetExhausted) throw error; return; } }; try { for (const repo of names(root, /^[a-f0-9]{24}$/)) { admit(); const repoDir = secureDirectory(join(root, repo)), entries = names(repoDir, /^(?:\d{13}-[a-f0-9]{16}|\.retired-\d{13}-[a-f0-9]{16}-[a-f0-9]{32})$/), runs = entries.filter((x) => /^\d{13}-/.test(x)), retired = entries.filter((x) => x.startsWith('.retired-')); repositories.push({ repo, repoDir, runs, retired }); } // Discover every live recheck pin before destructive cleanup. An exhausted // discovery pass returns without deleting a parent that may still be in use. for (const { repo, repoDir, runs } of repositories) for (const run of runs) { if (now - Number(run.split('-')[0]) > 30 * 86400_000) continue; const report = retainedReport(repoDir, repo, run), parent = report?.parent as Record | undefined; if ( !report || !['running', 'interrupted'].includes(report.status) || !Number.isFinite(Date.parse(report.deadline)) || Date.parse(report.deadline) <= now || !parent || typeof parent !== 'object' || Array.isArray(parent) || Object.keys(parent).sort().join(',') !== 'findingId,kind,runId' || parent.kind !== 'recheck' || typeof parent.runId !== 'string' || !/^\d{13}-[a-f0-9]{16}$/.test(parent.runId) || parent.runId === run || typeof parent.findingId !== 'string' || !/^[a-f0-9]{32}$/.test(parent.findingId) ) continue; const original = retainedReport(repoDir, repo, parent.runId); if (original?.status === 'finished' && original.findings.some((f) => f.id === parent.findingId)) retainedParents.add(`${repo}/${parent.runId}`); } for (const { repo, repoDir, runs, retired } of repositories) { for (const name of retired) cleanupRetiredRun(join(repoDir, name), admit); for (const run of runs) { admit(); const dir = secureDirectory(join(repoDir, run)); // Every destructive retention decision owns the same exclusive lease as // writers and replay. Whole runs are atomically retired before release. cleanupRun(dir, run, now, retainedParents.has(`${repo}/${run}`), admit); } } admit(); const legacy = join(root, 'legacy-imports'); if (fs.existsSync(legacy)) { admit(); const directory = fs.lstatSync(legacy); if (directory.isSymbolicLink() || !directory.isDirectory()) throw new CsoError('UNSAFE_PATH', 'Legacy report archive is not a private directory'); for (const name of names(legacy, /^[a-f0-9]{64}\.json$/)) { admit(); const file = join(legacy, name), stat = fs.lstatSync(file); if (stat.isSymbolicLink() || !stat.isFile() || (process.getuid && stat.uid !== process.getuid())) throw new CsoError('UNSAFE_PATH', 'Legacy report archive contains an unsafe artifact'); admit(); if (now - stat.mtimeMs > 30 * 86400_000) fs.unlinkSync(file); } } return { complete: true, visited }; } catch (error) { if (error instanceof RetentionBudgetExhausted) return { complete: false, visited }; throw error; } }