UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

2,070 lines • 164 kB
import { type AskOrigin, type AssertAllKeysHandled } from "./ask-origin.js";
import { type ReadRootGrantScreen } from "./read-root-grant-screen.js";
import { type Settlement } from "./gate-outcome.js";
export { policyCannotAnswer, rowDemandsAPerson } from "./real-approval.js";
import { type DecisionReason } from "./decision-receipt.js";
export { isEngineRefusal } from "./decision-receipt.js";
import { type RemovalGuardSeat } from "../tools/fs/removal-targets.js";
/** A tool call presented to a policy before it executes. */
/** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
export interface ToolCallFace {
    /** Every name a tool-name rule may spell this tool by: the name the call was made under first (the model's spelling),
     *  then the tool's wire name and each other alias that resolves to it on the leg's roster (`rosterToolName`). A rule
     *  reaches the call iff its tool name is on this list — read it through `ruleNamesOf`, never off `toolName` alone.
     *  Absent on a face rebuilt from a park row that predates it (the call's own name is then the list). */
    names?: readonly string[];
    family: import("./tool-face.js").ToolFamily;
    pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
    capabilityId: string;
    contractId: string;
    shapeDigest: string;
    implementationRevision: string;
    /** Which approval card this call renders under (the closed set `TOOL_APPROVAL_CARDS`; `generic` when the tool
     *  declares none). Minted with the face — a reserved card only for the engine tool that owns it — and copied onto the
     *  ask, the parked gate and the pending row, so no reader recovers the card from a name. Absent on a face rebuilt
     *  from a park row (a re-judgment reads no card). */
    approvalCard?: import("./tool-face.js").ToolApprovalCard;
}
export interface ToolCallRequest {
    /** The tool's wire name — the Runner's gate resolves a call made under an alias to the wire name of the tool it
     *  dispatches to before any policy runs (`rosterToolName`); the names a rule may use for it are `ruleNamesOf`. */
    toolName: string;
    args: unknown;
    toolCallId: string;
    /**
     * design/388 B6 (additive) — the mounted tool's FACE as the leg's roster minted it: the declared path target
     * (how the target may be confirmed by path), the display family, and the engine-minted identity
     * (`capabilityId` / `contractId` / `shapeDigest`) an exemption store keys on. Stamped at the gate entry
     * from the live roster, so identity follows the OBJECT the call dispatches to — a caller tool that merely
     * took a built-in's name carries its own face, never the built-in's. ABSENT only on a BARE request (a
     * policy invoked outside a Runner by a producer that stamped nothing): a PROTECTIVE fence (a deny-list)
     * then reads the catalog's declaration for the NAME, tighten-only; a CONTAINMENT fence (a grant admitting a
     * write by its path) REFUSES the request, typed (`policy.bare_request`, #626 ②) — stamp the face yourself
     * (`toolCallFaceOf(roster, name)`) when driving a policy outside a Runner.
     */
    face?: ToolCallFace;
    /**
     * The LIVE working directory this call's RELATIVE path arguments will actually resolve
     * against, read at adjudication time from the same tracked `cwdRef` the fs hand tools resolve with
     * (design/64 §16.3: a resident shell's `cd` persists and moves the fs tools' relative base with it).
     *
     * WHY IT EXISTS: every path guard here (sensitive-path, fs-write gate, session rules,
     * transcript integrity) re-resolves the model's spelling itself, and each was handed a STATIC root at
     * wiring time. After a `cd` the two sides of a single call disagreed about which file it was: the tool
     * wrote `<liveCwd>/pre-commit` while the guard judged `<wiringRoot>/pre-commit` — measured on a host
     * lane, `cd .git/hooks` + `Write("pre-commit")` landed a git hook past a guard that names `.git/hooks`.
     * Reading THIS field makes the guard's resolution base the same value the tool will use.
     *
     * Applies to the call's TARGET only. A guard's own configured directories (accept/exempt domains,
     * `allowDirs`) are DEPLOYMENT text and keep resolving against the static root —
     * a shell `cd` must never relocate an allow-domain.
     *
     * ABSENT when the run has no tracked cwd (read-only hands, no execution env) or when a policy is
     * invoked directly outside a Runner: consumers then fall back to their own configured root, which is
     * exactly the pre-RB-108 behavior. Read-only — a policy cannot move the shell by writing it back.
     *
     * RESIDUAL (recorded honestly — this is a snapshot of a mutable ref, not a frozen binding): the gate
     * reads the ref when it adjudicates and the tool reads it again when it executes. No interleaving that
     * moves the cwd in between is constructible on the current loop, and it is worth naming WHY, because
     * the reason is a property of the tool declarations rather than of this field: the loop adjudicates a
     * whole batch before executing it, but only calls classified concurrency-safe ever share a batch, and
     * every cwd MOVER (a `cd`-bearing Bash — `cd` is not on the readonly allowlist and any shell operator
     * disqualifies — plus the worktree enter/exit tools) and every path-confinable WRITE tool declares
     * `effect: "write"`, which derives `executionMode: "sequential"`, which forces a batch of one. A
     * deployment tool that declared itself read/parallel while moving the shared cwd would reopen the
     * window — at that declaration, not here.
     */
    cwd?: string;
    /**
     * The root this leg's tools resolve a ROOT-relative spelling against (a search tool's relative `path`, a
     * pattern with no `path`), stamped beside {@link cwd} at every Runner adjudication seat — the same
     * canonical spelling as `cwd` (the hands' own root, the value `cwd` starts at). Present whenever the leg
     * mounts file hands, read-only hands included; absent on a hands-less leg and on a bare request. A path
     * guard whose deployment supplied no base of its own resolves the call's target against `root` / `cwd`.
     * Read-only, like `cwd`.
     */
    root?: string;
    /** The call's target reading (`readCallTarget`: lexical / canonical / hop views per reading of the path slot), stamped by the Runner on a
     *  leg whose lanes can hold a path rule; it describes only the `args` object it was taken for (`callTargetReadingOf`). Read-only. */
    targetReading?: import("./effective-path-target.js").CallTargetReading;
    /**
     * An ENGINE-INTERNAL seat, not a fact of the call: the anchors of the policy now judging this request — the leg its
     * path rules were installed on (`installPolicyAnchors`). Present only on the copy an installed policy hands its own
     * inner `check` (where `createPermissionRulePolicy` reads it; an inner installation's anchors win over an outer one's);
     * the request the gate holds, an `AskRequest`, a hook's input and a parked row never carry it. A host neither sets nor
     * reads it.
     */
    pathRuleAnchors?: import("./permission-rule-model.js").PathRuleBases;
    /**
     * The CANONICAL directories THIS RUN'S PROMPT ADVERTISES AS WRITABLE — the admitted list of the root
     * fence's advertised-writable-directory seat (the memory engine's write home, the deployment's
     * scratchpad), in admission order, deduplicated. Stamped by the Runner on every policy adjudication of
     * the run, read live at adjudication time (a directory admitted by a later phase is on the list the
     * next call carries).
     *
     * WHY IT EXISTS: the fence and the write gate were judging one fact twice. The prompt tells the model
     * "write to it directly with the Write tool" and the fence opens the hole for exactly the directories
     * this list names — but a deployment-authored write gate knew nothing of them, so every advertised
     * write still asked (a memory entry plus its index = two questions the person was told would not be
     * asked). Reading THIS field makes the gate's exemption domain the same list the fence admitted,
     * instead of a second copy the deployment maintains by hand.
     *
     * A containment gate reads it EXACTLY as {@link import("./fs-write-gate-policy.js").FsWriteGatePolicyOptions.exemptDirs}:
     * a covered write whose canonical target lands inside one of these directories is allowed and not
     * asked. It grants nothing else — a DENY composed alongside (sensitive-path, an org rule) still
     * outranks it under the deny > ask > allow fold, and an unresolvable target still fails closed.
     *
     * ABSENT outside a Runner, or when the run has no hands, or when nothing was advertised. Absence is NO
     * exemption, never a wildcard (an empty list reads the same). Read-only — a policy cannot advertise a
     * directory by writing one back.
     */
    advertisedWritableRoots?: readonly string[];
    /**
     * The engine data root's spellings — the ONE exempt root every exemptible path row is judged under (the data-root
     * exemption, path-guard-rows.ts): resolved once per leg from `RunnerDeps.memoryEngineDir` (absent ⇒ the resolver's own
     * chain, `$AGENT_DATA_DIR ?? ~/.ai-agent`) and stamped by the Runner on every policy adjudication of the leg. A path guard
     * that judges a write under the exemption reads it here, so the guard and the engine's own write judgment speak about
     * the same directory instead of a second copy the deployment keeps by hand.
     *
     * ABSENT outside a Runner (a policy driven directly by a host): the guard then reads the resolver's own chain, the root
     * a Runner with no `memoryEngineDir` would stamp. Read-only, frozen.
     */
    exemptRoots?: readonly string[];
    /**
     * The request's statement that nobody can be asked about THIS call — the word of a run's own declaration
     * (`TaskSpec.approvalReach`, the closed set `APPROVAL_REACH`), stated per request. Present (`"none"`) on every request the
     * engine's synthetic probes hand a policy layer (the "would this call be refused outright?" judgments: the visibility
     * probes of a file the model did not ask to read, the background memory consolidation's write probes); absent on a call
     * the gate adjudicates, and on a bare request. A policy that would put the call to a person — an approval seat it awaits,
     * a hook it invokes, a card or a message it sends — answers at once instead: the engine's own factories read this member
     * before reaching a seat and answer {@link noApproverSeatDeny}; a deployment closure does the same. The signal handed
     * beside such a request is LIVE (the judge's wait for that layer): an aborted one means only that the task ended.
     * Read-only.
     */
    approvalReach?: import("./wiring-manifest.js").ApprovalReach;
    /**
     * design/80 D-E-core: read-only per-run budget snapshot so a STATELESS policy can self-limit against
     * state that survives suspend/resume (a fresh in-memory counter would reset every leg — the supervisor
     * escalation-budget gap). Built by core at prepare time from the DURABLE resource ledger
     * ({@link import("./checkpoint-store.js").ResourceLedger}) + the durable suspend chain; the policy CANNOT
     * widen it (no write-back). Present on every gated `check()` once the runner builds the gate; absent only
     * when a policy is invoked directly (outside a Runner). A NORMAL-ask escalation policy reads it; a SAFETY
     * ask (egress / irreversible) ignores it and ALWAYS asks (invariant #2 stays structural — core mints
     * `irreversible_ask` for safety tools regardless of any budget).
     *
     * ⚖️ SCOPE (A-005.13, ruled 2026-08-09): this snapshot is PER-RUN — it is the ledger of the run whose
     * gate is asking, NEVER the ledger of the run that authored the policy. A policy inherited down the
     * delegation chain (`InheritedGate.parentConstraints`) therefore self-limits against each CHILD's own
     * fresh ledger; the ancestor's counts do not travel (declared design — see the chain field's A-005.13
     * note for the reasoning and the closure-state alternative).
     */
    budget?: {
        /**
         * Durable resource $ this run may STILL spend (micro-USD); `undefined` = no $ ceiling configured. Frozen
         * total, prior-wins → it monotonically SHRINKS across legs and is NEVER re-granted by a resume (a
         * deny-resume continues with the same frozen remaining). **NEVER falsy-test** (`0` = exhausted, NOT
         * "unlimited" — only `undefined` is unlimited).
         */
        resourceRemainingMicroUsd?: number;
        /** Durable cumulative resource $ already spent across all prior legs (micro-USD); accumulates, never refunded. */
        resourceSpentMicroUsd: number;
        /** How many times this run has already suspended (the durable suspend chain). Lets the policy tighten as
         *  the chain grows — the precise complement to the unconditional `suspendLoopCap` backstop. */
        suspendCount: number;
    };
}
/** Where a decision came from, for audit (design/37). `"sandbox"` (F-012 L2) marks an allow the
 *  sandbox-admission leg resolved: the deployment declared an isolated execution env, every surviving
 *  ask on the call was engine-classified sandbox-local, and the call crosses no declared boundary —
 *  disclosed durably as `permission.sandbox_admitted`.
 *
 *  design/182 §7 adds the two ORG-layer words, both TIGHTENING-only:
 *   - `"org_rule"` — an organization policy rule spoke (a deny, or an ask that no configuration can
 *     dismiss). Distinct from `"rule"` (a deployment `ToolPolicy` rule) because the AUTHORITY differs:
 *     an org rule comes from the org's server-published snapshot, not from this deployment's policy.
 *   - `"org_unavailable"` — an org-governed deployment could not adjudicate against a snapshot, so the
 *     whole decision boundary failed closed (see `ORG_UNAVAILABLE_DECISION_REASON`, the single
 *     spelling this word is minted from). */
/** `read_only` (#619): the shell tool's command was PROVABLY read-only by the engine's transcribed upstream
 *  tables (`readOnlyShellVerdict`) and the allow layer let it run without a question — after every
 *  deny/ask lane and the person's own allow rules, never over a mandated ask. */
/**
 * Stamp {@link ToolCallRequest.advertisedWritableRoots} from the run's advertised-writable-directory seat
 * — the ONE place the seat becomes a request field, so every adjudication station stamps it alike (the
 * `callFaceSeat` shape).
 *
 * The list is COPIED AND FROZEN here, and that is load-bearing rather than tidy. The seat's `roots` getter
 * hands back its own admission ledger, and {@link combinePolicies} walks every layer with ONE request
 * object: a layer handed the live array could append to the very domain the write gate exempts, widening
 * it for that call and for every later one. `readonly` is a compile-time word; this is the runtime one.
 * (What a frozen array does not close is a layer REPLACING the whole field on the shared request — the
 * pre-existing shape that `cwd`, `budget` and `face` live with; it belongs to the fold, not to this seat.)
 */
export declare function advertisedWritableRootsSeat(seat: {
    readonly roots: readonly string[];
} | undefined): {
    advertisedWritableRoots?: readonly string[];
};
/** Stamp {@link ToolCallRequest.exemptRoots} from the leg's one resolution of the data root — the ONE place that value
 *  becomes a request field, beside {@link advertisedWritableRootsSeat} at every adjudication station. */
export declare function exemptRootsSeat(exemptRoots: readonly string[]): {
    exemptRoots: readonly string[];
};
/** Declared beside the receipt's list (decision-receipt.ts) — a new member added to either side is a compile error rather
 *  than a silent drift between the type and the screen; re-exported here, the module every consumer of the decision reads. */
export type { DecisionReason };
/**
 * The engine's ATTESTATION record — the one carrier of what an engine station knows about a decision and a policy cannot
 * state: WHO settled the call's wait and WHICH question that answered (the settlement, the ask's origin word and class set,
 * the digest of the arguments the answer licenses), and the facts the classifier stations, the in-fold unresolvable arms
 * and the policy fold record. It rides the decision as its `attestation` member and is read through {@link attestationOf}
 * alone; the decision object's identity carries nothing.
 *
 * Minted only by {@link mintAttestation} and combined only by {@link mergeAttestation}: both freeze the record and enter it
 * in a module-private brand set, so a value a caller composed — a literal, a copy, a structured clone — is not a record, and
 * the receipt refuses a decision carrying one. A record is bound to the call it was minted for (`toolCallId` + `toolName`):
 * handed back for another call it reads as absent. A spread of a decision keeps its record, and the record still says only
 * what the engine minted — a refusal record on an allow is a contradiction the gate's exit refuses, and a resolution's
 * answer binds the arguments it licensed (`inputDigest`).
 */
export interface EngineAttestation {
    readonly toolCallId: string;
    readonly toolName: string;
    /** What ended the call's wait. The answer group — `settlement`, `origin`, `classes`, `inputDigest` — travels as one. */
    readonly settlement?: Settlement;
    /** The settled ask's origin word. */
    readonly origin?: AskOrigin;
    /** The settled ask's class set (`ASK_CLASSES` order), when the minting station held one. */
    readonly classes?: readonly import("./ask-origin.js").AskClass[];
    /** `boundInputHashOf` of the arguments the settlement licenses: an approver's edit, the snapshot shown, or the asked args. */
    readonly inputDigest?: string;
    /** The FORM of a classifier round that did not rule (a classifier deny's cause). */
    readonly classifierCause?: import("./gate-outcome.js").ClassifierDenyCause;
    /** WHICH model answered a classification, on either arm. */
    readonly classifierRound?: import("./auto-mode.js").AutoModeClassifierRound;
    /** Why an in-fold deny refused an ask nobody could resolve. */
    readonly unresolvableCause?: import("./ask-unresolvable-notice.js").AskUnresolvableCause;
    /** The classes of the asks a policy fold collapsed — the proof behind a decision's `foldedAskClasses`. */
    readonly foldedClasses?: readonly import("./ask-origin.js").AskClass[];
    /** Where a path deny row refused the call — the row, the view of the target it met, the face the call is on. The gate's
     *  exit mints the refusal's card from it; the judge that found the row is its only writer. */
    readonly pathDenial?: import("../tools/fs/safety.js").PathDenialEvidence;
}
/** The members a station attests — everything but the call binding. */
export type AttestationMembers = Omit<EngineAttestation, "toolCallId" | "toolName">;
/** Mint a record for `call`. Engine stations only: not re-exported from `src/index.ts`. The values are DETACHED snapshots, so a
 *  caller that later mutates the settlement object it was handed cannot rewrite what the engine attests. */
export declare function mintAttestation(call: {
    toolCallId: string;
    toolName: string;
}, members: AttestationMembers): EngineAttestation;
/** Is `v` a record the engine minted (the brand alone — no binding)? The receipt's test. */
export declare function isEngineAttestation(v: unknown): v is EngineAttestation;
/** THE read: the record `d` carries FOR `call` — a branded record bound to that call, else `undefined` (a record bound to
 *  another call is a replayed one and reads as absent). Read off the own data member, never through an accessor. */
export declare function attestationOf(d: unknown, call: {
    toolCallId: string;
    toolName: string;
}): EngineAttestation | undefined;
/**
 * Combine two records for `call`, member GROUP by group: the later record's group stands where it has one, the earlier's
 * fills a group the later lacks. A record not bound to `call` contributes nothing. `answer` scope is the veto's merge: the
 * earlier record contributes ONLY its answer, and only when the later carries none — a person's yes rides a later layer's
 * deny (invariant I3) while the refusing station's own facts stay its own.
 */
