import type { ToolCallRequest } from "./tool-policy.js";
/**
 * WHY an `unavailable` verdict could not run — the closed set behind the verdict arm, the model-facing
 * sentence's parenthetical and the `auto_mode.classified` trace frame's `cause`, so the three spell one
 * word (the deny's structural face carries only the round's FORM, `GateDisposition.cause`). `error` = the model leg
 * threw or rejected (a route failure at classify time reads here too — the derived-route pre-flight fell
 * back BEFORE any decide, so there is no separate word for it; so does a question that never ASSEMBLED,
 * whether its build threw or outran its cap — nothing was dispatched either way); `timeout` = a rung's
 * DISPATCH cap.
 */
export declare const AUTO_MODE_UNAVAILABLE_CAUSES: readonly ["error", "timeout"];
export type AutoModeUnavailableCause = (typeof AUTO_MODE_UNAVAILABLE_CAUSES)[number];
/** Whether a value is a member of the unavailable-cause set — the screen a row reader applies to a stored word. */
export declare function isAutoModeUnavailableCause(v: unknown): v is AutoModeUnavailableCause;
/**
 * HOW a reply failed to carry a verdict — the closed set behind `parse_error.form`. `missing` = the reader found
 * NO answer (empty text, prose, an answer only inside a thinking block); `conflict` = it found TWO distinct
 * `<block>` answers (raw or thinking-stripped). The split decides who gets a SECOND ATTEMPT at the same
 * question, on both seats that offer one: a missing answer is asked for again (CC 2.1.259 `Vrr` re-asks an
 * unparseable reply) and handed on to the next candidate ({@link rungDidNotRule}); a conflict gets neither —
 * it carries a `yes`, and any second attempt that answered `no` would erase it. One rule, two seats: whatever
 * a re-ask may not overwrite, a later rung may not overwrite either. Not on any wire face; the trace frame and
 * the gate record keep the one word `parse_error`.
 */
export declare const AUTO_MODE_PARSE_FAILURES: readonly ["missing", "conflict"];
export type AutoModeParseFailure = (typeof AUTO_MODE_PARSE_FAILURES)[number];
/** Whether a value is a member of the parse-failure set. */
export declare function isAutoModeParseFailure(v: unknown): v is AutoModeParseFailure;
/**
 * WHY a classification moved DOWN the candidate ladder — the closed set behind
 * {@link AutoModeClassifierRound}'s `fallback.cause`, i.e. the ways a candidate produces NO RULING
 * ({@link rungDidNotRule}). Two words are the unavailable causes verbatim (the leg threw / rejected; the
 * round-trip cap elapsed); `parse_error` is the candidate that ANSWERED with no answer in it at all (the
 * `missing` form). A ruling is not a member, and neither is a CONTRADICTORY reply (`conflict`): the ladder
 * stops at a rung that settled the classification, whichever way it settled it.
 */
export declare const AUTO_MODE_FALLBACK_CAUSES: readonly ["error", "timeout", "parse_error"];
export type AutoModeFallbackCause = (typeof AUTO_MODE_FALLBACK_CAUSES)[number];
/** Whether a value is a member of the fallback-cause set — the screen a row reader applies to a stored word. */
export declare function isAutoModeFallbackCause(v: unknown): v is AutoModeFallbackCause;
/**
 * The deployment's answer to "what happens when the classifier seat does not rule?"
 * (`RunnerDeps.autoMode.classifierFallback`) — the closed set behind the candidate ladder's LENGTH:
 * `main` = [classifier seat, the run's main model]; `none` = [classifier seat].
 */
export declare const AUTO_MODE_CLASSIFIER_FALLBACKS: readonly ["main", "none"];
export type AutoModeClassifierFallback = (typeof AUTO_MODE_CLASSIFIER_FALLBACKS)[number];
/** Whether a value is a member of the classifier-fallback set. */
export declare function isAutoModeClassifierFallback(v: unknown): v is AutoModeClassifierFallback;
/** Read the knob: absent ⇒ the default `main`; a member ⇒ itself; ANYTHING ELSE ⇒ a loud refusal carrying
 *  `config.auto_mode_classifier_fallback_invalid`. A typo'd knob must not read as the default: `"non"` would
 *  keep paying for the rung it meant to turn off. */
