import type { NativeExactFileIdentity, NativeExactUnlinkResult, NativeNoReplaceResult } from "@gajae-code/natives"; export interface AtomicYamlExpectedPrecondition { path: string; hash: string; } export interface AtomicYamlSetPatch { path: string; op: "set"; value: unknown; expected?: AtomicYamlExpectedPrecondition; } export interface AtomicYamlUnsetPatch { path: string; op: "unset"; expected?: AtomicYamlExpectedPrecondition; } export type AtomicYamlPatch = AtomicYamlSetPatch | AtomicYamlUnsetPatch; /** Raised when a compare-and-swap precondition no longer matches durable YAML. */ export declare class AtomicYamlConflictError extends Error { readonly path: string; readonly expectedHash: string; readonly actualHash: string; readonly code = "ATOMIC_YAML_CONFLICT"; constructor(path: string, expectedHash: string, actualHash: string); } export interface AtomicYamlPatchRevision { path: string; beforeHash: string; afterHash: string; beforeRevision: number; afterRevision: number; } export type CasRestoreResult = { status: "restored"; receipt: CasReceipt; } | { status: "conflict"; paths: readonly string[]; } | { status: "discarded"; } | { status: "not-restorable"; }; /** * A receipt intentionally exposes only path-level hashes and opaque revisions. * The before values needed by restore stay in this module's closure. */ export interface CasReceipt { readonly revisions: readonly AtomicYamlPatchRevision[]; restore(): Promise; discard(): void; } export interface AtomicYamlUpdate { apply(current: Record): T | Promise; shouldWrite?(result: T): boolean; committed?(current: Record, result: T): void | Promise; } export interface AtomicYamlPatchOptions { /** Test seam for deterministic pre-rename and Windows sharing-violation failures. */ rename?: (from: string, to: string) => Promise; /** Test seam for an identity-checked atomic replacement. */ exactReplace?: (sourcePath: string, destinationPath: string, expectedSource: NativeExactFileIdentity, expectedDestination: NativeExactFileIdentity) => NativeExactUnlinkResult | Promise; /** Test seam for an atomic no-replace publication. */ noReplace?: (sourcePath: string, destinationPath: string) => NativeNoReplaceResult | Promise; /** Test seam for bounded retry timing. */ sleep?: (ms: number) => Promise; /** Test seam for Windows rename retry behavior. */ platform?: NodeJS.Platform; /** Called under the config lock before patches are applied. */ validateRoot?: (root: unknown, patches: readonly AtomicYamlPatch[]) => void | Promise; /** Called under the config lock after a successful CAS restore. */ onRestored?: (patches: readonly AtomicYamlPatch[]) => void | Promise; } /** A replacement failure never unlinks the destination as a fallback. */ export declare class AtomicYamlReplaceError extends Error { readonly configPath: string; readonly attempts: number; readonly cause: unknown; readonly preserveTempPath: boolean; readonly code = "ATOMIC_YAML_REPLACE_FAILED"; constructor(configPath: string, attempts: number, cause: unknown, preserveTempPath?: boolean); } /** * Raised when the LEXICAL config path no longer resolves to the canonical * target an operation is about to write: e.g. a `config.yml` symlink was * repointed while the operation waited in the per-file queue or ran. The * native identity check protects only the previously resolved destination, so * without this guard the write would modify an unrelated prior target and * report success while the now-active configuration is left unchanged. */ export declare class AtomicYamlRetargetError extends Error { readonly lexicalPath: string; readonly expectedCanonicalPath: string; readonly actualCanonicalPath: string; readonly code = "ATOMIC_YAML_RETARGETED"; constructor(lexicalPath: string, expectedCanonicalPath: string, actualCanonicalPath: string); } /** Set a dotted YAML path, creating object intermediates as needed. */ export declare function setByPath(value: Record, segments: readonly string[], nextValue: unknown): void; /** Delete a dotted YAML path without disturbing sibling keys or parent objects. */ export declare function deleteByPath(value: Record, segments: readonly string[]): void; /** Hash a dotted YAML path state for an expected-hash patch precondition. */ export declare function atomicYamlPathHash(value: Record, path: string): string; /** Build patches from current durable YAML while holding the shared queue and file lock. */ export declare function applyAtomicYamlPatchesWithCurrent(configPath: string, buildPatches: (current: Readonly>) => Promise | readonly AtomicYamlPatch[], options?: AtomicYamlPatchOptions): Promise; export interface AtomicYamlConfigTransaction { configPath: string; root: unknown; current: Readonly>; /** True once any write op has durably committed (a later CAS rejection then * leaves the target with partial writes, so recovery artifacts must stay). */ written: boolean; applyPatches(patches: readonly AtomicYamlPatch[], options?: AtomicYamlPatchOptions): Promise; /** * Delete top-level keys verbatim, including dotted key names (e.g. a flat * `"gjc.ralplan.maxIterations"` key that the patch grammar would otherwise * interpret as a nested path). Writes atomically under the same lock. */ removeTopLevelKeys(keys: readonly string[], options?: AtomicYamlPatchOptions): Promise; /** * Apply patches AND delete top-level keys verbatim in a SINGLE atomic * write, so an external editor's change cannot land between the two * operations (external editors do not participate in the file lock). The * returned receipt is not restorable (the deletions are not journaled). */ applyPatchesAndRemoveTopLevelKeys(patches: readonly AtomicYamlPatch[], topLevelKeys: readonly string[], options?: AtomicYamlPatchOptions): Promise; /** * Replace the whole document (used to revert the target when a later * verification fails). Writes atomically under the same lock; the returned * receipt is not restorable. */ replaceCurrent(next: Readonly>, options?: AtomicYamlPatchOptions): Promise; } /** * Run a caller-owned multi-step mutation under the config file's per-file queue * and cross-process lock. The current YAML is read once and exposed as * `root`/`current`; the callback may inspect it, decide patches, and apply them * (or perform adjacent durable actions such as marker/source transitions) * without re-acquiring the lock. A YAML parse failure surfaces before the * callback runs, so no migration action can execute against a malformed target. */ export declare function withAtomicYamlConfigTransaction(configPath: string, operation: (transaction: AtomicYamlConfigTransaction) => Promise): Promise; /** * Reserve a FIFO operation for a config file immediately. The patch supplier runs * only when this operation reaches the front of the in-process queue, which lets * Settings debounce/coalesce inside its already-reserved causal slot. */ export declare function enqueueAtomicYamlOperation(configPath: string, operation: (canonicalConfigPath: string, lexicalConfigPath: string) => Promise): Promise; /** * Reserve an atomic patch slot now, producing patches only after earlier slots * complete. Writers with state-aware merges use {@link reserveAtomicYamlUpdateSlot}. */ export declare function reserveAtomicYamlPatchSlot(configPath: string, patches: () => Promise | readonly AtomicYamlPatch[], options?: AtomicYamlPatchOptions): Promise; /** * Reserve a FIFO update slot and atomically persist a caller-owned YAML mutation. * The supplier runs only when its operation reaches the front of the queue. */ export declare function reserveAtomicYamlUpdateSlot(configPath: string, update: () => Promise> | AtomicYamlUpdate, options?: AtomicYamlPatchOptions): Promise; /** * Apply tagged patches through the one per-file in-process queue and the shared * cross-process file lock. Success means the temp file was fsynced and renamed. */ export declare function applyAtomicYamlPatches(configPath: string, patches: readonly AtomicYamlPatch[], options?: AtomicYamlPatchOptions): Promise;