export declare function mergeAttestation(call: {
    toolCallId: string;
    toolName: string;
}, earlier: EngineAttestation | undefined, later: EngineAttestation | undefined, scope?: "all" | "answer"): EngineAttestation | undefined;
/** `d` with `members` attested for `call`, merged over the record it already carries for that call (the new members' groups
 *  win). Always a fresh object — the receipt hands out frozen ones. */
export declare function attest<T extends object>(d: T, call: {
    toolCallId: string;
    toolName: string;
}, members: AttestationMembers): T;
/** `d` carrying `record` (a fresh object), or `d` itself when there is no record to put on it. */
export declare function withAttestation<T extends object>(d: T, record: EngineAttestation | undefined): T;
/** The `no_approver`-attested deny an engine policy answers on a request that states nobody can be asked about it
 *  ({@link ToolCallRequest.approvalReach}) instead of reaching a seat — read by a probe as "would ask an approver". */
export declare function noApproverSeatDeny(req: {
    toolCallId: string;
    toolName: string;
}, message: string, origin: AskOrigin): PermissionResult;
/** The `task_aborted`-attested deny an engine policy answers on an aborted signal instead of reaching a seat. */
export declare function abortedSeatDeny(req: {
    toolCallId: string;
    toolName: string;
}, message: string, origin: AskOrigin): PermissionResult;
/** THE question every engine factory asks before it would reach a person (an approver it awaits, a hook it invokes): may the
 *  seat be reached? Not when the task ended (an aborted signal ⇒ {@link abortedSeatDeny}, read first) nor when the request
 *  states that nobody can be asked about the call ({@link ToolCallRequest.approvalReach} ⇒ {@link noApproverSeatDeny});
 *  `undefined` ⇒ it may. `say` is the factory's own sentence for each. */
export declare function unaskedSeatAnswer(req: Pick<ToolCallRequest, "toolCallId" | "toolName" | "approvalReach">, signal: AbortSignal | undefined, say: {
    aborted: string;
    unreachable: string;
}, origin: AskOrigin): PermissionResult | undefined;
/**
 * design/252 G-7 — how long an approver-attribution identifier may be.
 *
 * Sized for the identifiers approval channels actually carry (a login, an email, an opaque account id,
 * a queue name) with room to spare, and bounded at all because this value lands on an operator-plane
 * frame that a deployment may persist: an unbounded field on a record channel is its own denial of a
 * readable record. An over-long value is REFUSED, never truncated — a cut identifier names a different
 * party, or nobody, and either is worse than the honest refusal.
 */
export declare const APPROVER_ATTRIBUTION_MAX_CHARS = 256;
/**
 * design/252 G-7 — screen a deployment-supplied approver identifier before it becomes an observation.
 *
 * THE POSTURE, stated so it is not mistaken for something stronger: core does not authenticate this
 * value, does not compare it to anything, and never reads it back to decide anything. It is a
 * TRANSCRIPTION of what the approval channel said about its own settlement — the channel (a server's
 * approval card, an HMAC-verified callback, an operator console) is where identity is established, and
 * a library that holds no identity surface cannot second-guess it. What core owns is that the value is
 * a value: a string, bounded, free of control bytes, or else loudly refused.
 *
 * Returns `{}` for "nothing supplied" (absent, and an empty string — an id of no characters is the
 * absence of an id, the same truthiness fold the deny-note seat uses), `{ approver }` for a value to
 * carry, or `{ defect }` with the sentence a caller puts in its own refusal.
 */
export declare function screenApproverAttribution(v: unknown): {
    approver?: string;
    defect?: string;
};
/** An integrity guard an ask stands on ({@link PermissionResult}'s `guards`). */
export type AskGuard = "dangerous_delete" | "unverifiable_delete" | "unverifiable_shell_text" | "transcript_write" | "artifact_shared_read";
/**
 * A three-state permission decision for a tool call (design/37). Upgrades the old two-state
 * `{allow|deny}`:
 *  - `allow` may carry `updatedInput` — REWRITTEN args (redact a secret, clamp a path/value) that
 *    REPLACE the model's args before execution (re-validated against the tool schema first).
 *    `deny` may ALSO carry `updatedInput` (1.256 复审 MED-2): it never executes anything, but a
 *    combined-policy deny reports the ACCUMULATED pre-deny rewrite (policy A redacted → policy B
 *    denied) so a deny OBSERVER (`classic.PermissionDenied`) sees the final post-rewrite args, not the
 *    stale un-redacted ones.
 *  - `ask` requests human confirmation; with no approver wired it resolves deterministically to deny
 *    (headless auto-deny, see `RunnerDeps.onAsk`). `requiresRealApproval: true` (RB-203
 *    confirmed P1) additionally refuses to let a BLANKET `onAsk: "allow"` circumvention clear it — that
 *    configuration is "no approver looked at this at all", indistinguishable from headless, for a rule
 *    whose whole point is that a blanket policy setting must not be the thing that clears it (see
 *    {@link resolveAsk}). A live auto-mode classifier verdict (tried earlier in the gate, if configured)
 *    or a genuine approver CALLBACK (actual judgment, even if it always says yes) still can — unless the
 *    callback itself reports its allow as the deployment's POLICY (`settledBy: "policy"`): that is the same
 *    "nobody judged this" a blanket seat is, and it is refused the same way.
 *  - `deny` blocks the call.
 * Human-readable text is `message`; read it via {@link decisionText}.
 *
 * What ended a wait is not a policy's to state: it rides only the `attestation` member, an engine-minted
 * record ({@link EngineAttestation}) the receipt refuses in any other form, and the gate's exit mints the one
 * settlement record ({@link import("./gate-outcome.js").GateOutcome}) from it; a policy that puts a `settledBy`
 * or `approver` member on its verdict is putting an unread property on it.
 * RB-479-B① (ruled 2026-08-02): the legacy `reason` field is REMOVED — one name for one thing. The ask arm's one-word
 * `guard` is retired the same way, for `guards`.
 *
 * The TYPE carries that retirement only where a decision is a literal in an annotated position; an inferred `check`
 * return compiles green with a stray `reason` or `guard`, so the enforcement is at RUNTIME — every point that consumes a
 * decision refuses one that carries a retired member ({@link RETIRED_DECISION_MEMBERS}), fail-closed
 * ({@link refuseOutOfContractDecision}).
 */
export type PermissionResult = {
    action: "allow";
    updatedInput?: unknown;
    message?: string;
    decisionReason?: DecisionReason;
    attestation?: EngineAttestation;
} | {
    action: "ask";
    updatedInput?: unknown;
    message?: string;
    decisionReason?: DecisionReason;
    requiresRealApproval?: boolean;
    /** This ask is the classifier DENIAL-LIMIT fallback — the block that reached a
     *  bound (CC 2.1.250 `FO`, count-then-judge) handed to a person instead of being denied.
     *  ENGINE-STAMPED beside `requiresRealApproval: true` and `decisionReason: "classifier"` at the
     *  classifier block sites (the gate's own and the inherited-lane arms); carries the COUNTS ONLY —
     *  the auto-deny window is a fact of the ask's ROUTE, so this type has no member to hold one
     *  ({@link import("./auto-mode.js").UnarmedDenialLimitFallback}) and the window appears first on
     *  the {@link AskRequest} the mint station builds, iff that request goes to a live function
     *  approver. Consumers: the gate's classifier step refuses to re-judge an ask carrying it (the
     *  classifier already spoke; rules and classifier stand BEFORE this ask, never after), and the ask
     *  resolver arms its deadline from the REQUEST's `autoDenyAfterMs`. Carried onto the approval
     *  request through one of the two route stations. A policy that self-declares it only ever opts
     *  its own ask OUT of classifier resolution and INTO the fallback's bounded lane (tightening — the
     *  same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
     *  route it would be a window for has not been chosen yet. */
    denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
    /** WHY a classifier station handed this call to a person: the classification did not rule (`error` / `timeout` — it could
     *  not run; `parse_error` — no verdict). Engine-stamped with `requiresRealApproval`; class `classifier_no_ruling`. */
    classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
    /** The allow layer's DISCLOSURE: the members that MATCHED this call and could not clear the ask (it is a class
     *  no allow rule clears — a mandated, governance, hook, ask-rule or mode question — or the call reached the layer
     *  marked), one record per member ({@link AllowRuleShadow}). ENGINE-STAMPED by the allow layer, overwriting a
     *  policy's own value; a DISPLAY member: the approval card and the park row render it, nothing reads it to
     *  decide, and the classifier never sees it (it is not part of `message`). Absent ⇒ no member matched. */
    allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
    /** backlog #239 disclosure (additive): WHY the reversibility probe did not clear this call, in the
     *  probe's own words. ENGINE-STAMPED at the irreversibility tighten (which constructs this decision
     *  wholesale), already neutralized + capped there; a `"maybe"` tier whose probe supplied no cause,
     *  and the `"always"` tier (which runs no probe), leave it absent. Same DISPLAY-ONLY posture as
     *  {@link allowRuleShadowed}: a policy that self-declares it can only put its own prose on its
     *  own card — nothing reads this member to decide anything, so it is not an authority channel. */
    probeReason?: string;
    /** backlog #239 disclosure (additive): the STRUCTURED cause, for a probe whose verdict this engine
     *  understands (see {@link import("./checkpoint-store.js").ProbeCause}). Engine-stamped and
     *  validated at the irreversibility tighten; same display-only posture as the prose sibling. */
    probeCause?: import("./checkpoint-store.js").ProbeCause;
    /** The reversibility probe declared this demotion STRUCTURAL — see
     *  {@link import("./types.js").ReversibilityVerdict.mandated}. Unlike its two `probe*` neighbours
     *  this is NOT display metadata: it joins the #144 mandate family, so the persisted-rule lane
     *  discloses a matching allow rule as shadowed instead of clearing the ask, and the card's
     *  "stop asking me this" offer is withheld. ENGINE-STAMPED at the irreversibility tighten, which
     *  constructs this decision wholesale — a policy that self-declares it is overwritten there, and
     *  in the one direction the stamp cannot reach (a caller composing its own ask) the claim is
     *  tightening-only: it can make an ask outlive a rule, never turn one into an allow. */
    probeMandated?: true;
    /** This ask is MANDATED — no persisted allow rule and no STANDING ANSWER (a release remembered from an
     *  earlier call, at any scope, a session included) may retire it; only an answer produced for THIS call
     *  clears it — a person, a hook, a deployment's blanket `onAsk: "allow"` seat, or the live deciders the
     *  ask's class row admits ({@link import("./ask-origin.js").MODE_ARBITRATION}); {@link requiresRealApproval}
     *  is the stronger demand that also refuses the blanket seat. ENGINE-STAMPED at the gate's single origin stamp from the ONE derivation
     *  the approval card's rule-offer word also reads ({@link import("./ask-origin.js").ruleClearanceOf},
     *  which lists the doors). TIGHTENING-ONLY: a layer below the mint may not clear one a layer above
     *  set, and a policy self-declaring it only opts its own ask OUT of automatic answering. Carried
     *  onto the approval request as {@link AskRequest.mandated}. */
    mandated?: true;
    /** The TABLE WORD the {@link mandated} bit stands on — see {@link AskRequest.mandate}. ENGINE-STAMPED at
     *  the same origin stamp as the bit; a policy's self-declared value is dropped there. */
    mandate?: import("./ask-origin.js").PersistedRuleMandate;
    /** The probe named a directory that would clear this ask
     *  ({@link import("./types.js").ReversibilityVerdict.readRootCandidate}). Display metadata like the two
     *  `probe*` above; ENGINE-STAMPED and VALIDATED at the tighten, and deliberately NOT merged onto an ask
     *  another layer raised, which a grant leaves standing. */
    probeReadRootCandidate?: import("./types.js").ReadRootGrantCandidate;
    /** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules` shape); carries the
     *  matched rule's text. Present ⇔ a rule someone WROTE says "ask about this", never for a `defaultAction:"ask"` fallback
     *  (an unmatched call is default-closed posture, not a per-call instruction — CC draws the same line: only a matched ask
     *  rule diverts the auto-mode lane, a mode-default ask stays classifier-eligible). Consumer: the gate's auto-mode
     *  classifier step refuses to resolve an ask carrying it — a person's standing "ask me each time" is not classifier
     *  hesitation, so a classifier verdict must not be the thing that clears it. A policy self-declaring the member only
     *  ever OPTS ITS OWN ask OUT of classifier auto-resolution (tightening — the false-claim direction is safe, unlike the
     *  engine-stamped `"hook"` word whose risk was laundering provenance AWAY). {@link combinePolicies} carries it onto the
     *  surviving ask when ANY folded-away concurrent ask bore it (monotone, tighten-only); it is consumed inside the gate
     *  and NOT copied onto the park/approval-card request — but it is the ONE input of the `ask_rule` origin word they DO
     *  carry ({@link origin}): a minter raising an ask for a written rule (a person's settings rule, an operator's must-ask
     *  list) that leaves it unset gets `policy` there, indistinguishable from hesitation. */
    matchedAskRule?: string;
    /** design/252 G-2 (additive): the RULE-PROVENANCE evidence behind this ask — see
     *  {@link AskRuleEvidence}. ENGINE-STAMPED inside the gate, once, after the org layer and the
     *  persisted-rule lane have both spoken; a policy that self-declares it is overwritten there
     *  (the member is a record of what the ENGINE's own governance layers did, so a layer's claim
     *  about itself is not evidence). Carried onto the approval request by the gate's own ask mint
     *  site. Display/reconciliation metadata, never adjudication input. */
    ruleEvidence?: AskRuleEvidence;
    /** design/375 §5.2②: the per-segment coverage table behind this surviving ask — which segments
     *  of a compound an eligible persisted rule already admits, read off the SAME store read that
     *  adjudicated the call (snapshot = adjudication snapshot). ENGINE-STAMPED at the gate's one
     *  stamp point whenever the persisted-rule lane reported one (overwriting a policy's claim);
     *  consumed only by the offer factory, which puts on the batch offer exactly the segments this
     *  table calls uncovered. Absent ⇒ all segments read as uncovered (over-offer, the safe way).
     *  Same display-only posture as {@link allowRuleShadowed}: never adjudication input, and a
     *  value nobody stamps over can only shrink or annotate the card's ADVISORY offers — every
     *  offered rule still passes the validator at redemption. */
    segmentCoverage?: readonly import("./permission-rule-model.js").SegmentCoverage[];
    /** WHICH AUTHORITY raised this ask — one word from the closed {@link import("./ask-origin.js").AskOrigin}
     *  set (org rule / org unavailable / hook / explicit ask rule / the classifier's denial-limit
     *  fallback / the reserved question tool / a marked call / plain policy). ENGINE-STAMPED inside the
     *  gate at the same single stamp point as `ruleEvidence`, derived from what the gate's own layers
     *  did ({@link import("./ask-origin.js").askOriginOf}) — a policy that self-declares it is overwritten
     *  there, so the member is never a channel a policy can loosen through. A DISCLOSURE word: the lanes
     *  read the ask's class set ({@link import("./ask-origin.js").classifyAsk}), an approval surface may
     *  render the card by it. Optional on the TYPE only because a decision is caller-composable; every ask
     *  the gate hands to its resolver carries it. */
    origin?: import("./ask-origin.js").AskOrigin;
    /** The ask's CLASS — the first member of {@link classes} (see {@link AskRequest.askClass}). ENGINE-STAMPED at the
     *  same single stamp as {@link origin}, from the class set the gate's lanes read; a policy that self-declares it is
     *  overwritten there, and no lane reads it back (they read the classification itself). */
    askClass?: import("./ask-origin.js").AskClass;
    /** The ask's whole class set, in {@link import("./ask-origin.js").ASK_CLASSES} order (see
     *  {@link AskRequest.classes}). Stamped beside {@link askClass}, under the same rule. */
    classes?: readonly import("./ask-origin.js").AskClass[];
    /** WHICH integrity guards stand behind this ask — a removal of a protected location, an unverifiable recursive delete,
     *  shell text the engine cannot read (or an environment that did not answer the read station), the session-transcript
     *  write. A set: one ask may stand on several, and each keeps its class. ENGINE-STAMPED by the guard policies and the
     *  gate fold; a fold-internal fact the class derivation reads ({@link import("./ask-origin.js").classifyAsk}) so the
     *  guards, which mint the same `requiresRealApproval` ask, are told apart by a fact and not by their prose. Never
     *  copied onto the approval request or the park row. Replaces the one-word `guard`, which a decision no longer carries:
     *  a decision that still does is refused by name ({@link RETIRED_DECISION_MEMBERS}). */
    guards?: readonly AskGuard[];
    /** The call could not be READ against a rule that may reach it — a content-form deny row, a content-form
     *  ask row, a persisted-rule row (the lexer's word), or rows the shell read station's boundary stands on that
     *  could not be read (`persisted_rows`). ENGINE-STAMPED at the four unreadable mint sites;
     *  the class derivation reads it as `rule_unreadable` (no mode releases it, a person may clear it, no
     *  automatic lane may). It replaces the former practice of stamping `matchedAskRule` on an unreadable
     *  read: a rule that COULD NOT be matched is not a rule that matched. Fold-internal; not on the request. */
    ruleUnreadable?: "content_deny" | "content_ask" | "persisted_call" | "persisted_rows";
    /** The ask was authored by the DEPLOYMENT's own governance (an operator's must-ask list, a managed command
     *  policy's default ask) — a fact a host's policy states on its own ask so the class derivation reads it
     *  as `operator_forced`: a permission mode releases nothing an operator mandated (`dontAsk` refuses it like
     *  every ask). Public: a host composing such a policy sets it; {@link combinePolicies} carries it onto the
     *  surviving ask (tighten-only). Not on the approval request — the card shows `origin` / `mandate`. */
    authoredBy?: "deployment";
    /** The classes of the OTHER asks a policy fold collapsed into this one
     *  ({@link import("./ask-origin.js").decisionAskClassesOf} per contributing ask, unioned), so a member the
     *  fold does not copy — a guard mark, an unreadable-rule fact, a bare real-approval bit — is not lost behind
     *  the ask that won the slot. ENGINE-WRITTEN by {@link combinePolicies}, which attests the list it wrote
     *  ({@link EngineAttestation.foldedClasses}); the receipt refuses the member unless the decision's record carries that very
     *  list (a policy's own, an edited list are refused). Fold-internal, never on the request. */
    foldedAskClasses?: readonly import("./ask-origin.js").AskClass[];
    attestation?: EngineAttestation;
} | {
    action: "deny";
    updatedInput?: unknown;
    message?: string;
    decisionReason?: DecisionReason;
    attestation?: EngineAttestation;
};
/**
 * design/252 G-2 — WHY a piece of rule-provenance evidence is not on an ask.
 *
 * The vocabulary exists because a bare `undefined` reads the same for facts that are opposite: "no
 * governance layer is wired here, so there is nothing to name" and "the layer ran and we could not
 * read what it said" are not the same audit answer, and collapsing them is how an evidence chain
 * comes to be reconstructed as "nothing governed this call". Every member of {@link AskRuleEvidence}
 * therefore ships as a value OR a named absence, never as silence.
 *
 *  - `"not_wired"` — the lane does not exist on this leg (an ungoverned deployment, no rule store).
 *    The field cannot apply; nothing was lost.
 *  - `"not_adjudicated"` — the lane exists but this call never reached it (an exempt tool, a call
 *    the rule grammar cannot describe). Applicable in principle, skipped in fact.
 *  - `"unavailable"` — the lane was consulted and could not read its source. The evidence is LOST,
 *    not absent, and this is the one member of the vocabulary that means an auditor should treat the
 *    chain as broken rather than empty.
 *  - `"no_match"` — consulted, readable, and nothing spoke for this call. A real negative answer.
 *  - `"not_reported"` — the supplying seam answered without the identity (a foreign overlay/store
 *    implementation, or one written before the identity was projected). Evidence lost at the seam.
 */