export declare function autoModeClassifierFallbackOf(v: unknown): AutoModeClassifierFallback;
/** Whether a present budget value is one the knob takes: a finite number of milliseconds in [1, 2147483647]. */
export declare function isAutoModeTimeoutMs(v: unknown): v is number;
/** Read the per-level budget knob (`RunnerDeps.autoMode.timeoutMs` / `AutoModeDeciderOptions.timeoutMs`):
 *  absent ⇒ {@link AUTO_MODE_DEFAULT_TIMEOUT_MS}; a finite number in [1, 2147483647] ⇒ itself; ANYTHING ELSE
 *  (NaN, ±Infinity, `0`, a negative, a value a host timer would fire at once, a string, an object) ⇒ a loud
 *  refusal carrying `config.auto_mode_timeout_invalid`. There is no OFF spelling: the budget bounds how long a
 *  permission gate waits, and a gate with no bound is not a setting this knob offers. */
export declare function autoModeTimeoutMsOf(v: unknown): number;
/** The classifier's verdict over one pending `ask`. Every arm may carry the classification's model
 *  attribution ({@link AutoModeClassifierRound}), present iff the decider was wired with a candidate ladder. */
export type AutoModeVerdict = ({
    kind: "allow";
} & AutoModeRoundAttributed)
/** `category` is the matched BLOCK rule name (207 output contract); `reason` starts with `[Rule Name]`. */
 | ({
    kind: "block";
    category: string;
    reason: string;
} & AutoModeRoundAttributed)
/** The classifier could not run: the model leg threw / rejected, or ran past the cap. NOT a decision — the
 *  gate denies and says so ({@link classifierUnavailableDenyMessage}). */
 | ({
    kind: "unavailable";
    cause: AutoModeUnavailableCause;
} & AutoModeRoundAttributed)
/** The model responded but not in the `<block>…` contract shape. Handled as a BLOCK with the CC
 *  parse-failure sentence ({@link classifierParseFailureDenyMessage}), counted by the denial limit. `form`
 *  says WHICH way the reply failed ({@link AutoModeParseFailure}) and decides who may try the question a
 *  second time: the classify leg re-asks a `missing` reply once and the ladder hands it to the next rung;
 *  a `conflict` gets neither ({@link rungDidNotRule}). */
 | ({
    kind: "parse_error";
    raw: string;
    form: AutoModeParseFailure;
} & AutoModeRoundAttributed);
/** The member every verdict arm shares: WHICH MODEL this classification asked and which one answered. */
export interface AutoModeRoundAttributed {
    /** Absent when the decider was given no candidate ladder (a hand-built decider, the cross-process rebuild's
     *  single fresh leg): it ran on whatever model the leg is bound to and can vouch for no name. Never faked. */
    readonly classifier?: AutoModeClassifierRound;
}
/**
 * WHICH MODEL answered one classification — the record a walk of the CANDIDATE LADDER
 * ({@link AutoModeDeciderOptions.candidates}) hands its consumers: `modelRequested` = the ladder's FIRST rung
 * (the seat the classification was for); `modelUsed` = the rung whose answer the gate acted on, and where NO
 * candidate ruled, the last rung ATTEMPTED (stated, not implied by absence); `fallback` = present exactly
 * when the ladder MOVED to reach `modelUsed` — which rung it left, which it arrived at, and why
 * ({@link AutoModeFallbackCause}); absent = the first rung answered.
 */
