UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

111 lines • 5.19 kB
import type { FileCacheMixin } from '../../cache/file/mixin.ts'; import { type OpRecord } from '../../observe/record.ts'; import { DriftPolicy } from '../../types.ts'; import type { MountEntry } from '../mount/mount.ts'; import type { FingerprintEntrySnapshot } from './types.ts'; /** * Raised at load time when a remote VFS'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); } 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 * `shell` 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, mountId?: string | null): 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<unknown>): Promise<void>; } /** * 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 installFingerprints(registry: RegistryLike, cache: Pick<FileCacheMixin, 'remove'>, drift: DriftQueue, state: { fingerprints?: FingerprintEntrySnapshot[]; live_only_mounts?: string[]; }, policy: DriftPolicy): Promise<void>; /** * Walk recorded ops and emit one pin per path still worth checking. * * A single forward pass over the time-ordered records, so the last word on * a path wins. Three things can be that last word: * * - an op that removed or replaced the object (`RETRACT_FINGERPRINT_OPS`) * drops the pin, and every pin beneath it — `rm -r` and a prefix rename * take a subtree with them; * - an op that changed the bytes without describing them — a write whose * backend returned no token, or any `append` — drops the pin too, * because the token on file no longer names what is there; * - an op carrying a token (`STAMP_FINGERPRINT_OPS`) replaces the pin * whole, never field-merging, so a read's revision cannot survive onto a * later write's fingerprint and pin replay to pre-write bytes. * * A read that reported no token changes nothing and leaves the pin alone. * Each token is what the backend returned at the moment the agent moved * the bytes, not a fresh stat at snapshot time. * * Paths on a mount that opts out of snapshot replay are never pinned; a * retraction still applies to them, because dropping a pin is the safe * direction and the mount a retraction names may no longer be the one that * set the pin. */ export declare function captureFingerprints(records: readonly OpRecord[], registry: RegistryLike): FingerprintEntrySnapshot[]; /** * Return mount prefixes whose VFS 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 * VFS 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<unknown>, path: string, recorded: string, mountId?: string | null): Promise<void>; export {}; //# sourceMappingURL=drift.d.ts.map