export type AskEvidenceAbsence = "not_wired" | "not_adjudicated" | "unavailable" | "no_match" | "not_reported";
/** The closed set above, for runtime domain checks at the seams that accept a caller-supplied value. */
export declare const ASK_EVIDENCE_ABSENCE_VALUES: readonly AskEvidenceAbsence[];
/**
 * design/252 G-2 — the machine-reconcilable provenance of the governance decision behind ONE ask.
 *
 * WHAT THIS IS: a PROJECTION of identity keys the engine's governance layers already hold — the org
 * snapshot's `revision`, the personal rule's add `dot`s — onto the surface a consumer can actually
 * read. It performs no new judgment and changes no verdict; removing it would leave every decision
 * byte-identical. The prose channels ({@link PermissionResult}'s ask `message`,
 * {@link AskRequest.allowRuleShadowed}) say the same things to a PERSON; those are sanitized,
 * capped, display-shaped strings, and reconciling a decision against a published policy revision by
 * regexing them is not an audit trail. This member is the machine's copy.
 *
 * WHAT THIS IS NOT: an authority channel. Nothing in the engine reads it back to decide anything, and
 * a host that ignores it entirely is governed exactly as before.
 *
 * ABSENCE DISCIPLINE (the reason each member has a `…Absent` twin): see {@link AskEvidenceAbsence}.
 * Exactly one of each pair is present — a member and its absence reason are never both set, and never
 * both missing, on evidence the engine stamped.
 */
export interface AskRuleEvidence {
    /** The org snapshot `revision` this call was adjudicated against — the published-policy version an
     *  auditor reconciles the decision against. Absent ⇒ {@link orgRevisionAbsent} names why. */
    readonly orgRevision?: number;
    /** Present iff {@link orgRevision} is not. `"unavailable"` here is the load-bearing one: the org
     *  layer spoke, its answer was "this deployment cannot see the organization's rules", and the ask
     *  in hand is the fail-closed tighten that followed — not an ask any published rule asked for. */
    readonly orgRevisionAbsent?: AskEvidenceAbsence;
    /** The org rule that spoke for this call, verbatim as published (org rules are administrator
     *  authored and are relayed unmodified — the prose channel's copy is the same text). Absent ⇒
     *  {@link orgRuleAbsent} names why. */
    readonly orgRule?: string;
    /** Present iff {@link orgRule} is not. */
    readonly orgRuleAbsent?: AskEvidenceAbsence;
    /** The add dots of the PERSONAL allow rule that matched this call but did not clear the ask (the
     *  #144 shadowed arm) — the deduplicated UNION across the covering set when the conjunction arm
     *  matched, so the array does not belong to one rule on those asks. The dots are the rule's durable
     *  identity — unlike
     *  {@link AskRequest.allowRuleShadowed}, which is a sanitized, length-capped DISPLAY value and
     *  deliberately not an identity channel. A rule is a set of adds (concurrent approvals on one text
     *  each redeem their own dot), so this is an array by construction: render/reconcile the entries as
     *  data, never re-derive one scalar id by joining them. Absent ⇒ {@link personalRuleDotsAbsent}. */
    readonly personalRuleDots?: readonly import("./permission-rule-model.js").RuleDot[];
    /** Present iff {@link personalRuleDots} is not. */
    readonly personalRuleDotsAbsent?: AskEvidenceAbsence;
}
/** The human/model-readable text of a decision. */
export declare function decisionText(d: PermissionResult): string | undefined;
/**
 * Gates tool calls before they execute (the production safety layer for "processing/changing data").
 *
 * `check` runs before each tool call. Return `{ action: "deny", message }` to block it — the model
 * receives an error tool result with the reason and can continue or report blocked. Return
 * `{ action: "allow" }` to permit it.
 *
 * `check` may be async, which is also how **human-in-the-loop approval** works: a deployment can
 * hold the promise open until an operator approves/denies. `signal` fires when the task aborts
 * (timeout / max turns / cancel) or the asking turn is interrupted (design/384), and on a synthetic probe when the judge
 * stops waiting for this layer — honor it to release a pending approval (F4; the Runner also races `check` against it). An
 * already-aborted one: see `check`. A request that states nobody can be asked about it: {@link ToolCallRequest.approvalReach}.
 */
export interface ToolPolicy {
    /** @contract policy.check.aborted_signal_reaches_nobody — handed an already-aborted signal, `check` reaches no person and
     *  no external approval (it answers at once, or with a `task_aborted` deny). The engine's probes do not rely on it: they hand a
     *  live signal and state on the request that nobody can be asked (`approvalReach: "none"`, answered with `noApproverSeatDeny`). */
    check(req: ToolCallRequest, signal?: AbortSignal): PermissionResult | Promise<PermissionResult>;
    /**
     * The policy's CONSTRAINT PROJECTION — its deterministic DENY-verdict half expressed as pure data
     * (F-012 L1), so a delegation chain / durable checkpoint can freeze what this policy refuses without
     * carrying the live closure. Optional: a policy without one is OPAQUE to the projection plane (its
     * live closure keeps the resume re-supply contract it has today). Built-in factories in this module
     * attach one where the deny half IS data (name allow/deny lists, shell command-name lists);
     * a custom closure policy simply opts out.
     *
     * The projection is a SUBSET contract, never a replacement: executing it yields every deny the
     * data half implies, and says nothing about the policy's live remainder (asks, approvals,
     * rewrites). `requiresLiveRemainder` states explicitly whether such a remainder exists — "has a
     * projection" ≠ "fully projectable", and consumers deciding whether a frozen projection COMPLETELY
     * describes the policy must read that bit, not the field's presence.
     */
    readonly projection?: ToolPolicyProjection;
    /** The policy's PATH rules as a function of the ANCHORS of the leg they are installed on (`createPermissionRulePolicy`
     *  sets it; present exactly when the policy judges a path target): given the leg's root and home, every path rule
     *  bound there — the face-keyed deny rows the prepared leg joins to its read and write tables (so the shell's reads and
     *  writes and the traversals meet the rule, not only the tool it names), and the rows its own `check` judges at the
     *  gate. A policy the Runner already installed answers with its own installation's rows whatever it is given. Carried
     *  through {@link combinePolicies}; read with {@link toolPolicyPathRules}. Its presence also tells the Runner to read
     *  each call's target ({@link ToolCallRequest.targetReading}). */
    readonly pathRules?: (anchors: import("./permission-rule-model.js").PathRuleBases) => readonly import("./path-rule-binding.js").BoundPathRule[];
    /** The guarded WRITE rows this policy refuses — rows of the one path table (path-guard-rows.ts), stated as data. The
     *  engine judges them for a writer the policy cannot see as a call (the shell's file commands) by its ONE write judgment:
     *  the leg's views of the target (spelling, real target, symlink chain) under the leg's one exempt root
     *  ({@link ToolCallRequest.exemptRoots}) beside the always-on write-protection table. A target one of them covers is not
     *  placed, so no permission mode answers the command and its ask reaches a person — it is not refused. The rows of ONE
     *  judgment: a row placed below a declared root is placed where that root resolves in `env` (resolved per call). A PURE
     *  statement: no side effect, no verdict; a call that throws leaves the target unplaced. Carried through
     *  {@link combinePolicies} (the members' rows, in order); read with {@link toolPolicyWriteGuardRows}. */
    readonly writeGuardRows?: (at: {
        readonly env?: import("../internal/harness-types.js").ExecutionEnv;
        readonly signal?: AbortSignal;
    }) => Promise<readonly import("./path-guard-rows.js").PathGuardRow[]>;
    /** The raw tool-name lists a name-keyed policy was built from — the prepare-time policy-name audit's feed (a retired
     *  name refuses the run, an unmounted one is advised). Carried through {@link combinePolicies} (the union of the
     *  members' groups); read with {@link toolPolicyNameSets}. */
    readonly nameSets?: readonly ToolPolicyNameSets[];
}
/**
 * The members a policy carries beside `check` — one table for every seat that carries them: {@link wrapToolPolicy} reads
 * each by name off the wrapped policy, {@link combinePolicies} folds each. The fence makes a new {@link ToolPolicy} member
 * a compile error until the table names it.
 */
declare const TOOL_POLICY_CARRIED_MEMBERS: readonly ["projection", "pathRules", "writeGuardRows", "nameSets"];
type ToolPolicyCarriedMember = (typeof TOOL_POLICY_CARRIED_MEMBERS)[number];
export type ToolPolicyCarriesEveryMember = AssertAllKeysHandled<Exclude<Exclude<keyof ToolPolicy, "check">, ToolPolicyCarriedMember>>;
/** The carried members that are set on `p`, each read by name — an accessor or a non-enumerable member included. A
 *  retired member `p` still holds would not be carried: it is refused by name instead (`config.deps_retired_key`). */
export declare function carriedMembersOf(p: {
    readonly [K in ToolPolicyCarriedMember]?: ToolPolicy[K];
}): {
    -readonly [K in ToolPolicyCarriedMember]?: ToolPolicy[K];
};
/**
 * The ONE way to wrap a policy: the wrapper decides through `check`; every member {@link ToolPolicy} declares beside it
 * (projection, name sets, path deny rows, guarded write rows — the carried-member table) rides as `inner` holds it, read
 * by name, so a class instance's accessor or a non-enumerable member rides too — a `{ check }` literal drops them, and
 * engine stations the wrapper never sees read each one. The carried members describe what `inner` refuses, so the wrapper
 * must refuse at least that (an observer, a tightener); one that releases what `inner` refuses is a policy of its own.
 * The ask-class brand is not carried: it attests the replaced `check`.
 */
export declare function wrapToolPolicy<P extends ToolPolicy>(inner: P, check: ToolPolicy["check"]): Pick<P, ToolPolicyCarriedMember> & ToolPolicy;
/** Every path rule the given policies carry (combined policies included), in order, bound at `anchors` — the anchors of
 *  the leg the policies are installed on (a policy already installed answers with its own installation's rows). The deny
 *  tables take the rows that guard a face (`faces` non-empty) of a deny rule. */
export declare function toolPolicyPathRules(policies: readonly (ToolPolicy | undefined)[], anchors: import("./permission-rule-model.js").PathRuleBases): readonly import("./path-rule-binding.js").BoundPathRule[];
/** The guarded write rows the given policies carry together (combined policies included): every member's rows of one
 *  judgment, in member order (a member that throws throws here — the judgment leaves the target unplaced). `undefined`
 *  when none carries any. */
export declare function toolPolicyWriteGuardRows(policies: readonly (ToolPolicy | undefined)[]): ToolPolicy["writeGuardRows"];
/**
 * One deterministic DENY component of a {@link ToolPolicyProjection} (F-012 L1). All fields are plain
 * serializable data — a component round-trips JSON/`structuredClone` unchanged, which is what lets a
 * chain of them persist on a checkpoint and be re-executed verbatim on resume.
 *
 *  - `tool_deny`: deny any call whose toolName is in `names`.
 *  - `tool_allowlist`: deny any call whose toolName is NOT in `names`.
 *  - `shell_command`: for calls to the named shell `tools`, parse the leading command name and deny
 *    per `deny`/`allow` lists; `unmatchedAction`/`unparseableAction` say whether the projection may
 *    speak for those arms (`"deny"`) or must stay silent (`"none"` — the live policy's ask arm owns
 *    them). Non-shell tools are out of scope (no opinion).
 */
export type ToolPolicyProjectionComponent = {
    kind: "tool_deny";
    names: readonly string[];
} | {
    kind: "tool_allowlist";
    names: readonly string[];
} | {
    kind: "shell_command";
    tools: readonly string[];
    deny: readonly string[];
    allow?: readonly string[];
    /** Verdict for a parsed command name outside `allow` (only meaningful when `allow` is present). */
    unmatchedAction: "deny" | "none";
    /** Verdict for a command string this projection cannot parse (compound/missing command). */
    unparseableAction: "deny" | "none";
};
/**
 * The deterministic data half of a {@link ToolPolicy} (F-012 L1): ordered deny components plus the
 * explicit live-remainder bit. See {@link ToolPolicy.projection} for the contract.
 */
export interface ToolPolicyProjection {
    readonly components: readonly ToolPolicyProjectionComponent[];
    /**
     * `true` when the policy has behavior BEYOND these components (an ask arm, an approval callback,
     * a rewrite) — the projection then freezes only the deny half and the live policy must still ride
     * the resume re-supply channel for the rest. `false` = the components plus "allow otherwise" ARE
     * the whole policy (fully projectable — e.g. a plain name allow/deny policy).
     */
    readonly requiresLiveRemainder: boolean;
}
/**
 * The entries of a tool-NAME list whose reach is a set of names rather than one name — the covering
 * spellings (`<ns>__<peer>`, `<ns>__<peer>__<glob>`) of ANY protocol namespace, which CC's rule
 * matcher resolves and an exact-membership test silently cannot. Every name-keyed lane in this file
 * consults these ALONGSIDE its exact set: a covering spelling can never equal a minted tool name (`*`
 * is outside the minted charset and a minted name always carries a tool segment), so leaving it in the
 * exact set too costs nothing and keeps the projection/audit faces reporting the operator's own spelling.
 *
 * Namespace-generic on purpose: the covering grammar is a property of the shared `<prefix><peer>__<tool>`
 * name shape, not of MCP, and keying it to one prefix made every sibling-namespace covering rule
 * (`a2a__<peer>__*`) compile clean and match nothing — inert in the deny half, and in an ALLOW list the
 * removal of that peer's whole tool set.
 *
 * The list is usually empty, which is why every call site tests it before scanning.
 *
 * Exported for the OTHER name-keyed lane in this engine — the persisted session/ancestor rules in
 * `session-rule-policy.ts`. Not part of the public API (`src/index.ts` re-exports by name and does not
 * list these two): every publisher of {@link ToolPolicyNameSets} must resolve a covering entry the same
 * way, because the prepare-time audit's exemption for these spellings speaks for ALL published name sets
 * at once — the four in-engine lanes do so by sharing this module's matcher. That premise is NOT
 * structural for an OUT-OF-ENGINE publisher (a host compiling its own settings into an exact-match ask
 * closure is fail-open on these spellings while the audit stays silent) — which is why the underlying
 * judgment predicates (`isNamespacedCoveringRuleName` / `namespacedRuleNameCovers`) ARE exported from
 * the package index: an external lane either compiles through `createPermissionRulePolicy` or
 * self-checks with those.
 */
export declare function namespacedCoveringEntries(entries: readonly string[] | undefined): readonly string[];
/** Does any covering entry reach the call — under ANY name it answers to, the same reading the exact lists take through
 *  `nameListHolds` (a covering entry and an exact entry in one list are one rule: it reaches the call iff its name is on the
 *  list)? See {@link namespacedCoveringEntries}. */
export declare function namespacedCoveringHit(covering: readonly string[], call: Pick<ToolCallRequest, "toolName" | "face">): boolean;
/**
 * The tool names a projection component is keyed on, and the member that holds them: a `tool_deny` / `tool_allowlist`'s
 * `names`, a `shell_command`'s `tools` (its `deny` / `allow` are command names, not tool names). A frozen chain holds them only
 * in the one spelling a checkpoint row records a tool name in; the resume screen reads them back by it before anything replays.
 */
export declare function projectionToolNames(c: ToolPolicyProjectionComponent): {
    readonly member: "names" | "tools";
    readonly list: unknown;
};
/**
 * Execute a persisted {@link ToolPolicyProjection} against a call (F-012 L1): returns the first
 * component's deny, or `undefined` when the projection has no opinion (it is deny-only by
 * construction — never an allow, never an ask). This is what a durable RESUME runs instead of
 * trusting a re-supplied policy's execution half for the frozen deny data: the projection persisted
 * at suspend is the authority for what the chain refused, whatever a re-supplied closure claims.
 * A tool name is matched as recorded, which is the rule syntax's reading only for a name in its canonical spelling: the resume
 * screen refuses a frozen chain holding any other ({@link projectionToolNames}) before this runs on it.
 */
export declare function checkToolPolicyProjection(projection: Pick<ToolPolicyProjection, "components">, req: Pick<ToolCallRequest, "toolName" | "args" | "face">): Extract<PermissionResult, {
    action: "deny";
}> | undefined;
/**
 * One entry of the persisted constraint-chain topology (F-012 L1): either a layer's projection
 * content, or an explicit OPAQUE placeholder for a layer that exports none. The placeholder is
 * load-bearing — the chain digest binds the ORDERED topology (layer boundaries included), so an
 * opaque layer's position is part of what a resume re-supply must reproduce.
 *
 * F-012: each entry ALSO carries the serializable DECISION-CHAIN METADATA posture —
 * whether the layer was classifier-armed (`autoModeArmed`) and its durable-mandate / content-mandate
 * flags. The digest binds these, so a cross-process resume re-supplying a same-shape chain whose
 * projections match but whose classifier was DROPPED (or whose mandate posture changed) — a
 * version-skew / reconfiguration widening — is rejected pre-CAS. The metadata is NOT read by
 * {@link checkToolPolicyProjection} (execution reads only `components`); it exists purely to be
 * digested. The frozen closures themselves (approver, decider) are non-serializable and stay outside
 * the digest — the re-supply trust contract governs their identity — but their PRESENCE/posture is
 * bound so a re-supply cannot silently shed a decision-chain layer.
 */
