/** * The reconcile-wide lock and the crash cursor. * * ONE lock guards a whole reconcile (not each `ext add` child) so an operator `ext add`/`remove` * cannot interleave between seed children and strand a half-applied refresh decision. It rides the * shared crash-safe advisory-lock primitive (atomic link publish so a racer never sees a canonical * lock before its owner record; serialized reclaim so no inspect→remove targets a later generation; * PID + process-start liveness so a recycled PID isn't mistaken for the holder; a bounded wait on a * live holder rather than a false "interrupted"; nonce-guarded release). * * The cursor is the SIGKILL floor: before each connector mutation the reconcile journals * `{nonce, package, phase}`, and clears it only after the stamp commits. A crash mid-reconcile leaves * the cursor behind; the next boot distinguishes a LIVE reconcile (lock still actively held → wait) * from a DEAD one (lock stale + cursor present → fail loud, `--repair`). */ export interface ReconcileLock { readonly nonce: string; release(): void; } /** Acquire the reconcile lock, waiting (bounded) on a live reconcile and reclaiming a dead owner's. * A reconcile installs four connectors via npm, so the wait bound is generous. */ export declare function acquireReconcileLock(): ReconcileLock; /** True while a LIVE process holds the reconcile lock (distinguishes an in-flight reconcile from a * crashed one when a cursor is present). */ export declare function reconcileLockActive(): boolean; /** * True iff THIS process is a genuine seed child: it carries `COTAL_EXT_SEEDING=` + * `COTAL_EXT_SEEDING_PARENT=`, and a LIVE reconcile actually holds the lock under exactly that * PID and nonce. A user cannot forge this to skip the mutation lock for an arbitrary `ext add` — the * bare-`=1` bypass is gone; the marker must match the live lock the parent published. */ export declare function isAuthenticSeedChild(): boolean; /** The mid-reconcile journal: which package is being mutated, in which phase, under which lock nonce. * `failed` is why the run that wrote it failed; a cursor without it was left by a run that stopped. */ export interface ReconcileCursor { readonly nonce: string; readonly package: string; readonly phase: "copy" | "add"; readonly failed?: string; } export declare function writeCursor(cursor: ReconcileCursor): void; /** Record on the cursor the run under `nonce` wrote that the run failed, and why. A cursor another run * left is kept as it is. */ export declare function markCursorFailed(nonce: string, reason: string): void; /** Read + SCHEMA-validate the cursor: a torn write, or a parsed-but-malformed one (missing/invalid * nonce, a non-official package, an unknown phase — e.g. `{}`) is corrupt, never a silent "no target" * that lets a repair clear the journal and report success while a connector is still torn. */ export declare function readCursor(): ReconcileCursor | undefined; export declare function clearCursor(): void; /** * A seed child's liveness marker. The PARENT records `pending` BEFORE it spawns (so the window never * opens ownerless); the CHILD upgrades it to `live` with its own PID + start token as its first act; * the parent clears it after the child exits. A `pending` marker whose parent has died is the ambiguous * window — the child may or may not have started — and is resolved fail-loud, never by guessing. */ export type ChildMarker = { readonly state: "pending"; readonly parentPid: number; readonly nonce: string; readonly ts: number; } | { readonly state: "live"; readonly childPid: number; readonly childStart?: string; readonly parentPid: number; readonly nonce: string; readonly ts: number; }; /** Parent: record intent to spawn a seed child before `spawnSync`, so a crash never leaves a gap a * concurrent repair could race through unaware. */ export declare function writePendingChildMarker(nonce: string): void; /** Child: upgrade the parent's pending marker to a live one carrying this child's identity. */ export declare function markSeedChildLive(nonce: string, parentPid: number): void; export declare function clearChildMarker(): void; export type SeedChildStatus = { readonly kind: "none"; } | { readonly kind: "live"; readonly pid: number; } | { readonly kind: "ambiguous"; readonly path: string; }; /** The state of any recorded seed child, for the orphan-vs-repair decision. A `live` child (its parent * SIGKILL'd mid-install → orphan) must not be raced. A `pending` marker whose parent is gone, or a * corrupt marker, is `ambiguous` (cannot tell "never spawned" from "child delayed") and is resolved * fail-loud. A dead/recycled child is `none` (safe to proceed). */ export declare function seedChildStatus(): SeedChildStatus; /** * The durable maintenance-recovery obligation. A `--repair`/`--reset` writes it BEFORE it * destructively quarantines a corrupt manifest/cursor, and clears it only at the final commit — so a * SIGKILL after the quarantine but before the reinstalls is not forgotten: the next boot fails loud * and the next repair re-derives the same obligation (rebuild the built-ins from on-disk truth, and/or * reinstall+verify every seeded built-in) instead of stamping success over missing connectors. */ export interface RecoveryObligation { readonly rebuildFromDisk?: boolean; readonly repairAllSeeded?: boolean; } export declare function writeRecovery(obligation: RecoveryObligation): void; /** Read + SCHEMA-validate the recovery obligation. A parsed-but-malformed marker (`{}`, `null`, an * array, non-boolean fields, or an all-false obligation the writer never emits) is CORRUPT, not an * empty valid obligation — it must throw so `recoveryPending()` stays true and maintenance recovers * conservatively, never silently clearing the journal over still-damaged state. */ export declare function readRecovery(): RecoveryObligation | undefined; /** True iff a recovery obligation is outstanding (any read failure counts — never silently ignored). */ export declare function recoveryPending(): boolean; export declare function clearRecovery(): void; /** Quarantine a CORRUPT (unparseable) crash CURSOR aside, so a `--repair`/`--reset` read of it can't * wedge. The cursor is a journal, not a liveness signal, so moving it aside is safe; a corrupt CHILD * MARKER is deliberately NOT touched here — it is resolved by {@link seedChildStatus}'s fail-loud * (a marker we can't read might mean an installer is running). Returns the quarantine path(s). */ export declare function sanitizeCorruptCrashState(): string[]; //# sourceMappingURL=lock.d.ts.map