/** * Workspace transition lock. * * One cross-process lock serializes mutation-class operations on a workspace. * A plan-family apply acquires it after confirmation — planning, network * acquisition, preview, and the confirmation decision stay lock-free — then * revalidates its candidate and applies while holding it; the workspace * transaction reuses the invocation's hold, or acquires its own when no * plan-family hold exists. A contending invocation waits with a visible * reason up to a bound, then terminates blocked with a machine-readable * reference to the holder. * * Each acquisition records a distinct owner token in the holder metadata. * Ownership is proven only by an exact token match — never inferred from a * pid or from missing or unreadable metadata — and a holder write that fails * releases the acquisition instead of holding anonymously. * * The lock file lives at `.axm/tmp/workspace-transition.lock`; its path is * load-bearing for operational tooling and must not move casually. * * Acquisition is atomic with respect to interruption: the narrow region from * the library granting the hold through finalizer registration is masked, so * an interrupt can never strand a granted lock, while contention waits stay * interruptible. Only the lock-is-held error class is absorbed by the bounded * wait; any other acquisition error surfaces immediately as a typed failure. * * @experimental This API is unstable and may change without notice. */ import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import type * as Scope from "effect/Scope"; import { type AppError } from "../app-error/index.js"; export declare const TRANSITION_LOCK_FILENAME = "workspace-transition.lock"; /** How long a contending invocation serializes behind the holder. */ export declare const TRANSITION_WAIT_BOUND_MILLIS = 60000; export interface TransitionLockHolder { readonly command: string; readonly pid: number; readonly candidateId?: string; } declare const WorkspaceTransitionCompromised_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "WorkspaceTransitionCompromised"; } & Readonly; /** * The hold is no longer provably owned: `proper-lockfile` failed to confirm * ownership within the staleness window, so a contender may already have * reclaimed the lock. Any further durable write by the original owner — * mutation and restoration alike — could overwrite a successor's work. */ export declare class WorkspaceTransitionCompromised extends WorkspaceTransitionCompromised_base<{ readonly workspaceDir: string; readonly lockPath: string; readonly cause: unknown; }> { } /** The live view of one acquisition this process currently holds. */ export interface HeldWorkspaceTransition { /** Fails when the hold is compromised; never succeeds and never ends otherwise. */ readonly compromised: Effect.Effect; /** Synchronous probe for boundaries that cannot race, such as restoration. */ readonly isCompromised: () => boolean; } export declare const isWorkspaceTransitionHeldByThisInvocation: (workspaceDir: string) => boolean; /** The live hold for a workspace this process acquired, when one exists. */ export declare const heldWorkspaceTransition: (workspaceDir: string) => HeldWorkspaceTransition | undefined; export declare const transitionLockPath: (path: Path.Path, workspaceDir: string) => string; export interface TransitionContention { /** The holder recorded by the invocation that owns the lock, when readable. */ readonly holder: Option.Option; readonly waitedMillis: number; } /** * Acquire the workspace transition lock, waiting up to the bound while * another invocation holds it. Resolves `None` when acquired (the release is * a scope finalizer) and `Some(contention)` when the bound elapsed. */ export declare const acquireWorkspaceTransitionLock: (args: { readonly workspaceDir: string; readonly holder: TransitionLockHolder; readonly waitBoundMillis?: number; /** Called once when the invocation starts waiting on another holder. */ readonly onWaiting?: (holder: Option.Option) => Effect.Effect; /** Staleness/refresh override for deterministic compromise tests only. */ readonly timingMillis?: { readonly stale: number; readonly update: number; }; }) => Effect.Effect, AppError, FileSystem.FileSystem | Path.Path | Scope.Scope>; export {}; //# sourceMappingURL=transition-lock.d.ts.map