export type ConstraintChainEntryMeta = {
    /** True iff the layer carried a frozen auto-mode classifier (`autoMode`). */
    autoModeArmed?: true;
    /**
     * The layer's serializable auto-mode ARMING RECIPE: the criteria half of the frozen classifier
     * (assembly inputs + knobs + the deployment's settings epoch), recorded so a CROSS-PROCESS redemption
     * can rebuild an equivalent decider over its own fresh model leg
     * ({@link import("./auto-mode-arming.js").rebuildAutoModeDecider}) instead of answering `unavailable`
     * forever and parking the same approval for a human on every redemption.
     *
     * OPT-IN and ADDITIVE: present only when the arming deployment asked for it (`RunnerDeps.autoMode.
     * persistArming`), so an unchanged deployment mints byte-identical entries and every pre-#503 row
     * (and every row minted without the opt-in) reads as absence = the pre-#503 behavior. Absence is also
     * what an OPEN BREAKER produces — see {@link constraintChainEntryOfLayer}.
     *
     * The digest binds it: a re-supply that DROPS the recorded recipe, or hands back a DIFFERENT one, is
     * rejected pre-CAS — so the re-supply contract is exactly "hand back what the row carries" (a redeeming
     * host reads the recipe off this field and passes it straight through). The verify side takes the
     * PRESENCE of this field from the row, never from the re-supplied layer's live classifier health, so a
     * row that recorded no recipe does not refuse a re-supply that offers one — the reasoning, and why that
     * direction cannot be recovered, is recorded on {@link constraintChainEntryOfSuppliedLayer}.
     * `checkToolPolicyProjection` never reads it: like the other metadata here it exists to be digested,
     * not to execute.
     */
    autoModeArming?: import("./auto-mode-arming.js").AutoModeArmingRecipe;
    durableMandate?: true;
    contentMandate?: true;
};
export type ConstraintChainEntry = ConstraintChainEntryMeta & ({
    opaque: true;
} | {
    components: readonly ToolPolicyProjectionComponent[];
    requiresLiveRemainder: boolean;
});
/** Project one policy into its chain entry (opaque placeholder when it exports no projection),
 *  carrying the layer's decision-chain metadata posture for the digest (round 12). */
export declare function constraintChainEntryOf(policy: ToolPolicy, meta?: ConstraintChainEntryMeta): ConstraintChainEntry;
/**
 * One inherited-chain LAYER as both chain-entry mint sites see it — structurally an
 * `InheritedGate.parentConstraints[]` element, typed here structurally so this module (which owns the
 * digest) does not depend on the runner's shapes. The classifier itself is not read: a decider carries no
 * state between rounds, so nothing about its health enters a chain entry.
 */
export interface ConstraintChainLayerView {
    policy: ToolPolicy;
    autoMode?: {
        arming?: unknown;
    };
    durableMandate?: boolean;
    contentMandate?: boolean;
}
/**
 * The WRITE-side mint: the chain entry a durable PARK records for a live inherited layer. The recipe is
 * carried whenever the layer has one (the deployment opted into `persistArming`). The former gate — "only
 * while the ancestor's breaker is untripped and untouched" — went with the breaker: a decider carries no
 * state between rounds, so there is no ancestor health to sample here and nothing that could make a
 * redemption run wider than the ancestor it inherits.
 *
 * ⚖️ WHAT THE ROW DOES NOT SAY. An armed layer whose deployment never opted in leaves the entry at
 * `autoModeArmed: true` with no recipe. The digest does not, and cannot, stop a re-supplier from handing
 * back a FRESH decider of its own: that is the pre-existing re-supply trust posture (see `resumeStream`'s
 * contract — a caller able to invoke it already holds full Runner authority, and the chain checks catch
 * shape MISTAKES, not a hostile trusted caller). What CORE hands forward is the recipe when there is one,
 * and nothing otherwise.
 */
export declare function constraintChainEntryOfLayer(layer: ConstraintChainLayerView): ConstraintChainEntry;
/**
 * The VERIFY-side mint: the entry a resume's RE-SUPPLIED layer must reproduce for the row it is
 * redeeming. Same body as the park mint, with the arming carry taken from the ROW rather than from the
 * layer — the digest equality is over bytes the row itself recorded, never over what a live object says at
 * a second instant. A row that DID record a recipe must have that recipe handed back: a re-supply that drops
 * the arming, or hands back a different one, mints a different entry and is refused pre-CAS
 * (`resume.parent_constraint_mismatch`). A row that recorded none does not refuse a re-supply that hands one
 * back — the recipe is not an enforcement object (the DECIDER classifies), and the re-supply trust posture
 * already permits a fresh decider; what the row's absence still means is that no redemption can reconstruct
 * the ancestor's classifier FROM THE CHECKPOINT.
 */
export declare function constraintChainEntryOfSuppliedLayer(layer: ConstraintChainLayerView, recorded: ConstraintChainEntry | undefined): ConstraintChainEntry;
/**
 * Content digest over an ordered constraint chain (F-012 L1) — the durable half's anti-tamper anchor.
 * Binds the chain TOPOLOGY (order, layer boundaries, opaque placeholders) and each projection's full
 * content; version-prefixed (`cpv1:`) so a digest computed under different projection semantics can
 * never false-match. Replaces the count-only re-supply contract: two chains of equal length with
 * different frozen deny data now digest differently.
 */
export declare function constraintChainDigest(chain: readonly ConstraintChainEntry[]): string;
/** Every member of the decision union — a member the union gains and this list lacks is a type error at `satisfies`.
 *  The receipt walks it: a contract member not held as an own enumerable data property is refused if a descriptor on the
 *  prototype chain holds one (an `undefined` data value spells absence). Not re-exported from `src/index.ts`. */
export type DecisionMember = PermissionResult extends infer U ? (U extends unknown ? keyof U : never) : never;
export declare const DECISION_MEMBERS: readonly DecisionMember[];
/**
 * The RECEIPT of a permission decision — the one point at which the engine takes a decision in from whoever composed it,
 * on every lane: each layer of {@link combinePolicies}' fold, the gate's adjudication entry and its approved-edit re-check,
 * the inherited lane's receipts of an ancestor policy's decision, the final-form re-check, the direct `check()` rechecks
 * (the resume edit, the park re-projection, the changed-files probe), and the PreToolUse hook's result seam (its own
 * reach, below). NOT re-exported from `src/index.ts`: an internal screen on inputs the engine receives.
 *
 * WHAT COMES BACK is the engine's own frozen copy, never the caller's object. A decision is caller-supplied data: a
 * stateful accessor, a Proxy, or a policy that keeps and mutates an object it returned could otherwise answer two
 * consumers with two values, and which consumer read which value would decide the call. So the caller's object is read
 * ONCE, here, and every station after the receipt (the fold's ranking, the origin stamp, the classifier's eligibility, the
 * mode arbitration, the approval request, the rewrite that executes) reads the copy; a copy handed to the screen again
 * comes back as it is, unread (so does the engine's own refusal, {@link isEngineRefusal}). In order: (1) the CARRIER's type — a decision that is not an object (a string, a function
 * holding the members) has no `action`; (2) the ONE READ — ONE function ({@link receiveContract}) for the decision and for the
 * records it nests (`receiveNestedRecords`, decision-receipt.ts), on an ORDINARY object (a Proxy, as the carrier or anywhere on its prototype
 * chain, is refused before a trap of its could run): each contract member absent or an own enumerable DATA property, its value
 * taken off the carrier's own descriptor (a member outside the contract is neither read nor carried — a policy's self-declared
 * record on another name finds no reader); any other spelling refused by name — an accessor (never invoked), a member the
 * prototype or a non-enumerable property supplies unless that descriptor spells absence (an `undefined` data value) — judged by
 * descriptor, never read; (3) a retired member ({@link RETIRED_DECISION_MEMBERS}: `reason`, `guard`) carried by the same presence law as a contract
 * member — own, held by an accessor, or supplied by the prototype — judged without a read (ignoring it silently dropped the policy's text or the ask's guard); (4) the VALUES, off the engine's
 * one-read record, never a second read of the caller's object — the retired window absent, `action` in
 * its closed set (`screenContractValues`, decision-receipt.ts — every closed-set member is held to its set there; a consumer ranks a decision by testing two of its words, so any other value used to rank as an
 * allow; a `canUseTool` result outside its set is a failure in CC) and `decisionReason` in {@link DECISION_REASONS} when
 * present (absence stays legal); (5) the DETACH —
 * the contract members' values structured-cloned ({@link tryCloneArgs}; one that cannot be, a function inside a rewrite,
 * is refused), the engine's `attestation` record re-attached by reference after its brand check, the copy frozen and
 * marked as this module's. A refusal is a deny in the screen's words, one code for every arm (`decisionReason: "rule"`),
 * carrying no `updatedInput` from the refused object (a rewrite from an EARLIER, contract-shaped layer still rides out of
 * {@link combinePolicies}' deny path). `null` / `undefined` carry nothing and take the carrier's refusal (the caller's own
 * read of `.action` would throw instead, and that throw was being reported as an aborted check).
 *
 * SCOPE: the receipt runs NONE of the carrier's code — a Proxy (its traps are the caller's code) is refused unread, and on an
 * ordinary object descriptor looks and `Object.getPrototypeOf` invoke nothing. What the receipt does not reach: code inside a
 * member's VALUE, run by the detach itself (an accessor inside a rewrite is invoked by the structured clone, and what it answers
 * there is what is cloned and what runs — one read, consistent) and by a refusal's rendering of a foreign word (`String(v)` on an
 * out-of-set `action`, after the deny is decided). Same-process JavaScript has no trust boundary a function can enforce.
 *
 * `reasonIsNonInput` (the HOOK seam): the same receipt, with the two differences the seam's consumers imply — a hook's
 * `decisionReason` is DISCARDED there (every mint point downstream re-stamps `"hook"`) and so is not held to its set, and a
 * hook result may leave `action` out (no opinion; the policy still adjudicates). `undefined`, `null` and `false` are no
 * opinion (what a hook body yields when it has nothing to say). A refusal the receipt mints at this seam is the ENGINE's
 * decision ({@link isEngineRefusal}) and exits through the engine's fail-closed arm (the deny observer), never as the hook's
 * own deny.
 */
export declare function refuseOutOfContractDecision(d: PermissionResult, opts?: {
    reasonIsNonInput?: boolean;
}): PermissionResult;
/** The raw tool-name lists a name-keyed policy was built from (audit feed, see block note above). */
export interface ToolPolicyNameSets {
    readonly allow?: readonly string[];
    readonly deny?: readonly string[];
    /** Names gated behind an approval decision ({@link createApprovalPolicy.requireApproval}) — a typo
     *  here silently WIDENS (the tool runs without ever asking), so it is audited too. */
    readonly ask?: readonly string[];
}
/** A policy that states its name lists ({@link ToolPolicy.nameSets} set). Composition-transparent:
 *  {@link combinePolicies} (and therefore {@link tightenTaskSpec}) unions children's groups onto the
 *  combined policy, so metadata survives the deployment's tighten/compose path. */
export type NamedToolPolicy = ToolPolicy & {
    readonly nameSets: readonly ToolPolicyNameSets[];
};
/** All name-set groups reachable on `p` — `[]` when it carries none (e.g. a custom closure policy,
 *  which simply opts out of the audit). */
export declare function toolPolicyNameSets(p: ToolPolicy | undefined): readonly ToolPolicyNameSets[];
/**
 * Name-based allow/deny. `deny` always wins. If `allow` is provided, only those tools are permitted
 * (everything else denied). Without `allow`, everything not in `deny` is permitted. Each entry is read by the rule
 * syntax (`ruleSpellingOf`): a name, or `Tool()` / `Tool(*)` — the bare `Tool`, as every list reader reads them, once however
 * many spellings write it; any other spelling is not a tool name (`onInvalidName`), and neither is the wildcard `*` (`*`,
 * `*()`, `*(*)`) in `deny`: it names no tool, and the entry would be kept and never match. In `allow` it is one entry no tool
 * carries — the list admits what else it names, and nothing for it.
 * Carries {@link NamedToolPolicy.nameSets} (the names as read) so the host can audit the lists against the mounted
 * pool ([c209-E] advisory); enforcement semantics are unchanged.
 */
export declare function createAllowDenyPolicy(opts: {
    allow?: string[];
    deny?: string[];
    /**
     * What to do with an entry that is not a tool NAME: a rule CONTENT form (`Bash(ps:*)`, `Edit(src/**)`), a spelling
     * the rule syntax reads no tool name in (`" Bash"`, `Bash\t(*)`, `-deploy`), or the wildcard `*` in the deny list.
     * `"throw"` (default, matching {@link createPermissionRulePolicy}'s posture toward a rule it cannot
     * compile) refuses at construction; `"skip"` drops the entry and reports it. Under BOTH, the entry never
     * silently narrows the allowlist.
     */
    onInvalidName?: "throw" | "skip";
    /** Called once per dropped entry under `"skip"`. */
    onInvalidNameIssue?: (issue: {
        entry: string;
        list: "allow" | "deny";
        message: string;
    }) => void;
    /**
     * What an `allow` list HIT means. `"grant"` (default): the deployment named this tool as permitted — a statement
     * (`decisionReason: "rule"`) that satisfies the fold's unstated rule for a passthrough-seat tool. `"narrowing"`: the
     * list only bounds WHICH tools a lane may see (an agent definition's `allowTools`); a hit is scope, not consent, and
     * answers a bare allow — the mode's unstated question still goes to a person. The deny list is unaffected.
     */
    scope?: "grant" | "narrowing";
}): NamedToolPolicy;
/**
 * What `createApprovalPolicy`'s `approve` may resolve to: a boolean, or the `AskOutcome` OBJECT minus the
 * two members this seat has no lane for — `"unavailable"` (a ROUTING answer whose only consumer is the
 * ask resolver's durable re-route) and `updatedInput` (an OPERATOR EDIT; see {@link ApproverEditLane}).
 * The latter is spelled `?: never` rather than merely omitted, the way this file spells every
 * prohibition a fresh object literal alone would not catch (a predeclared const, an annotated return).
 */
export type ApprovalSeatOutcome = boolean | (Extract<AskOutcome, {
    allow: boolean;
}> & {
    updatedInput?: never;
    readRootGrant?: never;
});
/**
 * Human-in-the-loop approval for selected tools. Tools in `requireApproval` call `approve(req)` and
 * are allowed only if it resolves true; tools in `deny` are always blocked; everything else is allowed
 * (override with `denyByDefault: true` to allow only `requireApproval` + an explicit `autoAllow`).
 *
 * This seat receives a bare `ToolCallRequest`, NOT the ask surface: the decision settles in-place
 * inside the policy, no AskRequest is minted and `resolveAsk` is never entered — so none of the
 * ask-side approval members (`hasBidiControls`, `boundInputHash`, `preview`, `riskAxes`, …) exist
 * here. A surface rendering the command text for a human on this seat must render through
 * {@link import("./permission-rule-model.js").renderUntrustedCommandText} (the display baseline)
 * or run its own screen — absence of the warning bit is "not on this surface", never "clean".
 *
 * WHAT `approve` MAY RETURN: a boolean, as always, or the `{allow, …}` OUTCOME OBJECT the `onAsk` seat
 * takes, read by the SAME screen ({@link readApproverOutcomeObject}) and narrowed to what this seat has
 * a lane for ({@link ApprovalSeatOutcome}) — so a deployment here can report WHO ended the wait
 * (`settledBy:"policy"` for its own policy's yes or no, `"timeout"` for its own elapsed window), attach the
 * decider's `reason` and name the `approver`. A bare `true` / `false` is unchanged: a person's approval or
 * refusal, the refusal with this factory's own terse sentence. Before the object arm a bare boolean was the
 * only spelling there was, so a machine's decision filed a PERSON's record.
 */
export declare function createApprovalPolicy(opts: {
    /** Tools that need an approval decision. */
    requireApproval: string[];
    /**
     * The approval decision (e.g. await an operator). Resolve true to allow, false to deny — or resolve
     * the `{allow, settledBy?, reason?, approver?, updatedInput?}` outcome object for everything a bare
     * boolean cannot say (see this factory's own doc for what each member means here). `signal`
     * fires when the task aborts — and, since design/384, when the asking turn is interrupted — race
     * your wait against it (e.g. an OA approval callback) so a never-answered request is released at
     * the deadline rather than holding the worker.
     */
    approve: (req: ToolCallRequest, signal?: AbortSignal) => ApprovalSeatOutcome | Promise<ApprovalSeatOutcome>;
    /** Always-denied tools. */
    deny?: string[];
    /** Always-allowed tools (only meaningful with `denyByDefault`). */
    autoAllow?: string[];
    /** When true, deny anything not in `requireApproval` or `autoAllow`. Default false. */
    denyByDefault?: boolean;
    /**
     * Optional self-contained deadline (ms): if `approve` hasn't resolved by then, deny. Belt-and-
     * suspenders for **direct** callers — the Runner already races `check` against the task signal, so
     * a Runner-driven task is bounded regardless; this protects a caller invoking `check()` without a
     * signal. OMIT to wait indefinitely on `approve` — omission is the only spelling of "no deadline"
     * (CLS-A-9): a written-down `0` is the tightest deadline (deny at once), and a non-finite value is
     * refused at construction.
     */
    approvalTimeoutMs?: number;
}): NamedToolPolicy;
/**
 * Combine policies with the three-state fold **deny > ask > allow** (design/37): the WORST outcome
 * across all policies wins. A `deny` short-circuits (nothing can override it); an `ask` does NOT
 * short-circuit — scanning continues, because a later policy may still `deny` (which outranks ask).
 * With no deny, the first `ask` wins; with neither, `allow`.
 *
 * **An allow is STATED or not.** A layer that names the call (an allow rule, a list entry, an approver's yes, a mode's
 * release) says WHY on its allow (`decisionReason`); a bare allow is "no objection" — an empty table, a list the call is
 * not on, a layer that does not know the tool. The combined allow carries the FIRST stated reason in layer order, and a
 * statement binds the arguments its layer judged: a layer that rewrites the arguments voids every statement before it (its
 * own allow, if it states one, speaks for the new arguments). For a tool whose permission seat is `passthrough` an
 * unstated allow is not a permission — the gate puts the mode's question to it; for every other tool the two forms run alike.
 *
 * **Idempotency requirement:** because the fold continues past an `ask` to look for a later `deny`, a
 * policy's `check()` can be invoked even after an earlier policy returned `ask`. `check()` MUST be
 * idempotent — repeated calls with the same {@link ToolCallRequest} return the same decision and
 * accumulate no side effects (e.g. don't fire a duplicate approval notification on each call).
 */