export interface AutoModeClassifierRound {
    readonly modelRequested: string;
    readonly modelUsed: string;
    readonly fallback?: {
        readonly from: string;
        readonly to: string;
        readonly cause: AutoModeFallbackCause;
    };
}
/**
 * The word for "this classification was NEVER PUT TO A MODEL" — the failure is in assembling the QUESTION (the
 * transcript window every rung shares), not in a rung's answer. The decider raises it for its own
 * {@link AutoModeDeciderOptions.assemble} seat; a hook that owns assembly throws an error carrying this code.
 *
 * WHY IT EXISTS. The ladder advances on "the rung produced no ruling", and a hook that rejects looks exactly
 * like that. But a classification whose prompt could not be built fails IDENTICALLY on every rung (the build is
 * one shared promise, by contract — "the same prompt, re-asked" is literal), so without this word the walk
 * records a fallback and a deny sentence naming two models for a classification in which NO MODEL WAS EVER
 * DISPATCHED. The verdict is unchanged (fail-closed `unavailable`); what this changes is that the record stops
 * attributing it to models that were never asked.
 *
 * Carried as a `code` on the thrown error rather than a class so any classify hook — including one this
 * package never constructed — can raise it without importing a constructor.
 *
 * A build that OUTRUNS its cap is the same fact and takes the same exit — see
 * {@link AutoModeDeciderOptions.assemble}, the seat that bounds it.
 */
export declare const AUTO_MODE_ROUND_NOT_ASKED_CODE = "auto_mode.round_not_asked";
/** Mint the marker above around whatever failed, keeping the original as `cause` for the host's own logs.
 *  TOTAL by construction: the failure it wraps is an arbitrary value, and an `instanceof` or a `.message` read
 *  on a revoked proxy (or a throwing getter) THROWS — which would replace the marker with an ordinary error and
 *  hand the walk back the attribution this whole seat exists to remove. So the detail is a guarded read that
 *  must already be a string, and the total describer otherwise. */
export declare function autoModeRoundNotAsked(reason: unknown): Error & {
    code: string;
};
/**
 * Why a rung did not settle the classification, or `undefined` when it SETTLED it and the ladder stops — the
 * ONE predicate behind the advance (there is no `if (fallback)` arm anywhere).
 *
 * A rung settles the classification two ways: it RULES (`allow` / `block`), or it answers with a
 * CONTRADICTION (`parse_error` of form `conflict` — two distinct `<block>` answers in one reply). The second
 * is not a ruling and stays the fail-closed deny it is; what it is NOT is an unanswered question. It holds a
 * `yes`, so asking the SAME question again — of the same model as a re-ask, or of the next rung as a fallback
 * — can only overwrite that `yes` with a `no` and allow a call the classifier already objected to. One rule
 * for both second attempts: {@link AUTO_MODE_PARSE_FAILURES} names the form, the leg reads it for the re-ask
 * and this predicate reads it for the walk. Only a reply with NO answer in it (`missing`) moves the ladder.
 *
 * @contract auto_mode.ladder.advances_on_no_evidence — the candidate ladder advances on exactly one
 * condition, and it is the ABSENCE of evidence: the rung's leg threw or rejected, its round-trip cap elapsed,
 * or its reply carried no answer at all. A rung that RULED and a rung whose reply CONTRADICTED itself both
 * settle the classification where they stand — the contradiction as the fail-closed `parse_error` deny it
 * already is, attributed to that rung and disclosing no fallback — so no later candidate is ever asked a
 * question an earlier one has already answered `yes` to.
 */
export declare function rungDidNotRule(v: AutoModeVerdict): AutoModeFallbackCause | undefined;
/**
 * The model-facing deny text for an `unavailable` verdict — CC 2.1.250 `x1t`, with the parenthetical `EIt`
 * spells for the causes this engine distinguishes (a wall-clock / connection timeout reads " (timed out)";
 * an unknown kind reads nothing). The subject is the classifier itself rather than a model id: without a
 * candidate ladder the decider cannot vouch for a name. One writer for every deny site. ROOT CAUSE, when
 * `attribution.fallback` is present: the sentence names BOTH rungs that failed to answer; a ladder that did
 * NOT move keeps the CC text byte for byte.
 */
export declare function classifierUnavailableDenyMessage(toolName: string, cause: AutoModeUnavailableCause, attribution?: AutoModeClassifierRound): string;
/**
 * The text an ask carries to a person when the classifier did not rule on it ({@link rungDidNotRule}) and the leg has an
 * approval seat (the gate's classifier station and the inherited station): the ask's own text first — the card still
 * says what is being asked — then why auto mode is asking at all, read off the same cause the ask's `classifierNoRuling`
 * member carries (a client that does not read the member reads this). One writer for both stations.
 */
