import { type FileHistoryRetentionPolicy } from "./file-history-retention.js";
import type { ExecutionEnv } from "../internal/harness-types.js";
/** rel path of `child` under `root` in "/" form (win root) / verbatim form (posix root); null when not under.
 *  Fold "\" only under a WIN-family root ({@link isWinFormPath}); a POSIX tree may legally contain literal
 *  backslashes in FILE NAMES, and folding them would corrupt both the track key and the restore target. */
declare function relUnder(root: string, child: string): string | null;
/** Join root + a "/"-form relPath using the ROOT's native separator (keys stay family-consistent). */
declare function joinUnder(root: string, rel: string): string;
/** The parent directory of an addressed path, as a pure family-aware string op (no host node:path). */
declare function parentDirOf(absPath: string): string | undefined;
/** The re-join verdict of {@link resolveTrackKey}: an absolute path to operate on, or a NAMED
 *  refusal (the fail-closed arm — a recorded key that would escape `root` is never coerced into
 *  something writable). A-082 idx5: named so the fence can be part of the public surface. */
export type FileHistoryTrackKeyResolution = {
    ok: true;
    abs: string;
} | {
    ok: false;
    reason: string;
};
/** The path family a track key was MINTED under — the half a bare key string cannot carry. */
export type FileHistoryKeyFamily = "posix" | "win";
/**
 * A track key's SELF-DESCRIPTION (design/381 §3 amendment, closing the A-082 r2 registered
 * residual): a key is a bare string, so it carries the shape it was minted in but never the family
 * it was minted under. Recorded beside every tracked path and carried by the session-sync envelope,
 * so a graph that arrives from ANOTHER MACHINE can be judged at the door instead of silently
 * re-joining under the local root as an oddly-named in-root file.
 */
export interface FileHistoryTrackKeyDescriptor {
    /** `"root-relative"` = re-join against the CURRENT root; `"absolute"` = restore where captured. */
    kind: "root-relative" | "absolute";
    family: FileHistoryKeyFamily;
}
/** The family of a root — the one place a backend asks "which family am I operating in?". */
export declare function trackKeyFamilyOf(root: string): FileHistoryKeyFamily;
/**
 * {@link trackKeyOf} plus the self-description a backend must record with it. Mint through THIS and
 * a graph stays judgeable after it crosses machines; mint through the bare key alone and the
 * provenance is gone for good (the residual this closes).
 */
export declare function describeTrackKey(root: string, absPath: string): {
    key: string;
} & FileHistoryTrackKeyDescriptor;
/**
 * Track-key coordinate (design/381 DV-6, the CC `wZu` relativization ported to the trusted task
 * root): a path under `root` is recorded root-RELATIVE (worktree/moved-root safe — restore re-joins
 * it against the CURRENT root), a path outside `root` is recorded verbatim-absolute.
 *
 * **Exported as part of the "MUST reuse, NOT re-implement" set** (A-082 idx5, the same §3 分工
 * clause that governs the two env halves): every backend mints keys from the `(absPath, root)` pair
 * `trackEdit`/`snapshot` hand it and re-joins them in `restore`, so this pair — not the env halves —
 * is where a hand-rolled backend re-introduces path escape. The escape check lives INSIDE the pair
 * (mint refuses to record an ambiguous rel form; {@link resolveTrackKey} refuses an unsafe recorded
 * one), so a backend that calls them cannot re-open the hole, and one that re-derives them must
 * re-derive the family-conditional bare-drive rule and the '..' fail-closed arm correctly on its own.
 */
export declare function trackKeyOf(root: string, absPath: string): string;
/**
 * Re-join a recorded track key against the CURRENT root (identity for absolute keys) — the
 * {@link trackKeyOf} mirror, exported under the same "MUST reuse" clause. A key that would escape
 * the root is REFUSED by name, never coerced: `{ok:false}` is the fail-closed arm a backend must
 * surface as a per-file `refused` row, not silently drop.
 *
 * CROSS-FAMILY PROVENANCE (A-082 codex r2, registered in slice 1 — CLOSED here in slice 2 exactly as
 * that note prescribed). A key is a bare STRING: it carries the shape it was minted in, never the
 * FAMILY it was minted under. Every key minted against this scope's own root resolves by the mirror
 * above — but a scope carried in from another machine (the {@link FileHistoryExport} envelope,
 * `adoptScope` across hosts) can hold a key whose family differs from the current root's, and under
 * a POSIX root a win-absolute record (`D:\other\x`) used to re-join UNDER the root as an oddly-named
 * in-root file (contained, never traversal — but not the file the record meant). The closure is the
 * §3 amendment the note named: a SELF-DESCRIBING key ({@link FileHistoryTrackKeyDescriptor}: kind +
 * source family) recorded beside every tracked path and carried by the versioned envelope, judged
 * HERE through the optional `minted` argument. Omit it and this function is byte-identical to
 * before; pass it and a foreign-family record is refused by name instead of coerced.
 */