export declare function combinePolicies(...policies: ToolPolicy[]): ToolPolicy;
/**
 * The shell tools whose `{ command }` arg a coarse command-name policy parses; other tools are out of scope. It is the
 * catalog's shell face ({@link shellFaceToolNames} — every definition whose `command` the command grammar judges, `Monitor`
 * among them: it runs an arbitrary shell `command` through the same env seam as Bash), spelled out for the wire; the
 * catalog's shell face is the truth, and a lockstep pin keeps the two one (a catalog row added or dropped turns it red):
 * the always-on parity gates keyed on it, the `Bash(...)` rules and the shell seat reach the same tools. EXPORTED: a
 * downstream governance layer reads it so its per-command ask-list gates the same tools core does.
 */
export declare const COARSE_SHELL_TOOLS: readonly ["Bash", "Monitor"];
/**
 * design/center §10 — a COARSE allow/deny gate keyed on the LEADING command NAME (`argv[0]`) of a single
 * simple shell command. It reuses the ONE shared simple-command parser
 * ({@link import("../tools/fs/index.js").parseLeadingCommandName}) — it does NOT re-implement argv[0] parsing
 * (a second parser would drift and open a circumvention). For a `bash`/`bash_readonly` call it extracts the leading
 * command name and:
 *  - if the name is in `deny` → `deny` (deny-wins, matching {@link createAllowDenyPolicy}; NOT last-match-wins,
 *    which would fight {@link combinePolicies}'s deny>ask>allow fold);
 *  - else if `allow` is provided and the name is NOT in it → `defaultAction` (`"ask"` default, or `"deny"`);
 *  - else → `allow`.
 * A command the parser cannot reduce to a single bare name (it has shell operators / a path-prefix / a leading
 * env-assignment) is treated by `defaultAction` (fail-toward-gate): such a command is exactly what would circumvent
 * an argv[0] filter, so it should not silently `allow`. NON-shell tools are out of scope → `allow` (this gate
 * only speaks about shell command names; compose it with other policies for the rest).
 *
 * **core ships NO opinionated command list** — only this mechanism. The actual `allow`/`deny` rule set is
 * injected by the profile/config/deployment.
 *
 * ⚠️ **NOT A SANDBOX — coarse filter / defense-in-depth only.** This matches `argv[0]` NAME and nothing else,
 * so the circumvention surface is large and the gate MUST NOT be relied on as containment:
 *   - command substitution / subshells (`$(curl …)`, backticks), pipes, redirects, `;`/`&&` chaining — all
 *     rejected as un-parseable here (→ `defaultAction`), so they don't sneak past `allow`, but that is a GATE
 *     not a guarantee of safety;
 *   - an indirection wrapper runs an arbitrary inner program under an allowlisted `argv[0]`: `sh -c '…'`,
 *     `env FOO=bar curl …`, `sudo rm …`, `timeout 5 curl …`, `xargs curl`, `find . -exec rm {} \;`,
 *     `python -c '…'`, `npm run <script>`, `git <subcommand>` — the inner action is INVISIBLE to an
 *     argv[0]-name filter. Allowlisting any such wrapper effectively allowlists everything it can launch.
 * Real isolation is the deployment's sandboxed `executionEnv` (a remote/E2B container with no egress, a
 * read-only FS, dropped capabilities). Use this only as a cheap first-line coarse filter layered over that.
 *
 * 🪤 **TRAP #1 — `TaskSpec.toolPolicy` OVERRIDES (does not merge) `RunnerDeps.toolPolicy`.** A consumer that
 * sets `TaskSpec.toolPolicy = createCoarseCommandNamePolicy(…)` directly **silently discards the deployment's
 * baseline policy** (the runner does `spec.toolPolicy ?? deps.toolPolicy`). To LAYER this on top of a baseline
 * you MUST compose explicitly: `combinePolicies(deploymentBaseline, createCoarseCommandNamePolicy(…))`
 * (deny-wins — both policies' denies still fire). {@link tightenTaskSpec} does this for you.
 *
 * On the full shell face a plain `ask` from this policy stays a plain ask: the shell seat's
 * `"maybe"` tier is the shape probe's instrument, not a risk label, so the park is the budgetable kind
 * (`human` under `durableApproval`) unless the probe mandated the call (an out-of-root read) or could not
 * answer. A command the shape tier's OWN tighten refuses (`rm -rf …` with no policy asking first) still parks
 * non-budgetable — the safety axis is judged per call by `safetyAxisOf`.
 */
