/**
 * vault — snapshot + hardened git execution (protocol rev 41 §6.6; task 5.5).
 *
 * Snapshot policy: clean tree → `HEAD`; dirty → REFUSED BY DEFAULT
 * (`SNAPSHOT_DIRTY_TREE`, before any object is created) unless
 * `allowDirty: true`, in which case a synthetic commit of tracked +
 * untracked-not-ignored paths is captured (parented on HEAD; parentless on
 * an unborn HEAD) and the result discloses exactly what got swept in
 * (`modified_captured` / `untracked_captured`). "Dirty" means any modified
 * or staged tracked path, or any untracked-not-ignored path — a project
 * whose build step writes gitignored output is never dirty on that account.
 * Unmerged index → `SNAPSHOT_CONFLICTED_INDEX` (checked first — allowDirty
 * does not touch it); detached HEAD is representable; linked worktrees, bare
 * layouts, sparse checkouts, shallow clones, submodules, SHA-256
 * repositories, alternates/grafts, and active filters on any captured path
 * are REFUSED BY NAME before any object is created.
 *
 * Ignore authority is FROZEN and read AS DATA: repository `.gitignore`,
 * `.git/info/exclude`, and the user's global excludes file. The global path
 * is discovered deterministically (round-6 M11) by parsing
 * `$XDG_CONFIG_HOME/git/config` then `~/.gitconfig` with a frozen config
 * subset (last value wins, `~/`/`$HOME` expansion only; `include`/`includeIf`
 * or an unreadable config/excludes file → refuse by name). git never sees the
 * user's config: the discovered file is handed to `git ls-files` as an
 * `--exclude-from` pattern source, so git's matcher runs over OUR discovery.
 *
 * Object creation NEVER invokes filters for any path (tracked or untracked):
 * bytes are hashed with `git hash-object --no-filters`; every included path is
 * first checked with `git check-attr filter`, and any configured filter (LFS
 * included) refuses the capture by name — no clean/smudge/process command is
 * ever started (the acceptance test asserts a sentinel the filter would write
 * never appears).
 *
 * The capture also records the CAPTURED SET (`snapshot.captured`) and a digest
 * over it (`snapshot.captured_digest`). The deploy lane re-derives that digest
 * after artifacts are collected and before a plan commits, and refuses
 * `SNAPSHOT_MOVED_DURING_DEPLOY` on any difference. The set is tracked plus
 * untracked-but-not-ignored — so a build rewriting gitignored output changes
 * nothing, deliberately.
 *
 * Every git invocation is argv-only (no shell), with `GIT_*` cleared,
 * `GIT_CONFIG_NOSYSTEM`, `GIT_CONFIG_GLOBAL=/dev/null` (no user config), an
 * empty `core.hooksPath`, `--no-replace-objects`, `core.fsmonitor=false`, and
 * no alternates. Repository-config `include`/`includeIf` is refused by name
 * (git applies it before any `-c`; there is no override switch).
 */