export declare function classifierNoRulingAskMessage(askText: string, cause: AutoModeFallbackCause): string;
/**
 * The model-facing deny text for a `parse_error` verdict — CC 2.1.259 `i7t` stem ("Auto mode could not evaluate
 * this action and is blocking it for safety"; 2.1.250 `Ure`): the classifier answered, but not with a verdict,
 * and auto mode blocks what it could not evaluate. With a candidate ladder wired the tail NAMES the model whose
 * reply carried no verdict (`attribution.modelUsed`) — a declared departure from upstream, whose card names no
 * model: the person reading this card is the one who can change the catalog, and on a single-model deployment
 * the name is the whole diagnosis. Without attribution the sentence is unchanged. The stem is a stable prefix
 * either way; one writer for both deny sites.
 */
export declare function classifierParseFailureDenyMessage(attribution?: AutoModeClassifierRound): string;
/**
 * Parse the classifier's output contract — the CC 2.1.259 verdict reader (`MTt`, its thinking strip `QTt`, its
 * reason reader `TTt`; unchanged from 2.1.250 `wCe`/`xCe`/`yCe`), plus ONE arm of sema's own:
 *   blocked: `<block>yes</block><category>Rule Name</category><reason>[Rule Name] …</reason>`
 *   allowed: `<block>no</block>`
 *
 * The verdict is the set of `<block>yes|no` hits, NOT a prefix match:
 *   1. CONFLICT, read on the RAW text (thinking included): `<block>(yes|no)\b` collected over the whole
 *      response — more than one distinct answer ⇒ no verdict (`parse_error`, form `conflict`). A scratch-space
 *      "yes" contradicting a final "no" is a contradiction, not an allow.
 *   2. HITS, read on the text with thinking blocks removed ({@link stripThinking}): `<block>(yes|no)\b` with
 *      the closing tag OPTIONAL, at ANY position. Two distinct answers ⇒ `conflict`; the one distinct answer ⇒
 *      `no` = allow, `yes` = block.
 *   3. BARE ANSWER (sema, ruled 2026-09-15 — small open models drop the tag; recorded as a divergence; a caller
 *      that knows the reply is not a complete answer passes `bareAnswer: false` and this arm is closed): zero
 *      hits AND the whole thinking-stripped text is an answer by {@link bareAnswerOf} ⇒ that word is the
 *      verdict (a bare `yes` blocks with empty category/reason) — unless a `<block>` answer inside the
 *      thinking disagrees with it (`conflict`). Zero hits and anything else — prose, a question with its
 *      answer, a reply that only answered inside its thinking — ⇒ `parse_error`, form `missing`.
 * So a preamble before the tag, prose after `<block>no`, a missing `</block>` and a reply that repeats
 * its answer all read as the answer they carry; the tag must be spelled `<block>yes` / `<block>no` with
 * no whitespace inside (the word-boundary form — `<block>yesterday` is not a hit) and case does not matter.
 *
 * A stated BLOCK stands with or without its tail: `<category>` (the BLOCK rule NAME the prompt asked
 * for, read case-insensitively as free text — the deployment authors those names, so there is no fixed
 * vocabulary to screen them against) and `<reason>` (first `<reason>…</reason>` pair on the stripped
 * text, trimmed) are carried when present and `""` when absent; the consumers render their own fallback
 * line from the tool name in that case. Downgrading a stated block to `parse_error` would lose the
 * classifier's explicit verdict on the exact calls it flagged.
 *
 * `parse_error` is fail-closed: the gate handles it as a BLOCK (CC 2.1.259's own outcome for a reply with no
 * verdict) that the denial limit counts like any other — never a silent allow, and never a question handed
 * to a person on the classifier's behalf.
 */
export declare function parseAutoModeResponse(text: string, options?: {
    bareAnswer?: boolean;
}): AutoModeVerdict;
/** What the decider hands the deployment's `classify` hook (the assembled prompt is the hook's job
 *  when it owns assembly; when using {@link buildAutoModePrompt} the decider passes its output). */
