import type { ExecutionEnv } from "../internal/harness-types.js";
import type { PermissionResult } from "./tool-policy.js";
import { type ToolCallLike } from "./tool-registry.js";
/**
 * How one table row matches the judged path:
 *  · `"basename"`     — the target's last segment equals the row name (CC `DANGEROUS_FILES` form);
 *  · `"segment"`      — ANY path segment equals the row name (CC `DANGEROUS_DIRECTORIES` form);
 *  · `"segment-run"`  — a CONSECUTIVE run of segments equals the row's `/`-separated segments
 *                       (CC `DANGEROUS_DIRECTORY_PATHS` form, e.g. `.config/git`).
 */
export type WriteProtectedKind = "basename" | "segment" | "segment-run";
/** One row of the write-protection table: a LITERAL name and how it matches. The `name` is also the
 *  row's stable identity — the string a refusal/ask message cites. */
export interface WriteProtectedRow {
    readonly name: string;
    readonly kind: WriteProtectedKind;
}
/**
 * One deployment-authored entry for the replacement seat (`RunnerDeps.writeProtectedPaths`).
 * String shorthand: a bare name ≡ `{ name, kind: "segment" }` (the WIDER single-segment kind —
 * over-matching is the fail-safe direction for a tighten); a name containing `/` ≡
 * `{ name, kind: "segment-run" }`. Spell `kind: "basename"` explicitly when last-segment-only
 * matching is the intent. A name opening with the home word (`~/…`) refuses: the table places no row at a home.
 */
export type WriteProtectedEntry = string | WriteProtectedRow;
/** A table hit: which row matched (the row's canonical name + kind). */
export interface WriteProtectedHit {
    readonly name: string;
    readonly kind: WriteProtectedKind;
}
export declare const WRITE_PROTECTED_DEFAULT_TABLE: readonly WriteProtectedRow[];
/**
 * The ONE SEGMENT fold of this module, applied to BOTH sides of every comparison (table names at
 * compile, path segments at match), in the reference implementation's own order:
 *  · `toLowerCase` plus the two confusable letters its fold maps (U+0131 dotless ı → i, U+017F long ſ → s);
 *  · the INVISIBLE FORMAT characters it strips — the zero-width joiners/non-joiners, the bidirectional
 *    overrides and the byte-order mark (U+200C–U+200F, U+202A–U+202E, U+206A–U+206F, U+FEFF), verbatim.
 *    A segment carrying one of these LOOKS like the row it is not, on both sides of every comparison, so
 *    the strip is a deception axis rather than a platform one: it applies to every reading below, not to
 *    the win32 one alone. It can only ADD matches, which on a TIGHTEN costs one extra question — and the
 *    data-root passthrough, where an added match would be a guard BYPASSED, deliberately does not use this
 *    fold at all (its comparison is byte-exact; see {@link relativeUnderDataRoot}).
 *  · …and a strip that would EMPTY the segment keeps the segment instead (the reference fold's own `|| n`):
 *    an all-invisible component names a real file, and folding it to "" would make it collide with nothing
 *    or with everything depending on the caller.
 * Deliberately NOT full Unicode confusable folding: that would be a different, wider claim than the one made
 * (the read deny set's ASCII-contract doc states the same boundary for its own fold). The NTFS data-stream
 * suffix the reference fold also strips is a win32 NAMESPACE fact, not a comparison one, and lives with the
 * other component normalization of those views ({@link ComponentNormalization}) so a POSIX file legally
 * named `a:b` keeps its own identity on the POSIX reading.
 */
export declare function foldWriteProtectSegment(s: string): string;
/**
 * Resolve the ACTIVE table under the deployment seat (validated, loud — #123's bad-value states all
 * throw and name the knob). `undefined` = the default table; `[]` = NO table (an explicit, legal
 * posture — the whole-table escape hatch's empty end); a non-empty list REPLACES the default table
 * whole (compose additions as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, drop rows by filtering the
 * exported table — the visible/deletable admin face). Exact duplicates fold (idempotent, not one of
 * #123's bad-value states). Exported so an admin face can preview the effective table under a
 * candidate configuration with the engine's own rules.
 */
export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
/**
 * WHY the ENVIRONMENT half of one judgment could not contribute — the canonicalizer refused the spelling,
 * a link the tree could not follow, a namespace spelling it declines, an environment call that threw.
 * Reported through {@link WriteProtectionCompileOptions.onViewAbsent}; it is a DISCLOSURE, never a verdict
 * (see the absence law on {@link WriteProtectionMatcher.judge}).
 */