import type { VaultHeadTarget } from "../namespaces/vault.types.js";
/** `MAX_GIT_OBJECT_BYTES` — any single object above this is `GIT_OBJECT_TOO_LARGE`. */
export declare const VAULT_MAX_GIT_OBJECT_BYTES: number;
/** `refs/run402/deploys/latest` — the single protocol-owned deploy ref (round-3 H9). */
export declare const VAULT_DEPLOY_REF = "refs/run402/deploys/latest";
/** Probed git version range: `[2.32, ∞)` — `GIT_CONFIG_GLOBAL` landed in 2.32. */
export declare const VAULT_MIN_GIT_VERSION: readonly [2, 32];
export interface HardenedGitOptions {
    /** Bytes written to stdin (closed immediately when omitted). */
    input?: Uint8Array | string;
    /** Extra environment members — only `GIT_INDEX_FILE` and the author/committer identity are ever passed here. */
    env?: Record<string, string>;
    /** Accept these non-zero exit statuses instead of throwing. */
    okStatuses?: number[];
    /** Max stdout bytes (packs are large). Default 1 GiB. */
    maxBuffer?: number;
}
export interface HardenedGitResult {
    status: number;
    stdout: Buffer;
    stderr: string;
    text(): string;
    lines(): string[];
    /** NUL-separated output → entries (trailing NUL dropped). */
    nul(): string[];
}
/** The environment every vault git process runs under — nothing inherited except PATH. */
export declare function hardenedGitEnv(extra?: Record<string, string>): Record<string, string>;
/**
 * The `RUN402_*` environment this process was configured with — for the ONE
 * vault git invocation that touches the network (`cloneVaultRemote`):
 * that clone spawns the `git-remote-run402` helper,
 * which must authenticate as THIS session's wallet and config dir. Under the
 * bare {@link hardenedGitEnv} the helper saw no `RUN402_CONFIG_DIR` /
 * `RUN402_WALLET` / `RUN402_API_BASE` and fell back to the machine's default
 * config — a different wallet, not a member of the vault's org — so a
 * `resume` on any machine that selects its identity by env (a laptop with
 * several agent config dirs, a CI job, a harness with `RUN402_WALLET`) died
 * `VAULT_ACCESS_DENIED` "while resolving the project's vault" right
 * after a successful claim (kygit-handoff live rerun, 2026-09-03). Only
 * `RUN402_*` keys pass; git's own hardening is untouched.
 */
export declare function run402PassthroughEnv(source?: NodeJS.ProcessEnv): Record<string, string>;
/** The argv prefix that neutralizes hooks, fsmonitor, replace refs, and filter autodetection for every invocation. */
export declare const HARDENED_GIT_ARGV_PREFIX: () => string[];
/**
 * Run git with explicit args (no shell) in `cwd` under the hardened env.
 * Throws `GIT_COMMAND_FAILED` on a non-ok exit.
 *
 * Node's `execFile` reports a MISSING `cwd` with the exact same `ENOENT` as a
 * missing `git` binary, so a caller that forgot to create its working
 * directory (e.g. `recover`'s `out_dir` before this fix) saw the misleading
 * `GIT_UNAVAILABLE: git could not be executed` even though git itself was
 * perfectly installed. Disambiguate BEFORE spawning: a missing `cwd` is its
 * own distinct, honest refusal, never folded into "git is unavailable."
 */
export declare function hardenedGit(cwd: string, args: string[], options?: HardenedGitOptions): Promise<HardenedGitResult>;
/**
 * The repository a `git-remote-*` helper was invoked for.
 *
 * `repo_dir` is what every hardened git call takes as `cwd`. Because
 * {@link hardenedGitEnv} builds its environment from scratch — `GIT_DIR` is
 * never inherited — git's discovery starts at that directory, and starting it
 * AT the git directory makes discovery land on that repository and no other.
 */
export interface GitInvocationRepo {
    /** The absolute git directory git named for this invocation. */
    git_dir: string;
    /** Hand this to `hardenedGit` as `cwd`; discovery from here resolves to `git_dir`. */
    repo_dir: string;
}
/**
 * Resolve — and PROVE — the repository git invoked this helper for.
 *
 * git always sets `GIT_DIR` when it runs a remote helper against a
 * repository: `.git` (relative, cwd already moved to the top level) for
 * fetch/push, the linked worktree's git dir inside a worktree, `.` in a bare
 * repository, and the ABSOLUTE path of the not-yet-populated target during
 * `git clone`. It is unset only for a repository-free invocation such as
 * `git ls-remote <url>` run outside any checkout, which needs no repository
 * at all.
 *
 * So `process.cwd()` is NOT the repository: during a clone it is the directory
 * the user ran clone FROM, which is routinely inside some unrelated repo. A
 * helper that discovers its repository from cwd therefore writes the vault's
 * decrypted objects into a repository the user never named — the leak this
 * function exists to make impossible. It fails closed: no `GIT_DIR`, a
 * `GIT_DIR` that is not a repository, or a `GIT_DIR` whose own
 * `--absolute-git-dir` disagrees with the path we were handed, all refuse.
 * The caller must write nothing after a refusal.
 */