export declare function createCoarseCommandNamePolicy(opts: {
    /** Command names (argv[0]) explicitly allowed. If provided, a name NOT listed falls to `defaultAction`. */
    allow?: string[];
    /** Command names (argv[0]) explicitly denied — `deny` always wins (checked before `allow`). */
    deny?: string[];
    /** What to do for a command not matched by `allow` (or un-parseable): `"ask"` (default) or `"deny"`. */
    defaultAction?: "ask" | "deny";
    /** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
    tools?: string[];
}): ToolPolicy;
/**
 * CC 220 "Unverifiable Deletion Target" parity (auto-mode rule; its text is in 220–245, gone from 2.1.250 on): ASK on a recursive+forced delete whose
 * target is a variable / substitution that cannot be resolved from the command itself — fail-closed in the
 * sense that with no live classifier/approver the ask degrades to deny (see {@link PermissionResult}'s
 * `ask` doc), never to allow. The guard reads ONE plan, the read station's (`removalReadingOf`,
 * removal-targets.ts): its firing conditions are `removalFindingsOf`'s there. The
 * message carries the CC-sanctioned remediation (re-run with the resolved LITERAL path written into the
 * command, or assign the variable in the same command) — it doubles as the `askMessage` a classifier sees.
 *
 * This used to be an unconditional `deny`, on the theory that the CC rule text forbids
 * auto-allow («must be explicitly approved … cannot be auto-allowed by a permission rule»).
 *
 * RB-341 (2026-07-29) re-checked that citation against the 220 corpus and corrected it in BOTH directions.
 * The RB-203 note claimed the phrase "traces to the 206 bundle; it does not appear anywhere in the 220
 * corpus for THIS rule" — that sentence was wrong and is retracted. The phrase has 8 hits in 220, and two
 * of them are this exact rule's own message text: `pretty220` 461365 (the `rm -rf $UNSET/*`
 * variable-expansion arm, verbatim «e.g. `rm -rf $UNSET/*` becomes `rm -rf /*`. This requires explicit
 * approval and cannot be auto-allowed by permission rules.») and 461444 (its command-substitution twin).
 * Five more are the sibling deterministic `rm`/`rmdir` arms (457337/457364/457379/457387/457402) and one is
 * an unrelated `find` rule (249564).
 *
 * The `ask` decision survives that correction, on a stronger anchor than the one RB-203 used:
 *
 *  - **It is not a deny in 220 either.** Every one of those messages is built by the same constructor
 *    (`T7e`, 457298), whose `behavior` is literally `"ask"` — a deterministic gate that ASKS. The phrase
 *    scopes to permission RULES: no static allow rule may clear the gate. sema's equivalent is
 *    `combinePolicies`'s deny > ask > allow fold — an `allow` from any composed policy loses to this ask,
 *    in either composition order (pinned in tool-policy.test.ts).
 *  - **A live classifier MAY clear it, in 220 as here.** `T7e` marks the reason `classifierApprovable:
 *    false`, but pairs it with `circuitBreaker: "dangerousRemoval"`, and 220's auto-mode gate diverts an
 *    ask away from the classifier only when `!classifierApprovable && !(circuitBreaker !== undefined &&
 *    isAutoModePermissionSurface(ctx))` (604049-604053). The circuit-breaker arm exempts exactly this
 *    family, so on the auto-mode surface the classifier DOES adjudicate it. That is consistent with the
 *    rule's listing in the classifier prompt's **SOFT BLOCK** bucket ("destructive or irreversible — User
 *    Intent Rule CAN clear these", 519698 / rule text 519726), escapable when the user's own words (or an
 *    agent proposal they affirmed) name the exact target — the same design sema's classifier prompt already
 *    implements verbatim (`auto-mode-prompt-assets.ts`, unchanged). A `classifierApprovable:false` reason
 *    WITHOUT a `circuitBreaker` is the shape that genuinely forbids the classifier — e.g. the design-agent
 *    consent ASK at 480999/481016, which takes the fallback-to-human path this rule does not. This rule is
 *    not that shape, so "follow `classifierApprovable:false` literally" would be STRICTER than CC.
 *
 * So a hard `deny` was wrong twice over: it short-circuited `combinePolicies`'s fold before a live
 * classifier ever ran, and CC's classifier does run here. `ask` reuses the existing generic per-ask
 * classifier arm (`hooks.ts`, `decision.action === "ask"`): a `block` verdict still denies
 * (`decisionReason: "classifier"`, fully audited), an `allow` verdict still requires a live classifier to
 * have actually cleared it, and "no classifier configured" still means deny.
 *
 * A review-confirmed P1 gap: the one piece of NEW plumbing this DOES need — `requiresRealApproval: true`
 * (see {@link PermissionResult}'s `ask` doc) — closes a real gap the first cut of this fix missed. A
 * deployment using the documented `onAsk: "allow"` circumvention mode (sandbox-is-the-boundary posture, the
 * shape a sandboxed autonomy container runs) would otherwise have this rule's `ask` rubber-stamped by that blanket
 * configuration with NO classifier ever consulted and NO judgment of any kind applied — silently WEAKER
 * than the unconditional deny it replaced, for exactly the deployments that opted into the most permissive
 * mode for OTHER, unrelated reasons. `requiresRealApproval` makes `resolveAsk` treat a blanket
 * `onAsk:"allow"` as equivalent to no approver at all for this specific ask (deny, same as headless) while
 * leaving every other path untouched: a live classifier (tried first, unaffected), a real approver
 * CALLBACK (genuine per-call judgment, even one that usually says yes), and "no classifier + no approver"
 * (already denied). Core wires this policy ALWAYS-ON in the runner's gate composition (prepare-task); it is
 * exported so deployments can also compose/test it directly.
 *
 * `requiresRealApproval` also turns out to be the correct sema analog of the one thing
 * `classifierApprovable: false` still buys in 220 that a `circuitBreaker` does NOT take back — 220's
 * bypassPermissions arm refuses to auto-allow an ask carrying `circuitBreaker === "dangerousRemoval"`
 * (603856-603869), i.e. a blanket MODE setting cannot clear it, only judgment can. That is exactly the
 * `onAsk:"allow"` refusal above, so no new hard bit is owed. The field's remaining 220 effects have no core
 * surface to attach to: it suppresses the "always allow / don't ask again" affordance in the CC permission
 * dialog (908517 / 909563) and re-attaches a matching user `ask` rule on the auto-mode surface (461507).
 * Core has neither a persisted always-allow surface nor rule re-attachment — noted for whichever UI layer
 * grows one.
 */
export declare function createUnverifiableDeletePolicy(opts?: {
    /** Extra variable names treated as well-known-safe roots, ADDED to the default set `TMPDIR`/`HOME`
     *  (`KNOWN_SAFE_ROOTS`, removal-targets.ts — `HOME` is a registered sema divergence from CC's temp-only carve-out).
     *  Each must be a shell identifier; `PWD` / `OLDPWD` are refused — their value is where the command stands, read off
     *  the working-directory thread, not a root the shell provides. A bad entry throws here. */
    safeVars?: readonly string[];
    /** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
    tools?: string[];
    removalSeat?: RemovalGuardSeat;
}): ToolPolicy;
export declare function createTranscriptIntegrityPolicy(opts?: {
    /** Transcript directories to protect. Default: `($AGENT_DATA_DIR ?? ~/.ai-agent)/sessions`. */
    sessionsDirs?: readonly string[];
    /** argv[0] allowlist for read-only access to the transcript dir (default {@link BASH_READONLY_DEFAULT_ALLOW}). */
    readAllow?: readonly string[];
    /** Override the shell tool names this gate parses (default {@link COARSE_SHELL_TOOLS}, the shell face). */
    tools?: string[];
}): ToolPolicy;
/**
 * Provenance of the DELEGATION whose child raised an ask (ruled 2026-08-04) — filled by the delegation
 * tool when it forwards the parent run's approver into a child, never by the child itself (it rides the
 * trusted `ToolExecuteContext.onAsk` seat, which no tool argument reaches).
 *
 * The INNERMOST (issuing) frame wins: a grandchild's ask carries the grandchild's own spawn facts, not
 * the intermediate one's — each delegation forwards an already-stamped request verbatim.
 *
 * Relation to the sibling source-identity fields on {@link AskRequest}: `fromSubagent`/`sourceAgentName`
 * answer "a delegated child asked, and this is its display identity" from the CHILD's own prepare; this
 * one answers "which delegation CALL it belongs to" from the SPAWNER's side — the correlation an approver
 * UI needs to attach the approval card to the Agent card the operator is already looking at.
 */
export interface AskDelegationProvenance {
    /** The spawning tool call in the PARENT run (`ToolExecuteContext.toolCallId` at spawn time) — the
     *  delegation's stable identity, minted by the engine. */
    readonly parentToolCallId: string;
    /** Delegation nesting depth of the ISSUING child: 1 = a child of the run that owns the approver,
     *  2 = its grandchild, … (as counted by the delegation-tool tree that spawned it). */
    readonly depth: number;
    /** The explicit spawn name / agent-definition name of the issuing child, when it has one.
     *  **UNTRUSTED-for-display** (the spawning model chooses `name`): redact + bound before rendering,
     *  never adjudication input — same posture as {@link AskRequest.sourceAgentName}. */
    readonly agentName?: string;
}
/** The structured context an `onAsk` approver receives for an `ask` decision (design/37).
 *  Lifecycle (design/384): the wait this request fronts can be released by the run's abort AND by a
 *  turn-level interrupt; after that release the engine no longer awaits the approver, so anything an
 *  approver retains off this object it must release itself, keyed on its `signal` argument's abort
 *  (see {@link OnAsk} for the full detached-settlement contract). */
export interface AskRequest {
    toolName: string;
    /** The allow-layer members that MATCHED this call and could not clear the ask (see {@link PermissionResult}'s ask arm),
     *  one sanitized record per member — so the card renders "your rule is alive, just outranked" from data; the same
     *  account is rendered once onto {@link message} at this request's mint. A DISPLAY value, not an identity channel:
     *  a stored rule's identity travels on {@link AskRuleEvidence.personalRuleDots}. */
    allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
    /** WHY the reversibility probe did not clear this call — the account of the tighten the
     *  card was showing WITHOUT until now (the gate knew the cause and dropped it at this seam, leaving the
     *  approver the action but not the reason it is being asked about). Present only for a `"maybe"`-tier
     *  tighten whose probe supplied a cause; the durable park route carries the same string as
     *  `RiskDescriptor.probeReason`. Neutralized + length-capped by the gate. UNTRUSTED-for-display,
     *  never adjudication input. */
    readonly probeReason?: string;
    /** The STRUCTURED cause behind the tighten — a machine-readable code plus the operand
     *  families as arrays with honest totals, so this surface renders its OWN sentence rather than one the
     *  engine had to write before the check's edge cases were known. See
     *  {@link import("./checkpoint-store.js").ProbeCause}, whose contract is that the ARRAY is the
     *  structure: render the entries as data, never re-derive structure by splitting or joining them.
     *  The durable park route carries the same value as `RiskDescriptor.probeCause`. */
    readonly probeCause?: import("./checkpoint-store.js").ProbeCause;
    /**
     * The ask-facing face of {@link import("./types.js").ReversibilityVerdict.readRootCandidate}, which owns
     * the meaning, the absence reading and the spelling contract: PRESENT ⇒ declaring this one directory
     * clears THIS ask, so a card can offer "add it and stop being asked" beside "yes, this once". Filled at
     * the synchronous MINT station by the tighten that ran the probe — the three INHERITED-lane sites present
     * an ancestor's decision from upstream of it and carry none, and the durable park row has NO twin: a
     * parked out-of-root ask renders the confirmation alone.
     */
    readonly readRootCandidate?: import("./types.js").ReadRootGrantCandidate;
    /** design/252 G-2: the RULE-PROVENANCE evidence behind this ask — the org snapshot revision and the
     *  matched rules' own identity keys, each present as a value or as a named absence (see
     *  {@link AskRuleEvidence}). Present on EVERY ask the engine mints, so a consumer never has to tell a
     *  missing field from a field that means something: the gate's own mint site fills it from what its
     *  layers did, and the three INHERITED-lane sites — which present an ANCESTOR policy's decision, from
     *  upstream of both lanes — fill it with an all-`"not_adjudicated"` record rather than a fabricated
     *  revision. Optional on the TYPE only because a deployment may invoke its approver directly.
     *  RECONCILIATION metadata, never adjudication input. */
    readonly ruleEvidence?: AskRuleEvidence;
    toolCallId: string;
    /** The (post-rewrite) args the tool would run with. */
    args: unknown;
    /** the tool's approval display projection of {@link args} (AgentTool.approvalPreview),
     *  clamped + control-char-sanitized by the gate. **UNTRUSTED, ADVISORY display metadata**: render
     *  with contextual escaping and show {@link args} alongside — the preview can misrepresent the
     *  executable action and never replaces args. Never adjudication input. */
    readonly preview?: unknown;
    /**
     * #457 ② — PRESENT ⇔ this tool DECLARES an approval preview and the gate could not present a usable
     * one, naming which way it failed: `"oversize"` (produced, but past the 16KiB display bound — the
     * truncation stub still rides {@link preview}) or `"unavailable"` (the projection threw, or its value
     * has no JSON serialization, so nothing was produced at all).
     *
     * ABSENT is the ordinary world and covers three cases that are NOT withholdings: the tool declares no
     * preview, the projection declined to speak for these args, and the projection succeeded. A surface
     * must therefore read presence, never absence: absence is not a claim that the preview is faithful.
     *
     * A DISCLOSURE, not a verdict and not a transform — the same posture as {@link hasBidiControls}. Core
     * refuses nothing and narrows nothing on account of it: {@link args} and {@link boundInputHash} are
     * always carried, so unlike a surface whose only content IS the projection, a core approval card can
     * still show the person exactly what will run. What the bit buys is the honest sentence a card owes
     * when it is rendering less than the tool meant it to — and the ground for a surface that chooses to
     * withhold its own PERSISTENT "allow and stop asking me this" affordance while the action cannot be
     * reviewed as intended (upstream's form: a withheld preview vetoes the standing row, leaving one-time
     * approval only). Core deliberately does NOT apply that veto to {@link ruleOffers} itself — core's
     * offers speak for shell COMMAND text, which rides `args` and is shown whatever the preview did.
     *
     * Filled at the ask MINT sites that resolve a preview (the synchronous gate leg), not at `resolveAsk`
     * — the chokepoint receives an already-built request and has no tool table to project from. The
     * durable park leg's twin is `PendingAction.tool_approval.previewWithheld` on the row, judged over
     * that row's own fidelity-projected args snapshot; the two faces can honestly differ exactly as the
     * bidi bit's two faces can.
     */
    readonly previewWithheld?: "oversize" | "unavailable";
    /** Canonical digest of {@link args} as presented in THIS ask, computed by the engine's one
     *  `boundInputHashOf` — the same digest a durable park binds its checkpoint to, so an aggregating
     *  approver can reconcile a synchronous ask row against a parked checkpoint row for the same call
     *  by `(toolCallId, hash)` equality. RECONCILIATION metadata only, never adjudication input: it
     *  repeats for two asks over identical args, and an `updatedInput` edit round re-mints it over the
     *  edited args. Filled at the `resolveAsk` chokepoint (present for every wired-approver call, so a
     *  mint site added later is covered by construction); optional on the type because a deployment may
     *  invoke its approver function directly. */
    readonly boundInputHash?: string;
    /** A call to the engine's `Skill` tool: the hash of {@link boundGrants} — the digest a parked approval of the same card is judged by, so a live card reconciles against its parked row by `(toolCallId, boundGrantDigest)` and an answer is remembered by "this tool + this binding". Present exactly on that call (whoever asked, however the question was worded); every other card carries {@link boundGrantAbsence} instead; `message` renders the same lists narrowed to what releases in the leg's mode. Reconciliation metadata, never adjudication input. */
    readonly boundGrantDigest?: string;
    /** The value {@link boundGrantDigest} hashes: `{ skill, allowedTools, disallowedTools }` in canonical spelling (de-duplicated, sorted), `skill: null` when no loaded skill is named; a fresh copy per ask; same presence. Display metadata (deployment text — render with contextual escaping). */
    readonly boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
    /** Why this card carries no {@link boundGrantDigest} — every card the engine mints carries exactly one of the two: `not_bindable` (the call is not an activation of the engine's `Skill` tool, so it binds no grant and an answer may be remembered by the tool) or `unreadable` (a Skill card whose binding cannot be read — said by a stored row's reader; a live card always has its digest). The same factory mints both members; the park row and the inbox projection carry the same member. Reconciliation metadata, never adjudication input. */
    readonly boundGrantAbsence?: import("./skill-grant-binding.js").BoundGrantAbsence;
    /**
     * design/179 §4, re-formed by design/375 §3.1 (BREAKING — this seat REPLACES the retired
     * `ruleSuggestions` key, which is never emitted again; the new name is the safe failure posture for
     * the hard-cut window: a consumer that has not upgraded simply renders no "don't ask again" row,
     * fail toward asking, instead of mis-rendering a consent surface through its old parser) — the
     * persistable allow-rule OFFERS this exact call could be covered by, so a surface can render
     * "allow, and stop asking me this" with something concrete behind it. Present only when a persisted
     * allow-rule lane is armed AND the call is one the lane can speak for (a redirection or a
     * substitution yields NO offer, which is the honest answer rather than an option that would be
     * refused on redemption) AND the ask is one a persisted rule could actually clear — a mandated ask
     * (operator shellGate:"always", the tool's own egress/irreversibility marks, a
     * `requiresRealApproval` demand, or — #502, the one PER-CALL member of that family — a demotion the
     * call's own reversibility probe declared structural, which for the built-in shell probe means a read
     * outside the session's allowed directories) and an ask carrying {@link allowRuleShadowed} offer
     * none.
     *
     * CONTRACT — see {@link import("./permission-rule-model.js").RuleOffer} for the closed union, the
     * per-element parse/degrade rules and the original-index-keying obligation. Array order is display
     * order: at most 2 offers, the whole-string EXACT single always index 0 when present, the BATCH
     * offer (a compound's per-segment conjunction — choosing it is one yes to all of its 1..5 rules at
     * once) always last. Selection indices (`confirmRuleApproval`'s `selectedOffer`) are keyed
     * against this order; redemption TICKETS are NOT — they key on the consent record's flat
     * CANDIDATE list (a batch's members each carry their own ticket), so a chosen offer is redeemed
     * via `redeemRuleBatch`, never by indexing a ticket array with an offer index. The durable park
     * row carries the same array under the same contract.
     *
     * ADVISORY display metadata, never adjudication input, and never a rule by itself: minting one is a
     * separate act that goes through the approval-record protocol, so a surface that ignores this field
     * changes nothing about what is allowed. The AUTHORITATIVE render source for a consent card is the
     * PREPARED approval record's own offers; this member is the ask-time projection, which may drift
     * from it across a coverage change — harmlessly, since the record is what gets confirmed.
     */
    readonly ruleOffers?: readonly import("./permission-rule-model.js").RuleOffer[];
    /**
     * design/382 §2.4 (adversarial-review r3, additive) — the RELATIVE-CD RESOLUTION BASE the offers
     * above were minted with: the live tracked working directory at adjudication time (the RB-108
     * value; equal to the task root until an observable `cd` moves the tracker). Present only beside
     * {@link ruleOffers} when the run has a tracked cwd. A consumer preparing the AUTHORITATIVE
     * consent record threads it as `prepareCardApproval`'s `execCwd`, so the record re-mints the SAME
     * directory member this projection displayed — without it, a moved tracker would make the record
     * resolve `cd ./x` from the task root and persist a rule for a directory the command never
     * enters. Display/reconstruction context only, never adjudication input.
     */
    readonly execCwd?: string;
    /**
     * #490 修② — WHY {@link ruleOffers} is absent, when the rule-offer lane is in play and has nothing
     * to give. A CLOSED set, mutually exclusive with {@link ruleOffers} (never both, never neither once
     * the lane is in play):
     *  · `"mandated"` — no persisted rule could ever clear this ask: an org/governance
     *    `requiresRealApproval` demand, an operator `shellGate:"always"` or the tool's own
     *    egress/irreversibility marks, a hook-raised ask, an ask whose authority is an ANCESTOR's
     *    approver rather than this task's rule lane, or (#502) a demotion this CALL's reversibility
     *    probe declared structural — for the built-in shell probe, a read outside the directories this
     *    session declared. A surface must NOT point at rule-writing here — "allow rules silence the
     *    classifier's questions, never a mandated one", so a rule minted in answer to this line would
     *    never take effect where it was minted. Whether a DIRECTORY grant is worth pointing at is not this
     *    seat's to say: it is answered per ask by {@link readRootCandidate}, which is present exactly when
     *    widening the session's read directories would clear this one and absent for every other member of
     *    the family — so a surface reads that seat and never infers an answer from this word;
     *  · `"allow_outranked"` — an allow-layer member already matched this call and this question outranks it
     *    ({@link allowRuleShadowed} says which): another allow rule would be outranked the same way;
     *  · `"ask_rule_matched"` — an explicit `ask` rule matched: it keeps asking while it stands;
     *  · `"lane_cannot_speak"` — the lane is armed and this ask IS clearable, but the offer lane
     *    produced no rule for this command. Usually because the grammar has no text for it (a
     *    substitution, a subshell, a backgrounded process, or a simple command carrying a redirection —
     *    a compound is judged per segment, so this arm means EVERY segment was unspeakable, since one
     *    dirty segment among clean ones yields a batch instead). It also covers the narrower case where a
     *    text EXISTS but the card's own array-order contract declines to present it alone (a simple
     *    command whose exact form the validator refuses offers nothing rather than promoting a lone
     *    prefix to index 0 — see `suggestRulesForCommand`). The seat says "this card has no rule to
     *    offer you", which is true of both; it does not claim the grammar is empty, and a surface must
     *    not word it as though no rule could ever be written for the command.
     *
     * ABSENT is not a claim, exactly as {@link previewWithheld}'s absence is not: it also covers the
     * ordinary "there are offers" case and the three STRUCTURAL doors that answer with silence on
     * purpose (no rule lane wired, a tool the lane does not speak for, a task that cannot hold a rule at
     * all) — a reason line on every ask of a deployment that never had the feature would be noise about
     * a lane that does not exist. Read presence, never absence.
     *
     * ADVISORY disclosure metadata, never adjudication input: it changes nothing about what is allowed,
     * and a surface that ignores it renders exactly the card it rendered before. The durable park row
     * carries the same seat under the same contract, filled by the same one factory.
     */
    readonly ruleOffersAbsence?: import("./ask-origin.js").RuleOffersAbsence;
    /** The human/model-readable prompt explaining what needs approval. */
    message: string;
    /** Runner-filled source identity (design S1d §1): the end-user {@link AskRequest} issuing task's
     *  `principal` (design/62). An AGGREGATING `onAsk` (one approver fanning over N delegated workers)
     *  attributes asks per-user by this. Worker/tool cannot set it (not a tool arg). Undefined ⇒ no principal.
     *  A non-aggregating `onAsk` simply ignores it (additive, no-op). `readonly`: Runner-populated metadata,
     *  not a caller/worker-settable field. Populated ONLY on the synchronous `resolveAsk`/`onAsk` path — a
     *  durable suspend/checkpoint flow does NOT invoke `onAsk` on the suspend side, so v1 ask-aggregation
     *  requires `checkpointStore` unset (design S1d §1.4 P-a). */
    readonly principal?: string;
    /** Runner-filled source-task identity (= the issuing task's session id). An aggregating `onAsk` uses it
     *  to tell which worker an ask came from (nested grandchildren each carry their own session id).
     *  **worker-unforgeable for Runner-created delegated subagents** (a child never sets sessionId — the
     *  Runner mints it); a top-level caller MAY continue its own session id via `TaskSpec.sessionId`, so this
     *  is NOT a global "never forgeable" run id — aggregation only aggregates delegated workers, the context
     *  where it holds (design S1d §1.3). `readonly` + same sync-path-only / P-a caveat as `principal`.
     *  Undefined ⇒ no source task id. **NOT a subagent discriminator** (the HOST's own asks
     *  carry it too — it is always the issuing task's session id); "from a background agent" attribution
     *  keys on {@link fromSubagent}, never on this field's presence. */
    readonly sourceTaskId?: string;
    /** design/153 §1 (RB-39②) — Runner-filled: present (`true`) exactly when the gate raising this ask
     *  belongs to a DELEGATED SUBAGENT (the prepareTask `isSubagent` fact — trusted `internals.parentTaskId`
     *  presence; a child cannot self-declare or suppress it, internals never ride a TaskSpec). Undefined ⇒ the
     *  host / a top-level task. THE explicit "from a background agent" discriminator (replaces presence-
     *  or equality-heuristics on {@link sourceTaskId}). `readonly`, sync-path-only, S1d posture. */
    readonly fromSubagent?: true;
    /** design/153 §1 — Runner-filled: the issuing task's display identity (a subagent's explicit agent
     *  name, else its agent-type; undefined for the host and unnamed children). **UNTRUSTED-for-display**
     *  (the spawning model chooses names): redact + bound before rendering; never adjudication input.
     *  `readonly`, same posture as {@link fromSubagent}. */
    readonly sourceAgentName?: string;
    /** Runner-filled: present (`true`) exactly when the issuing run is a **delegated child** — ANY
     *  spawn leg, FORK INCLUDED (the trusted `RunInternals.isDelegatedChild` fact, set unconditionally
     *  by every core spawn path). It selects which REFUSAL POSTURE a human deny speaks back to this
     *  run's model: a parent-thread run is told to stop and wait for the user (its transcript has a
     *  user turn coming), a delegated child is told to adapt or report the limitation. A fork is
     *  deliberately IN: it inherits the parent's authority (design/110 — which is why the RB-330
     *  non-fork facet of `effectiveDelegationFacts`, serving the authority/context faces, excludes it), but
     *  its interaction contract is one-shot — "report once and stop … no waiting for the user"
     *  (FORK_DIRECTIVE_FRAME) — so a stop-and-wait refusal would instruct it to do the impossible
     *  (confirmed). NOT the same fact as {@link fromSubagent} either: that is
     *  the design/153 attribution discriminator (`parentTaskId` presence), which a directly-started
     *  workflow's children lack even though refusals must still speak the child posture to them.
     *  `readonly`, trusted (internals never ride a TaskSpec), sync-path-only like its siblings.
     *
     *  Key division, at a glance (the two child-ish keys serve DIFFERENT faces — neither implies the other):
     *  - `fromSubagent` — ATTRIBUTION: "a nameable parent task issued this" (`parentTaskId` presence);
     *    drives display/identity surfaces.
     *  - `isDelegatedChild` — REFUSAL POSTURE: "no user turn ever lands in this run's transcript";
     *    drives which deny text the model is told (stop-and-wait vs adapt-or-report), forks included. */
    readonly isDelegatedChild?: true;
    /** Carried from the originating {@link PermissionResult}'s ask
     *  variant of the same name: `true` ⇒ {@link resolveAsk} must not let an allow NO PERSON JUDGED clear
     *  this ask — a blanket `onAsk: "allow"`, or an approver's allow it reports as the deployment's policy
     *  (`settledBy: "policy"`) (see that field's doc). `readonly`, filled by the gate from the decision, never
     *  a caller/worker-settable field. */
    readonly requiresRealApproval?: boolean;
    /**
     * PRESENCE ONLY, and a FACT ABOUT THIS ASK: no persisted allow rule and no STANDING ANSWER — a release
     * remembered from an earlier call, at any scope, a session included — can retire this question; it is
     * answered only for THIS call, by an answerer (see the ask-arm member of the same name for who may). ABSENT is not the inverse claim: a person's own explicit `ask` rule and a
     * matching allow rule the lane already refused both leave it absent and still block rule clearance
     * ({@link ruleOffersAbsence} words them `ask_rule_matched` / `allow_outranked`). The wire twin of the
     * {@link PermissionResult} ask-arm member of the same name, minted once by the engine at the gate's
     * origin stamp and copied here by the four mint stations. The DOORS are enumerated once, at
     * {@link import("./ask-origin.js").ruleClearanceOf}: the tool seat's own egress / irreversibility
     * marks, an operator's per-call shell doctrine, the read station's structural demotion or a probe
     * that could not answer, a write the protection judge matched or could not resolve, governance
     * ({@link requiresRealApproval}), a hook's screening authority, and an ask an ANCESTOR's approver
     * decides. Not a door: a person's own explicit `ask` rule, which {@link ruleOffersAbsence} words
     * `ask_rule_matched` — a rule they wrote is speaking, and a posture answering it crosses no mandate.
     *
     * WHY a surface must read THIS member and not its neighbour. {@link ruleOffersAbsence} answers what
     * the RULE-OFFER LANE has to give, and that lane is structurally silent for any tool it does not
     * speak for, any deployment that wired no rule store and any task that cannot hold a rule — its
     * absence is not a claim (its own doc says so). Reading it as "not mandated" made two read faces of
     * one command disagree: the same out-of-root read asked as a mandated question on the shell tool and
     * as an apparently ordinary one on the monitor tool. This member is a property of the QUESTION,
     * present on every ask that carries one, whatever lane is wired.
     *
     * WHAT IT DOES NOT SAY: that no answerer may answer. An approver — a person, a hook, a deployment's
     * blanket `onAsk: "allow"` seat — resolves a mandated ask normally; {@link requiresRealApproval} is
     * the stronger, separate demand and is the one the engine's blanket seat refuses. A surface whose
     * POSTURE seat declines to auto-answer mandated cards reads this member to know which they are.
     * ABSENT is not the opposite claim on an older engine: a consumer that must tell "this engine mints
     * no such member" from "this ask is ordinary" reads the engine version, not this seat. Filled by the
     * gate, never caller/worker-settable; tightening-only.
     */
    readonly mandated?: true;
    /** The ask was minted by the deployment's own governance layer (the {@link PermissionResult} ask arm's member of the same
     *  name, carried through every request mint and onto the park row): a person's question under every mode — no persisted
     *  rule, standing answer or mode release retires it. A host approval seat that settles asks from a session grant reads
     *  it to keep its hands off. Filled by the gate, never caller/worker-settable. */
    readonly authoredBy?: "deployment";
    /** WHICH word of the closed {@link import("./ask-origin.js").PERSISTED_RULE_MANDATES} set {@link mandated} stands
     *  on — present ⇔ that table word is the bit's WHOLE reason (`probe_mandate` = a read outside the session's
     *  directories). ABSENT beside `mandated: true` = another door holds too (governance, a hook, an ancestor's
     *  approver, a policy's own claim) — absence never names a word. Stamped by the gate beside
     *  the bit, copied by the four mint stations; the park row carries the same word. Never adjudication input. */
    readonly mandate?: import("./ask-origin.js").PersistedRuleMandate;
    /** Present ⇔ this ask is the classifier DENIAL-LIMIT fallback (see the
     *  {@link PermissionResult} ask-arm member of the same name): the counts that tripped the bound and
     *  this ask's own auto-deny window. Two readers: a card renders it as a countdown; {@link resolveAsk}
     *  arms its deadline from `autoDenyAfterMs` (> 0 ⇒ an unanswered function approver auto-denies at
     *  that deadline — the `denial_limit_window_expired` settlement on the gate record). Filled
     *  by the gate from the decision, never a caller/worker-settable field. This is the ARMED type: the
     *  decision's member carries the counts alone, and the only two ways to reach this one are the route
     *  stations — `AutoModeDenialTracker.armTimedWindow` (a hand-out to a live function approver, the one
     *  writer of a non-zero window) and `unarmedWindow` (every route that cannot count down). An adapter
     *  that forwards a decision's member here does not compile, which is the point: the window is the
     *  route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
    readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
    /** The {@link PermissionResult} ask-arm member of the same name (class `classifier_no_ruling`), carried by the four mint
     *  stations; the `message`'s second paragraph says the same for a client that does not read this. */
    readonly classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
    /** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
     *  member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
     *  The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
     *  derive it from the ancestor's decision under the station facts (a self-declared value on that
     *  decision is never read). An approver renders the card by it; a durable park persists the same word
     *  on its row (`PendingAction.tool_approval.origin`). Filled by the mint station, never written by a
     *  caller/worker directly — a policy can only STEER the derivation toward a stricter class by
     *  self-declaring one of its tighten-only inputs (`decisionReason: "hook"`, `matchedAskRule`,
     *  `denialLimitFallback`), never toward `policy`; a card that attributes an ask to a hook or a rule is
     *  therefore reading the deployment's own claim about its own ask, which is the direction that cannot
     *  loosen anything. */
    readonly origin?: import("./ask-origin.js").AskOrigin;
    /** The ask's CLASS: the first member of {@link classes} in {@link import("./ask-origin.js").ASK_CLASSES} order — the
     *  row key of {@link import("./ask-origin.js").MODE_ARBITRATION} a permission mode reads. Filled by every station that
     *  fills {@link origin}, from the same classification the gate's lanes arbitrated this ask by; never written by a
     *  caller or worker (a policy's self-declared value is replaced at the stamp), and a policy can move it only through
     *  the tighten-only inputs the classification reads. ABSENT = unclassified: a request from a station that fills no
     *  {@link origin} (the shared-artifact read confirmation), or a request built by a deployment itself. An approver
     *  that answers by class treats absence as a class it will not answer. */
    readonly askClass?: import("./ask-origin.js").AskClass;
    /** Every class this ask belongs to, in {@link import("./ask-origin.js").ASK_CLASSES} order, `classes[0] ===
     *  askClass`. One ask can carry several (an org verdict beside a hook's question); an approver that answers by class
     *  reads the whole set — a class it will not answer anywhere in it is a question it does not answer. Present exactly
     *  when {@link askClass} is. */
    readonly classes?: readonly import("./ask-origin.js").AskClass[];
    /** Engine-judged risk axes of the action awaiting approval, for the human decision surface: a
     *  coarse `requiresRealApproval` cannot tell "cannot be undone" from "data leaves the machine",
     *  and those call for different scrutiny. Filled by the gate from the SAME resolved axes the
     *  safety tightens read (tool declarations + caller `toolAxes` folds) — never derived from tool
     *  names, never caller/worker-settable. `irreversible: true` covers both the declared-`"always"`
     *  and the `"maybe"` (unproven-reversible) tiers. ABSENT — the whole object or either member —
     *  means the engine did not judge that axis: render it as unjudged, never fold absence to `false`. */
    readonly riskAxes?: {
        readonly irreversible?: boolean;
        readonly egress?: boolean;
    };
    /** Which approval card this ask renders under — the closed set `TOOL_APPROVAL_CARDS`, `generic` when the tool declares
     *  none; the same word the parked gate and the pending row carry for the same call. Display routing only: the card is
     *  untrusted-for-display input (a tool may declare any non-reserved card), so a specialised card validates the call's
     *  arguments itself. A word outside the set reads as `generic`. ABSENT: an ask the host built itself, or an engine
     *  older than this member. */
    readonly approvalCard?: import("./tool-face.js").ToolApprovalCard;
    /** ruled 2026-08-04 — present exactly when this ask was raised inside a DELEGATED child whose approver
     *  is the ancestor run's (the `ToolExecuteContext.onAsk` seat the delegation tool forwards). Additive:
     *  an approver that ignores it is unaffected; a durable park/suspend flow never invokes `onAsk` at all,
     *  so this — like `principal`/`sourceTaskId` — exists only on the synchronous resolution path.
     *  See {@link AskDelegationProvenance} for the trust posture of each member. */
    readonly delegation?: AskDelegationProvenance;
    /**
     * PRESENCE ONLY: the command/argument text this ask is about carries at least one DIRECTIONAL
     * format control ({@link BIDI_CONTROL_RE} — the closed set: U+061C, U+200E/U+200F, the embeddings
     * and overrides U+202A–U+202E, the isolates U+2066–U+2069). Those characters change nothing about
     * what EXECUTES and everything about what a terminal or a card SHOWS: bytes that run
     * `rm -rf /` can display as a benign line, which is precisely the deception an approval surface
     * cannot afford to render unannotated.
     *
     * A WARNING BIT, not a verdict and not a transform. Core does NOT strip, reorder, refuse or rewrite
     * anything on account of it — {@link args} is delivered byte-identical either way, and the decision
     * stays the approver's. What a surface owes the person is a visible "the text below can display
     * differently from what it runs" and a rendering that neutralizes the controls (see
     * {@link import("./permission-rule-model.js").renderUntrustedCommandText} for the display baseline).
     *
     * Read it as PRESENCE-or-nothing: the field is either `true` or ABSENT. It is never written `false`,
     * because absence means "not detected", which honestly covers both "clean" and "the bounded scan did
     * not reach it" (a pathologically deep or huge argument graph stops at the scan budget) — a `false`
     * would claim a proof the scan does not offer. Judged over the EXECUTING argument snapshot and the
     * tool's own {@link preview} projection, i.e. the payload; {@link message} is deliberately out of
     * scope (engine/policy-composed prose, not the thing that runs).
     *
     * Filled at the `resolveAsk` chokepoint, beside {@link boundInputHash} — every wired `onAsk`
     * approver call crosses it, so an ask mint site added later is covered by construction. Optional
     * on the type because a deployment may invoke its approver function directly.
     *
     * SCOPE, stated so absence is not read as a clean bill on the other routes: this is the
     * SYNCHRONOUS `onAsk` ask, like `principal`/`sourceTaskId` — the closure claim is over AskRequest
     * mint sites, not over every approval callback. The other two human-decision routes:
     * ① a durable park/suspend never invokes `onAsk` and never mints an AskRequest — its twin of this
     * bit is the PERSISTED `PendingAction.tool_approval.hasBidiControls` on the parked row (judged at
     * the park mint over the row's own fidelity-projected args snapshot + preview, so the two faces
     * can honestly differ on a shape-changing backend), projected to the inbox as
     * `CheckpointSummary.hasBidiControls` — see those fields in `checkpoint-store.ts` for the reading
     * contract; absence there is still "not detected", never a clean bill, and an inbox rendering
     * `RiskDescriptor` still owes its own screen (or the display baseline above);
     * ② {@link createApprovalPolicy}'s
     * `approve` seat resolves in-place inside the policy and mints no AskRequest at all — its
     * callback receives a bare `ToolCallRequest` (no bit, no {@link boundInputHash}, none of this
     * surface), so a deployment doing HITL through that seat must render through
     * {@link import("./permission-rule-model.js").renderUntrustedCommandText} or run its own screen.
     */
    readonly hasBidiControls?: true;
}
/**
 * How an `ask` decision is resolved when a policy/hook requests human confirmation (design/37):
 *  - `"deny"` (and the default when omitted) — **headless auto-deny**: no approver, so `ask` resolves
 *    deterministically to `deny` with a model-readable reason. The safe default for stateless automation.
 *  - `"allow"` — auto-approve every `ask` (e.g. a trusted batch run).
 *  - a function — await an operator's decision (true=allow, false=deny). `signal` fires when the task
 *    aborts AND (design/384, when the gate threaded a per-call signal) when the asking turn is
 *    interrupted — a bare user halt or a steer-now boundary cut releases the ask exactly as the run's
 *    own end does; race your wait against it so an unanswered ask is released at the deadline, not hung.
 *    Since design/384 the engine no longer waits for you after the signal fires: `resolveAsk` races its
 *    await against the signal and settles the gate as an abort-family deny on its own. Your still-pending
 *    promise is DETACHED — a resolve the released wait never consumes is you releasing your wait, never
 *    a verdict (an unconsumed approval is disclosed as a notice, not honored — no claim is made about
 *    which settled first); an unconsumed reject is disclosed on the deployment's error face, never
 *    silently swallowed. Cleanup of anything you hold for the wait (the request
 *    payload, the signal, your own timers) is YOUR responsibility, keyed on the signal's abort.
 *    G1 three-value: the function may also return `"unavailable"` — an affirmative "no operator
 *    is reachable for THIS ask right now" (judged PER-ASK inside the callback, not at wire time). It is a
 *    ROUTING verdict, not a decision: the gate re-routes the ask onto the durable park leg (same behavior
 *    as if no live approver were wired — checkpoint + suspend, the 1.293 fallback), and when no park
 *    facility is armed the ask falls to the headless auto-deny (fail-closed). This lets a durable
 *    deployment wire ONE callback that serves live synchronous three-choice cards while attached AND
 *    still lands asks in the durable approval inbox when the operator goes away.
 *
 * **Contract:** the function MUST be fail-closed (a throw → deny, surfacing the real cause), idempotent
 * for a given request, and tolerant of being called after the task signal aborted. `"unavailable"` MUST
 * only be returned when NO operator interaction happened (a partial interaction that then reported
 * unavailable could double-resolve the same approval once the park re-surfaces it).
 */
export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal) => AskOutcome | Promise<AskOutcome>);
/**
 * What an `onAsk` approver may return (object arm):
 *  - `boolean` — plain verdict (true=allow, false=deny). STRICT fold (RB-482 #17, server bridge
 *    zero-ripple confirmed): only the literal `true` approves; an out-of-contract truthy from an
 *    untyped bridge is a fail-closed deny naming the defect (the historical truthy leniency was a
 *    fail-open on the security face with no live producer).
 *  - `"unavailable"` — the G1 per-ask routing verdict (see {@link OnAsk}).
 *  - `{ allow, updatedInput?, settledBy?, reason? }` — verdict PLUS an operator EDIT of the presented args
 *    (whole-replacement form, e.g. ctrl+g "edit script in $EDITOR"): the human approved a MODIFIED
 *    action, and executing the un-edited args would betray that consent. `allow` folds STRICTLY
 *    (`allow === true`) — an object arm is a deliberate caller, so no truthy leniency — and
 *    `updatedInput` is honored only on allow (a deny edit never executes anything). The edit is
 *    applied as the resolved decision's own rewrite: the gate re-validates it against the tool
 *    schema exactly like a hook/policy rewrite.
 *
 * #114② (2026-08-09) — `settledBy` on the object arm is the SYNCHRONOUS leg's only channel for saying
 * what ended the wait. This leg's window belongs to the HOST (`onAsk` is a callback the deployment
 * owns; the engine starts no timer for it — with the ONE #548 exception below), so a host that timed
 * its own approval card out could
 * previously only report a plain `false`, which the engine correctly recorded as `"human"` — a person
 * refusing. The three words the host may self-report:
 *  - `"human"` — a person answered (identical to omitting the field);
 *  - `"timeout"` — the host's own approval window elapsed with no answer;
 *  - `"policy"` — the deployment's OWN policy answered, either way: a machine decided, not a person (an
 *    unattended lane answering by class, a headless lane whose approver seat exists but can show nobody a
 *    prompt). Its allow settles `policy_allowed`, its refusal `policy_refused`. Spelled `true` / `false`, such
 *    an answer filed a PERSON's record, and its refusal told the model the USER did not want to proceed and to
 *    wait for their direction — a sentence about a person, in a run where none is coming; with the word the
 *    refusal reads {@link policyRefusalMessage} instead, and the note travels as the policy's words. A
 *    policy's allow is the same allow a person's is — the edit, the `readRootGrant` and every lane after the
 *    gate treat it alike — and only the record names a different party, with ONE exception that is about the
 *    party itself: an ask carrying {@link AskRequest.requiresRealApproval} demands a person, and a policy's
 *    allow is refused there exactly as the blanket `"allow"` seat is (`blanket_allow_refused`). OMITTING the
 *    field still reads as `"human"`, so an unrevised host is unchanged.
 * `"timeout"` is legal ONLY with `allow: false` — an elapsed window is a wait nobody answered, and it cannot be
 * what APPROVED an action — so `{allow: true, settledBy: "timeout"}` is refused fail-closed.
 * `"aborted"` is deliberately NOT accepted here — that word names the engine's OWN fail-closed ends
 * (abort, throw, unavailable, out-of-contract value), each already stamped at its own arm, and a
 * self-reported one would let a host relabel its refusal as an engine failure.
 *
 * The ONE ask on this leg the engine DOES time: a classifier denial-limit fallback (the ask
 * carrying `denialLimitFallback` with a non-zero `autoDenyAfterMs`). Its window is core's, because
 * the fallback exists to bound a classifier that would otherwise deny without end. When it elapses
 * the approver's promise is DETACHED (design/384: the wait is released, not cancelled — an approver
 * that answers afterwards is not consulted, and an `allow` it returns late becomes the
 * `task.late_approval` notice), and the engine's own deny settles as `denial_limit_window_expired`
 * (the host's own elapsed window settles as `approval_window_expired` with `who.party: "host"`).
 *
 * `reason` — the SYNCHRONOUS leg's seat for a model-readable reason attached to a deny, the exact
 * counterpart of the durable leg's `ResumeOutcome` `policy_ask` `reason` ("Model-readable reason
 * attached to a deny"): the words the decider typed with their "no" (an approval card's rejection
 * input), relayed to the model so it can follow the person's direction instead of guessing. In-place
 * conditions, all screened at {@link resolveAsk}:
 *  - meaningful ONLY on a deny (`allow: false`); on an allow the seat is NEVER READ (accepted and
 *    unused, literally: a junk value or a throwing accessor riding an approval cannot
 *    destroy the approval). Same net posture as the durable leg, where a `reason` on an `allow` row
 *    is never rendered;
 *  - UNTRUSTED input (a human's free text riding a callback): it reaches the model only inside a
 *    `delimitUntrusted` fence, the same containment the durable resume's reviewer note gets, and
 *    bounded to `REVIEWER_NOTE_MAX_BODY` code points (an oversized note arrives truncated with an
 *    ellipsis, never verbatim and never silently dropped — both legs share the one bound);
 *  - on a deny, a present non-string is refused loudly (fail-closed deny naming the defect — the
 *    domain the durable leg enforces pre-CAS for the same field), a THROWING read is a fail-closed
 *    deny naming the true cause (never a raw rejection out of the gate), and the empty string reads
 *    as absent (truthiness, the durable consumer's own read).
 *
 * `approver` (design/252 G-7) — the ATTRIBUTION seat: the identifier the approval channel reports for
 * the party that ended this wait. Core does not authenticate it, compare it, or read it back to decide
 * anything; it transcribes it onto the settlement observation next to `settledBy`, so an audit that can
 * already say "a person ended this wait" can also say which one, without core growing an identity
 * surface it deliberately does not have. Read the two together: `settledBy:"policy"` with an `approver` names
 * WHICH of the deployment's answering seats decided (a closed word the host owns, never a person).
 * Screened, not trusted (see {@link screenApproverAttribution}): a non-string, an over-long value or one
 * carrying control characters is a loud fail-closed refusal at {@link resolveAsk}, the same posture this
 * seam takes for every other out-of-contract value — and the same reason, since a defective attribution
 * that were quietly dropped would leave a settlement looking unattributed rather than misreported. It is
 * carried on the DECIDER arms (a person's or a policy's allow or refusal) and never on an end the engine
 * produced (an abort, a throw, an unclonable edit): nobody approved those. A host's own elapsed window has
 * no attribution seat on its record (`approval_window_expired` names the host as a party, not a seat).
 *
 * `readRootGrant` — "yes, and add this directory": the card's {@link AskRequest.readRootCandidate} `dir`, sent back
 * byte for byte (`shell.read_boundary.grant_candidate_is_the_grant`). Beside `allow: true` on an ask carrying that
 * candidate, THIS run's read directories include it from then on (read faces only; `covers: "exact"` = that one
 * path) — announced `approval.read_root_granted`. Anything else changes nothing and is announced
 * `approval.read_root_grant_rejected`; the verdict it rode stands. A seat with no card (a policy's `approve`)
 * refuses it. Keeping the directory beyond this run is the deployment's (`TaskSpec.additionalReadDirectories`).
 */