export interface AutoModeClassifyInput {
    /** The pending tool call — FINAL post-hook/post-rewrite args (the same args the gate adjudicated). */
    req: ToolCallRequest;
    /** The human-readable text of the ask being decided (policy/safety message), when present. */
    askMessage?: string;
}
/** One rung of the ladder, as the leg is told about it. `index` is the ADDRESS (a ladder may carry two rungs
 *  whose model id is the same string), while `model` is the NAME the record will carry. */
export interface AutoModeLadderRung {
    readonly index: number;
    readonly model: string;
}
/** The pluggable model leg: given the classify input, the RUNG the ladder is on and whatever
 *  {@link AutoModeAssembleFn} built for this classification, return that model's raw text response.
 *  Deployment-injected; MAY throw / reject — the decider fail-closes. A rejection carrying
 *  {@link AUTO_MODE_ROUND_NOT_ASKED_CODE} says the failure was in assembling the QUESTION rather than in this
 *  rung's answer, and stops the walk instead of blaming the next rung. `rung` is `undefined`
 *  for a decider wired with no ladder; `assembled` is `undefined` for one wired with no `assemble` seat (the
 *  leg then owns assembly, inside this rung's cap). All trailing parameters are OPTIONAL, not `| undefined`:
 *  a caller INVOKING it with one, two or three arguments must keep compiling. */
export type AutoModeClassifyFn<T = void> = (input: AutoModeClassifyInput, signal?: AbortSignal, rung?: AutoModeLadderRung, assembled?: T) => Promise<string>;
/** The pluggable ASSEMBLY leg: build this classification's question ONCE, before any rung is asked, on its own
 *  turn of {@link AutoModeDeciderOptions.timeoutMs}. The question is built from state that is not the model's
 *  (a transcript window, a store read), so its cost is not a rung's: a build that fails or outruns the cap is
 *  the {@link AUTO_MODE_ROUND_NOT_ASKED_CODE} fact (nothing dispatched, no rung blamed), and the rung cap
 *  bounds a DISPATCH alone — which is what makes that cap's `timeout` word true. MAY throw / reject; the
 *  decider fail-closes and does not start the walk. Its signal aborts when the run does AND at the cap.
 *  Omitted ⇒ `assembled` reaches `classify` as `undefined`. */