export declare function resolveTrackKey(root: string, key: string, minted?: FileHistoryTrackKeyDescriptor): FileHistoryTrackKeyResolution;
export interface FileHistoryError {
    /** `not_found` = no boundary for that (scope, entryId); `aborted` = the signal fired (or the
     *  fencing discarded a timed-out attempt); `conflict` = a non-destructive-target law refused
     *  (adopt/import onto a scope with its own history) — and ONLY that, because a caller decides
     *  whether to stay quiet on it: the engine's fork-carry seat treats `conflict` as "already carried,
     *  or grown its own" and says nothing, so an adoption that failed for any OTHER reason (a corrupt
     *  source graph, a blob it cannot reach, a consumed-id ledger it cannot carry — including the
     *  tombstones a publish owes for unreadable residue it is about to replace) must NOT wear this
     *  code or the failure disappears into that silence — those are `adopt_failed`, which the
     *  ADOPTION FAMILY shares: `importHistory` publishes under the same target law and answers the
     *  same code for the same non-conflict failures (its `unsupported_format` names a bad ENVELOPE or
     *  work its shape drove; a fault in the PUBLISH step itself — the disk refusing a good graph —
     *  answers `adopt_failed`); `unsupported_format`
     *  = a foreign-epoch or wrong-version envelope; the rest name the verb that failed. */
    code: "track_failed" | "snapshot_failed" | "restore_failed" | "adopt_failed" | "not_found" | "aborted" | "unsupported_format" | "conflict";
    message: string;
}
export type FileHistoryResult = {
    ok: true;
} | {
    ok: false;
    error: FileHistoryError;
};
/**
 * {@link FileHistoryStore.trackEdit}'s answer. `minted` is the ANNUL HANDLE in boolean form (#491):
 * true = THIS call created the first-touch record, so the caller may {@link
 * FileHistoryStore.annulTrack} it if the edit that record was minted for never lands; false = the
 * path was already tracked (a cheap no-op), and annulling would destroy a record some EARLIER edit
 * legitimately owns. A backend that cannot tell answers `false` — the safe side (a stale record is
 * a cowork hazard, but discarding someone else's pristine baseline is worse).
 */
export type FileHistoryTrackResult = {
    ok: true;
    minted: boolean;
} | {
    ok: false;
    error: FileHistoryError;
};
/**
 * Three-state discriminant + full per-file ledger on EVERY arm (design/381 DV-15). Refusals are
 * never folded into benign skips; `base_unavailable` (DV-14) is a refusal reason.
 */
export type FileHistoryRestoreResult = {
    /** `"complete"` = every target applied or identical; `"partial"` = ≥1 refused or failed, others
     *  applied; `"failed"` = the operation itself could not run (e.g. no such boundary). */
    outcome: "complete" | "partial" | "failed";
    /** Track keys written or deleted (the converged set). */
    filesChanged: string[];
    /** Track keys already equal to the target (untouched — content-hash proven, never mtime). */
    identicalSkipped: string[];
    /** Per-file security/consistency refusals (skipped, counted, never failing the whole restore). */
    refused: Array<{
        path: string;
        reason: string;
    }>;
    /** Per-file operational failures (env write/remove errors, aborts after k writes). */
    failed: Array<{
        path: string;
        reason: string;
    }>;
    /** Files whose recorded POSIX mode could not be re-applied (env without the optional mode face). */
    modeNotPreserved?: number;
    /** Present on outcome:"failed" (operation-level cause) and optionally on "partial". */
    error?: FileHistoryError;
};
export interface FileHistoryDiffStats {
    filesChanged: string[];
    insertions: number;
    deletions: number;
}
/** The line-diff bound (see {@link countRestoreLineDiff}): a pair whose edit distance exceeds this
 *  is counted as a whole-file replacement. Public so a preview UI can state what it is showing. */
export declare const FILE_HISTORY_DIFF_LINE_BUDGET = 2000;
/**
 * The per-file line arithmetic behind {@link FileHistoryStore.diffStats} — ONE rule both bundled
 * backends use, because "how much will this rewind change" is a claim, and two backends answering it
 * differently would be two claims.
 *
 * `undefined` on either side means "does not exist" (a creation or a deletion), so every arm of the
 * restore is expressible: bytes → bytes is a line diff, absent → bytes is a pure insertion, bytes →
 * absent a pure deletion.
 *
 * TWO HONESTY BOUNDS, both stated rather than tuned in silence:
 *  · BINARY content (a NUL byte anywhere) contributes 0/0 while still being reported as CHANGED —
 *    "lines" is not a fact about a binary file, and printing a number derived from stray `\n` bytes
 *    would be inventing one.
 *  · Beyond {@link FILE_HISTORY_DIFF_LINE_BUDGET} the greedy diff STOPS and the pair is counted as a
 *    whole-file replacement (every current line deleted, every target line inserted). That is not a
 *    fallback to a worse estimate so much as the shape such a pair actually has — a diff that large
 *    IS a rewrite — and it keeps a preview from turning into an O(N·M) walk over two big files.
 */
export declare function countRestoreLineDiff(current: Uint8Array | undefined, target: Uint8Array | undefined): {
    insertions: number;
    deletions: number;
};
/**
 * Versioned session-sync envelope; `formatVersion` is the refuse-not-coerce gate (design/381 §5.3).
 * COMPLETE-state export: the tracked set and EVERY retained version record travel — including v1
 * records no surviving boundary references (they are restore()'s pristine fallback and reap()
 * retains them independently) and the parent-identity guards (root-relative under root) that
 * restore's preflight needs. An import that received only boundary references could neither run the
 * parent-chain check nor serve a tracked-later-than-target restore; round-trip-after-reap is a
 * contract-kit test.
 */
export interface FileHistoryExport {
    formatVersion: "file-history/1";
    tracked: Array<{
        /** Root-relative when captured under the task root, verbatim-absolute otherwise. */
        relOrAbsPath: string;
        /**
         * The key's SELF-DESCRIPTION (§3 amendment, A-082 r2). A bare key string cannot say which
         * family minted it, so a graph that crosses machines was previously re-joined against the local
         * root on a guess. Both members travel with every path and the import door refuses an envelope
         * whose values are outside the closed sets (refuse-not-coerce, same posture as `formatVersion`);
         * {@link resolveTrackKey} then judges each foreign-family key at restore time.
         */
        keyKind: FileHistoryTrackKeyDescriptor["kind"];
        keyFamily: FileHistoryKeyFamily;
        /**
         * DV-14: false ⇒ the pristine base could not be captured (durable base-unavailable marker); the
         * chain then starts above v1 and restore-below-base must keep refusing after import. Travels
         * with the graph; never inferred.
         */
        baseAvailable: boolean;
        /** Full retained chain (v1 first iff baseAvailable). `blobHash:null` = existed-not marker. */
        versions: Array<{
            version: number;
            blobHash: string | null;
            mode?: number;
            parentIdent?: string;
            backupTime: string;
        }>;
    }>;
    boundaries: Array<{
        entryId: string;
        files: Array<{
            relOrAbsPath: string;
            version: number;
        }>;
    }>;
}
/**
 * The IDENTITY of an envelope — sha256 over its canonical (sorted) form, so the same graph digests
 * the same however a backend ordered its rows. It is what makes `importHistory`'s idempotent
 * re-publish decidable WITHOUT extra bookkeeping: a target already holding a graph that digests
 * equal to the incoming one is the SAME import arriving twice (ok), while any other non-empty target
 * is locally-grown history the import must never overwrite (typed `conflict`). Exported under the
 * "MUST reuse" clause: two backends that computed identity differently would disagree about which
 * retries are safe.
 */
