/** * Idempotency checkpoint and exclusive setup lock for `hikoutei setup`. * * The setup flow mutates Google Cloud resources, so an interrupted run must * resume from the same project and spreadsheet instead of creating * duplicates. The checkpoint is a versioned JSON file (default * `.hikoutei-setup-state.json`) written atomically (unique per-run temp * file + rename) with mode 0600. It never contains tokens or key material — only identities, * paths, the spreadsheet id, a non-secret creation marker, and the * non-secret key provenance discriminant (`keyOrigin`). * * Statuses form a strict progression: * - `project_selected`: the project id is decided (persisted before project * creation) and no key or spreadsheet exists yet. * - `key_create_started`: a local opaque key marker (a UUID) and a sorted/ * deduplicated baseline of the pre-existing user-managed service-account * key resource names were persisted BEFORE the single gcloud key create. * The marker derives a deterministic private sibling staging path, so a * crash at any key boundary (before gcloud, after the remote/local * create, after the hardlink install, before/after staged cleanup, before * `key_ready`) resumes by reconciling the staged and/or final key file * against the current user-managed key list. Only the invocation that * just persisted this state may issue the one key create; every resume * is reconcile-only and NEVER creates, even when no stage/final/delta is * visible — a lagging IAM key list must never permit a duplicate. When * no credential and no post-baseline key are visible, the resume polls * the key list plus staged/final evidence through a bounded propagation * window and then fails with `key_create_uncertain` (nothing is deleted * automatically and the create is never retried); the user inspects the * cloud keys and intentionally resets the key checkpoint to start * fresh. * - `key_ready`: the service-account key was secured (owner-only mode 0600), * validated, and installed at the final key path; spreadsheet creation * follows. Every later status implies key readiness: a missing key in * `key_ready` or later is invalid. `key_ready` and every later status * carry `keyOrigin` — a non-secret discriminant recording whether the * key was CREATED by this setup run or REUSED from a pre-existing * credential — so a resumed run keeps the verify-phase freshness * evidence (new keys get the Invalid JWT Signature propagation retry) * without conflating it with the current run's reuse summary. * - `spreadsheet_create_started`: a local opaque creation marker (a UUID) * was generated and persisted BEFORE the single remote create attempt. * The create request carries the marker as a private Drive `appProperties` * entry, so a crash between the remote create and the next checkpoint * write is reconciled on resume by querying Drive for that exact marker. * Setup NEVER creates a second spreadsheet from this state: an unknown * outcome fails with `sheet_create_uncertain` and is reconciled on the * next run. A create rejected up front (HTTP 400/403) with zero marker * matches rolls back to `key_ready` (preserving `keyOrigin`) and fails * with `sheet_create_failed` so the next run starts a fresh marker after * the user fixes the issue. * - `spreadsheet_created`: the spreadsheet id was persisted immediately * after creation, before any sharing step. * - `spreadsheet_share_started`: the share write-ahead. The spreadsheet id * and `keyOrigin` were persisted BEFORE the idempotent writer-permission * ensure could create or upgrade the service account's permission. The * final `shareOrigin` is deliberately absent: the attempt outcome is not * known yet, so a stored shareOrigin here is contradictory. A crash or * failure between the remote permission mutation and the * `spreadsheet_shared` write leaves this status; the next run reruns * the idempotent ensure/ownership verification and persists a * conservative `shareOrigin: "fresh"` when this status was LOADED * (the prior attempt may have created/upgraded before crashing) — a * false-positive fresh only adds bounded 403/404 retries and is safe. * - `spreadsheet_shared`: the service account was granted writer access and * Drive metadata was verified. * - `complete`: the `.env` file was written; the state is retained so reruns * stay no-ops. Starting fresh requires removing both the checkpoint and * the key file (or passing the matching `--project` for recovery). * * A `complete` checkpoint may additionally carry `pool`, the provisioned * credential pool for `--sa-count` runs (entry 1 duplicates the primary * fields; entries 2..N are the additional accounts), and `poolKeyStarted`, * the write-ahead for one in-flight pool key (marker plus baseline, * persisted before that entry's single key create and cleared when the * entry joins `pool`; a resume reconciles it only and never creates a * second key). The pool is * append-reconciled on resume: a stored pool never conflicts with a new * `--sa-count` (saCount is a run option, not checkpoint state), and * already-recorded entries are skipped. * * The spreadsheet URL is never stored: it is derived deterministically from * the spreadsheet id, so a stored URL can never disagree with the id. * `projectMode` records whether the project was explicit (`--project`) or * generated (`hikoutei-`); resume behavior differs between the two. * * This module also validates an existing service-account key file: a key may * only be reused when its `project_id` and `client_email` match the project * and service account of the current run, and its `private_key` must parse * as an RSA private key (the key material itself is never returned). * * The exclusive setup lock (`.lock`) prevents concurrent runs: it is * an EMPTY DIRECTORY created with `mkdir` (mode 0700) and removed with * `rmdir`. The atomic create-or-fail semantics of `mkdir` mean a second * process can never acquire while the owner holds the lock, and no metadata * is stored inside (none is needed — there is no read-then-delete ownership * race between cooperating setup processes because the directory stays * non-removable and non-reacquirable until the owner atomically removes it). * EEXIST (any pre-existing entry: file, directory, or symlink) fails with * `setup_in_progress` and the entry is never touched; any other acquire * failure is a distinct lock failure (`setup_lock_failed`). A crash leaves * the empty directory behind — it is never removed automatically, and * manual removal is required only when the user is certain no setup is * running. The owner releases by removing the exact directory it created * (device/inode verified), so a replacement acquired after release is never * deleted. */ import { type SetupStateWriteFs } from "./setupPaths.js"; export { SETUP_LOCK_SUFFIX, SETUP_STATE_TEMP_SUFFIX, defaultSetupStateWriteFs, fsyncParentDirectory, noFollowFlag, nonBlockFlag, setupLockPath, setupStateTempPath, writeAllSync, type SetupStateWriteFs, } from "./setupPaths.js"; /** Default checkpoint file name, resolved against the current directory. */ export declare const SETUP_STATE_FILE_NAME = ".hikoutei-setup-state.json"; /** * Derives the canonical service-account email for a name and project. * * The single source of truth for the `sa@.iam.gserviceaccount.com` * identity: the setup flow derives the email it creates/uses from here, and * checkpoint validation requires any stored `saEmail` to equal this exact * derivation, so an attacker-controlled or corrupted checkpoint can never * introduce a different service-account identity. */ export declare function serviceAccountEmail(saName: string, projectId: string): string; /** Current checkpoint schema version; a different version is `setup_state_invalid`. */ export declare const SETUP_STATE_VERSION = 1; /** Checkpoint file permission after write (owner read/write only). */ export declare const SETUP_STATE_FILE_MODE = 384; export { SERVICE_ACCOUNT_KEY_FILE_MODE, SERVICE_ACCOUNT_KEY_ID_PATTERN, } from "./keyContract.js"; /** * Restricted format of a creation marker: a lowercase UUID v4 as produced by * `node:crypto` `randomUUID`. The marker is used inside a Drive `files.list` * query and as a Drive `appProperties` value, so the format is validated * before it ever reaches the API. */ export declare const CREATION_MARKER_PATTERN: RegExp; /** * Restricted format of a Drive file or permission id. * * Drive ids are opaque URL-safe identifiers (ASCII alphanumerics, `_`, and * `-`). Anything else — whitespace, control characters, newlines, or any * other character — is refused at the untrusted SDK boundary before the id * can reach a URL, the `.env` file, a summary, or a command label. */ export declare const DRIVE_ID_PATTERN: RegExp; /** True when the value is a non-empty URL-safe Drive id. */ export declare function isValidDriveId(value: unknown): value is string; /** * Canonical GCP project id format. * * GCP project ids are 6-30 characters of lowercase ASCII letters, digits, * and hyphens, must start with a lowercase letter, and must not end with a * hyphen. The setup flow passes project ids to `gcloud` and stores them in * the checkpoint, so the format is enforced at the CLI boundary AND at the * checkpoint boundary: an option-like or malformed identifier (for example * `--project=--flag`) is rejected before any subprocess, API call, or file * mutation. */ export declare const GCP_PROJECT_ID_PATTERN: RegExp; /** * Canonical service-account name format. * * GCP service-account names follow the same 6-30 character shape as * project ids (lowercase letters, digits, and hyphens; lowercase-letter * start; alphanumeric end). The name derives the canonical * `sa@.iam.gserviceaccount.com` identity, so the format is * enforced at the CLI boundary AND at the checkpoint boundary: an * option-like or malformed name is rejected before any subprocess, API * call, or file mutation. */ export declare const SERVICE_ACCOUNT_NAME_PATTERN: RegExp; /** True when the value is a well-formed GCP project id. */ export declare function isValidGcpProjectId(value: unknown): value is string; /** True when the value is a well-formed service-account name. */ export declare function isValidServiceAccountName(value: unknown): value is string; /** True when the value is a well-formed creation marker (UUID v4). */ export declare function isValidCreationMarker(value: unknown): value is string; /** * True when the value is a well-formed key marker (UUID v4). * * The key marker shares the restricted UUID v4 format with the spreadsheet * creation marker (both are opaque locally generated ids that later appear * in paths or API queries), but it is a distinct domain value: it derives * the deterministic private staging path of the service-account key and is * stored only in `key_create_started` checkpoints. */ export declare function isValidKeyMarker(value: unknown): value is string; /** * Unique per-invocation checkpoint temp path (PID + random UUID). * * Every save uses a fresh sibling name, so a crashed run leaves only an * inert orphan that never blocks the next save. The reserved base name * (`setupStateTempPath`) is retained for the path-collision checks as * defense in depth: no setup artifact may ever live at the fixed * `.tmp` name, but saves must not depend on clearing it. */ export declare function uniqueSetupStateTempPath(statePath: string): string; /** How the project was decided; drives resume behavior. */ export type ProjectMode = "explicit" | "generated"; /** * Whether the service-account key was created by this setup or pre-existed. * * A non-secret provenance discriminant persisted from `key_ready` onward: * `created` means the key at the recorded path was provisioned by the setup * run (or its crashed predecessor, recovered through the key write-ahead * reconciliation); `reused` means the credential pre-existed the setup * (matched by identity). The verify phase retries Invalid JWT Signature * propagation only for `created` keys, and the value survives resumes via * the checkpoint. */ export type KeyOrigin = "created" | "reused"; /** * Whether the service-account writer permission was granted by this setup * or pre-existed. * * A non-secret provenance discriminant persisted from `spreadsheet_shared` * onward: `fresh` means the writer permission was created or upgraded by * this setup run; `reused` means an existing writer/owner permission was * reused. The verify phase retries 403/404 propagation failures only for * `fresh` shares, and the value survives resumes via the checkpoint so a * shared-but-unverified state keeps its propagation evidence. */ export type ShareOrigin = "fresh" | "reused"; /** * One provisioned service account of a credential pool. * * Non-secret identities only (names, emails, paths — never key material): * entry 1 duplicates the primary `saName`/`saEmail`/`keyPath` fields, and * entries 2..N are the additional pool accounts (`-`) sharing * the same spreadsheet. A resumed run skips entries already present. */ export interface SetupPoolEntry { readonly saName: string; readonly saEmail: string; readonly keyPath: string; } /** * Write-ahead marker for one in-flight credential-pool key (entries 2..N). * * Persisted on the `complete` checkpoint BEFORE the single gcloud key * create for that pool entry so a crash at any pool-key boundary resumes * by reconciliation instead of creating a second key: the stored marker * derives the deterministic private sibling staging directory and the * stored baseline scopes the current user-managed key list. Only the * invocation that just persisted this write-ahead may issue the one * create for the entry; every resume is reconcile-only. Cleared when the * entry is persisted to `pool`. Non-secret identities only — never key * material. */ export interface PoolKeyWriteAhead { /** 1-based pool index of the in-flight entry (always >= 2). */ readonly index: number; readonly saName: string; readonly saEmail: string; readonly keyPath: string; /** UUID marker deriving the deterministic private staging path of the key. */ readonly keyMarker: string; /** Sorted/deduplicated baseline of pre-existing user-managed key resource names. */ readonly keyBaseline: readonly string[]; } /** Progression statuses of a setup run; later statuses mean earlier work is done. */ export type SetupStateStatus = "project_selected" | "key_create_started" | "key_ready" | "spreadsheet_create_started" | "spreadsheet_created" | "spreadsheet_share_started" | "spreadsheet_shared" | "complete"; /** Fields shared by every checkpoint status. */ interface SetupStateCommon { readonly version: typeof SETUP_STATE_VERSION; readonly projectId: string; /** `explicit` when `--project` was given; `generated` when `hikoutei-` was decided. */ readonly projectMode: ProjectMode; /** Email of the human account that owns the spreadsheet (from tokeninfo). */ readonly ownerEmail: string; readonly saName: string; readonly saEmail: string; readonly keyPath: string; readonly spreadsheetTitle: string; } /** * Runtime-validated checkpoint state. * * The discriminated union makes the spreadsheet fields unrepresentable * before the spreadsheet exists: `spreadsheet_create_started` carries only * the creation marker, and only `spreadsheet_created` and later statuses * carry the spreadsheet id. `key_create_started` carries only the key * marker (deriving the deterministic staging path) and the sorted/ * deduplicated baseline of pre-existing user-managed key resource names; * `key_ready` and later statuses carry no key fields — a key checkpoint * never stores key material — but DO carry `keyOrigin`, the non-secret * provenance discriminant that preserves verify freshness across resumes. * `spreadsheet_share_started` is the share write-ahead: it carries the * spreadsheet id and `keyOrigin` but NO `shareOrigin` (the permission * mutation may not have happened yet). `spreadsheet_shared` and * `complete` additionally carry `shareOrigin`, the non-secret provenance * of the SA writer permission, so a resumed shared-but-unverified state * keeps its 403/404 propagation freshness. The URL is never stored — it * is derived deterministically from the id. */ export type SetupState = (SetupStateCommon & { readonly status: "project_selected"; }) | (SetupStateCommon & { readonly status: "key_create_started"; /** UUID marker deriving the deterministic private staging path of the key. */ readonly keyMarker: string; /** Sorted/deduplicated baseline of pre-existing user-managed key resource names. */ readonly keyBaseline: readonly string[]; }) | (SetupStateCommon & { readonly status: "key_ready"; readonly keyOrigin: KeyOrigin; }) | (SetupStateCommon & { readonly status: "spreadsheet_create_started"; readonly creationMarker: string; readonly keyOrigin: KeyOrigin; }) | (SetupStateCommon & { readonly status: "spreadsheet_created" | "spreadsheet_share_started"; readonly spreadsheetId: string; readonly keyOrigin: KeyOrigin; }) | (SetupStateCommon & { readonly status: "spreadsheet_shared"; readonly spreadsheetId: string; readonly keyOrigin: KeyOrigin; /** * Non-secret provenance of the SA writer permission; required once the * share step is done so a resumed shared-but-unverified state keeps its * 403/404 propagation freshness. */ readonly shareOrigin: ShareOrigin; }) | (SetupStateCommon & { readonly status: "complete"; readonly spreadsheetId: string; readonly keyOrigin: KeyOrigin; /** * Non-secret provenance of the SA writer permission; required once the * share step is done so a resumed shared-but-unverified state keeps its * 403/404 propagation freshness. */ readonly shareOrigin: ShareOrigin; /** * Credential pool (entry 1 duplicates the primary fields). Absent for * single-SA runs; a present pool is non-empty and append-reconciled on * resume (a stored pool never conflicts with a new --sa-count). */ readonly pool?: readonly SetupPoolEntry[]; /** * Write-ahead for one in-flight pool key (see `PoolKeyWriteAhead`). * Present only while entries 2..N are being provisioned; cleared when * the entry joins `pool`. A resume reconciles this entry only and * never issues a second create for it. */ readonly poolKeyStarted?: PoolKeyWriteAhead; }); /** Result of loading the checkpoint file from disk. */ export type LoadSetupStateResult = { readonly status: "none"; } | { readonly status: "loaded"; readonly state: SetupState; } | { readonly status: "invalid"; readonly message: string; }; /** Inputs the current run is compared against a loaded checkpoint. */ export interface StateCompatibilityInput { /** Explicit `--project`, or undefined when the checkpoint project is used. */ readonly projectId: string | undefined; readonly saName: string; /** Explicit `--spreadsheet-title`, or undefined to accept the stored title. */ readonly spreadsheetTitle: string | undefined; readonly keyPath: string; readonly ownerEmail: string; } /** Result of comparing a run against a loaded checkpoint. */ export type StateCompatibility = { readonly status: "ok"; } | { readonly status: "conflict"; readonly message: string; }; export { type KeyMetadataResult, type SecureKeyReadResult, type ServiceAccountKeyMetadata, } from "./keyContract.js"; /** * Filesystem operations the secure checkpoint load uses; injectable for * tests (replacement-descriptor coverage without a racy real swap). * * Both `lstatSync` and `fstatSync` expose the device/inode so the load can * BIND the opened descriptor to the entry the lstat verified: a file * replaced between the type check and the open is refused before a single * byte is read. */ export interface SetupStateLoadFs { lstatSync(path: string): { isSymbolicLink(): boolean; isFile(): boolean; readonly dev: number; readonly ino: number; }; openSync(path: string, flags: number): number; fstatSync(fd: number): { isFile(): boolean; readonly dev: number; readonly ino: number; }; readFileSync(fd: number, encoding: "utf8"): string; closeSync(fd: number): void; } /** * Loads and validates the checkpoint file through ONE descriptor boundary. * * A missing file is `none` (fresh run); a file that cannot be inspected, * opened, read, parsed, or validated is `invalid` so the flow can fail with * `setup_state_invalid` instead of guessing. The entry is lstat-verified as * a regular file first (a symlink is refused outright; directories, FIFOs, * sockets, and devices are refused too, so a FIFO can never block), then * opened with `O_NOFOLLOW` (where the platform defines it — a symlink * swapped in between the type check and the open fails with ELOOP/EMLINK * and is refused) plus `O_NONBLOCK` (where the platform defines it), and * the descriptor is fstat-verified as a regular file with the SAME * device/inode the lstat observed BEFORE a single byte is read — covering * a non-regular OR replaced entry between the lstat check and the open * and preventing a FIFO open/read from blocking. The descriptor is * closed in a `finally`. Error messages never include file contents. */ export declare function loadSetupState(statePath: string, fs?: SetupStateLoadFs): LoadSetupStateResult; /** * Validates an untrusted checkpoint payload and promotes it into `SetupState`. * * Returns `null` when the payload is not a record, has the wrong version, a * missing/empty field, an unknown status, or spreadsheet fields missing from * a status that requires them. A stored `spreadsheetUrl` is rejected for * every status (the URL is derived from the id, so a stored one can only * disagree); a `creationMarker` outside `spreadsheet_create_started` and a * `spreadsheetId` before `spreadsheet_created` are contradictory and * rejected too. */ export declare function validateSetupState(value: unknown): SetupState | null; /** * True when a name is a user-managed service-account key resource name for * the given project and service account. * * The format is `projects//serviceAccounts//keys/` * with a hex key id. Older gcloud emits this full resource name for `keys * list --format=value(name)`; newer gcloud (e.g. 574) emits only the bare * key id (the last segment), which `parseUserManagedKeyList` reconstructs * into this canonical shape. The comparison uses exact string equality on * the project and email segments (no regex interpolation of user-controlled * text). */ export declare function isServiceAccountKeyResourceName(name: string, projectId: string, saEmail: string): boolean; /** * Writes the checkpoint atomically with mode 0600 and exclusive temp * acquisition. * * The content is written to a unique sibling temp file (PID + UUID by * default; an explicit `tempPath` is accepted for tests) created with * `O_CREAT|O_EXCL|O_WRONLY` plus `O_NOFOLLOW` where supported, then renamed * over the target so readers never observe a partial file. Because the temp * name is unique per invocation, a crashed run leaves only an inert orphan * that never blocks the next save. The exclusive create is the only * acquisition path: a pre-existing temp entry — symlink, hardlink, or * regular file — is NEVER followed, truncated, or unlinked; the save fails * safely and leaves it untouched. Owner-only mode 0600 is applied and * verified THROUGH the still-open temp descriptor (`fchmod` + `fstat` on * the descriptor) before the content is written and fsynced, so the mode is * guaranteed regardless of the process umask and no pathname `chmod` is * ever performed. The write happens through the opened descriptor, is * fsynced and closed, and the path is re-verified against the created inode * before the rename, so an alias swapped in after the open can never be * renamed onto the state path. Cleanup after a failed rename removes only * the temp inode this invocation created. Throws on filesystem failure; * the caller preserves carrier codes (`SetupPathSafetyError`) and falls * back to `setup_state_write_failed` for other errors. */ export declare function saveSetupState(statePath: string, state: SetupState, tempPath?: string, fs?: SetupStateWriteFs): void; /** * Checks a loaded checkpoint against the current run's options and the * active human account. * * A checkpoint is bound to one human owner, project, service-account name, * spreadsheet title, and key path; any mismatch means the user is trying to * reuse state for a different setup and gets `setup_state_conflict` instead * of silently reusing foreign resources. The current run's identifiers are * validated against the canonical GCP formats here as defense in depth * (the CLI entry already rejects malformed `--project`/`--sa-name` values * before anything runs): an option-like or malformed identifier is a * conflict, never a silent proceed. */ export declare function checkStateCompatibility(state: SetupState, input: StateCompatibilityInput): StateCompatibility; export { parseServiceAccountKeyJson, readServiceAccountKeySecurely, readServiceAccountKeyCredentialSecurely, type KeyFileFs, } from "./keyMaterial.js"; /** Filesystem operations the exclusive setup lock uses; injectable for tests. */ export interface LockFs { mkdirSync(path: string, options: { readonly mode: number; }): void; openSync(path: string, flags: number): number; fstatSync(fd: number): { readonly dev: number; readonly ino: number; isDirectory(): boolean; }; closeSync(fd: number): void; rmdirSync(path: string): void; } /** * Identity of the lock directory a run created. * * `dev`/`ino` describe the directory; `token` is an opaque process-local * owner token registered in a per-process acquisition registry that ties * the identity to the open descriptor of the directory, held from acquire * until release. While that descriptor stays open the original inode * cannot be recycled, and because the token is process-unique and the * registry entry is removed on release, a stale identity — or a fresh * directory that reused the device/inode — can never be mistaken for this * run's lock. Never serialized or persisted: process-local only. */ export interface LockIdentity { readonly dev: number; readonly ino: number; readonly token: number; } /** Result of acquiring the exclusive setup lock. */ export type SetupLockResult = { readonly status: "held"; readonly identity: LockIdentity; } | { readonly status: "busy"; readonly message: string; } | { readonly status: "failed"; readonly message: string; }; /** * Acquires the exclusive setup lock as an EMPTY DIRECTORY (mode 0700). * * `mkdir` is atomic create-or-fail, so a second process can never acquire * while the owner holds the lock and no metadata needs to be stored inside * (none is). An EEXIST — a pre-existing file, directory, or symlink — is * `busy` (`setup_in_progress`) and the entry is never read, removed, or * replaced, regardless of what it is; automatic stale-lock takeover is * deliberately disabled because a probe-then-remove takeover is racy. * Any other acquire failure (EACCES, ENOENT, EROFS, ...) is `failed` * (`setup_lock_failed`), never `busy`. * * The created directory is opened WITHOUT following symlinks and its * identity is taken THROUGH that descriptor; the descriptor is kept open * for the whole lock lifetime and registered under a process-unique owner * token in the per-process acquisition registry (the opaque `token` in * `LockIdentity`). While the descriptor stays open the original inode * cannot be recycled, so a replacement directory created after this lock * is removed can never carry the same device/inode; `releaseSetupLock` * removes only the directory the registered owner descriptor still * refers to. A directory that cannot be opened or verified is removed * again and the acquire fails closed rather than hold a lock of unknown * identity. */ export declare function acquireSetupLock(lockPath: string, fs?: LockFs): SetupLockResult; /** * Releases the setup lock by removing the exact directory this run created. * * Ownership is proven through the per-process acquisition registry: the * identity's opaque `token` must still map to the open descriptor this * run registered at acquire. The path is opened WITHOUT following symlinks * (`O_NOFOLLOW` where the platform defines it) and its identity is * compared against the identity of the OWNED descriptor, never against * `{dev, ino}` numbers alone. While the owned descriptor stays open the * original inode cannot be recycled, so a replacement lock acquired by * another process after this run's directory disappeared — or a symlink * planted at the path — can never match and is never deleted; and because * the registry entry is removed on release, a stale identity cannot * resolve again even after the filesystem (or the process's descriptor * table) reused the old numbers. Node offers no atomic owner-bound * directory removal (no `rmdirat`/`unlinkat` without a raw libuv * binding), so this descriptor-verified check-then-remove is the * strengthened boundary; the setup lock's own semantics (an empty * directory that is non-removable and non-reacquirable until the owner * removes it) keep cooperating setup runs from racing. On uncertain * identity the release FAILS CLOSED: a leftover lock directory simply * fails the next acquire with `busy` until removed manually. The owned * descriptor is always closed and its registry entry dropped on release, * so no descriptor leaks; a stale identity whose token is no longer * registered is ignored and removes nothing. Never throws; failures are * ignored because a leftover lock directory is never deleted on uncertain * identity. */ export declare function releaseSetupLock(lockPath: string, identity: LockIdentity, fs?: LockFs): void; /** Generates a fresh creation marker for one setup run. */ export declare function generateCreationMarker(): string; //# sourceMappingURL=checkpoint.d.ts.map