export type AutoModeAssembleFn<T> = (input: AutoModeClassifyInput, signal?: AbortSignal) => Promise<T>;
export interface AutoModeDeciderOptions<T = void> {
    classify: AutoModeClassifyFn<T>;
    /** Build the question ONCE per classification, before the walk, on its own turn of {@link timeoutMs} — see
     *  {@link AutoModeAssembleFn}. Omitted = the leg assembles inside `classify`, inside the rung cap. */
    assemble?: AutoModeAssembleFn<T>;
    /**
     * The CANDIDATE LADDER this decider walks, in order — the models a classification may be asked on, NOT a
     * second way to say what `classify` says (that hook is the DISPATCH; this is the sequence it dispatches
     * to). Rung 0 is the seat the classification is for; every later rung is consulted only when the one
     * before it produced NO RULING ({@link rungDidNotRule}). The FIRST ruling ends the walk and is the
     * verdict; when no rung rules, the LAST rung's own non-ruling verdict stands.
     *
     * Omitted = a one-rung anonymous ladder: the leg as wired, no attribution on the verdict. An EMPTY array,
     * or a rung that is not a non-empty string, is REFUSED loudly at construction (a deployment that computed
     * its ladder and got nothing has a bug, not a default). COST: {@link timeoutMs} bounds ONE rung's dispatch
     * and, once, the {@link assemble} call — worst case, the cap times the rungs walked, plus one.
     */
    candidates?: readonly string[];
    /** Hard cap on one classification round-trip — one LADDER RUNG's DISPATCH, not the whole classification
     *  (see {@link candidates}) — and, separately, the one {@link assemble} call that precedes the walk (ONE
     *  number: a second knob would be a second answer to "how long may a classification make the gate wait").
     *  Omitted ⇒ {@link AUTO_MODE_DEFAULT_TIMEOUT_MS}. A present value that is not a finite number of at least 1
     *  and at most 2147483647 (a host timer fires at once past that) is REFUSED at construction
     *  (`config.auto_mode_timeout_invalid`, {@link autoModeTimeoutMsOf}) — never clamped, never read as the
     *  default, and there is no OFF: a permission gate always has a bound. */
    timeoutMs?: number;
    /**
     * Fired once per `decide` call with the verdict the gate is about to act on and the wall time it waited
     * (`ms`, integer ≥ 0; a timed-out classification reads the deadline, one that walked two rungs the whole
     * walk). `cause` is present iff the verdict is `unavailable`; `classifier` iff a ladder was wired. A
     * throwing hook never breaks the gate.
     */
    onClassified?: (info: AutoModeClassified) => void;
}
/** What {@link AutoModeDeciderOptions.onClassified} receives — one record per decision. */
export interface AutoModeClassified {
    /** The gated call the decision was about (`AutoModeClassifyInput.req.toolCallId`). */
    toolCallId: string;
    /** Wall-clock milliseconds between the decide call and its verdict. */
    ms: number;
    verdict: AutoModeVerdict["kind"];
    cause?: Extract<AutoModeVerdict, {
        kind: "unavailable";
    }>["cause"];
    /** Present exactly when no model was asked because the QUESTION did not assemble: `"timeout"` = the build
     *  outran its cap, `"failed"` = it threw (also what a classify hook owning its own assembly reports). ABSENT
     *  = a model was asked. The verdict is `unavailable` / `error` either way — nothing was dispatched — so this
     *  is what tells an operator "the build is slow" from "the provider is down". */
    notAsked?: "failed" | "timeout";
    /** The classification's model attribution, present exactly when the decider was wired with a candidate ladder
     *  ({@link AutoModeClassifierRound}) — the same object the verdict carries. */
    classifier?: AutoModeClassifierRound;
}
export interface AutoModeDecider {
    /** Never rejects. Any internal failure surfaces as `unavailable`/`parse_error` (fail-closed). */
    decide(input: AutoModeClassifyInput, signal?: AbortSignal): Promise<AutoModeVerdict>;
    /** The per-level budget this decider holds each rung's dispatch (and the assembly) to, in ms — the resolved
     *  {@link AutoModeDeciderOptions.timeoutMs}; what a notice about a timed-out classification names. Absent on a decider a
     *  host implements by hand. */
    readonly timeoutMs?: number;
}
/** The per-run decider: timeout + fail-closed error mapping, nothing carried between rounds. One instance per
 *  run (it binds the run's model leg and assembled prompt); a round that failed says nothing about the next,
 *  which asks the model again. */
export declare function createAutoModeDecider<T = void>(opts: AutoModeDeciderOptions<T>): AutoModeDecider;
/** The deployment's bounds for the denial limit (`RunnerDeps.autoMode.denialLimit`). Every member
 *  optional; an omitted member takes its CC default. A present member with a bad value is REFUSED
 *  loudly at construction (never clamped, never read as the default). */
export interface AutoModeDenialLimitOptions {
    /** Consecutive classifier blocks (no classifier/human allow between them) that fall back to a person.
     *  Default 3. A positive integer. */
    maxConsecutive?: number;
    /** Total classifier blocks per run that fall back to a person and reset the budget. Default 20. A
     *  positive integer. */
    maxTotal?: number;
    /** The fallback ask's auto-deny window, ms. Default 120_000; `0` = no window (the ask waits on the
     *  approver alone). A non-negative integer no greater than 2147483647 (a host timer truncates larger
     *  delays and fires at once — the fail-closed direction, and invisible). */
    autoDenyAfterMs?: number;
}
/** What BOTH shapes below say, and the whole of what a reader that only renders the card needs: the
 *  counts that tripped the bound and which bound it was. Declared once so the two faces cannot drift.
 *  A reader that only renders takes {@link DenialLimitFallbackFace} rather than this, so that an ARMED
 *  object LITERAL is accepted too (a parameter of this type would meet the excess-property check). */