export type AskOutcome = boolean | "unavailable" | {
    allow: boolean;
    updatedInput?: unknown;
    settledBy?: "human" | "timeout" | "policy";
    reason?: string;
    approver?: string;
    readRootGrant?: string;
};
/**
 * ruled 2026-08-04 — forward an approver into a delegated child, stamping every ask it raises with the
 * delegation it came from ({@link AskDelegationProvenance}).
 *
 * Only the FUNCTION form is wrapped: `"allow"`/`"deny"` are policy SETTINGS, not approvers, and
 * {@link resolveAsk} reads them structurally — wrapping would invent an approver where the deployment
 * declared a blanket mode.
 *
 * An ask that ALREADY carries provenance passes through untouched: chained down a delegation tree, the
 * INNERMOST (actually issuing) frame is the one the approver must see, and each outer hop would
 * otherwise overwrite it with its own shallower one.
 */
export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDelegationProvenance): OnAsk;
/**
 * ruled 2026-08-04 — the APPROVER BEHIND a possibly-forwarded `onAsk`: the identity that answers
 * "is this the same human/UI?" across delegation hops.
 *
 * Consumers compare approvers by function reference to decide whether one consultation's consent covers
 * another (the RB-40 duplicate-frame collapse). A provenance wrapper is a different function object but
 * the same decision-maker, so a bare `===` would ask one operator the same question twice for one tool
 * call. Never used for authority (it grants nothing); only for "these two consultations are the same
 * consultation". A non-wrapper value (including `"allow"`/`"deny"`/undefined) is its own identity.
 */
export declare function askApproverIdentity(onAsk: unknown): unknown;
/**
 * Is this `onAsk` seat a LIVE APPROVER — a person the run can actually reach?
 *
 * Only a function is. A blanket `"allow"`/`"deny"` string is a policy setting (the same reading the
 * interaction-posture door gives it), and an absent seat is nobody at all; neither can answer an ask, so
 * neither may outrank a durable park.
 *
 * ONE definition on purpose. Two sites must agree on this — the run's own sync-first decline, and the
 * mandate frozen into the chain its children inherit — and when they were two hand-written booleans they
 * drifted: the decline narrowed to the function test while the freeze kept testing mere presence, and
 * each still read correctly on its own, so nothing pointed at the pair. A blanket seat was therefore not
 * an approver for the parent's asks and an approver for its children's. Declared here, in the vocabulary
 * beside {@link askApproverIdentity}, because the two sites are now two prepare phases (the inherited-gate
 * fold and the wiring station) plus the orchestrator, and each reaches DOWN for the one definition.
 */
