import { type AutoModeDecider, type AutoModeDenialLimitOptions, type AutoModeDenialTracker, type DenialLimitFallback, type UnarmedDenialLimitFallback } from "../auto-mode.js";
import { type AutoModeArmingRecipe } from "../auto-mode-arming.js";
import { type ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
import { type EngineNotice } from "../types.js";
import { type AskClass, type AskClassFacts, type AskClassification, type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
import type { Prepared, StopForDenialLimit } from "./contracts.js";
/**
 * The classifier DENIAL-LIMIT arms of the tool gate's inherited (delegation) lane, extracted
 * from `prepareTask` as a phase module (extract, don't accrete). The gate's OWN
 * classifier block site lives in tool-policy; these are the pieces the runner threads around it:
 *  · the tracker attached to a re-supplied chain entry ({@link attachRebuiltDenialTrackers});
 *  · the run's ONE typed stop seat + the closure that fills it ({@link createDenialLimitStop});
 *  · the frozen classifier's judgment with the count-then-fallback step, shared by the two inherited
 *    wrapper arms and the approved-edit re-check ({@link judgeInheritedClassifier});
 *  · the post-resolution settlement of a fallback ask — a person's allow resets the streak, a deny
 *    nobody made stops the run ({@link settleDenialLimitFallback});
 *  · the request-side carry of the ask's origin word and its fallback member, with the auto-deny window
 *    armed at the hand-out to a live approver and nowhere else ({@link gateAskCarry} for the gate's own
 *    station, {@link inheritedAskCarry} for the three inherited-lane ones).
 * Each helper is pure over what prepare hands it; the fold/ask seams themselves stay in the runner.
 */
/** An ancestor chain entry's classifier half as the arms read it: the decider, the per-run denial
 *  tracker beside it (absent on an entry that counts nothing), and — on a re-supplied entry — the
 *  ancestor's recorded arming recipe, whose denial-limit bounds the fresh tracker is built under. */
export interface InheritedAutoMode {
    decider: AutoModeDecider;
    denialTracking?: AutoModeDenialTracker;
    arming?: AutoModeArmingRecipe;
    /** The ancestor's live mode seat: the decider answers only while it reads `auto` (absent ⇒ as recorded). */
    liveMode?: {
        readonly current: import("../ask-origin.js").PermissionMode;
    };
}
/**
 * A re-supplied chain entry (a cross-process redemption rebuilt its ancestor's classifier from the
 * recorded recipe — `rebuildAutoModeDecider` hands back a decider and its arming, nothing live)
 * carries no denial tracker, and an entry with a decider but no tracker would count nothing: the
 * rebuilt classifier could block without bound on the redeemed leg, the exact gap the limit closes.
 * Attach a FRESH tracker (a fresh COUNT is the documented cross-process semantics) under the ancestor's recorded BOUNDS tightened by this deployment's own
 * (`tightenDenialLimit` — the same rule and the same call the arming fold makes, so the
 * recipe's account of the criteria and the tracker's actual bounds cannot drift). Before this fix the
 * bounds came from this deployment alone, so an ancestor that allowed three consecutive blocks
 * before a person had to look could be redeemed into a leg that allowed five. An entry with no
 * READABLE recipe leaves the deployment's own bounds in force — there is no ancestor account to bind
 * by, and manufacturing one would state criteria nobody chose.
 *
 * Entries that already carry a tracker (the in-process chain from a live ancestor) are kept BY
 * IDENTITY; only a tracker-less armed entry is re-wrapped (its `policy`, the identity the fold and
 * the pass-through compare, is untouched). ONE tracker per DECIDER identity, not per entry: a parent
 * attaches its one decider to both its hook entry and its caller-policy entry, and a count split
 * across two trackers could alternate forever without either reaching the bound. The bounds are
 * therefore folded across EVERY entry naming that decider before any tracker is minted — two entries
 * of one decider carrying different recipes get the strictest reading of the two rather than
 * whichever the iteration happened to reach first. The mint stays lazy, so a bad bound refuses only a
 * prepare that actually has such an entry (the armed leg's own mint screens the knob first).
 */
export declare function attachRebuiltDenialTrackers<T extends {
    autoMode?: InheritedAutoMode;
}>(entries: ReadonlyArray<T> | undefined, denialLimit: AutoModeDenialLimitOptions | undefined): T[] | undefined;
/**
 * The gate's typed stop seat (`Prepared.gateStopRef`) and the one closure that fills it — the
 * nobody-judged arm of the denial-limit fallback: the fallback ask ended with no one having judged it
 * ⇒ stop the run with `classifier.denial_limit` (CC throws "Agent aborted: too many classifier denials
 * in headless mode"). Once per run leg (the first such fallback owns the terminal); the deny that
 * triggered it stands as that call's result. Minted once so every mint site — the main gate's seat, the
 * inherited-lane arms, and the durable resume station — stops the SAME leg through the SAME seat.
 * `abort` is the run's own abort (controller + harness — the one a cancel fires; handed in because the harness
 * is built after the seat): the durable stop fires it before the resume leg's prompt, whose door reads it.
 *
 * TWO LANES, ONE SENTENCE. The synchronous lane reached no approver at all; the durable lane parked the
 * fallback, and the decide that came back named a settlement word the ONE table
 * ({@link SETTLEMENT_NOBODY_JUDGED}) reads as "nobody judged this". Both are the same fact — the
 * classifier's bound fell back to a person and no person answered — so both stop through here and say
 * the same thing, differing in ONE clause: which of the two ways nobody answered. Composing the
 * sentence in one place is what keeps them from drifting into two accounts of one event.
 */
export declare function createDenialLimitStop(opts: {
    sessionId: string;
    runId: string;
    onNotice: ((notice: EngineNotice) => void) | undefined;
    abort: () => void;
}): {
    gateStopRef: Prepared["gateStopRef"];
    stopForDenialLimit: StopForDenialLimit;
};
/** The ask arm of a permission decision — what the inherited lane arbitrates. */
export type InheritedAsk = Extract<PermissionResult, {
    action: "ask";
}>;
/**
 * The delegation half of the live gate's classifier eligibility: the SAME class row (`classifyAsk` over the
 * ask's own members) answers for the FROZEN classifier — an ask raised by a hook, minted by an
 * explicit `ask` permission rule, or already a denial-limit fallback is exactly as out of the frozen
 * decider's reach as the live one's, because the person's / hook's standing decision travels with the
 * frozen entry and a frozen decider resolving it would re-open the same hole one process over. The
 * three GATE-HELD facts of the derivation are not this station's to judge: an ancestor's frozen
 * classifier runs inside the policy fold, BEFORE the live gate's org layer has spoken and before its
 * question-tool and marked-call lanes run, so they read as absent here and those asks meet their
 * exclusions downstream, where the facts exist. Excluded asks fall through to the frozen approver
 * chain, the delegation lane's original chain. The word and the class set are derived inside
 * {@link judgeInheritedClassifier} and threaded to every reader ({@link inheritedAskCarry}).
 * The MANDATE marks here are empty (the base names no seat): the policy-chain phase builds each call's facts over this
 * base with THIS task's tool seat — the seat the request's risk axes are read from — so a marked tool's ask is
 * classified by the seat at the station as at the gate. What the ancestor's ask carries on ITSELF still answers.
 */
export declare const INHERITED_STATION_FACTS: AskOriginFacts;
/** The members of a surviving ask that ride onto the approval request with a COMPUTED value — what the
 *  four request mint stations (the gate's own and the three inherited-lane ones) spread after the seats
 *  they spell themselves: the ask's origin word, its class (the word and the set, carried together) and its
 *  denial-limit fallback. */
export interface AskRequestCarry {
    origin?: AskOrigin;
    askClass?: AskClass;
    classes?: readonly AskClass[];
    denialLimitFallback?: DenialLimitFallback;
}
/** The GATE's own mint station: every member is read off the surviving decision, which is the gate's
 *  OWN snapshot (its stamp point spreads the policy's object and stamps the word and the class onto the copy),
 *  so the origin and the class are the engine-derived ones and the fallback member is the one the gate's
 *  classifier step minted. */
export declare function gateAskCarry(decision: InheritedAsk, liveApprover: boolean, tracker: AutoModeDenialTracker | undefined): AskRequestCarry;
/** An INHERITED-lane mint station (the two wrapper arms and the approved-edit re-check): the ask is an
 *  ancestor policy's own decision, never engine-stamped, so EVERY member comes from the judgment
 *  {@link judgeInheritedClassifier} handed back — the word and the classification it derived under the station
 *  facts and the fallback member it captured — each read ONCE there and threaded here, never re-read off the
 *  caller-owned object (a stateful accessor could otherwise answer the card with one value and the
 *  settlement with another; a self-declared `origin` / `askClass` / `classes` on the ancestor's decision is never
 *  read). */
export declare function inheritedAskCarry(judged: Pick<Extract<InheritedClassifierJudgment<InheritedAsk>, {
    kind: "resolve";
}>, "origin" | "classification" | "fallback" | "ask">, liveApprover: boolean, tracker: AutoModeDenialTracker | undefined): AskRequestCarry;
/** What {@link judgeInheritedClassifier} hands back to the arm that called it. */
export type InheritedClassifierJudgment<A extends InheritedAsk> = 
/** The frozen classifier allowed. `result` is the allow the arm RETURNS — minted at the judging station so
 *  the model attribution rides the same one object the deny arm's does (an arm re-minting its own
 *  `{action:"allow"}` drops it); it already carries the ask's rewrite when the arm declared one. */
{
    kind: "allow";
    result: PermissionResult;
}
/** A block below the bound — the exact deny the ancestor's own gate would have produced. */
 | {
    kind: "deny";
    result: PermissionResult;
}
/** Proceed to the frozen approver with `ask`: the incoming ask unchanged (classifier excluded or not
 *  armed), or — at the bound — the fallback ask minted from it (`mintedHere`).
 *  `fallback` is the member riding the ask either way (an incoming one is a deeper ancestor's).
 *  `origin` is the ask's word under the station facts, threaded to the request mint: on the two
 *  unchanged-ask branches it is the SAME read the exclusion judged by (a caller-owned decision with a
 *  stateful accessor cannot answer the exclusion with one word and the card with another); on the
 *  bound branch it is derived from the re-spoken ask this station minted (its own object).
 *  `classification` is the class set of the ask handed on, under the same station facts: on the two unchanged-ask
 *  branches the one read the exclusion judged by, on the unavailable and bound branches a read of the object this
 *  station minted (each adds a real-approval bit, which the set must reflect). */
/** `ask` is the incoming ask unchanged (the arm's receipt snapshot of the ancestor's decision), or — an UNAVAILABLE classification with a live frozen
*  approver — that ask with the classifier sentence appended; without one it is a `deny` whose attested cause reads `unavailable`. */
 | {
    kind: "resolve";
    ask: A;
    fallback: UnarmedDenialLimitFallback | undefined;
    mintedHere: boolean;
    origin: AskOrigin;
    classification: AskClassification;
};
/**
 * The ancestor's frozen classifier, run BEFORE its frozen approver in the ancestor's own gate order,
 * with the denial-limit count folded in (CC 2.1.250 count-then-judge): `allow` ⇒ the classifier's
 * auto-allow (and the streak resets); `block` below the bound ⇒ deny, the exact verdict the ancestor's
 * gate would have produced; `block` AT the bound ⇒ the ask is re-spoken in the classifier's voice as
 * the fallback (`decisionReason:"classifier"`, `requiresRealApproval`, the member carrying counts +
 * window) for the arm to resolve at the frozen approver; `parse_error` ⇒ the same block path with the CC
 * parse-failure sentence (counted); `unavailable` ⇒ the gate's own exit (`classifierUnavailableArm`) on the
 * frozen approver — a live one resolves the incoming ask, none ⇒ a DENY that says so (CC 2.1.250 form), never
 * counted; excluded / no classifier ⇒ the incoming ask, unchanged. The member may ALREADY be on the incoming
 * ask (a deeper ancestor's fallback, minted inside the nested fold — the case the exclusion names):
 * it rides to the approver and the headless arm unchanged, whichever tracker minted it; only a
 * tracker that counted THIS streak is reset by the person's later allow (`mintedHere`).
 * `req` is the request as the classifier must see it (the arm's presented / edited args);
 * `subject` is the noun the deny names ("this call" on the wrapper arms, "the approved edit" on the
 * re-check).
 *
 * ONE MINT for the classification's model attribution: every decision this station hands back — the allow it
 * mints from `allowRewrite` as much as either deny — carries it on the identity-keyed seat beside the one
 * carrying the FORM, and the arms return the station's object instead of re-minting their own. The
 * descendant's fold reads both seats at its single attestation read. A decider with no candidate ladder
 * vouches for no name and the seat stays empty (absence = unknown); a block AT the bound reaches no seat at
 * all, since it becomes an ask and the disposition then names that resolution.
 */
/** The provenance sentence an inherited card carries when the ancestor's ask brought no words of its own — the one spelling the request mint and the frame join share. */
export declare const inheritedAskFallbackMessage: (toolName: string) => string;
export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
    autoMode: InheritedAutoMode | undefined;
    /** The leg's live permission mode (the gate's seat): under `dontAsk` / `bypassPermissions` / `plan` the decider is not
     *  dispatched (the mode's arbitration decides first). Absent ⇒ a decider on the chain is dispatched (subject to the
     *  entry's own `autoMode.liveMode`). */
    liveMode?: () => import("../ask-origin.js").PermissionMode;
    ask: A;
    req: ToolCallRequest;
    signal: AbortSignal;
    subject: "this call" | "the approved edit";
    /** The rewrite the arm's allow would have carried (the ask's own `updatedInput`, the re-check's edited
     *  args) — handed IN so the allow is minted here, attested, once; absent ⇒ a bare allow. */
    allowRewrite?: unknown;
    /** An `unavailable` verdict's fork facts (`classifierUnavailableArm`): is the frozen approver live; who hears it. */
    unavailableSeat?: ClassifierUnavailableSeat;
    /** The station's facts for this call (the base with this task's tool seat); absent ⇒ {@link INHERITED_STATION_FACTS}. */
    facts?: AskClassFacts;
}): Promise<InheritedClassifierJudgment<A>>;
/** The resolver's answer as the settlement reads it. */
export type FallbackResolution = Pick<ResolvedAsk, "action" | "settlement" | "approverUnavailable">;
/** A deny nobody JUDGED, at a wrapper arm — read off the one table over the settlement words
 *  ({@link SETTLEMENT_NOBODY_JUDGED}) rather than listed here, so a word added to the set answers this
 *  question by getting a row and not by falling out of a disjunction. `approver_unavailable` is NOT
 *  headless here — it floats to the main gate, which owns that arm (a park, or the fail-closed refusal). */