export declare function resolveGitInvocationRepo(env?: {
    GIT_DIR?: string | undefined;
}, cwd?: string): Promise<GitInvocationRepo>;
/** `git version` parsed as [major, minor]; refuses below {@link VAULT_MIN_GIT_VERSION}. */
export declare function probeGitVersion(cwd?: string): Promise<[number, number]>;
export interface GitConfigDiscoveryEnv {
    HOME?: string;
    XDG_CONFIG_HOME?: string;
}
/** The frozen config subset: `[section "sub"]` headers + `key = value` lines; quoting per git-config; comments `#`/`;`. */
export interface ParsedGitConfigEntry {
    section: string;
    subsection: string | null;
    key: string;
    value: string;
    file: string;
}
export type GlobalExcludesRefusal = {
    code: "VAULT_CONFIG_INCLUDE_REFUSED";
    file: string;
    key: string;
} | {
    code: "VAULT_CONFIG_UNREADABLE";
    file: string;
} | {
    code: "VAULT_EXCLUDES_PATH_UNSUPPORTED";
    file: string;
    value: string;
} | {
    code: "VAULT_EXCLUDES_UNREADABLE";
    file: string;
};
export interface GlobalExcludesDiscovery {
    /** The resolved excludes path — `null` when no file exists (an absent DEFAULT path is fine; an absent EXPLICIT `core.excludesFile` is also fine — git ignores it too). */
    path: string | null;
    /** Which config (if any) set `core.excludesFile`. */
    source: "core.excludesFile" | "default";
    /** The config files that were read (in read order). */
    configs_read: string[];
    /** The pattern bytes, read as data, when `path` exists. */
    patterns: string | null;
}
/** Parse a git config file as data (frozen subset). Throws by name on `include`/`includeIf`. */
export declare function parseGitConfigAsData(text: string, file: string): ParsedGitConfigEntry[];
/**
 * Deterministic discovery of the user's global excludes file (M11):
 * `$XDG_CONFIG_HOME/git/config` (default `~/.config/git/config`) THEN
 * `~/.gitconfig`, last value wins; default `$XDG_CONFIG_HOME/git/ignore`
 * falling back to `~/.config/git/ignore`. Every refusal names its file.
 */