export interface WriteProtectionViewAbsence {
    /** The environment's own sentence, verbatim — what it said when asked to resolve this target. */
    readonly cause: string;
    /** The failure was specifically a symlink whose real target is unknowable, as opposed to a spelling the
     *  tree refuses or a probe that errored. */
    readonly unresolvedSymlink: boolean;
}
/**
 * The judge's answer for ONE covered write ({@link WriteProtectionMatcher.judge}):
 *  · `clear` — no row on any view that could answer: the write is not this layer's business;
 *  · `hit` — a row matched; `via` says which view named it: `"spelling"` (the anchored spelling, no
 *    filesystem consulted), `"chain"` (a spelling the leaf symlink chain passes through, judged as a
 *    spelling) or `"target"` (the resolved real target, the spelling being innocent);
 *  · `unresolvable` — NO half of the judgment answered at all. The judge itself no longer mints this: an
 *    environment that cannot resolve the target has ANSWERED, and an answer of "I cannot resolve this"
 *    leaves the lexical half's judgment standing (the absence law below). What is left for this verdict is
 *    the judgment that never happened — the gate's bounded wait elapsing or the task ending before
 *    anything came back (`awaitWriteProtectionJudgment`, gate-bounded-await.ts) — where there is no
 *    lexical answer to stand on either, and the consumer REFUSES. `unresolvedSymlink` is that minter's
 *    `false`: a wait that produced nothing knows nothing about what it was waiting for.
 */
export type WriteProtectionVerdict = {
    readonly kind: "clear";
} | {
    readonly kind: "hit";
    readonly hit: WriteProtectedHit;
    readonly via: "spelling" | "chain" | "target";
} | {
    readonly kind: "unresolvable";
    readonly cause: string;
    readonly unresolvedSymlink: boolean;
};
/** What one judgment knows about the call, all optional — every member has an absence reading. */
export interface WriteProtectionJudgeContext {
    /** The call's LIVE tracked working directory (see {@link WriteProtectionMatcher.matchPath}). */
    readonly cwd?: string;
    /** The base a RELATIVE spelling resolves against on the TARGET view — the tool's own declared base
     *  ({@link pathTargetBaseOf}). Absent ⇒ `cwd`, then the compile-time `rootPath`, then the environment's
     *  own working directory: the same fallback chain the write tool itself walks. */
    readonly base?: string;
    /** Bounds the filesystem probes of the target view. */
    readonly signal?: AbortSignal;
    /** The path is a CANONICAL key the caller already resolved at the seat its program writes it (the shell read station's
     *  established write target: where the path lands, or — for a removal or a rename — the entry itself, its canonical
     *  parent joined with the last name, a link there not followed). It is judged where a key is judged — the readings and
     *  the canonical data-root exemption — never as a spelling (the lexical passthrough reads a spelling, and a key reached
     *  through a linked data root does not spell itself inside it) and never followed: no link chain is walked from it, as
     *  the program walks none from where it writes. Without an environment the key is judged as a spelling (the absence law). */
    readonly canonical?: true;
}
/**
 * WHICH reading of a write's TARGET a compiled judge has — its own word, and the vocabulary the wiring
 * manifest discloses it with. `"target"`: the judge holds an execution environment, so a spelling that
 * misses the table is judged a SECOND time on its resolved real target (an alias, a symlink, a `..` walk
 * that lands on a protected row is seen). `"spelling-only"`: no environment, so the spelling IS the whole
 * judgment and a path reaching a row only through an alias is cleared — the module header's absence law,
 * unchanged by this word, which only makes it READABLE. Never derived by a reader from the judge's seats.
 */
export declare const WRITE_PROTECTION_TARGET_VIEWS: readonly ["spelling-only", "target"];
/** One of {@link WRITE_PROTECTION_TARGET_VIEWS}. */
export type WriteProtectionTargetView = (typeof WRITE_PROTECTION_TARGET_VIEWS)[number];
/** The compiled judge over one resolved table. `matchPath` is the SPELLING view alone (literal folded
 *  comparisons, no filesystem access — the admin preview face); `judge` is the whole judgment (see the
 *  module header for the two views). */