export declare function headlessDenyAtFold(r: FallbackResolution): boolean;
/** The re-check's reading: the approved-edit helper has NO park route (an approved edit is
 *  re-adjudicated in place), so an approver that reports unavailable is as headless as none at all. */
export declare function headlessDenyAtRecheck(r: FallbackResolution): boolean;
/**
 * After the frozen approver answered a fallback ask (CC `yR`): a person's allow ends the ancestor's
 * streak — only on the tracker that counted it (`mintedHere`; a deeper ancestor's fallback leaves
 * this entry's count alone); a deny nobody made (`headless`, the arm's own reading) is the headless
 * arm — the run stops through the one seat (`stop`). Any other answer settles nothing here. A no-op
 * when the ask was not a fallback.
 */
export declare function settleDenialLimitFallback(opts: {
    fallback: UnarmedDenialLimitFallback | undefined;
    mintedHere: boolean;
    tracker: AutoModeDenialTracker | undefined;
    resolved: FallbackResolution;
    headless: (r: FallbackResolution) => boolean;
    stop: StopForDenialLimit;
    toolName: string;
    toolCallId: string;
}): void;
/**
 * The observer `resolveAskBound` hands `resolveAsk` for a DETACHED approver's
 * settlement (the race arm released the wait; the approver's promise lives on):
 *  · an approval the released wait never consumed becomes the `task.late_approval` notice a host
 *    can forward on its own wire (the wire-facing half of "release, not a verdict" — the notice
 *    asserts non-consumption only, never an arrival order the resolver cannot prove);
 *  · an unconsumed rejection goes to the deployment's error face (`onError(phase:"hook")`) — a
 *    callback failure the engine no longer awaits is still a callback failure, never silence.
 */
export declare function lateAskSettlementObserver(args: {
    toolName: string;
    toolCallId: string;
    sessionId: string;
    runId: string;
    taskId?: string;
    onNotice: import("../types.js").RunnerDeps["onNotice"];
    onError: import("../types.js").RunnerDeps["onError"];
}): (late: {
    kind: "approve";
} | {
    kind: "error";
    error: unknown;
}) => void;