export declare function discoverGlobalExcludes(env?: GitConfigDiscoveryEnv): GlobalExcludesDiscovery;
export type VaultRepositoryRefusalCode = "VAULT_BARE_REPOSITORY" | "VAULT_LINKED_WORKTREE" | "VAULT_SPARSE_CHECKOUT" | "VAULT_SHALLOW_CLONE" | "VAULT_SUBMODULES_UNSUPPORTED" | "GIT_OBJECT_FORMAT_UNSUPPORTED" | "VAULT_ALTERNATES_UNSUPPORTED" | "VAULT_GRAFTS_UNSUPPORTED" | "VAULT_CONFIG_INCLUDE_REFUSED" | "VAULT_NOT_A_REPOSITORY";
export interface VaultRepositoryInspection {
    top_level: string;
    git_dir: string;
    git_common_dir: string;
    object_format: "sha1";
    head: VaultHeadTarget & {
        resolved_oid: string | null;
    };
    unmerged_paths: string[];
}
/** Inspect `dir`; every unsupported layout is refused with its named code. */
export declare function inspectRepository(dir: string): Promise<VaultRepositoryInspection>;
export interface VaultSnapshotOptions {
    /** The work tree (any directory inside it). */
    dir: string;
    /** Discovery environment; defaults to the process's HOME / XDG_CONFIG_HOME. */
    env?: GitConfigDiscoveryEnv;
    /** When set, also move the LOCAL `refs/run402/deploys/latest` to the snapshot (keeps a synthetic commit reachable across `git gc`). Default true. */
    update_local_deploy_ref?: boolean;
    /** Commit message for a synthetic commit. */
    message?: string;
    /** Clock (synthetic commit timestamps). */
    now?: () => Date;
    /**
     * Capture a dirty tree anyway. Default `false` — a dirty tree (any
     * modified/staged tracked path, or any untracked-not-ignored path) is
     * refused with `SNAPSHOT_DIRTY_TREE` before any object is created, in
     * BOTH capture lanes (manual `repos capture` and the deploy-lane
     * capture). Has no effect on a clean tree, and never overrides
     * `SNAPSHOT_CONFLICTED_INDEX` (an unmerged index is checked first and
     * refuses regardless). See {@link VaultSnapshot.modified_captured} /
     * {@link VaultSnapshot.untracked_captured} for the disclosure this
     * produces once set.
     */
    allowDirty?: boolean;
}
/** How many dirty-tree paths `SNAPSHOT_DIRTY_TREE`'s `details.modified`/`details.untracked` list before capping. */
export declare const SNAPSHOT_DIRTY_TREE_LIST_CAP = 500;
export interface VaultSnapshotRefusalPath {
    path: string;
    filter: string;
}
export interface VaultSnapshot {
    /** `head` = the tree was clean and the snapshot IS `HEAD`; `synthetic` = a commit was created for a dirty tree. */
    kind: "head" | "synthetic";
    /** The snapshot commit oid — the `vault_commit`. */
    oid: string;
    /** The tree the snapshot resolves to. */
    tree_oid: string;
    /** What `HEAD` was at capture — symref (possibly unborn) or detached. */
    head: VaultHeadTarget;
    /** `HEAD`'s commit when it had one. */
    head_oid: string | null;
    /** Paths included in a synthetic commit (tracked + untracked-not-ignored); empty for `head`. */
    paths: string[];
    /**
     * The CAPTURED SET, always populated for both kinds: every tracked or
     * untracked-but-not-ignored path present on disk at capture, with the mode
     * and the raw-bytes oid the snapshot corresponds to. This is the set the
     * correspondence check re-derives — NOT the whole working directory, and
     * deliberately NOT gitignored build output (see {@link capturedSetDigest}).
     */
    captured: VaultCapturedFile[];
    /** `sha256` over {@link captured} — the value re-derived before a plan commits. */
    captured_digest: string;
    /** The global excludes file honored (as data), when one existed. */
    global_excludes_path: string | null;
    top_level: string;
    /**
     * Disclosure for an `allowDirty: true` capture: tracked paths that were
     * modified/staged relative to `HEAD` (or, on an unborn `HEAD`, every
     * populated index path) and got swept into the capture. Empty for a clean
     * tree (`kind: "head"`) — there was nothing to sweep in.
     */
    modified_captured: string[];
    /**
     * Disclosure for an `allowDirty: true` capture: untracked-not-ignored
     * paths that got swept into the capture. Empty for a clean tree
     * (`kind: "head"`).
     */
    untracked_captured: string[];
}
/** One member of the captured set: path, mode, and the oid of its RAW bytes (`--no-filters`). */
export interface VaultCapturedFile {
    path: string;
    mode: "100644" | "100755" | "120000";
    oid: string;
}
/**
 * `sha256` over the captured set, in git's own index-info line form
 * (`<mode> <oid>\t<path>\n`, path-sorted) so the serialization is the one the
 * tree is built from rather than a second, drifting encoding.
 *
 * WHAT THIS COVERS, EXACTLY: the captured source set — tracked plus
 * untracked-but-not-ignored. It does NOT cover gitignored paths, so a build
 * that rewrites `dist/` between capture and commit changes nothing here, by
 * design: a project with a build step would otherwise be undeployable. The
 * guarantee this digest buys is "the captured source did not change while the
 * artifacts were produced", NOT "the artifacts are a function of that source".
 */
export declare function capturedSetDigest(files: VaultCapturedFile[]): string;
/**
 * Re-derive the captured set from the work tree, without writing any object.
 *
 * `global_excludes_path` is the file the capture honored — passed through
 * rather than rediscovered, so the comparison is about the TREE and cannot be
 * perturbed by the user's git config changing mid-deploy.
 */