export declare function isLiveApproverSeat(onAsk: OnAsk | undefined): boolean;
/** Where a durable-approval MANDATE on a leg comes from — the closed set of its two sources. */
export declare const DURABLE_MANDATE_SOURCES: readonly ["force_durable_gate", "durable_approval"];
export type DurableMandateSource = (typeof DURABLE_MANDATE_SOURCES)[number];
/**
 * Is every interactive permission ask of this leg MANDATED to gate durably — and by which source?
 * `force_durable_gate`: the per-principal control-plane entitlement, in force regardless of any seat.
 * `durable_approval`: the task's own opt-in with NO live approver seat (a blanket string or nobody at all
 * cannot answer an ask, so the park is the only honest resolution). A live seat beside the opt-in is not a
 * mandate: the leg's asks — and its children's, at the same frozen seat — resolve there.
 *
 * ONE definition on purpose, like {@link isLiveApproverSeat} beside it: the chain a parent freezes for its
 * children and the loud "mandated but no store" disclosure at the wiring station used to derive the same
 * fact with two hand-written conditions, and the second one read only the entitlement — a task that opted
 * in and had no store degraded to the live chain in silence.
 */
export declare function durableMandateSourceOf(args: {
    forceDurableGate: boolean;
    durableApprovalDeclared: boolean;
    onAsk: OnAsk | undefined;
}): DurableMandateSource | undefined;
/**
 * Has this leg DECLARED a durable park opt-in, from EITHER source (the per-principal
 * entitlement or the task's own `durableApproval`) — with NO live-seat narrowing. Deliberately
 * NOT {@link durableMandateSourceOf}: that one asks "is every ask MANDATED to gate durably" (a
 * live approver seat beside a mere `durableApproval` opt-in defeats the mandate), while this one
 * asks "does this leg have a durable park FACILITY at all" — the question-face mount decision and
 * the inherited-mandate float both need the bare declaration, seat or no seat, and had each spelled
 * `forceDurableGate === true || durableApprovalDeclared` by hand (two call sites, `prepare-
 * question-face.ts` and `prepare-policy-chain.ts`, one drifting from the other silently if either
 * grows a third source or a typo'd operator).
 */
export declare function durableParkOptInDeclared(args: {
    forceDurableGate: boolean;
    durableApprovalDeclared: boolean;
}): boolean;
/** EXACT structural clone for the approval boundary (alias isolation) — fail-closed, never lossy,
 *  never identity: a JSON round-trip would silently DROP unclonable members (presented ≠ executing =
 *  consent misrepresentation) and an identity fallback would hand the approver the live executing
 *  object (in-place mutation + TOCTOU). Tool args are model-JSON in practice, so failure is exotic —
 *  and exotic means deny, not leak.
 *
 *  Exported for the DURABLE half of the same boundary (the runner's park mint): a checkpoint row holds
 *  the args a human will approve and a resume will execute, so it needs exactly this snapshot — and
 *  exactly this refusal when the value cannot be one. Not part of the package's public surface
 *  (index.ts does not re-export it); one implementation so the two halves cannot drift on what
 *  "safely presentable" means. */
export declare function tryCloneArgs<T>(v: T): {
    ok: true;
    value: T;
} | {
    ok: false;
    reason: string;
    cause?: unknown;
};
/** The ONE mint of a refusal's reported args: the deny carrying the args its refusing station or layer JUDGED, which the gate's
 *  exit reports (`finalInput`, the deny observer's `input`); a deny already carrying the args its own layers judged (a composed
 *  chain's, nested ones included) keeps them. Read by the gate's lanes and by {@link combinePolicies}. Only a deny is changed. */
export declare function refusalOf<D extends PermissionResult>(deny: D, judged: unknown): D;
/**
 * A PreToolUse hook's `ask` folded over a verdict by `deny > ask > allow` — the ONE law for the gate's first screening and for
 * an approved edit's re-screen. A deny stands. Over an allow, the hook's own ask, RECONSTRUCTED rather than spread (its message,
 * its real-approval demand, its rewrite) under the hook provenance the engine stamps. Over an ask, that ask keeps every member,
 * takes the hook provenance and the hook's real-approval demand, and its message carries the hook's beside its own — a person
 * clearing one ask that stands for two gates sees both reasons.
 */
export declare function foldHookAsk(verdict: Exclude<PermissionResult, {
    action: "deny";
}>, hookAsk: Extract<PermissionResult, {
    action: "ask";
}>): Extract<PermissionResult, {
    action: "ask";
}>;
export declare function foldHookAsk(verdict: PermissionResult, hookAsk: Extract<PermissionResult, {
    action: "ask";
}>): PermissionResult;
/**
 * THE args of one tool-gate pass — what every judge of the call reads and what the call runs with: ONE engine-owned
 * value, the {@link tryCloneArgs} snapshot of what was handed in, deep-frozen. The gate mints it at its entry from the
 * model's args, and every value that replaces it comes through here too (a hook's or a policy's rewrite at the decision
 * receipt, a person's edit at the approver's answer), so the args of a pass are always such a value. Because it cannot
 * change, it is handed to every judge as it is — a policy, the org and stored-rule lanes, the classifier, an extension's
 * check, the person's card — and none of them can make what runs differ from what was judged: an in-place write throws
 * (a rewrite is an answer's `updatedInput`). An owned value handed back is itself; a primitive is immutable as it is.
 * A value the snapshot refuses is refused the same way (exotic args are a refusal, never a shared reference).
 */
export declare function ownArgs<T>(v: T, root?: string): {
    ok: true;
    value: T;
} | {
    ok: false;
    reason: string;
    cause?: unknown;
};
/** A call's argument reading (the loop's: the tool's preparation, the schema's coercions) whose every value is engine-owned
 *  like a pass's args ({@link ownArgs}) — the gate's face seat and the resumed call's belt seat read through this one. */
export declare function ownedReading(read: (args: unknown) => unknown): (args: unknown) => unknown;
/**
 * A label for a value that was THROWN at us, safe to put in a fail-closed refusal. Never coerces: `String(x)`
 * throws on a null-prototype object or a untrusted `toString`/`Symbol.toPrimitive`, and the thrower here is
 * deployment-supplied (a getter inside the args can throw any object at `structuredClone`, an approver can
 * throw one at the gate). A coercion failure inside the catch would escape `resolveAsk` as a raw TypeError —
 * turning a classified fail-closed DENY into an unclassified crash, i.e. the failing input escaping the very
 * classification the refusal exists to give.
 *
 * The result is CONTAINED, not just described. Every caller interpolates it into a refusal that reaches
 * the model inside a `<system-reminder>` frame, and the text is not ours: a `DataCloneError` renders the
 * offending value INTO its message (`structuredClone` of a function yields that function's SOURCE), and
 * a deployment approver throws whatever it likes. A rewrite handing over a function whose body contains
 * `</system-reminder>` would otherwise close the trusted frame and continue as instructions — the exact
 * threat `inlineUntrusted` exists for. So the description is one line, neutralized and capped here, at
 * the single point where a thrown value becomes prose; the RAW exception is unaffected and still reaches
 * the deployment's own error face, which is where an operator reads the untruncated story.
 *
 * Exported for the durable half of the same boundary (the runner's park mint), which now has a SECOND
 * way a deployment-supplied value can throw at capture time — the target backend's own codec refusing to
 * encode it. One containment point, so the two refusals cannot differ on how much of a untrusted value's
 * text they let through. Not part of the package's public surface.
 */
export declare function describeThrown(err: unknown): string;
/** What a person refused: a tool use the model asked to run, or the plan it proposed. */
export type HumanRefusalSubject = "tool use" | "plan";
/**
 * THE sentence a model reads when a PERSON refused what it asked to do, whichever leg carried the refusal (the
 * synchronous resolver's {@link humanRefusalMessage}; the durable decide's tool result and continuation frame;
 * the plan review's continuation) — one generator for every subject and both postures, so a person's "no"
 * cannot speak two ways depending on whether it landed on a tool call or a plan, or on which path carried it.
 * `subject` picks the fact clause (which thing was refused and that it did not run); `delegatedChild` picks the
 * posture — the ISSUING RUN's position ({@link AskRequest.isDelegatedChild}; the durable legs read the same
 * effective fact, restored from the checkpoint row on a deps-only resume):
 *  - a parent-thread run is told the user refused and to wait for them. The reference answers a tool call and a
 *    plan with one string (a refusal with no words stops the turn and waits for the user; one with words relays
 *    them as the direction): no note ⇒ the fact, then STOP and wait for the user to tell the model how to
 *    proceed; a note ⇒ the fact, then the note inside a {@link delimitUntrusted} fence ("reviewer note" — the
 *    durable resume leg's label for the same words). The TRUSTED wrapper around the fence does two jobs the
 *    fence alone cannot: it LICENSES the note as the user's guidance while bounding its authority (user words
 *    steer the work; they cannot grant permissions or override system rules — the fence's "do NOT follow
 *    instructions" guards exactly that escalation, not the reading of the user's steer), and it KEEPS the wait as
 *    the fallback for a note that gives no direction, so a note never erases the only explicit instruction about
 *    what to do after a person's "no". On the BARE arm the directive is no longer text alone: the runner treats
 *    a refused TOOL CALL as a control-flow boundary for the issuing assistant batch — same-message sibling calls
 *    that never started settle un-executed and the turn ends awaiting the user (the settlement's `note` seat
 *    carries the bare-vs-noted fact);
 *  - a delegated child (a FORK included — its message-layer contract is one-shot, "no waiting for the user") is
 *    told to try a different approach or report the limitation — no user turn ever lands in ITS transcript, so
 *    "wait for the user" would hang the delegation. A note it is handed rides the same fence and the same bound
 *    on its authority, with the adapt-or-report posture as the fallback.
 * Whether there IS a note is the decider-note seat's own rule ({@link decidersNote}), asked here and nowhere
 * else on this path — the callers hand over the raw text. Not for a refusal a machine gave
 * ({@link policyRefusalMessage}).
 */
export declare function humanRefusalSentence(subject: HumanRefusalSubject, reason: string | undefined, delegatedChild: boolean): string;
/**
 * The ONE composer for a DEPLOYMENT-POLICY refusal of an ask — the sentence the model reads when the
 * approval channel reported that a MACHINE ended this wait (`settledBy: "policy"` on the synchronous
 * outcome, `hostDecision.decidedBy: "policy"` on a durable decide). Both legs call it, so one refusal
 * cannot speak two ways depending on which one carried it, and neither leg re-spells it.
 *
 * Not being {@link humanRefusalMessage} is the point of its existence. A person's "no" is the user's
 * direction, and the posture that follows it is "stop and wait for the user"; a policy's is a standing
 * capability of this run, where no user turn is coming — so this sentence names the deployment as the
 * refuser, says interactive approval is what the action needs and this context has none, and directs the
 * model at the work it CAN still do. A note rides a fence labelled as the POLICY's words, never licensed
 * as the user's guidance, because no user gave it. `askMessage` is the ask's own prompt where the caller
 * has one (the synchronous leg does); a durable decide answers a call parked in an earlier leg and has
 * none, and the head then simply names the call.
 */
export declare function policyRefusalMessage(toolName: string, askMessage: string | undefined, note: string | undefined): string;
/**
 * A {@link resolveAsk} result: always a TERMINAL `allow`/`deny` (never `ask`). `approverUnavailable`
 * is the out-of-band G1 three-value marker: the live approver returned `"unavailable"` for this ask —
 * the carried `deny` is the FAIL-CLOSED FALLBACK, and the gate may instead re-route the ask onto the
 * durable park leg (the only consumer; everywhere else the result reads as a plain deny).
 */
export type ResolvedAsk = (Extract<PermissionResult, {
    action: "allow";
}> & {
    /** The EXACT data snapshot the approver was shown (the `structuredClone` of the presented args). On
     *  a plain approval (no edit) the gate EXECUTES this snapshot, not the original object: a stateful
     *  getter or an external alias must not be able to make the executed action differ from the
     *  approved one (shown == executed, by construction). Set only on the function-approver path
     *  (string modes present nothing). */
    presentedInput?: unknown;
    approverUnavailable?: never;
    /** The answer's `readRootGrant` ({@link AskOutcome}), screened against the request's candidate. */
    readRootGrant?: ReadRootGrantScreen;
}) | (Extract<PermissionResult, {
    action: "deny";
}> & {
    presentedInput?: unknown;
    approverUnavailable?: true;
    /** A grant riding a refusal: always `rejected` (`on_refusal`). */
    readRootGrant?: ReadRootGrantScreen;
});
/** What one resolver ARM composes: the resolution plus the settlement it ended the wait with — composed at the arm, every deny
 *  naming one (there is no unclassified refusal), an allow only when a DECIDER approved (a blanket `"allow"` asked nobody). The
 *  exported {@link resolveAsk} turns the settlement into the resolution's `attestation`. Not re-exported from `src/index.ts`. */
export type ResolverArmResult = (Extract<ResolvedAsk, {
    action: "allow";
}> & {
    settlement?: Settlement;
}) | (Extract<ResolvedAsk, {
    action: "deny";
}> & {
    settlement: Settlement;
});
/**
 * The DIRECTIONAL format controls, as a closed set (see {@link AskRequest.hasBidiControls}):
 * ALM (U+061C), LRM/RLM (U+200E/U+200F), the embeddings and overrides (U+202A–U+202E) and the
 * isolates (U+2066–U+2069). These reorder what a reader SEES without changing a byte of what runs.
 *
 * Why the whole directional family and not only the four the reordering demos use: the bit is a
 * WARNING, and for a warning the safe direction is to fire on a member a reader would have to
 * enumerate correctly to be safe from. ALM is in for that reason, though it is the least-abused one.
 *
 * Deliberately NARROWER than `permission-rule-model`'s `CONTROL_CHARS_RE` and than
 * {@link APPROVER_REFUSED_CHARS_RE}: those are REFUSAL/ESCAPE classes covering every control and
 * zero-width character (a rule text or an identifier has no use for any of them). This one answers a
 * different question — "can this display as something other than what it is" — and a zero-width joiner
 * or a soft hyphen does not reorder anything, so folding them in would make the bit fire on ordinary
 * text and mean nothing where it matters.
 */
export declare const BIDI_CONTROL_CHARS = "\\u061C\\u200E\\u200F\\u202A-\\u202E\\u2066-\\u2069";
/**
 * Does any string reachable in `value` carry a {@link BIDI_CONTROL_RE} member? Bounded, cycle-safe,
 * and never throwing — every caller is on an approval/projection path, where a scan that failed must
 * degrade to "not detected" rather than turn an ask into an error (the `deliverEngineNotice`
 * posture: a derived disclosure must never become the failure of the thing it describes).
 *
 * OBJECT KEYS are scanned as well as values: a key is displayed text too, and an argument object
 * `{ "cmd<RLO>": … }` renders its own reordering in any card that prints the shape (the marker
 * is spelled out here on purpose — a literal one in this comment would reorder the comment).
 *
 * `limits` parameterizes the two budgets WITHOUT opening a second reader: the character class and
 * the traversal stay defined exactly here, only the allowance varies by caller. Omitted ⇒ the mint
 * tier above (the resolveAsk chokepoint and the durable park mint, which scan one call's own
 * payload). A per-row projection caller (the checkpoint summary backfill, which may face
 * deployment-written rows of arbitrary size on a many-row read) passes its own smaller tier so one
 * row's work is bounded regardless of the row's provenance. Both members are required when the
 * object is given — a half-specified tier would silently mix two tiers in one scan.
 *
 * Exported as the ONE judge of the question: a host that renders or sanitizes text next to an approval
 * card answers "does this carry a directional control" with this function, and composes a wider class
 * (control characters, line separators) from {@link BIDI_CONTROL_CHARS} rather than restating the members.
 * The allowances above are part of the answer, not an implementation detail: a payload past them reads
 * "not detected", exactly as the bit on the card does.
 */
export declare function carriesBidiControls(value: unknown, limits?: {
    maxNodes: number;
    maxChars: number;
}): boolean;
/**
 * Resolve an `ask` decision to a terminal `allow`/`deny` via {@link OnAsk}. Centralizes the headless
 * auto-deny default, fail-closed error handling, and stable deny reasons so every ask site is
 * consistent. Never returns `ask`.
 *
 * Every arm that ENDED A WAIT composes the {@link import("./gate-outcome.js").Settlement} for it, at the
 * arm: the approver's own verdicts settle as a person's unless the outcome names another word on
 * {@link AskOutcome.settledBy} (its own window, its own policy), and the fail-closed ends around them
 * (abort, a throw, an "unavailable" report, an unclonable presentation, an out-of-contract value) settle
 * as the engine's own nobody-ended family; a policy's allow on a `requiresRealApproval` ask is refused as the
 * blanket seat's is ({@link unjudgedAllowRefused}). The three POSTURE arms at the top — blanket `"allow"`, its
 * `requiresRealApproval` refusal, and the headless auto-deny — name no person and no window on purpose:
 * nobody was asked, so there is no wait for anyone to have ended, and `decisionReason: "mode"` is already
 * the honest word for what produced them.
 */
export declare function resolveAsk(req: AskRequest, onAsk: OnAsk | undefined, signal?: AbortSignal, 
/** design/384 — observer for a DETACHED approver's settlement, consulted only after the race
 *  arm released the wait on `signal`'s abort: `"approve"` = a value that reads as an approval
 *  went unconsumed by the released wait (a person said yes to an action that will never run —
 *  the caller turns this into its own disclosure, e.g. an engine notice; no arrival-order claim
 *  is made); `"error"` = the detached promise
 *  rejected, or its members threw on the post-release read (the caller's error face, never
 *  silence). An unconsumed NON-approve resolve is the approver releasing its wait — no verdict,
 *  no record, this observer is not consulted. Optional and advisory: absent, the detached
 *  settlement is still swallow-guarded (no unhandled rejection), it just leaves no trace. */
onLateSettlement?: (late: {
    kind: "approve";
} | {
    kind: "error";
    error: unknown;
}) => void): Promise<ResolvedAsk>;