import { spawn } from "node:child_process"; import { randomUUID as nodeRandomUUID } from "node:crypto"; import { chmod, mkdir, mkdtemp, readFile, readdir, readlink, rename, rm, stat, unlink, } from "node:fs/promises"; import { hostname as localHostname } from "node:os"; import { isAbsolute, join, normalize } from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { readJson, syncDirectory, writeAtomicJson, } from "./atomic-json.js"; const OWNER_KEYS = [ "schema", "generation", "piSessionId", "piSessionFile", "pid", "processStartIdentity", "bootIdentity", "executable", "hostname", "rootId", "createdAt", ] as const; const GUARD_KEYS = [ "schema", "generation", "pid", "processStartIdentity", "bootIdentity", "executable", "hostname", "createdAt", ] as const; export interface ProcessIdentity { readonly pid: number; readonly processStartIdentity: string; readonly bootIdentity: string | null; readonly executable: string; readonly hostname: string; } export type ProcessInspection = | { readonly kind: "live"; readonly identity: ProcessIdentity } | { readonly kind: "absent" } | { readonly kind: "unknown"; readonly diagnostic: string }; export interface ProcessIdentityInspector { current(): Promise; inspect(pid: number): Promise; } export interface ReceiverLeaseOwnerV1 { readonly schema: 1; readonly generation: string; readonly piSessionId: string; readonly piSessionFile: string; readonly pid: number; readonly processStartIdentity: string; readonly bootIdentity: string | null; readonly executable: string; readonly hostname: string; readonly rootId: string; readonly createdAt: string; } interface LifecycleGuardV1 { readonly schema: 1; readonly generation: string; readonly pid: number; readonly processStartIdentity: string; readonly bootIdentity: string | null; readonly executable: string; readonly hostname: string; readonly createdAt: string; } export interface ReceiverLeaseManagerOptions { readonly namespaceDirectory: string; readonly rootId: string; readonly inspector: ProcessIdentityInspector; readonly randomUUID?: () => string; readonly now?: () => Date; readonly guardWaitMs?: number; } export type ReceiverLeaseRefusalReason = | "remote-owner" | "unverifiable-owner" | "corrupt-owner" | "lifecycle-busy" | "lifecycle-unverifiable"; export type ReceiverLeaseAcquireResult = | { readonly kind: "acquired"; readonly owner: ReceiverLeaseOwnerV1; readonly reclaimed?: { readonly owner: ReceiverLeaseOwnerV1; readonly quarantinePath: string; }; } | { readonly kind: "held"; readonly owner: ReceiverLeaseOwnerV1 } | { readonly kind: "refused"; readonly reason: ReceiverLeaseRefusalReason; readonly diagnostic: string; }; export type ReceiverLeaseReleaseResult = | { readonly kind: "released" } | { readonly kind: "not-owner"; readonly owner?: ReceiverLeaseOwnerV1 } | { readonly kind: "refused"; readonly diagnostic: string }; export type ReceiverLeaseProjection = | { readonly kind: "empty" } | { readonly kind: "owned"; readonly owner: ReceiverLeaseOwnerV1 } | { readonly kind: "corrupt"; readonly diagnostic: string }; export class LeaseFenceError extends Error { constructor(message: string) { super(message); this.name = "LeaseFenceError"; } } interface AcquireRequest { readonly piSessionId: string; readonly piSessionFile: string; } type OwnerState = | { readonly kind: "live" } | { readonly kind: "stale"; readonly reason: string } | { readonly kind: "remote" } | { readonly kind: "unknown"; readonly diagnostic: string }; type GuardResult = | { readonly ok: true; readonly value: T } | { readonly ok: false; readonly reason: ReceiverLeaseRefusalReason; readonly diagnostic: string }; /** Owns the generation-fenced receiver lifecycle inside one extension namespace. */ export class ReceiverLeaseManager { readonly #namespaceDirectory: string; readonly #rootId: string; readonly #inspector: ProcessIdentityInspector; readonly #randomUUID: () => string; readonly #now: () => Date; readonly #guardWaitMs: number; constructor(options: ReceiverLeaseManagerOptions) { if (!isAbsolute(options.namespaceDirectory)) { throw new TypeError("Receiver lease namespace must be absolute"); } if (options.rootId.length === 0) throw new TypeError("Receiver rootId is required"); this.#namespaceDirectory = normalize(options.namespaceDirectory); this.#rootId = options.rootId; this.#inspector = options.inspector; this.#randomUUID = options.randomUUID ?? nodeRandomUUID; this.#now = options.now ?? (() => new Date()); this.#guardWaitMs = options.guardWaitMs ?? 1_000; } async acquire( request: AcquireRequest, priorReclaim?: { readonly owner: ReceiverLeaseOwnerV1; readonly quarantinePath: string }, ): Promise { validateAcquireRequest(request); await this.#initialize(); const guarded = await this.#withLifecycleGuard(async (current) => { const projection = await this.inspectLease(); if (projection.kind === "corrupt") { return { kind: "refused" as const, reason: "corrupt-owner" as const, diagnostic: projection.diagnostic, }; } if (projection.kind === "empty") { const owner = ownerFromRequest( request, current, this.#rootId, this.#randomUUID(), this.#now(), ); const created = await this.#createReceiver(owner); return created ? { kind: "acquired" as const, owner, ...(priorReclaim ? { reclaimed: priorReclaim } : {}), } : { kind: "retry" as const }; } const state = await classifyOwner(projection.owner, current, this.#inspector); switch (state.kind) { case "live": return { kind: "held" as const, owner: projection.owner }; case "remote": return { kind: "refused" as const, reason: "remote-owner" as const, diagnostic: `Receiver owner is on remote host ${projection.owner.hostname}`, }; case "unknown": return { kind: "refused" as const, reason: "unverifiable-owner" as const, diagnostic: state.diagnostic, }; case "stale": { const reread = await this.inspectLease(); if (reread.kind !== "owned" || !sameOwner(reread.owner, projection.owner)) { return { kind: "retry" as const }; } const quarantinePath = join( this.#namespaceDirectory, "quarantine", `receiver.${safePathPart(projection.owner.generation)}.${this.#now().getTime()}.${safePathPart(this.#randomUUID())}`, ); await rename(this.#receiverPath(), quarantinePath); await syncDirectory(this.#namespaceDirectory); return { kind: "reclaimed" as const, owner: projection.owner, quarantinePath, }; } } }); if (!guarded.ok) { return { kind: "refused", reason: guarded.reason, diagnostic: guarded.diagnostic }; } if (guarded.value.kind === "retry") return this.acquire(request, priorReclaim); if (guarded.value.kind === "reclaimed") { return this.acquire(request, { owner: guarded.value.owner, quarantinePath: guarded.value.quarantinePath, }); } return guarded.value; } async release(generation: string): Promise { requireNonEmpty(generation, "lease generation"); await this.#initialize(); const guarded = await this.#withLifecycleGuard(async () => { const projection = await this.inspectLease(); if (projection.kind === "empty") return { kind: "not-owner" as const }; if (projection.kind === "corrupt") { return { kind: "refused" as const, diagnostic: projection.diagnostic }; } if (projection.owner.generation !== generation || projection.owner.rootId !== this.#rootId) { return { kind: "not-owner" as const, owner: projection.owner }; } const releasedPath = join( this.#namespaceDirectory, `.receiver.released-${safePathPart(generation)}-${safePathPart(this.#randomUUID())}`, ); await rename(this.#receiverPath(), releasedPath); await syncDirectory(this.#namespaceDirectory); await rm(releasedPath, { recursive: true }); await syncDirectory(this.#namespaceDirectory); return { kind: "released" as const }; }); return guarded.ok ? guarded.value : { kind: "refused", diagnostic: guarded.diagnostic }; } async inspectLease(): Promise { let raw: unknown; try { raw = await readJson(join(this.#receiverPath(), "owner.json")); } catch (error) { return { kind: "corrupt", diagnostic: `Receiver owner JSON is invalid: ${toError(error).message}` }; } if (raw === undefined) { try { await stat(this.#receiverPath()); } catch (error) { if (isNodeError(error) && error.code === "ENOENT") return { kind: "empty" }; throw error; } return { kind: "corrupt", diagnostic: "receiver.lock exists without a complete owner.json" }; } try { return { kind: "owned", owner: parseReceiverLeaseOwnerV1(raw) }; } catch (error) { return { kind: "corrupt", diagnostic: toError(error).message }; } } async withCurrentLease(generation: string, operation: () => Promise): Promise { requireNonEmpty(generation, "lease generation"); await this.#initialize(); const guarded = await this.#withLifecycleGuard(async (current) => { const projection = await this.inspectLease(); if (projection.kind !== "owned" || projection.owner.generation !== generation || projection.owner.rootId !== this.#rootId || !ownerIsCurrentProcess(projection.owner, current)) { throw new LeaseFenceError("Receiver lease generation is not current"); } return operation(); }); if (!guarded.ok) throw new LeaseFenceError(guarded.diagnostic); return guarded.value; } async #initialize(): Promise { await mkdir(this.#namespaceDirectory, { recursive: true, mode: 0o700 }); await mkdir(join(this.#namespaceDirectory, "quarantine"), { recursive: true, mode: 0o700 }); await mkdir(join(this.#namespaceDirectory, "lifecycle-reclaims"), { recursive: true, mode: 0o700 }); } async #createReceiver(owner: ReceiverLeaseOwnerV1): Promise { const prepared = await mkdtemp(join(this.#namespaceDirectory, ".receiver.prepare-")); await chmod(prepared, 0o700); let installed = false; try { await writeAtomicJson(join(prepared, "owner.json"), owner, { createOnly: true }); await syncDirectory(prepared); await rename(prepared, this.#receiverPath()); installed = true; await syncDirectory(this.#namespaceDirectory); return true; } catch (error) { if (isNodeError(error) && (error.code === "EEXIST" || error.code === "ENOTEMPTY")) { return false; } throw error; } finally { if (!installed) await rm(prepared, { recursive: true, force: true }); } } async #withLifecycleGuard(operation: (current: ProcessIdentity) => Promise): Promise> { const current = await this.#inspector.current(); validateProcessIdentity(current, "current process identity"); const deadline = Date.now() + this.#guardWaitMs; while (true) { const beforeAcquire = await this.#waitForNoReclaimIntent(current, deadline); if (!beforeAcquire.ok) return beforeAcquire; const guard: LifecycleGuardV1 = { schema: 1, generation: this.#randomUUID(), pid: current.pid, processStartIdentity: current.processStartIdentity, bootIdentity: current.bootIdentity, executable: current.executable, hostname: current.hostname, createdAt: this.#now().toISOString(), }; try { await writeAtomicJson(this.#guardPath(), guard, { createOnly: true }); } catch (error) { if (!isNodeError(error) || error.code !== "EEXIST") throw error; const existing = await this.#readGuard(); if (existing.kind === "corrupt") { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: existing.diagnostic }; } if (existing.kind === "empty") continue; const state = await classifyProcessRecord(existing.guard, current, this.#inspector); if (state.kind === "stale") { const reclaimed = await this.#reclaimLifecycleGuard(current, deadline); if (!reclaimed.ok) return reclaimed; continue; } if (state.kind === "remote" || state.kind === "unknown") { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: state.kind === "remote" ? `Lifecycle guard belongs to remote host ${existing.guard.hostname}` : state.diagnostic, }; } if (Date.now() >= deadline) { return { ok: false, reason: "lifecycle-busy", diagnostic: "Receiver lifecycle operation is busy; no owner was displaced", }; } await delay(5); continue; } const afterAcquire = await this.#scanReclaimIntents(current); if (!afterAcquire.ok) { await this.#releaseGuard(guard.generation); return afterAcquire; } if (!afterAcquire.clear) { await this.#releaseGuard(guard.generation); if (Date.now() >= deadline) { return { ok: false, reason: "lifecycle-busy", diagnostic: "Receiver lifecycle reclaim is active; no owner was displaced", }; } await delay(5); continue; } let value: T | undefined; let operationError: unknown; try { value = await operation(current); } catch (error) { operationError = error; } let releaseError: unknown; try { await this.#releaseGuard(guard.generation); } catch (error) { releaseError = error; } if (operationError !== undefined && releaseError !== undefined) { throw new AggregateError( [operationError, releaseError], "Receiver lifecycle operation and guard release both failed", ); } if (operationError !== undefined) throw operationError; if (releaseError !== undefined) throw releaseError; return { ok: true, value: value as T }; } } async #readGuard(): Promise< | { readonly kind: "empty" } | { readonly kind: "owned"; readonly guard: LifecycleGuardV1 } | { readonly kind: "corrupt"; readonly diagnostic: string } > { let raw: unknown; try { raw = await readJson(this.#guardPath()); } catch (error) { return { kind: "corrupt", diagnostic: `Lifecycle guard JSON is invalid: ${toError(error).message}` }; } if (raw === undefined) return { kind: "empty" }; try { return { kind: "owned", guard: parseLifecycleGuardV1(raw) }; } catch (error) { return { kind: "corrupt", diagnostic: toError(error).message }; } } async #releaseGuard(generation: string): Promise { const existing = await this.#readGuard(); if (existing.kind !== "owned" || existing.guard.generation !== generation) { throw new LeaseFenceError("Receiver lifecycle guard generation changed before release"); } await unlink(this.#guardPath()); await syncDirectory(this.#namespaceDirectory); } async #reclaimLifecycleGuard( current: ProcessIdentity, deadline: number, ): Promise> { const intent: LifecycleGuardV1 = { schema: 1, generation: this.#randomUUID(), pid: current.pid, processStartIdentity: current.processStartIdentity, bootIdentity: current.bootIdentity, executable: current.executable, hostname: current.hostname, createdAt: this.#now().toISOString(), }; const intentPath = join( this.#reclaimPath(), `reclaim.${safePathPart(intent.generation)}.json`, ); await writeAtomicJson(intentPath, intent, { createOnly: true }); try { while (true) { const existing = await this.#readGuard(); if (existing.kind === "empty") return { ok: true, value: true }; if (existing.kind === "corrupt") { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: existing.diagnostic }; } const state = await classifyProcessRecord(existing.guard, current, this.#inspector); if (state.kind === "stale") { const reread = await this.#readGuard(); if (reread.kind !== "owned" || !sameGuard(reread.guard, existing.guard)) continue; const stalePath = join( this.#namespaceDirectory, "quarantine", `lifecycle.${safePathPart(existing.guard.generation)}.${this.#now().getTime()}.${safePathPart(this.#randomUUID())}.json`, ); try { await rename(this.#guardPath(), stalePath); await syncDirectory(this.#namespaceDirectory); return { ok: true, value: true }; } catch (error) { if (isNodeError(error) && error.code === "ENOENT") continue; throw error; } } if (state.kind === "remote" || state.kind === "unknown") { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: state.kind === "remote" ? `Lifecycle guard belongs to remote host ${existing.guard.hostname}` : state.diagnostic, }; } if (Date.now() >= deadline) { return { ok: false, reason: "lifecycle-busy", diagnostic: "A live lifecycle guard appeared during stale reclaim; no owner was displaced", }; } await delay(5); } } finally { await unlink(intentPath); await syncDirectory(this.#reclaimPath()); } } async #waitForNoReclaimIntent( current: ProcessIdentity, deadline: number, ): Promise> { while (true) { const scan = await this.#scanReclaimIntents(current); if (!scan.ok) return scan; if (scan.clear) return { ok: true, value: true }; if (Date.now() >= deadline) { return { ok: false, reason: "lifecycle-busy", diagnostic: "Receiver lifecycle reclaim is active; no owner was displaced", }; } await delay(5); } } async #scanReclaimIntents(current: ProcessIdentity): Promise< | { readonly ok: true; readonly clear: boolean } | { readonly ok: false; readonly reason: ReceiverLeaseRefusalReason; readonly diagnostic: string } > { const entries = await readdir(this.#reclaimPath()); let liveIntent = false; for (const entry of entries) { if (!entry.startsWith("reclaim.") || !entry.endsWith(".json")) continue; const path = join(this.#reclaimPath(), entry); let raw: unknown; try { raw = await readJson(path); } catch (error) { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: `Lifecycle reclaim intent is invalid: ${toError(error).message}`, }; } if (raw === undefined) continue; let intent: LifecycleGuardV1; try { intent = parseLifecycleGuardV1(raw); } catch (error) { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: toError(error).message, }; } const state = await classifyProcessRecord(intent, current, this.#inspector); if (state.kind === "stale") { try { await unlink(path); await syncDirectory(this.#reclaimPath()); } catch (error) { if (!isNodeError(error) || error.code !== "ENOENT") throw error; } continue; } if (state.kind === "remote" || state.kind === "unknown") { return { ok: false, reason: "lifecycle-unverifiable", diagnostic: state.kind === "remote" ? `Lifecycle reclaim intent belongs to remote host ${intent.hostname}` : state.diagnostic, }; } liveIntent = true; } return { ok: true, clear: !liveIntent }; } #receiverPath(): string { return join(this.#namespaceDirectory, "receiver.lock"); } #guardPath(): string { return join(this.#namespaceDirectory, "receiver.lifecycle.guard.json"); } #reclaimPath(): string { return join(this.#namespaceDirectory, "lifecycle-reclaims"); } } export function parseReceiverLeaseOwnerV1(value: unknown): ReceiverLeaseOwnerV1 { const object = strictRecord(value, OWNER_KEYS, "receiver owner"); if (number(object, "schema") !== 1) throw new Error("receiver owner schema must be 1"); const owner: ReceiverLeaseOwnerV1 = { schema: 1, generation: nonEmptyString(object, "generation"), piSessionId: nonEmptyString(object, "piSessionId"), piSessionFile: absolutePath(object, "piSessionFile"), pid: positiveInteger(object, "pid"), processStartIdentity: nonEmptyString(object, "processStartIdentity"), bootIdentity: nullableString(object, "bootIdentity"), executable: absolutePath(object, "executable"), hostname: nonEmptyString(object, "hostname"), rootId: nonEmptyString(object, "rootId"), createdAt: isoTimestamp(object, "createdAt"), }; return owner; } function parseLifecycleGuardV1(value: unknown): LifecycleGuardV1 { const object = strictRecord(value, GUARD_KEYS, "receiver lifecycle guard"); if (number(object, "schema") !== 1) throw new Error("receiver lifecycle guard schema must be 1"); return { schema: 1, generation: nonEmptyString(object, "generation"), pid: positiveInteger(object, "pid"), processStartIdentity: nonEmptyString(object, "processStartIdentity"), bootIdentity: nullableString(object, "bootIdentity"), executable: absolutePath(object, "executable"), hostname: nonEmptyString(object, "hostname"), createdAt: isoTimestamp(object, "createdAt"), }; } function ownerFromRequest( request: AcquireRequest, identity: ProcessIdentity, rootId: string, generation: string, now: Date, ): ReceiverLeaseOwnerV1 { return { schema: 1, generation: requireNonEmpty(generation, "lease generation"), piSessionId: requireNonEmpty(request.piSessionId, "pi session ID"), piSessionFile: requireAbsolute(request.piSessionFile, "pi session file"), pid: identity.pid, processStartIdentity: identity.processStartIdentity, bootIdentity: identity.bootIdentity, executable: identity.executable, hostname: identity.hostname, rootId, createdAt: now.toISOString(), }; } async function classifyOwner( owner: ReceiverLeaseOwnerV1, current: ProcessIdentity, inspector: ProcessIdentityInspector, ): Promise { return classifyProcessRecord(owner, current, inspector); } async function classifyProcessRecord( record: Pick, current: ProcessIdentity, inspector: ProcessIdentityInspector, ): Promise { if (record.hostname !== current.hostname) return { kind: "remote" }; const inspection = await inspector.inspect(record.pid); if (inspection.kind === "absent") return { kind: "stale", reason: "PID is absent" }; if (inspection.kind === "unknown") return inspection; const observed = inspection.identity; if (record.processStartIdentity !== observed.processStartIdentity) { return { kind: "stale", reason: "PID start identity changed" }; } if (normalize(record.executable) !== normalize(observed.executable)) { return { kind: "stale", reason: "PID executable changed" }; } if (record.bootIdentity !== null && observed.bootIdentity !== null && record.bootIdentity !== observed.bootIdentity) { return { kind: "stale", reason: "host boot identity changed" }; } return { kind: "live" }; } function ownerIsCurrentProcess(owner: ReceiverLeaseOwnerV1, current: ProcessIdentity): boolean { return owner.hostname === current.hostname && owner.pid === current.pid && owner.processStartIdentity === current.processStartIdentity && normalize(owner.executable) === normalize(current.executable) && (owner.bootIdentity === null || current.bootIdentity === null || owner.bootIdentity === current.bootIdentity); } function sameOwner(left: ReceiverLeaseOwnerV1, right: ReceiverLeaseOwnerV1): boolean { return JSON.stringify(left) === JSON.stringify(right); } function sameGuard(left: LifecycleGuardV1, right: LifecycleGuardV1): boolean { return JSON.stringify(left) === JSON.stringify(right); } function validateAcquireRequest(request: AcquireRequest): void { requireNonEmpty(request.piSessionId, "pi session ID"); requireAbsolute(request.piSessionFile, "pi session file"); } function validateProcessIdentity(identity: ProcessIdentity, name: string): void { if (!Number.isSafeInteger(identity.pid) || identity.pid <= 0) throw new Error(`${name} PID is invalid`); requireNonEmpty(identity.processStartIdentity, `${name} start identity`); requireAbsolute(identity.executable, `${name} executable`); requireNonEmpty(identity.hostname, `${name} hostname`); } function strictRecord( value: unknown, allowed: readonly string[], name: string, ): Record { if (typeof value !== "object" || value === null || Array.isArray(value)) { throw new Error(`${name} must be an object`); } const object = value as Record; const unknown = Object.keys(object).filter((key) => !allowed.includes(key)); if (unknown.length > 0) throw new Error(`${name} contains unknown fields: ${unknown.join(", ")}`); return object; } function nonEmptyString(object: Record, key: string): string { const value = object[key]; if (typeof value !== "string" || value.length === 0) throw new Error(`${key} must be a non-empty string`); return value; } function nullableString(object: Record, key: string): string | null { const value = object[key]; if (value === null) return null; if (typeof value !== "string" || value.length === 0) throw new Error(`${key} must be null or a non-empty string`); return value; } function absolutePath(object: Record, key: string): string { return requireAbsolute(nonEmptyString(object, key), key); } function positiveInteger(object: Record, key: string): number { const value = number(object, key); if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${key} must be a positive safe integer`); return value; } function number(object: Record, key: string): number { const value = object[key]; if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} must be a number`); return value; } function isoTimestamp(object: Record, key: string): string { const value = nonEmptyString(object, key); if (!Number.isFinite(Date.parse(value))) throw new Error(`${key} must be an ISO timestamp`); return value; } function requireNonEmpty(value: string, name: string): string { if (value.length === 0) throw new TypeError(`${name} is required`); return value; } function requireAbsolute(value: string, name: string): string { if (!isAbsolute(value)) throw new TypeError(`${name} must be absolute`); return normalize(value); } function safePathPart(value: string): string { return value.replace(/[^A-Za-z0-9._-]/g, "_"); } function isNodeError(error: unknown): error is NodeJS.ErrnoException { return error instanceof Error && "code" in error; } function toError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)); } /** Production process proof for Darwin and Linux. Unsupported platforms fail closed. */ export class LocalProcessIdentityInspector implements ProcessIdentityInspector { async current(): Promise { const inspected = await this.inspect(process.pid); if (inspected.kind !== "live") { throw new Error(inspected.kind === "unknown" ? inspected.diagnostic : "Current process identity is unexpectedly absent"); } return inspected.identity; } async inspect(pid: number): Promise { if (!Number.isSafeInteger(pid) || pid <= 0) { return { kind: "unknown", diagnostic: "PID must be a positive safe integer" }; } if (process.platform === "linux") return inspectLinuxProcess(pid); if (process.platform === "darwin") return inspectDarwinProcess(pid); return { kind: "unknown", diagnostic: `Process identity inspection is unsupported on ${process.platform}`, }; } } async function inspectLinuxProcess(pid: number): Promise { try { const stat = await readFile(`/proc/${pid}/stat`, "utf8"); const close = stat.lastIndexOf(")"); if (close < 0) return { kind: "unknown", diagnostic: `Malformed /proc/${pid}/stat` }; const fields = stat.slice(close + 2).trim().split(/\s+/); const startIdentity = fields[19]; if (!startIdentity) return { kind: "unknown", diagnostic: `Missing start time in /proc/${pid}/stat` }; const executable = await readlink(`/proc/${pid}/exe`); let bootIdentity: string | null = null; try { bootIdentity = (await readFile("/proc/sys/kernel/random/boot_id", "utf8")).trim() || null; } catch { bootIdentity = null; } return { kind: "live", identity: { pid, processStartIdentity: startIdentity, bootIdentity, executable: normalize(executable), hostname: localHostname(), }, }; } catch (error) { if (isNodeError(error) && (error.code === "ENOENT" || error.code === "ESRCH")) { return { kind: "absent" }; } return { kind: "unknown", diagnostic: `Linux process inspection failed: ${toError(error).message}` }; } } async function inspectDarwinProcess(pid: number): Promise { const processResult = await runBounded("/bin/ps", ["-p", String(pid), "-o", "lstart="]); if (processResult.exitCode === 1 && processResult.stdout.trim().length === 0) return { kind: "absent" }; if (processResult.exitCode !== 0) { return { kind: "unknown", diagnostic: `Darwin ps failed: ${processResult.stderr.trim()}` }; } const startIdentity = processResult.stdout.trim(); if (startIdentity.length === 0) { return { kind: "unknown", diagnostic: "Darwin ps returned an empty start identity" }; } const files = await runBounded("/usr/sbin/lsof", ["-a", "-p", String(pid), "-d", "txt", "-Fn"]); if (files.exitCode !== 0) { return { kind: "unknown", diagnostic: `Darwin executable inspection failed: ${files.stderr.trim()}` }; } const executable = files.stdout .split("\n") .find((line) => line.startsWith("n/") && line !== "n/usr/lib/dyld") ?.slice(1); if (!executable || !isAbsolute(executable)) { return { kind: "unknown", diagnostic: "Darwin lsof did not return an absolute executable path" }; } const bootResult = await runBounded("/usr/sbin/sysctl", ["-n", "kern.boottime"]); const bootIdentity = bootResult.exitCode === 0 ? bootResult.stdout.trim() || null : null; return { kind: "live", identity: { pid, processStartIdentity: startIdentity, bootIdentity, executable: normalize(executable), hostname: localHostname(), }, }; } async function runBounded( binary: string, argv: readonly string[], ): Promise<{ readonly exitCode: number | null; readonly stdout: string; readonly stderr: string }> { return new Promise((resolve) => { const child = spawn(binary, argv, { shell: false, stdio: ["ignore", "pipe", "pipe"] }); const stdout: Buffer[] = []; const stderr: Buffer[] = []; let stdoutBytes = 0; let stderrBytes = 0; child.stdout.on("data", (chunk: Buffer) => { stdoutBytes += chunk.length; if (stdoutBytes <= 64 * 1024) stdout.push(Buffer.from(chunk)); else child.kill("SIGKILL"); }); child.stderr.on("data", (chunk: Buffer) => { stderrBytes += chunk.length; if (stderrBytes <= 64 * 1024) stderr.push(Buffer.from(chunk)); else child.kill("SIGKILL"); }); child.once("error", (error) => resolve({ exitCode: null, stdout: "", stderr: error.message })); child.once("close", (exitCode) => resolve({ exitCode, stdout: Buffer.concat(stdout).toString("utf8"), stderr: Buffer.concat(stderr).toString("utf8"), })); }); }