export declare function deriveCapturedSet(options: {
    top_level: string;
    global_excludes_path?: string | null;
}): Promise<VaultCapturedFile[]>;
/** What changed between two captured sets. Empty on every list means the set is byte-identical. */
export interface VaultCapturedSetDrift {
    added: string[];
    removed: string[];
    modified: string[];
}
/** Name the paths that moved — added, removed, or changed content/mode. */
export declare function diffCapturedSets(before: VaultCapturedFile[], after: VaultCapturedFile[]): VaultCapturedSetDrift;
/** True when nothing in the captured set moved. */
export declare function capturedSetUnchanged(drift: VaultCapturedSetDrift): boolean;
/**
 * Capture the work tree per the §6.6 policy. Branches, index, and work tree
 * are left byte-identical; a synthetic commit lives only in the object
 * database (plus the local deploy ref when enabled).
 */
export declare function captureSnapshot(options: VaultSnapshotOptions): Promise<VaultSnapshot>;
/** `git check-attr filter` over paths; returns every path whose `filter` attribute is set (LFS included). */
export declare function detectActiveFilters(top: string, paths: string[]): Promise<VaultSnapshotRefusalPath[]>;
/**
 * Sensitive untracked-path denylist (design D10, verbatim). Applies to the
 * UNTRACKED set only — a tracked file is the user's own committed choice and
 * is never filtered by this list. `--include-sensitive <glob>` re-admits a
 * named path by matching the SAME glob grammar against the candidate.
 */
export declare const VAULT_HANDOFF_SENSITIVE_DENYLIST: readonly string[];
/**
 * A slash-free glob matches the path's BASENAME at any depth (gitignore's
 * "no slash" rule). A glob ending `/**` (e.g. `.aws/**`) matches the named
 * directory component at ANY depth in the tree, plus everything under it —
 * a deliberately broader-than-gitignore reading for a credential-directory
 * denylist, where over-excluding is the safe failure mode. Any other
 * slash-bearing glob matches the full relative path exactly.
 */
export declare function globMatchesGitPath(glob: string, relPath: string): boolean;
/** True iff `relPath` (an untracked-not-ignored path) matches the sensitive denylist and is not re-admitted by `includeSensitive`. */
export declare function isHandoffSensitivePath(relPath: string, includeSensitive?: readonly string[]): boolean;
/** What {@link VaultHandoffCaptureOptions.message} learns once capture figures are known — everything a Handoff Note's `capture` block needs, before the outer commit exists. */
export interface VaultHandoffCaptureStats {
    base_head_oid: string;
    /** `HEAD`'s symref name (`refs/heads/<branch>`, stripped) — `null` on a detached HEAD. */
    branch: string | null;
    modified_captured: string[];
    untracked_captured: string[];
    sensitive_excluded: string[];
    ignored_not_transferred_count: number;
}
export interface VaultHandoffCaptureOptions {
    /** The work tree (any directory inside it). */
    dir: string;
    env?: GitConfigDiscoveryEnv;
    /**
     * The outer commit's message (the Handoff Note). A plain string is used
     * verbatim (the caller has already composed and secret-scanned it). A
     * FUNCTION is called with the real capture figures right before the
     * outer `commit-tree` — the one point at which a Handoff Note's
     * `capture` block can be filled with true numbers, since the note (and
     * the commit that carries it) cannot exist before those numbers are
     * known. Throwing inside it (the client-side secret scan) aborts the
     * capture before the outer commit is written; the two throwaway parent
     * commits (index/untracked) are already unreachable garbage at that
     * point, exactly as if the capture had never run.
     */
    message: string | ((stats: VaultHandoffCaptureStats) => string);
    now?: () => Date;
    /** Re-admit named untracked paths the sensitive denylist would otherwise exclude. */
    includeSensitive?: string[];
}
/**
 * A stash-shaped checkpoint (design D1): one synthetic commit, ALWAYS
 * created (a clean tree still produces it — there is one shape, one code
 * path). `oid`'s tree is the worktree state of TRACKED paths only; its
 * three parents are `base_head_oid` (parent 1), `index_commit_oid` (parent
 * 2, tree = the real index), and `untracked_commit_oid` (parent 3,
 * parentless, tree = the admitted untracked set — the EMPTY tree when
 * nothing untracked survives the denylist). Restore is `git clone` at
 * `base_head_oid` then `git stash apply --index <oid>`.
 */