export interface WriteProtectionMatcher {
    /** WHICH reading of a target this judge HAS — minted at compile and read verbatim by everyone who needs
     *  the fact; a reader re-deriving it from the seats is a second opinion, free to drift. */
    readonly targetView: WriteProtectionTargetView;
    /** The resolved rows this judge was compiled from (canonical names — the disclosure face). */
    readonly rows: readonly WriteProtectedRow[];
    /** Judge ONE path SPELLING. Returns the first matching row (basename rows first, then segment,
     *  then segment-run — deterministic), or null.
     *
     *  `cwd` = the call's LIVE tracked working directory, read at judgment time off the ONE ref the fs
     *  hand tools resolve with — the law every path guard here follows: judge the file the tool will write.
     *  Absent ⇒ judged exactly as given — the historical reading, and the honest answer for a host
     *  that tracks no cwd. */
    matchPath(path: string, cwd?: string): WriteProtectedHit | null;
    /**
     * Judge ONE covered write by the file the tool will write: the spelling view first (`matchPath`; a hit
     * there is the answer and costs no I/O), then — on a miss, and only when the judge was compiled with an
     * execution environment — the resolved real target through the shared `canonicalizeTarget`.
     *
     * @contract write_protection.judges_the_real_target — the always-on write-protection table and the
     * opt-in sensitive-path deny set differ in VOCABULARY (literal rows / segment globs) and SEVERITY
     * (ask / deny), never in REACH: both judge the file the tool will write — the spelling anchored at the
     * live cwd AND its resolved real target, through one canonicalizer. Where they DO part: a target the write-protection
     * judge cannot resolve leaves its resolved view ABSENT (the spelling still judges); the deny set still REFUSES whole. An environment-less judge has no target view and answers by the spelling alone —
     * the same absence law the cwd follows (absent ⇒ the spelling is judged exactly as given). REACH is what
     * this sentence promises, not a LOCK: the identity is resolved at judgment time and re-resolved by the
     * tool at execute time (the module header states that window and the seat that would close it).
     */
    judge(path: string, ctx?: WriteProtectionJudgeContext): Promise<WriteProtectionVerdict>;
}
/** Compile-time seats of {@link compileWriteProtection}. Every member is optional and has an absence reading. */
export interface WriteProtectionCompileOptions {
    /** The engine data root for the passthrough in the module header (same seat and same meaning as
     *  `createSensitivePathPolicy`'s): omitted ⇒ the knob its consumers read (`$AGENT_DATA_DIR ?? ~/.ai-agent`). */
    readonly dataRoot?: string;
    /** The execution environment the hands write against — the TARGET view resolves in ITS filesystem
     *  (a remote/sandboxed task resolves in its container). Omitted ⇒ no target view: the spelling is the
     *  judgment (a host with no hands has no filesystem to resolve against). */
    readonly env?: ExecutionEnv;
    /** The task root — the base a `base: "root"` write tool's relative spelling resolves against, and the
     *  path-family source when the call carries no cwd. Omitted ⇒ relative spellings resolve against the
     *  cwd, then the environment's own working directory. */
    readonly rootPath?: string;
    /**
     * Called at most ONCE per judgment, at the moment the ENVIRONMENT half is found unable to contribute
     * ({@link WriteProtectionViewAbsence}) — before the judgment returns, and on the hit arm as well as the
     * clear one, because the fact is about the ENVIRONMENT rather than about the answer it did not change.
     * Omitted ⇒ the absence is silent, which is the honest default for a judge compiled outside a leg.
     * A throwing sink is the caller's bug and is not caught here; the leg that wires one wraps it.
     *
     * Deliberately carries no call identity: what it reports is a deployment-level fact (this environment
     * cannot answer path questions), and its consumer announces once per leg rather than once per write.
     */
    readonly onViewAbsent?: (absence: WriteProtectionViewAbsence) => void;
}
/**
 * Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
 * resolved table is EMPTY (`[]` replacement) — the caller then mounts no tighten at all, keeping an
 * opted-out deployment's decision path byte-identical to a build without this layer.
 *
 * The data-root spellings are read ONCE here at compile (a judge that re-read `process.env` per call
 * would answer two calls of one run differently); their CANONICAL keys are resolved per judgment, on the
 * target view only, and only once a row has matched there (see {@link canonicalDataRootKeys} for why they
 * are never cached). The passthrough is a property of the JUDGE, not of the default table — a deployment
 * that replaces the table wholesale gets the same law over its own rows.
 */
export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMatcher | undefined;
/**
 * The gate tighten's DECISION for one judgment — the words the fold writes on the pass, minted beside the
 * verdict they read so the two cannot drift:
 *  · `hit` ⇒ `ask` (`safety`; no `requiresRealApproval` — one explicit look, clearable by an approver, a
 *    blanket `onAsk:"allow"`, the classifier or a matching persisted rule). The matched row NAME goes on the
 *    message (table names are deployment-validated vocabulary); the model-authored target path deliberately
 *    does not — `message` is the classifier's `askMessage`, and the approval card already shows the args. The
 *    spelling form's sentence is the historical one byte for byte; the target form says the target RESOLVED.
 *  · `unresolvable` ⇒ `deny` naming the cause: an unjudged target is never vouched, and an ask would be a
 *    question about a file nobody can name — the rule the canonicalizing deny set follows for the same reason.
 *  · `clear` ⇒ `undefined`: the surviving decision stands.
 */