export interface DenialLimitCounts {
    /** Consecutive blocks INCLUDING the one that tripped the bound. */
    readonly consecutive: number;
    /** Total blocks this run INCLUDING the one that tripped the bound (read before the total-bound reset). */
    readonly total: number;
    /** Which bound tripped. */
    readonly limit: "consecutive" | "total";
}
/**
 * The DECISION-layer member of a denial-limit fallback ask (`PermissionResult` ask arm): the counts
 * that tripped the bound, and NOTHING about a window. A window is a fact of the ask's ROUTE — a live
 * function approver can count down, a durable park row and a headless deny cannot — and the route is
 * chosen downstream of the count. The type says so: there is no window member to write here, and the only
 * ways to reach a {@link DenialLimitFallback} (the member an {@link
 * import("./tool-policy.js").AskRequest} carries) are the two route stations,
 * {@link AutoModeDenialTracker.armTimedWindow} and {@link unarmedWindow}.
 */
export interface UnarmedDenialLimitFallback extends DenialLimitCounts {
    /** FORBIDDEN on a decision, and stated rather than merely omitted. Leaving the member out would only
     *  stop a FRESH object literal (TypeScript's excess-property check); a policy that writes its window
     *  through a `const`, an `as const`, or an annotated return type would still compile — and would then
     *  have its number silently replaced at whichever route station the ask reached. `never` makes the old
     *  shape a compile error at the policy boundary instead. (A host that casts past the type still meets
     *  the route stations' runtime belt: they write the route's number, never the decision's.) */
    readonly autoDenyAfterMs?: never;
}
/** The REQUEST-layer member (`AskRequest`, a durable park row, a checkpoint summary's echo): the same
 *  counts with the route's window already decided. Reachable only through the two route stations —
 *  {@link AutoModeDenialTracker.armTimedWindow} for a hand-out to a live function approver, and
 *  {@link unarmedWindow} for every route that cannot count down (a durable park, a headless deny, a
 *  non-function `onAsk` seat, a fallback no tracker of this run minted). Neither station accepts an
 *  ALREADY-armed member: a window is decided once, at the route, and re-routing an armed one would be
 *  a second decision with the first one silently discarded. */
export interface DenialLimitFallback extends DenialLimitCounts {
    /** The auto-deny window for THIS ask, ms; `0` = none. Non-zero only where a live function approver
     *  is about to be called and the streak's timed card is still due — {@link
     *  AutoModeDenialTracker.armTimedWindow} is its one writer. The ask resolver reads this member to
     *  arm its deadline. */
    readonly autoDenyAfterMs: number;
}
/** Either face of the member — what every READ-ONLY consumer accepts. A reader that renders the card
 *  has no interest in the route, so it must take both, and as a UNION rather than as the shared base:
 *  the base alone would reject a caller's four-member object literal on the excess-property check,
 *  which is a break with nothing behind it (the routing invariant constrains where a window is WRITTEN,
 *  never who may read one). */
export type DenialLimitFallbackFace = UnarmedDenialLimitFallback | DenialLimitFallback;
/**
 * A stored or foreign value read back AS a fallback member, or `undefined` when it is not one: exactly
 * the four members, each of the right kind (finite non-negative counts, the closed `limit` word, a
 * finite non-negative window), nothing else carried. A deployment-written row (a checkpoint store is a
 * host's) can hold any shape; the faces that echo it (the inbox summary) read through here, so a bad
 * shape reads as ABSENT rather than reaching a consumer as a malformed card. Not a parser of intent —
 * `"3"` is not 3.
 */
export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback | undefined;
/** The window-less route's member, spelled in ONE place: every station that hands a fallback somewhere
 *  nothing can count down (a durable park row, a headless deny, a string `onAsk` seat, a member this
 *  run's tracker did not mint) passes through here rather than writing a `0` of its own. */
