{
  "version": 3,
  "sources": ["../../src/lock.ts"],
  "sourcesContent": ["import { randomUUID } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport lockfile from \"proper-lockfile\";\nimport { ensureStateDir, lockPath } from \"./config.js\";\nimport { LOCK_GUARD_STALE_MS, LOCK_GUARD_UPDATE_MS } from \"./lock-policy.js\";\nimport { LOCKFILE_FS_ADAPTER } from \"./lockfile-fs.js\";\nimport type { CommandOptions, LockFile } from \"./types.js\";\n\nconst LOCK_STALE_MS = 30 * 60 * 1000;\nconst MAX_PROCESS_ID = 2_147_483_647;\n\ninterface Guard {\n\trelease: () => Promise<void>;\n\tthrowIfCompromised: () => void;\n\tisCompromised: () => boolean;\n}\n\nexport type LockInspection =\n\t| { status: \"missing\" }\n\t| { status: \"unreadable\" }\n\t| { status: \"valid\"; lock: LockFile };\n\nexport async function withLock<T>(\n\tcommand: string,\n\tfn: () => Promise<T>,\n\toptions: { reclaimStale?: boolean } = {},\n): Promise<T> {\n\tawait ensureStateDir();\n\tconst lock: LockFile = {\n\t\tid: randomUUID(),\n\t\tpid: process.pid,\n\t\tcommand,\n\t\tstartedAt: new Date().toISOString(),\n\t};\n\tlet guard: Guard | undefined;\n\tlet result: T | undefined;\n\tlet failed = false;\n\tlet failure: unknown;\n\ttry {\n\t\ttry {\n\t\t\tguard = await acquireGuard();\n\t\t} catch (error) {\n\t\t\tif (!isLockHeldError(error)) throw error;\n\t\t\tthrow await describeHeldLock();\n\t\t}\n\n\t\tlet inspection = await inspectLock();\n\t\tif (inspection.status === \"valid\" && isStaleLock(inspection.lock)) {\n\t\t\tif (!options.reclaimStale) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`pi-sync lock is stale (pid ${inspection.lock.pid}). Run /sync unlock --stale, then retry.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tguard.throwIfCompromised();\n\t\t\tconst rechecked = await inspectLock();\n\t\t\tif (\n\t\t\t\trechecked.status !== \"valid\" ||\n\t\t\t\trechecked.lock.id !== inspection.lock.id ||\n\t\t\t\t!isStaleLock(rechecked.lock)\n\t\t\t) {\n\t\t\t\tthrow new Error(\"pi-sync lock changed while preparing transaction recovery; retry.\");\n\t\t\t}\n\t\t\tawait fs.rm(lockPath(), { force: true });\n\t\t\tinspection = { status: \"missing\" };\n\t\t}\n\t\tif (inspection.status === \"valid\") {\n\t\t\tthrow new Error(\n\t\t\t\t`pi-sync is already running (${inspection.lock.command}, pid ${inspection.lock.pid}, started ${inspection.lock.startedAt}).`,\n\t\t\t);\n\t\t}\n\t\tif (inspection.status === \"unreadable\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"pi-sync lock metadata is unreadable. Run /sync unlock --stale after verifying no sync is running.\",\n\t\t\t);\n\t\t}\n\n\t\tawait fs.writeFile(lockPath(), JSON.stringify(lock, null, \"\\t\"), { flag: \"wx\" });\n\t\tguard.throwIfCompromised();\n\t\tresult = await fn();\n\t\tguard.throwIfCompromised();\n\t} catch (error) {\n\t\tfailed = true;\n\t\tfailure = error;\n\t}\n\n\ttry {\n\t\tconst current = await readLock();\n\t\tif (current?.id === lock.id) await fs.rm(lockPath(), { force: true });\n\t} catch (error) {\n\t\tif (!failed) {\n\t\t\tfailed = true;\n\t\t\tfailure = error;\n\t\t}\n\t}\n\tif (guard) {\n\t\tconst releaseError = await releaseGuard(guard);\n\t\tif (releaseError && !failed) {\n\t\t\tfailed = true;\n\t\t\tfailure = releaseError;\n\t\t}\n\t}\n\tif (failed) throw failure;\n\treturn result as T;\n}\n\nexport async function inspectLock(): Promise<LockInspection> {\n\ttry {\n\t\tconst text = await fs.readFile(lockPath(), \"utf8\");\n\t\tif (text.trim().length === 0) return { status: \"unreadable\" };\n\t\tconst parsed = JSON.parse(text) as unknown;\n\t\treturn isLockFile(parsed) ? { status: \"valid\", lock: parsed } : { status: \"unreadable\" };\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return { status: \"missing\" };\n\t\tif (error instanceof SyntaxError) return { status: \"unreadable\" };\n\t\tthrow error;\n\t}\n}\n\nexport async function readLock(): Promise<LockFile | undefined> {\n\tconst inspection = await inspectLock();\n\treturn inspection.status === \"valid\" ? inspection.lock : undefined;\n}\n\nexport async function lockFileExists(): Promise<boolean> {\n\ttry {\n\t\tawait fs.stat(lockPath());\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n\t\tthrow error;\n\t}\n}\n\nexport function isLockGuardHeld() {\n\treturn lockfile.check(lockPath(), {\n\t\tfs: LOCKFILE_FS_ADAPTER,\n\t\tlockfilePath: `${lockPath()}.guard`,\n\t\trealpath: false,\n\t\tstale: LOCK_GUARD_STALE_MS,\n\t});\n}\n\nexport function isStaleLock(lock: LockFile) {\n\ttry {\n\t\tprocess.kill(lock.pid, 0);\n\t\treturn false;\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ESRCH\") return true;\n\t\treturn Date.now() - Date.parse(lock.startedAt) > LOCK_STALE_MS;\n\t}\n}\n\nexport async function unlock(ctx: ExtensionCommandContext, options: CommandOptions) {\n\tthrowIfAborted(options.signal);\n\tawait ensureStateDir();\n\tthrowIfAborted(options.signal);\n\tlet guard: Guard;\n\ttry {\n\t\tguard = await acquireGuard();\n\t} catch (error) {\n\t\tif (!isLockHeldError(error)) throw error;\n\t\tctx.ui.notify((await describeHeldLock()).message, \"warning\");\n\t\treturn;\n\t}\n\n\tlet failed = false;\n\tlet failure: unknown;\n\ttry {\n\t\tawait unlockGuarded(ctx, options);\n\t} catch (error) {\n\t\tfailed = true;\n\t\tfailure = error;\n\t}\n\tconst releaseError = await releaseGuard(guard);\n\tif (releaseError && !failed) {\n\t\tfailed = true;\n\t\tfailure = releaseError;\n\t}\n\tif (failed) throw failure;\n}\n\nasync function unlockGuarded(ctx: ExtensionCommandContext, options: CommandOptions) {\n\tthrowIfAborted(options.signal);\n\tlet inspection = await inspectLock();\n\tthrowIfAborted(options.signal);\n\tif (inspection.status === \"missing\") {\n\t\tctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n\t\treturn;\n\t}\n\tif (inspection.status === \"unreadable\") {\n\t\tif (!options.stale) {\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Pi-sync lock metadata is unreadable. Use /sync unlock --stale only after verifying no sync is running.\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tinspection = await inspectLock();\n\t\tthrowIfAborted(options.signal);\n\t\tif (inspection.status === \"unreadable\") {\n\t\t\t// Legacy writers expose an empty file before writing owner metadata, so no\n\t\t\t// automatic test can prove this file is abandoned. The explicit --stale\n\t\t\t// flag is the user's confirmation that no legacy sync is still running.\n\t\t\tthrowIfAborted(options.signal);\n\t\t\tawait fs.rm(lockPath(), { force: true });\n\t\t\tif (!options.signal?.aborted) {\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Removed unreadable pi-sync lock. No settings, files, sync state, or remote data were changed.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (inspection.status === \"missing\") {\n\t\t\tctx.ui.notify(\"No pi-sync lock is present.\", \"info\");\n\t\t\treturn;\n\t\t}\n\t}\n\tif (!isStaleLock(inspection.lock)) {\n\t\tctx.ui.notify(\"Lock owner is still live; refusing to remove it.\", \"warning\");\n\t\treturn;\n\t}\n\tthrowIfAborted(options.signal);\n\tawait fs.rm(lockPath(), { force: true });\n\tif (!options.signal?.aborted) {\n\t\tctx.ui.notify(\n\t\t\t\"Removed stale pi-sync lock. No settings, files, sync state, or remote data were changed.\",\n\t\t\t\"info\",\n\t\t);\n\t}\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n\tif (!signal?.aborted) return;\n\tthrow signal.reason instanceof Error\n\t\t? signal.reason\n\t\t: new DOMException(\"The operation was aborted\", \"AbortError\");\n}\n\nasync function releaseGuard(guard: Guard) {\n\ttry {\n\t\tawait guard.release();\n\t\treturn undefined;\n\t} catch (error) {\n\t\treturn guard.isCompromised() ? undefined : error;\n\t}\n}\n\nasync function acquireGuard(): Promise<Guard> {\n\tlet compromisedError: Error | undefined;\n\tconst release = await lockfile.lock(lockPath(), {\n\t\tfs: LOCKFILE_FS_ADAPTER,\n\t\tlockfilePath: `${lockPath()}.guard`,\n\t\trealpath: false,\n\t\tstale: LOCK_GUARD_STALE_MS,\n\t\tupdate: LOCK_GUARD_UPDATE_MS,\n\t\tonCompromised: (error) => {\n\t\t\tcompromisedError = error;\n\t\t},\n\t});\n\treturn {\n\t\trelease,\n\t\tthrowIfCompromised: () => {\n\t\t\tif (compromisedError) throw compromisedError;\n\t\t},\n\t\tisCompromised: () => compromisedError !== undefined,\n\t};\n}\n\nasync function describeHeldLock() {\n\tconst current = await readLock();\n\tif (current && isStaleLock(current)) {\n\t\treturn new Error(\"pi-sync lock owner exited; retry shortly while the lock guard expires.\");\n\t}\n\tif (current) {\n\t\treturn new Error(\n\t\t\t`Pi-sync is currently running (${current.command}, pid ${current.pid}, started ${current.startedAt}).`,\n\t\t);\n\t}\n\treturn new Error(\n\t\t\"Pi-sync is currently running (lock metadata is unreadable or still being written).\",\n\t);\n}\n\nfunction isLockHeldError(error: unknown) {\n\treturn (error as NodeJS.ErrnoException).code === \"ELOCKED\";\n}\n\nfunction isLockFile(value: unknown): value is LockFile {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst lock = value as Partial<LockFile>;\n\treturn (\n\t\ttypeof lock.id === \"string\" &&\n\t\tlock.id.length > 0 &&\n\t\tNumber.isInteger(lock.pid) &&\n\t\t(lock.pid ?? 0) > 0 &&\n\t\t(lock.pid ?? 0) <= MAX_PROCESS_ID &&\n\t\ttypeof lock.command === \"string\" &&\n\t\tlock.command.length > 0 &&\n\t\ttypeof lock.startedAt === \"string\" &&\n\t\tNumber.isFinite(Date.parse(lock.startedAt))\n\t);\n}\n"],
  "mappings": "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",
  "names": []
}