export declare function fileHistoryExportDigest(data: FileHistoryExport): string;
/**
 * The import DOOR's structural judgment, shared by every backend (a second implementation is a second
 * opinion about what is safe to accept): the envelope must carry THIS format version and every row
 * must be shaped and self-described from the closed sets — `unsupported_format` otherwise, never a
 * coercion. Backends call this BEFORE fetching a single blob, so a foreign-epoch payload costs
 * nothing and changes nothing.
 */
export declare function validateFileHistoryExport(data: FileHistoryExport): FileHistoryResult;
/**
 * design/381 §3 — per-edited-file rewind history (CC FileHistory topology).
 *
 * Replaces the E19 whole-tree FileSnapshotStore. Three verbs mirror the reference module:
 * `trackEdit` (backup-before-first-edit), `snapshot` (version the tracked set at a turn boundary),
 * `restore` (converge ONLY the tracked set to a recorded boundary).
 *
 * Store discipline (contract-kit enforced):
 * - NEVER throw: every failure is encoded in the returned result (FileSystem posture).
 * - snapshot MUST NOT fail the caller's turn: a failed boundary capture degrades to "no boundary"
 *   with disclosure (onError phase:"rewind"). trackEdit is DIFFERENT (DV-14): its `{ok:false}`
 *   means "no durable first-touch state exists" and the CALLER's default policy is to REFUSE the
 *   edit (typed, loud) — silently proceeding would let a post-recovery retry mint already-modified
 *   bytes as pristine v1. Any proceed-unprotected policy is an explicit, adjudicated caller arm,
 *   never the store's own behavior.
 * - Structurally out of scope: files the agent never edited. No store method may enumerate, read,
 *   or write ANY path outside the tracked set plus its recorded restore targets. (The cowork
 *   guarantee lives HERE, as a contract clause.)
 * - BOUNDED by default: one boundary per turn, forever, is unbounded growth. Both bundled backends
 *   trim their own scope after each commit (see {@link FileHistoryStore.reap} for the rule, the
 *   default and the host override); an out-of-tree backend owes its own bound or an explicit
 *   statement that the host must schedule the GC.
 */