export declare function unarmedWindow(fallback: UnarmedDenialLimitFallback): DenialLimitFallback;
export type DenialLimitVerdict = {
    limitReached: false;
} | {
    limitReached: true;
    fallback: UnarmedDenialLimitFallback;
};
/** The per-run denial tracker. Lives beside {@link AutoModeDecider} (same owner, same lifetime). */
export interface AutoModeDenialTracker {
    /** A classifier `block`: count first, judge second (CC `eme` → `tme`). Returns whether THIS block is
     *  the one that falls back to a person, with the fallback's counts. There is no window here — the
     *  route is not known yet, and the minted member has no member to hold one; see {@link armTimedWindow}. */
    recordBlock(): DenialLimitVerdict;
    /** The ONE writer of the timed card (CC `u8t`: the timed shape exists only when a dialog is present,
     *  the CONSECUTIVE bound tripped, and no timed card was shown this streak). Called by the station
     *  that is about to hand `fallback` to a live function approver; returns the member that ask carries:
     *  the configured window when the card is due, else the same counts with `0` ({@link unarmedWindow}).
     *  Sets the once-per-streak mark and `windowArmedAt` exactly when it hands out a non-zero window. A
     *  fallback this tracker did not mint (a deeper ancestor's, or a policy's self-declared member) comes
     *  back with no window — the tracker arms only the streak it counted, and consults each minted member
     *  ONCE (a second call with the same member arms nothing), so the hand-out is a recorded fact, not a
     *  convention. */
    armTimedWindow(fallback: UnarmedDenialLimitFallback): DenialLimitFallback;
    /** A classifier `allow`, a person's allow of a fallback ask (CC `yR`), or — in auto mode — an allow rule or the
     *  read-only reader clearing the ask (the reference resets the streak when its pipeline answers allow): consecutive → 0
     *  and the timed-card mark cleared. A hook's allow does not call this. */
    recordAllow(): void;
    /** The current counts (diagnostics / pins). `windowArmedAt` = the epoch-ms instant
     *  {@link armTimedWindow} handed the CURRENT streak's fallback a non-zero window to a live approver
     *  (the instant CC's `timedFallbackShown` mark was set); present ⇔ that mark stands, absent otherwise
     *  — a fallback parked or denied headless WITHOUT reaching a function approver never arms it, a
     *  total-bound card is never timed, a disarmed window (`autoDenyAfterMs: 0`) arms nothing, and every
     *  reset (`recordAllow`, the total-bound budget reset) clears it. Present ⇒ the ask was handed to a
     *  function approver carrying a window; the resolver arms its deadline from it unless it exits before
     *  the wait (an already-aborted signal, unclonable args, or an approver that answers "unavailable" and
     *  sends the ask to the park) — the card counts as shown in those cases too, which errs toward a
     *  person being asked without a countdown, never toward an auto-deny nobody was shown. */
    snapshot(): {
        consecutive: number;
        total: number;
        timedFallbackShown: boolean;
        windowArmedAt?: number;
    };
}
export declare function autoModeDenialLimitOf(v: unknown): Required<AutoModeDenialLimitOptions>;
/** The deployment face's three value knobs, read by their one readers — every leg's prepare runs it while
 *  `RunnerDeps.autoMode` is present, whatever mode the leg starts in, so a bad knob refuses the run before a steer or a
 *  parent's live ceiling can reach an arming that would have refused it. */
export declare function screenAutoModeKnobs(face: {
    readonly timeoutMs?: unknown;
    readonly classifierFallback?: unknown;
    readonly denialLimit?: unknown;
}): void;
/** Build the per-run tracker over {@link autoModeDenialLimitOf}'s bounds (a bad knob refuses there). */
export declare function createAutoModeDenialTracker(opts?: AutoModeDenialLimitOptions): AutoModeDenialTracker;
/** The fallback ask's text — CC's sentence family, single-sourced for every mint site (the main gate
 *  and the inherited-lane arms): `Classifier denial limit exceeded, falling back to prompting: <limit
 *  sentence>` + a blank line + `Latest blocked action: <the classifier's own reason, or the tool name>`.
 *  The reason is the classifier MODEL's text — the caller neutralizes it before it gets here. */
export declare function denialLimitFallbackMessage(fallback: DenialLimitFallbackFace, latestBlockedAction: string): string;
/** The limit sentence alone (CC's two forms) — shared by the fallback ask and the headless terminal. */
export declare function denialLimitSentence(fallback: DenialLimitFallbackFace): string;