export declare function writeProtectionTightenDecision(toolName: string, verdict: WriteProtectionVerdict): PermissionResult | undefined;
/**
 * @contract write_protection.verdict_is_the_mandate — the judge's two non-clear verdicts are the two facts
 * a standing allow rule may not retire, and `clear` is neither: a MATCHED row is the mandate a live decider
 * may still clear, a target it could not RESOLVE is the mandate no automatic lane may. These two predicates
 * are the ONE reading of a verdict as either fact, beside the decision mint, so the fold records what the
 * judge said and never re-derives "was this write protection?" from the decision it got back.
 */
export declare function writeProtectionMandated(verdict: WriteProtectionVerdict): boolean;
/** The second half of {@link writeProtectionMandated}'s contract: the judge could not name this target
 *  (the arm that owns the ACTION denies on the same verdict). */
export declare function writeProtectionUnresolved(verdict: WriteProtectionVerdict): boolean;
/**
 * The ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`). Returns `null` SYNCHRONOUSLY when the
 * call is not this layer's business — not a covered write, or no resolvable target string — and otherwise
 * the judgment of {@link WriteProtectionMatcher.judge}, a promise. The split is load-bearing for the gate:
 * a call the judge does not cover must not cost the fold a suspension point.
 */
export type WriteProtectionCheck = (toolName: string, args: unknown, face?: ToolCallLike["face"], cwd?: string, signal?: AbortSignal) => Promise<WriteProtectionVerdict> | null;
/**
 * ONE leg's write-protection mount: the gate input and the judge's own {@link WriteProtectionTargetView},
 * handed back together so the two readers of this compile — the gate station that mounts the check and the
 * disclosure face that reports which reading it got — read ONE compile. `undefined` in place of the whole
 * record is "no judge" (an emptied table), a different fact from a judge whose target view is absent.
 */
export interface WriteProtectionMount {
    /** The gate input (`ToolGateInput.writeProtectionCheck`). */
    readonly check: WriteProtectionCheck;
    /** The compiled judge's own word for the reading it has of a write's target. */
    readonly targetView: WriteProtectionTargetView;
}
/**
 * Build the ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`): covered tools are the
 * path-confinable write set (Write/Edit/NotebookEdit — the shared spelling), the judged target is
 * the SAME string the tool itself will resolve (`writeTargetPath`, the shared single source with
 * the sensitive-path guard and the fs-write gate), and the verdict is the table judge's. A covered
 * write with NO resolvable target string returns null here — this layer is ADDITIVE friction on a named
 * set of targets, not a containment boundary (a target-less call cannot land on a named row, and the
 * tool's own schema validation refuses it before any write); the fail-closed treatment of that case
 * belongs to the containment gates (fs-write-gate-policy documents that split). A target string the
 * environment cannot RESOLVE is the judge's own `unresolvable` verdict, not this null.
 * Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
 * `opts` are the judge's own seats, passed straight through ({@link compileWriteProtection}).
 *
 * @contract write_protection.membership_is_the_call — WHICH calls the always-on table judges is a
 * PREDICATE OVER THE CALL and nothing else: the call's own face declares a path target whose access is not
 * a read, so a custom writer joins this judge by DECLARING what it writes and a tool that declares nothing
 * is not this layer's business under any name. There is deliberately NO name-list seat beside it, and the
 * asymmetry with the opt-in deny policy — which does take one (`createSensitivePathPolicy({ tools })`, its
 * own pre-declaration hatch) — is the intended shape rather than a gap: a name list on a default-ON floor
 * is a way to narrow that floor silently, one tool name at a time, while the seat this layer gives a
 * deployment for narrowing is the whole-table replacement, which says what it removes. A deployment that
 * wants name-list membership composes the deny policy alongside; the two then answer under the shared
 * `deny > ask > allow` fold, which is what makes the missing seat cost nothing.
 */
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMount | undefined;
/** {@link createWriteProtectionCheck} over a judge already compiled — the engine's leg compiles once and hands the same
 *  judge to the call check and to the shell station (engine-internal; not re-exported from the package root). */
export declare function writeProtectionMountOver(matcher: WriteProtectionMatcher, rootPath: string | undefined): WriteProtectionMount;