export interface FileHistoryStore {
    /**
     * Backup `absPath`'s CURRENT state before an agent edit (first touch per scope only; later calls
     * are cheap no-ops). The first touch is ONE atomic durable record — pristine blob+record,
     * existence=false marker (rewind of an agent-CREATED file deletes it), or base-unavailable marker
     * (DV-14). Reads bytes via `env` (remote-safe); stores them host-side. `root` is the trusted task
     * root: paths under it are recorded root-RELATIVE (worktree/moved-root safe), paths outside it
     * verbatim-absolute; the parent-identity guard is recorded in the same frame.
     * `{ok:false}` = NO durable first-touch state was persisted — see the discipline block: the
     * caller's default is to REFUSE the edit (DV-14), because proceeding leaves the path
     * indistinguishable from never-touched. `{ok:true, minted}` — see {@link FileHistoryTrackResult}:
     * `minted:true` means this call created the record, which is what makes {@link annulTrack} legal
     * for it (#491).
     */
    trackEdit(scope: string, absPath: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryTrackResult>;
    /**
     * Discard the first-touch record `trackEdit` MINTED for `absPath` — the compensating verb for the
     * one ordering the topology cannot avoid (#491): the pre-image capture must run BEFORE the write,
     * so a write that then fails (a lost create race, ENOSPC, EACCES, a read-only mount) leaves a
     * record for an edit that never happened. Left standing, an `existed-not` v1 makes an untouched
     * path a rewind DELETE target: whoever writes those bytes next — the user, a peer process — gets
     * them removed by a restore that reports `complete`, which is precisely the structural
     * out-of-scope guarantee this contract opens with. So the record must be retractable.
     *
     * Discipline (contract-kit enforced): NEVER-throw like every other verb, and no env IO AT ALL
     * unless the caller asks for the proof read below. Retraction is legal ONLY while the record is
     * still exactly what `trackEdit` minted — a record referenced by ANY committed boundary, or one that has grown
     * past its first version, is REFUSED (typed `conflict`) and left intact, because history that a
     * boundary already froze is no longer the caller's to withdraw. An absent record is an idempotent
     * `{ok:true}` (the retry path). Callers annul ONLY what their own `trackEdit` reported as
     * `minted:true`: a path that was already tracked belongs to the edit that first touched it, and
     * discarding that baseline would be the fake-pristine hole DV-14 exists to weld shut.
     *
     * `verify` is the EVIDENCE arm (adversarial round 2). A caller whose write failed with a code the
     * FileSystem contract defines as "nothing was written" (`already_exists`, `precondition_failed`)
     * has proof and passes nothing. Every other failure — a plain write is not contractually atomic,
     * so ENOSPC, a dying transport or a thrown backend may have TRUNCATED the file before reporting —
     * must hand an `env` here: the store then re-reads the path ONCE and retracts only if the world
     * still matches what the mint recorded (bytes still absent for an existed-not mint, still the same
     * hash for a captured one). A file that changed keeps its record, because the alternative is
     * deleting the only baseline of a file the agent really did modify — damage rewind could no longer
     * reach, which is strictly worse than a stale record.
     */
    annulTrack(scope: string, absPath: string, root: string, verify?: {
        env: ExecutionEnv;
        signal?: AbortSignal;
    }): Promise<FileHistoryResult>;
    /**
     * Record a history boundary keyed by `entryId` — the TURN'S OWN initial session entry, minted
     * after that entry is appended and BEFORE any tool executes, so the boundary means "file state
     * when `entryId`'s turn began" (≡ the reference's user-message-uuid snapshot; every turn
     * incarnation appends a fresh entry, so keys never alias across re-branches from the same fork
     * point). For each tracked file: re-stat via `env`; unchanged (metadata may prove inequality,
     * content hash proves equality — DV-13: never mtime) ⇒ reference the prior version; changed ⇒
     * store a new version; missing ⇒ record a deletion marker. Zero directory-tree traversal, zero
     * size gate. Idempotent per (scope, entryId) over COMMITTED boundaries.
     *
     * Commit visibility + fencing + CAPTURE LEASE (contract-kit enforced): a boundary becomes visible
     * to canRestore/restore ONLY after its complete version mapping is durable. Each attempt is
     * FENCED (a timed-out/aborted attempt may never commit — {@link boundaryPublishVerdict}, no grace
     * threshold), and the boundary's capture window is a LEASE that the caller closes before the
     * first tool executes: after lease close, a fresh CAPTURE (any env read) for that entryId is
     * refused — a retry may only PUBLISH a mapping whose bytes were fully captured before close.
     * The close is NOT persisted and the store has no close verb (design/381 §10 S-6): close is the
     * calling Runner's in-process discipline; after a crash the new process has no capture path for
     * the old entryId (its own prepare mints only its own turn's boundary), and abandoned staging is
     * judged by COMPLETENESS alone — complete ⇒ publishable, incomplete ⇒ abandoned (that turn has no
     * boundary) — with ZERO env reads on the retry path. A turn whose pre-turn capture failed simply
     * has no boundary (disclosed; the reference behaves the same) — there is NO mid-turn re-capture,
     * because re-reading after edits would publish post-turn bytes under a pre-turn key. An entryId
     * whose boundary was COMMITTED and later REAPED is closed by the same law and for the same reason
     * (see {@link FileHistoryStore.reap}): it refuses typed with zero env reads rather than
     * re-capturing a world that has moved on. Retention can therefore make a boundary unavailable,
     * but never wrong.
     */
    snapshot(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryResult>;
    /**
     * Converge ONLY the tracked set to the boundary `entryId`: for each tracked file, target-version
     * bytes ⇒ write via `env` (the guarded-write arm and the symlink/parent-chain/kind preflights are
     * the design/381 slice-3 hardening — until then the write is plain and ADVISORY, stated, never
     * overclaimed); deletion marker ⇒ remove; identical ⇒ untouched; tracked-later-than-target ⇒ fall
     * back to its v1 (pristine) version, and a base-unavailable path (DV-14) is REFUSED with reason
     * `base_unavailable` (left at its current state — never overwritten with a fake baseline).
     * Per-file refusals SKIP that file and are counted, never failing the whole restore (CC form).
     * Paths recorded relative to `root` re-join against the CURRENT root (worktree/moved-cwd safe).
     *
     * Partial outcome is FIRST-CLASS with a three-state discriminant and the per-file ledger always
     * travels: applied / identicalSkipped / refused / failed are SEPARATE columns (a security refusal
     * must never hide inside a benign skip count), so a caller can distinguish "nothing was mutated"
     * from "the env died after k writes". Restore is per-file idempotent (identical files skip), so
     * the documented recovery for a partial outcome is: re-run the same restore; it converges. There
     * is NO all-or-nothing guarantee and none is claimed. Task-level policy on `"partial"` lives in
     * the CALLER (prepare defaults to fail-loud; see DV-15).
     */
    restore(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryRestoreResult>;
    /** Whether a boundary exists for (scope, entryId). */
    canRestore(scope: string, entryId: string): Promise<boolean>;
    /**
     * Optional dry-run: per-file change stats for a prospective restore (CC diffStats; design/381
     * slice 3). A-2 two-state law: `null` = preview UNAVAILABLE ≠ an empty stats object = a true
     * zero-coverage answer — an absent preview must never be rendered as "nothing will change".
     *
     * It is an UPPER BOUND on what a restore will touch, and the reason is worth stating rather than
     * discovering: the preview answers the CONTENT question (which tracked files differ from the
     * boundary, and by how many lines), while the per-file security preflight — symlink/kind, parent
     * chain — is decided by `restore` against the world as it stands THEN. A file listed here can
     * therefore come back refused, and a preview that pre-ran those checks would be making promises
     * about a world it does not own. Files whose target cannot be resolved at all (a missing version
     * record or blob, an unsafe key) are left OUT: the restore reports them per file, and a preview
     * cannot show the size of a change it cannot compute.
     */
    diffStats?(scope: string, entryId: string, env: ExecutionEnv, root: string): Promise<FileHistoryDiffStats | null>;
    /**
     * GC: drop boundaries in `scope` not in `keepEntryIds`; then drop version records (and content
     * blobs) referenced by NO surviving boundary — EXCEPT v1 records AND base-unavailable markers
     * (DV-14), which are retained while the scope lives (the pristine fallback and its explicit
     * refusal state are both part of the graph). Returns the number of boundaries dropped.
     *
     * WHO CALLS IT (design/381 §片2 — the bound is the backend's, not a chore left to the host): both
     * bundled backends drive this verb THEMSELVES. Every committed boundary is followed by a
     * retention pass that trims the scope to the newest
     * {@link import("./file-history-retention.js").DEFAULT_FILE_HISTORY_BOUNDARY_KEEP} boundaries —
     * an engine default a deployment overrides through the store's own `retention` option, including
     * `keep: "unbounded"` to switch the self-trim off and take the schedule back. The pass is
     * best-effort by construction (a GC failure never fails the turn that triggered it) and it only
     * ever removes boundaries the caller could not name any more anyway.
     *
     * A backend outside this repo therefore owes ONE of two things, and silence is neither: its own
     * bound (this verb, driven from its own commit path), or a documented statement that the host
     * must schedule `reap`. An unbounded boundary graph is not a tuning choice — one boundary per
     * turn, forever, is a disk that fills with no line anywhere admitting it would.
     *
     * WHAT THE SELF-TRIM DOES NOT COVER, said here rather than left to be discovered: it runs at a
     * COMMIT, so a graph that arrives whole — `adoptScope`, `importHistory` — is published whole and
     * comes under the bound at that scope's next commit, not before. That is deliberate rather than
     * an oversight (round 2 raised it): both verbs promise all-or-nothing visibility of the graph the
     * caller handed over, and trimming during publication would silently discard history the host
     * explicitly asked to transfer. The overshoot is bounded by the envelope the host chose, which is
     * a different thing from the per-turn source this bound exists to stop.
     *
     * A REAPED entryId IS SPENT (adversarial review — a law this verb acquired the day anything
     * started calling it): dropping a boundary must not hand its key back. Until boundaries could be
     * dropped, the committed mapping was itself the record that an entryId had been consumed, and
     * `snapshot`'s idempotent short-circuit read it; a store that forgot a reaped id would let a
     * later `snapshot` for that key take the CAPTURE path and publish post-turn bytes under a
     * pre-turn key. Both bundled backends therefore tombstone what they reap and refuse a later
     * capture of it, typed and with zero env reads. The ledger is IDS, not mappings, and it is
     * unbounded — stated plainly because a bound's own residue growing forever would be a poor joke:
     * one id string per boundary ever reaped, against the complete tracked-set mapping each of those
     * boundaries used to carry. It is the cheap part of what the bound throws away, by two orders of
     * magnitude, and it is what makes the bound safe rather than merely small.
     *
     * Concurrency is unchanged and stays the caller's to reason about: a pass that runs while another
     * entryId's capture is parked at an env read may drop a version that capture staged, and that
     * capture then refuses TYPED (its turn has no boundary) rather than publishing a dangling
     * reference — the arm the commit paths already validate for.
     */
    reap(scope: string, keepEntryIds: string[]): Promise<number>;
    /**
     * Cross-session carry (CC copyFileHistoryForResume): make `toScope` see `fromScope`'s boundaries
     * + versions (content-addressed backends copy references, zero bytes). Same-scope call is a
     * no-op. ATOMIC-VISIBILITY, same law as snapshot: the WHOLE adopted graph (tracked set, every
     * version incl. base-availability state, every referenced blob) is staged and verified, then
     * published in one step — `toScope` either sees the complete history or none of it. A
     * failed/partial adopt publishes NOTHING; the resume still proceeds (same availability posture as
     * the reference's tolerated partial copy, without the half-visible graph) and the failure is
     * disclosed. EMPTY-TARGET ONLY: adoption is refused (loud, typed `conflict`) once `toScope` has
     * ANY history of its own — adoption's seat is the fork/resume boundary BEFORE the new session's
     * first turn; a retry after the target has begun writing would need a destructive merge
     * (version-number collisions, overwritten target history) that this contract deliberately
     * forbids. Retry while the target is still empty is idempotent and repairs abandoned staging.
     *
     * WHO CALLS IT — THE ENGINE DOES, since design/381 slice 3, and the seat is worth knowing because
     * it decides WHEN this verb runs against your backend. `prepareTask` carries a forked session's
     * history on the first turn it prepares for it, keyed on the LINEAGE FACT
     * (`SessionMetadata.forkedFrom`) rather than on the code path that forked — so the Agent tool's
     * fork route, `warmResume`, and a deployment that forks a session and then runs a task on it all
     * reach the same seat. It runs BEFORE the restore seat, so the very first task on a fork may
     * itself ask to restore files to a pre-fork boundary.
     *
     * The empty-target law is what makes that safe to attempt rather than schedule: a scope that
     * already holds history answers the typed `conflict`, which at that seat MEANS "already carried,
     * or grown its own" and is treated as a no-op. Only a non-conflict failure is disclosed
     * (`onError` phase `"rewind"`), because that one is a carry the deployment expected and did not
     * get. A failed carry never blocks the fork's run.
     *
     * A HOST MAY STILL CALL IT — the engine's seat covers sessions the engine prepares, and a
     * deployment that copies history between scopes for its own reasons (a migration, a scope rename)
     * is calling the same verb under the same law. What a MISSED carry gets is degraded, not broken,
     * and it is worth naming precisely: the new scope starts EMPTY, so nothing the old one tracked is
     * restorable from it. A rewind aimed at a pre-fork boundary answers "no boundary" (honest, typed)
     * instead of converging those files, and the new lineage's own first touch of a path records THAT
     * path's then-current bytes as its pristine v1 — a correct baseline for the new lineage, just not
     * the old one's history. Nothing is silently mis-restored; the carry simply does not happen.
     */
    adoptScope(fromScope: string, toScope: string): Promise<FileHistoryResult>;
    /**
     * Session-sync export/import (2c), versioned envelope — see {@link FileHistoryExport}. Optional.
     * importHistory obeys the SAME atomic-visibility + staged-verify law as adoptScope: every
     * referenced blob fetched and integrity-checked, then the scope's graph published in one step; a
     * mid-import failure publishes nothing. NON-DESTRUCTIVE TARGET LAW (same family as adoptScope): a
     * first import requires an EMPTY target graph; a retry is legal only while the target is still
     * empty OR already holds a graph with the exact same import identity/digest (pure idempotent
     * re-publish). A non-empty target with any other content ⇒ typed `conflict` refusal — atomic
     * visibility prevents half-graphs, this law prevents publishing the WRONG complete graph over
     * locally-grown history.
     */
    /**
     * NO `dispose` / lifecycle verb, and that is a decision rather than an omission (design/381 slice 3,
     * the R-6 review candidate — recorded here because the next reader will otherwise wonder). The case
     * for one: a directly-constructed file backend holds no store lock, so the single-writer premise is
     * enforced only where a deployment assembles the backend (its data-root LOCK), and a `dispose`
     * would let a store hold a lease and release it. The case against, which won:
     *  · every verb here is self-contained — a store holds no cross-call resource, so `dispose` would be
     *    an obligation on EVERY backend (a SQL twin included) in order to serve one backend's optional
     *    locking;
     *  · a lock released by a verb nobody is required to call fails WORSE than no lock: a crash leaves a
     *    stale lease that blocks a store which today simply works, and the recovery is manual;
     *  · the single-writer premise is the deployment's to make. The assembled backend is where it is
     *    enforced, and a directly-constructed store is the same "I know what I am doing" path every
     *    sibling file store in this family offers.
     * A deployment that needs cross-process exclusion takes the assembled backend, or brings its own.
     */
    exportHistory?(scope: string): Promise<FileHistoryExport | null>;
    importHistory?(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
}
/**
 * design/381 §3 + §10 T-5 — the FENCING judge for a boundary-capture attempt, exposed as a
 * direct-callable function (the G6b grey-box seat) and used by every in-tree backend as the single
 * commit gate: an attempt whose signal fired — timeout or abort, no matter how close to completion
 * the capture got — may NEVER publish its mapping. ABSOLUTE, no grace threshold (§10 T-5: "无宽限
 * 期" — the verdict has no time axis at all): a late-completing capture may have read bytes AFTER
 * the lease closed (post-edit bytes), and publishing them under the pre-turn key is exactly the
 * poisoning the fence exists to stop.
 */
export declare function boundaryPublishVerdict(attempt: {
    aborted: boolean;
}): {
    publish: true;
} | {
    publish: false;
    reason: "aborted";
};
/** One captured state of one file, as read through the env (the shared capture half). */
export type FileBackupCapture = 
/** `mode` = the env's POSIX permission bits when it HAS that face (design/381 DV-8); absent when
 *  the env cannot express them — never fabricated, so a restore can tell "this mode was recorded
 *  and could not be applied" from "there was never a mode to keep". */
{
    state: "file";
    bytes: Uint8Array;
    hash: string;
    mode?: number;
    parentIdent?: string;
} | {
    state: "absent";
    parentIdent?: string;
} | {
    state: "aborted";
}
/** Non-ENOENT read failure: the file may exist but its bytes are not capturable right now. */
 | {
    state: "unreadable";
    cause: string;
};
/**
 * Capture ONE file's current state through the ExecutionEnv — **the env-operating half every
 * backend MUST reuse** (design/381 §3 分工, the E19 captureManifest condition carried forward:
 * re-implementing it per backend is how symlink-escape/fail-closed bugs come back). Used by
 * `trackEdit` (first-touch pristine) and `snapshot` (per-file re-read). `not_found` is the sole
 * absent signal (the read itself is the authoritative existence probe — a separate stat would only
 * re-open the same race one window later). The parent-identity guard rides the same frame:
 * `canonicalPath(parent)` — root-relativized when under `root` (DV-6: a legal whole-root move must
 * keep verifying; an in-root parent-chain swap must keep refusing) — recorded best-effort (an env
 * that cannot canonicalize omits it; the slice-3 preflight then has nothing to compare, which is
 * the honest degraded form, never a fabricated identity).
 */
export declare function captureFileBackupViaEnv(env: ExecutionEnv, absPath: string, root: string, signal?: AbortSignal): Promise<FileBackupCapture>;
/**
 * The retraction PROOF READ (design/381 #491 + adversarial round 2), shared by every backend so the
 * evidence rule cannot drift: does the path still look exactly as the first touch recorded it? An
 * existed-not mint requires the path to still be absent; a captured mint requires the same content
 * hash. Anything else — including an unreadable path — leaves the record standing, because the only
 * failure this arm exists to distinguish is "the write never landed" from "the write may have
 * changed the file". A base-unavailable marker is REFUSED here rather than waved through
 * (adversarial round 3): it carries no bytes to compare, but the marker itself is the durable
 * refusal state that stops a later touch from minting already-modified bytes as pristine — erasing
 * it on an ambiguous failure re-opens exactly the DV-14 hole it exists to close. It stays
 * retractable through the `"proven"` path, which needs no evidence read at all.
 */
export declare function worldStillMatchesMint(env: ExecutionEnv, absPath: string, root: string, baseAvailable: boolean, mintedBlobHash: string | null, signal?: AbortSignal): Promise<{
    unchanged: true;
} | {
    unchanged: false;
    reason: string;
}>;
/**
 * DV-13's metadata rule applied to the MODE axis (design/381 slice 3), and it is a rule about
 * EVIDENCE rather than about equality: metadata may prove two states DIFFERENT, never the same. Two
 * known-and-unequal modes are a proven difference, so the file is versioned again even though its
 * bytes are identical (the permission bit is part of the state a rewind restores). An UNKNOWN mode
 * on either side proves nothing and must not manufacture a version — which is also what keeps a
 * graph captured before this field existed from re-versioning its whole tracked set on the first
 * boundary after an upgrade.
 */
export declare function modeProvenDifferent(recorded: number | undefined, current: number | undefined): boolean;
/** The target one file must converge to (bytes+hash, or "must not exist"). */
export type FileRestoreTarget = {
    bytes: Uint8Array;
    hash: string;
} | {
    absent: true;
};
/**
 * The recorded facts the slice-3 preflight judges the current world against, plus the abort signal
 * (design/381 §6 slice 3). Every member is OPTIONAL on purpose: a record captured by an env without
 * the mode face, or without canonicalization, carries neither — and a guard with nothing recorded to
 * compare must degrade to "no opinion", never to a fabricated expectation.
 */
export interface FileRestoreApplyOptions {
    /** The task root the recorded key was re-joined against — the coordinate `parentIdent` lives in. */
    root: string;
    /** The parent identity recorded WITH the version being restored ({@link parentIdentityViaEnv}). */
    parentIdent?: string;
    /** The POSIX mode recorded with the version being restored (design/381 DV-8). */
    mode?: number;
    signal?: AbortSignal;
}
export type FileRestoreApplyOutcome = 
/** `modeNotPreserved` rides the two NON-refusal arms: the bytes converged but the recorded mode
 *  could not be re-applied (no env mode face, or the env refused/failed the change). A disclosure,
 *  never a failure — the file's CONTENT is what the restore promised. */
{
    outcome: "applied";
    modeNotPreserved?: true;
} | {
    outcome: "identical";
    modeNotPreserved?: true;
} | {
    outcome: "refused";
    reason: string;
} | {
    outcome: "failed";
    reason: string;
};
/**
 * Converge ONE file to `target` through the ExecutionEnv — **the env-operating + security half
 * every backend MUST reuse** (design/381 §3 分工). skip-if-identical is content-hash proven (DV-13:
 * metadata may only ever prove INEQUALITY, never equality; mtime is never consulted), so an
 * identical file is untouched (no write, no mtime churn). Deletion markers remove the current file
 * (ENOENT tolerated = already converged).
 *
 * THE PREFLIGHT FAMILY (design/381 slice 3, DV-6 — the env-level approximation of the reference's
 * fd-level `O_NOFOLLOW` + dev/ino re-check, ordered cheapest-refusal-first). Every arm SKIPS the
 * one file and is counted; none of them fails the whole restore:
 *  · KIND — the addressed object (no-follow `fileInfo`) must be a regular file or absent. A symlink
 *    at the leaf is refused rather than followed: the recorded bytes belong to the file the agent
 *    edited, not to whatever a link now points at. A directory/socket/FIFO in its place is the same
 *    refusal for the same reason.
 *  · PARENT CHAIN — the current canonical parent identity must equal the one recorded with the
 *    version ({@link parentIdentityViaEnv}, root-relative under the root). A legal whole-root move
 *    keeps it equal (E4 passes); an in-root ancestor swapped for a link out of the tree does not
 *    (E5 refuses, and nothing outside the root is written). BOTH must hold at once — relaxing to
 *    "moves also refuse" or "swaps also pass" is a mis-fix, not a tuning choice.
 *  · The guard is EVIDENCE-BASED, not assumption-based: a record with no parent identity (an env
 *    that cannot canonicalize) and a parent that cannot be canonicalized NOW (the ordinary "the
 *    directory was deleted and this restore recreates it" case) both mean "nothing to compare", and
 *    inventing a refusal there would break the recreate arm rather than close a hole.
 *
 * THE WRITE uses `env.writeFileGuarded` when the env has it — one atomic verify-and-write whose
 * expectation names the addressed path and forbids following a leaf link, so the window between
 * this preflight and the write is closed at the backend. An env WITHOUT it falls back to plain
 * `env.writeFile` and the convergence is then ADVISORY on the TOCTOU axis: the preflight above is a
 * check-then-act, and a sufficiently fast racer can still swap the leaf in between. Stated here
 * because the alternative — a two-step emulation behind an atomic-looking face — is exactly what
 * that capability's own contract forbids.
 *
 * MODE (DV-8): a recorded mode is re-applied through the optional `setFileMode` face after the
 * bytes land (and on an otherwise-identical file whose mode drifted, which IS a convergence). An env
 * without the face — or one that refuses the change — yields `modeNotPreserved`, disclosed by the
 * caller's ledger; it never turns a successful content restore into a failure.
 */
export declare function applyFileRestoreViaEnv(env: ExecutionEnv, absPath: string, target: FileRestoreTarget, opts: FileRestoreApplyOptions): Promise<FileRestoreApplyOutcome>;
/**
 * ONE file's contribution to {@link FileHistoryStore.diffStats}, shared by both bundled backends:
 * `undefined` = this file would NOT change (already at the target, or already absent for a deletion
 * marker), otherwise the line arithmetic of the change. Content-hash proven, exactly like the
 * restore's own identical-skip — a preview that used a cheaper test than the operation it previews
 * would disagree with it.
 */
export declare function previewFileDelta(env: ExecutionEnv, absPath: string, targetHash: string | null, targetBytes: Uint8Array | undefined, targetMode?: number): Promise<{
    insertions: number;
    deletions: number;
} | undefined>;
interface FileHistoryVersionRecord {
    version: number;
    /** Content-addressed blob hash, or null = "did not exist at this point" (existed-not marker —
     *  serves both the pre-creation v1 arm and later deletion arms). */
    blobHash: string | null;
    mode?: number;
    parentIdent?: string;
    backupTime: string;
}
interface TrackedFileState {
    /** DV-14: false ⇒ the pristine base could not be captured; a durable refusal state, not an
     *  absence — versions then start ABOVE v1 and v1 is never minted later. */
    baseAvailable: boolean;
    /** The key's self-description (§3 amendment): recorded at the mint so a graph that later crosses
     *  machines can be judged instead of guessed. */
    keyDesc: FileHistoryTrackKeyDescriptor;
    /** Set when a LATER trackEdit for this path was served (answered `minted:false`) — that lane is
     *  now relying on this record as its own pre-edit baseline, so the original minter may no longer
     *  retract it (adversarial round 1: two lanes racing one path, the first failing its write, would
     *  otherwise delete the baseline the SECOND lane's successful edit depends on). */
    observedByLaterTouch?: boolean;
    versions: FileHistoryVersionRecord[];
}
/** One staged per-file outcome of a capture attempt (commit assigns version numbers atomically). */
type StagedFileCapture = {
    kind: "unchanged";
    version: number;
} | {
    kind: "changed";
    blobHash: string | null;
    mode?: number;
    parentIdent?: string;
};
/**
 * In-memory, content-addressed reference {@link FileHistoryStore}. Blobs are deduplicated by sha256
 * ACROSS all scopes and versions (a file unchanged between boundaries stores zero new bytes); each
 * tracked file keeps a version chain, each boundary a complete `trackKey → version` mapping over
 * the tracked set as of its mint. Single-process (the default-deps reference; the durable
 * file-backed impl is design/381 slice 2).
 *
 * Grey-box fault seams (design/381 §7 C7 / §10 T-4): `persistFirstTouch` and `publishBoundary` are
 * `protected` COMMIT points — a test subclass overrides one to throw and the public verb answers
 * the typed `{ok:false}` the contract promises (the store itself still never throws).
 *
 * RETENTION is built in (design/381 §片2): each committed boundary is followed by a trim back to
 * {@link import("./file-history-retention.js").DEFAULT_FILE_HISTORY_BOUNDARY_KEEP} newest boundaries per scope, overridable through
 * {@link InMemoryFileHistoryStoreOptions.retention}. Even a single-process reference store needs
 * it — a long-lived process IS the unbounded case.
 */
export interface InMemoryFileHistoryStoreOptions {
    /** design/381 §片2 — the built-in boundary bound and its override. Omitted ⇒ the engine default
     *  ({@link import("./file-history-retention.js").DEFAULT_FILE_HISTORY_BOUNDARY_KEEP}); a malformed policy is REFUSED here, at the wiring
     *  site, rather than silently becoming a different policy inside a GC pass nobody watches. */
    retention?: FileHistoryRetentionPolicy;
}
export declare class InMemoryFileHistoryStore implements FileHistoryStore {
    private readonly scopes;
    /** Resolved boundary cap, or `null` for the explicit `"unbounded"` opt-out (host owns the GC). */
    private readonly boundaryKeep;
    private readonly blobs;
    /** Blob hashes written by an in-flight capture BEFORE its commit — a racing reap must treat them
     *  as live (same MAJOR-1 posture as the retired whole-tree store). REFCOUNTED, not a set
     *  (adversarial round): two concurrent captures can lease the same new hash, and the first
     *  attempt's release must not strip the second attempt's liveness. */
    private readonly inFlightBlobs;
    constructor(opts?: InMemoryFileHistoryStoreOptions);
    /**
     * The RETENTION pass, run after a boundary commits (design/381 §片2 — see {@link FileHistoryStore.reap}
     * for why the backend owns this rather than the host). Boundaries are an insertion-ordered map
     * here, so commit order is exact and the selection needs no clock.
     *
     * Never throws and never fails the commit that triggered it: the boundary is already published and
     * a GC that could not run is a graph that is merely larger than its bound — the strictly safer of
     * the two failure directions, and the same posture the reference implementation's own eviction has.
     */
    private applyBoundaryRetention;
    private leaseBlob;
    private releaseBlob;
    private scope;
    /** PROTECTED COMMIT SEAM (fault-injectable): durably record one path's first-touch state.
     *  Synchronous single assignment = the "ONE atomic durable record" of DV-14 for this backend. */
    protected persistFirstTouch(scope: string, key: string, state: TrackedFileState): void;
    /** PROTECTED COMMIT SEAM (fault-injectable): publish a captured boundary in ONE synchronous step
     *  — version records + mapping become visible together or not at all (atomic visibility). */
    protected publishBoundary(scope: string, entryId: string, staged: Map<string, StagedFileCapture>): void;
    trackEdit(scope: string, absPath: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryTrackResult>;
    annulTrack(scope: string, absPath: string, root: string, verify?: {
        env: ExecutionEnv;
        signal?: AbortSignal;
    }): Promise<FileHistoryResult>;
    snapshot(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryResult>;
    restore(scope: string, entryId: string, env: ExecutionEnv, root: string, signal?: AbortSignal): Promise<FileHistoryRestoreResult>;
    canRestore(scope: string, entryId: string): Promise<boolean>;
    diffStats(scope: string, entryId: string, env: ExecutionEnv, root: string): Promise<FileHistoryDiffStats | null>;
    reap(scope: string, keepEntryIds: string[]): Promise<number>;
    adoptScope(fromScope: string, toScope: string): Promise<FileHistoryResult>;
    exportHistory(scope: string): Promise<FileHistoryExport | null>;
    /** The export projection as a SYNCHRONOUS function — the import's final claim needs to compare a
     *  target's identity without yielding (see the publish step). */
    private exportOf;
    importHistory(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
    /** The import body inside the blob lease (see {@link importHistory}). */
    private importStaged;
}
/** Test-only window (family-aware path-coordinate pins + the DV-6 track-key coordinate): pure
 *  helpers whose behavior carries a fence/restore invariant. Not re-exported from src/index.ts. */
export declare const __testables: {
    joinUnder: typeof joinUnder;
    relUnder: typeof relUnder;
    stripTrailSep: (p: string) => string;
    trackKeyOf: typeof trackKeyOf;
    resolveTrackKey: typeof resolveTrackKey;
    parentDirOf: typeof parentDirOf;
};
export {};
