import type { OpRecord } from '../../observe/record.ts'; import { DriftPolicy } from '../../types.ts'; import type { MountEntry } from '../mount/mount.ts'; /** * Raised at load time when a remote resource's live fingerprint differs * from what was recorded in the snapshot. * * Indicates the underlying source has been modified since the snapshot * was taken, so reading current bytes would silently diverge from what * the original agent saw. Surface to the caller rather than mask. */ export declare class ContentDriftError extends Error { readonly path: string; readonly snapshotFingerprint: string; readonly liveFingerprint: string | null; constructor(path: string, snapshotFingerprint: string, liveFingerprint: string | null); } export interface FingerprintEntry { path: string; mount_prefix: string; fingerprint?: string | null; revision?: string | null; } interface RegistryLike { tryMountFor(path: string): MountEntry | null; allMounts(): readonly MountEntry[]; } export interface MountLookup { tryMountFor(path: string): MountEntry | null; } /** * Fingerprint checks a load queued, drained on the first async op. * * `Workspace.load` records one entry per read whose snapshot manifest * carried a fingerprint but no stable revision (a pinned read needs no * check: the pin guarantees the bytes). The first `dispatch` or * `execute` drains them, so downstream code can rely on consistent * state. Mirrors the Python `DriftQueue` in `snapshot/drift.py`. */ export declare class DriftQueue { private entries; private isPending; get pending(): boolean; /** Paths still queued for a check (audit surface). */ get paths(): string[]; /** Drop any queued state (a re-install starts fresh). */ clear(): void; queue(path: string, fingerprint: string): void; /** * Stat every queued path in parallel; throw on the first drift. * Subsequent calls are no-ops. Stats run concurrently so first-op * latency does not scale linearly with the number of recorded reads. */ drain(registry: MountLookup, statFn: (path: string) => Promise): Promise; } /** * Walk a loaded snapshot's fingerprint manifest. For entries with a * revision, install the pin on the owning mount so replay reads pin to * that revision. For fingerprint-only entries, queue the path on the * drift queue. OFF skips the checks and evicts the snapshot cache for * fingerprinted paths so reads serve current state. * * Idempotent: clears queued state before installing. Called from * `Workspace.fromState`. */ export declare function installDriftState(registry: RegistryLike, cache: { evictPaths(paths: Iterable): void; }, drift: DriftQueue, state: { fingerprints?: FingerprintEntry[]; live_only_mounts?: string[]; }, policy: DriftPolicy): void; /** * Walk recorded ops and emit one entry per distinct read on a * snapshot-capable mount. * * Pure aggregation over `records`. Each read carries the `fingerprint` * and/or `revision` the backend returned at the moment the agent read * the bytes (populated from the GET response, not a fresh stat at * snapshot time). This avoids the race where the upstream changes * between read and snapshot. * * Skips paths whose owning mount has `supportsSnapshot=false` (live-only * backends like Gmail/Slack/Linear) and reads where the backend returned * neither marker. */ export declare function captureFingerprints(records: readonly OpRecord[], registry: RegistryLike): FingerprintEntry[]; /** * Return mount prefixes whose resource opts out of snapshot replay. * * These mounts will serve current state at load time with no drift * detection. Surfaced in the snapshot manifest so the load layer can * log them and so users can audit which paths are non-replayable. */ export declare function liveOnlyMountPrefixes(registry: RegistryLike): string[]; /** * Stat `path` and throw {@link ContentDriftError} if the live fingerprint * does not match `recorded`. No-op if the mount cannot be resolved or the * resource cannot fingerprint. * * The caller provides `statFn` (typically a thin wrapper over * {@link Workspace.dispatch}) so that drift.ts stays decoupled from the * workspace's op-resolution machinery. */ export declare function checkDrift(registry: MountLookup, statFn: (path: string) => Promise, path: string, recorded: string): Promise; export {}; //# sourceMappingURL=drift.d.ts.map