export interface VaultHandoffSnapshot {
    oid: string;
    tree_oid: string;
    base_head_oid: string;
    index_commit_oid: string;
    untracked_commit_oid: string;
    head: VaultHeadTarget;
    top_level: string;
    /** Tracked paths whose on-disk content differs from `base_head_oid`'s tree (staged, unstaged, or both) and are present in `tree_oid`. */
    modified_captured: string[];
    /** Untracked-not-ignored paths admitted into `untracked_commit_oid`'s tree (denylist already applied). */
    untracked_captured: string[];
    /** Untracked-not-ignored paths excluded by the sensitive denylist (before any `includeSensitive` re-admission). */
    sensitive_excluded: string[];
    /** Ignored (gitignore/exclude) files present on disk — never transferred, counted only. */
    ignored_not_transferred_count: number;
    captured: VaultCapturedFile[];
    captured_digest: string;
    global_excludes_path: string | null;
}
/**
 * Capture a stash-shaped handoff checkpoint. Reuses every hardened-execution
 * guarantee `captureSnapshot` already provides (filter-free hashing, frozen
 * ignore authority, refusal-by-name for conflicted/linked/submodule/sparse/
 * shallow/LFS layouts) via {@link inspectRepository} + {@link
 * detectActiveFilters}; the shape below is the ONLY thing new.
 */
export declare function captureHandoffSnapshot(options: VaultHandoffCaptureOptions): Promise<VaultHandoffSnapshot>;
/**
 * Check out `oid`'s tree into `targetDir` through a temporary index
 * (`read-tree` + `checkout-index`) — filter-free, hook-free, and isolated
 * from the mutable work tree. Returns the absolute target directory.
 */
export declare function materializeSnapshot(repoDir: string, oid: string, targetDir?: string): Promise<string>;
/** `snapshot_oid_hmac` = HMAC(K_digest("snapshot_oid"), JCS({oid, format:"sha1"})) — the keyed commitment the platform compares without ever seeing the oid. */
export declare function snapshotCommitment(kRepo: Uint8Array, repoId: string, epoch: string, oid: string): string;
/** The client-side `vault_commit` line — printed ALWAYS; commit ids never reach the platform. */
export declare function vaultCommitLine(snapshot: Pick<VaultSnapshot, "oid" | "kind">): string;
/** Object sizes for a set of oids (`cat-file --batch-check`), for the per-object cap on tracked history. */
export declare function findOversizeObjects(repoDir: string, tips: string[], cap?: number): Promise<Array<{
    oid: string;
    size_bytes: number;
}>>;
/**
 * True iff `ancestor` is reachable from `descendant` (`merge-base --is-ancestor`).
 * A commit this object store does not hold at all (git exits 128, "Not a
 * valid commit name") answers FALSE, never throws: on a multi-writer vault a
 * ref can name a commit only another writer's clone has (a handoff
 * checkpoint minted elsewhere), and the callers use this answer to decide
 * what to RETAIN — "not provably reachable" keeps the tip as a root, the
 * conservative outcome, whereas a throw refused every push and mint from a
 * checkout that had not seen the other writer's objects.
 */
export declare function isAncestor(repoDir: string, ancestor: string, descendant: string): Promise<boolean>;
/** Does the object exist locally? */
export declare function hasObject(repoDir: string, oid: string): Promise<boolean>;
/**
 * Which of `oids` exist locally — ONE `cat-file --batch-check` process for
 * the whole set (vault-delta-fetch task 3.1), identical per-oid
 * semantics to {@link hasObject}. `--batch-check` answers `<oid> <type>
 * <size>` for a present object and `<oid> missing` otherwise, one line per
 * input line, so presence is exactly "second column is not `missing`".
 */
export declare function hasObjects(repoDir: string, oids: string[]): Promise<Set<string>>;
//# sourceMappingURL=vault-snapshot.d.ts.map