import type { AutoModeRules, AutoModeWindowOptions, DerivedDenyRule } from "./auto-mode-prompt.js";
import type { AutoModeDenialLimitOptions } from "./auto-mode.js";
/** The recipe format's version. A reader that does not know a version REFUSES it (never guesses): the
 *  recipe names the criteria a classifier enforces, and a partially-understood criteria set is the one
 *  thing a permission gate may not improvise. Version 2: the breaker threshold (`failureThreshold`) left the
 *  criteria set with the breaker itself; a version-1 recipe named a criterion no decider enforces any more and is
 *  refused as unknown (the redemption keeps its honest refusal), never read as a version-2 recipe. */
export declare const AUTO_MODE_ARMING_RECIPE_VERSION = 2;
/**
 * The SERIALIZABLE half of an auto-mode arming — everything `createAutoModeDecider` +
 * `buildAutoModePrompt` need except the model leg itself. Plain data by construction (JSON /
 * `structuredClone` round-trips), because it rides a durable checkpoint row and is bound by the
 * constraint-chain digest.
 *
 * Deliberately NOT carried: the model/roster selection (the redeeming deployment routes its own classifier
 * leg; a recorded model id would name a catalog entry that need not exist in the redeeming fleet).
 */
export interface AutoModeArmingRecipe {
    /** {@link AUTO_MODE_ARMING_RECIPE_VERSION}. An unknown version is refused, never partially read. */
    v: number;
    /** `buildAutoModePrompt` rule overrides for the four paired sections (omitted = CC defaults verbatim). */
    rules?: AutoModeRules;
    /** Settings-plane deny rules rendered into `<settings_deny_rules>`. */
    settingsDenyRules?: readonly string[];
    /**
     * The ENGINE's deny lines rendered beside them — the arming leg's effective read-deny table as rules
     * (`readDenyClassifierRules`: the rule text and its letter-case note). Prompt BODY, compared by the fold exactly as `settingsDenyRules` is: a
     * redeeming host recomputes its own from its deployment's read-deny configuration and the row's task
     * additions, and a difference refuses the rebuild (`settings_moved`) — the same rule as a moved deployment
     * setting, no exemption.
     */
    derivedDenyRules?: readonly DerivedDenyRule[];
    /** `## Session Context` facts appended to the assembled document. */
    sessionContext?: readonly string[];
    /** Transcript-window bounds for the classify prompt. */
    window?: AutoModeWindowOptions;
    /** Classify round-trip cap, ms. */
    timeoutMs?: number;
    /**
     * The classifier DENIAL-LIMIT bounds (`RunnerDeps.autoMode.denialLimit`, CC 2.1.250 `FO`/`AKe`):
     * how many blocks the ancestor's classifier was allowed before a person had to look, and the window the
     * fallback ask ran under. A KNOB triple, not prompt body — every member is orderable, so the fold takes
     * the strictest of the two sides rather than refusing on a difference.
     *
     * It belongs on the recipe because the tracker CANNOT travel (it is live state) while
     * its BOUNDS are exactly the kind of criteria the recipe exists to carry: without them a cross-process
     * redemption rebuilt the ancestor's classifier and counted its blocks against the REDEEMING
     * deployment's own bounds, which may be looser — the ancestor's "three strikes and a person looks"
     * silently became the redeemer's five. (A `requiresRealApproval` fallback ask still has a person as its
     * only exit either way, which is why this is a widening of the BUDGET and not of the gate.)
     *
     * Members are canonicalized under the SAME screen `createAutoModeDenialTracker` applies to the live
     * face — a recorded value that tracker would refuse makes the whole recipe malformed here rather than
     * being floored into one it accepts, because the rebuild hands these very numbers back to it.
     */
    denialLimit?: AutoModeDenialLimitOptions;
    /**
     * The cross-session lane's classifier rule was spliced into the assembled prompt (the lane was
     * mounted on the arming leg). Part of the PROMPT BODY: the rebuild re-splices the same engine
     * constant when the bit is set, so the recorded `promptDigest` is reproducible, and the fold treats
     * the bit as body (a rebuild under a deployment whose own face does not declare it refuses as
     * `settings_moved` — the two prompts differ by a rule block). Absent = the rule was not spliced.
     */
    crossSessionMessagesRule?: true;
    /**
     * A digest of the EXACT classifier system prompt this arming assembled (see
     * `rebuildAutoModeDecider`). The recipe records the deployment's OVERRIDES; the bulk of the criteria —
     * the base shell and the permissions document — lives in the build's own SHA-locked assets, which the
     * recipe cannot carry and a redemption assembles from its OWN binary. Under a rolling upgrade two
     * builds can accept the same recipe and enforce different default rules, and every recipe and digest
     * check would pass. Binding the assembled prompt closes that: a redeeming build whose assembly differs
     * by one byte refuses to rebuild instead of enforcing criteria the ancestor never ran.
     */
    promptDigest?: string;
    /**
     * The deployment's auto-mode SETTINGS GENERATION at arming time — an opaque string the deployment
     * owns (a config revision, a settings hash, a publish id). It is a DECLARATION axis and can only make
     * the rebuild stricter: an epoch that MOVED while the recorded body still equals the redeeming
     * deployment's own is harmless (the fold proceeds), and an epoch that is EQUAL while the bodies
     * differ is a contradiction the fold refuses loudly rather than resolving by picking a side.
     */
    settingsEpoch?: string;
}
/** The deployment assembly face this recipe is minted from — structurally `RunnerDeps.autoMode`. */
export interface AutoModeArmingFace {
    rules?: AutoModeRules;
    settingsDenyRules?: readonly string[];
    sessionContext?: readonly string[];
    window?: AutoModeWindowOptions;
    timeoutMs?: number;
    denialLimit?: AutoModeDenialLimitOptions;
    settingsEpoch?: string;
    /** `true` when the cross-session lane's classifier rule is spliced into this deployment's classifier
     *  prompt (the Runner sets it from its own lane mount; a redeeming host declares it from its). */
    crossSessionMessagesRule?: boolean;
}
/**
 * The ONE tighten rule for the denial-limit bounds, shared by {@link foldAutoModeArming} and by
 * the runner's rebuilt-tracker mint (a second computation of "the strictest of these two" is how the
 * recipe's account and the tracker's actual bounds would come to disagree). Both sides' EFFECTIVE values
 * are materialized first (an omitted bound is the default the tracker applies, never "unbounded" — the
 * same correction the knob fold below already carries), and the result is explicit for the same reason.
 *
 * · `maxConsecutive` / `maxTotal` — the MINIMUM. A lower bound falls back to a person SOONER, which is
 *   the fail-closed direction on both: the classifier gets less rope, never more.
 * · `autoDenyAfterMs` — the SHORTER WINDOW wins, and `0` is not a short window: it is the special value
 *   for "no window at all" (the fallback ask blocks on the approver, which is the shape a total-bound
 *   card and every park already run under, #559). Read numerically, `0` would read as the strictest
 *   value and would silently DISARM an ancestor's window; read as "no window", a declared absence of one
 *   never removes the other side's. Both surviving directions are deny-ward — an auto-deny can only ever
 *   refuse — so neither can widen the gate.
 */
export declare function tightenDenialLimit(a: AutoModeDenialLimitOptions | undefined, b: AutoModeDenialLimitOptions | undefined): Required<AutoModeDenialLimitOptions>;
/**
 * Canonicalize + VALIDATE an arming recipe: the plain-data form that persists, or `undefined` when the
 * input is not one (unknown version, a non-string in a rule list, a NaN knob, a non-object).
 *
 * IDEMPOTENT by construction (the output is a fresh object built from known keys only, with the
 * absent/empty collapse and the knob normalization already applied), which is what lets the mint side
 * and the resume RE-SUPPLY side run it independently and still digest identically — the two sites never
 * see the same object, only the same values.
 *
 * A malformed recipe yields `undefined` rather than a partially-pruned one: silently dropping half a
 * rule list would change the criteria a classifier enforces while every downstream face still called it
 * "the ancestor's arming". The LOUD half of this rule lives at the mint (prepare-task announces a
 * requested-but-unpersistable arming); here the answer is simply "no recipe", which degrades to the
 * pre-#503 behavior (an honest refusal at the redemption).
 */
export declare function sanitizeAutoModeArmingRecipe(value: unknown): AutoModeArmingRecipe | undefined;
/**
 * Mint a recipe from a deployment's auto-mode assembly face (`RunnerDeps.autoMode`): the same values the
 * Runner arms its own decider with, canonicalized. `undefined` when the face is not serializable — the
 * caller is expected to say so out loud rather than park a silently thinner arming.
 *
 * Used on BOTH ends of the boundary: the Runner mints it at arming time (so a park can record it), and a
 * redeeming host mints its OWN face into the `current` half of {@link foldAutoModeArming}. `bind` carries
 * what is the ENGINE's rather than the deployment's: the assembled prompt's digest (the arming side only)
 * and the engine's deny lines (`derivedDenyRules`, both sides — a redeeming host passes the lines its own
 * read-deny configuration yields over the row's task additions, `readDenyClassifierRules`).
 */
export declare function autoModeArmingRecipeOf(face: AutoModeArmingFace, bind?: {
    promptDigest?: string;
    derivedDenyRules?: readonly DerivedDenyRule[];
}): AutoModeArmingRecipe | undefined;
/** Why a rebuild refused. Every arm leaves the redemption on the PRE-#503 path (honest `unavailable` →
 *  the frozen approver chain → a human), so a refusal costs a second park, never a widened gate. */
export type AutoModeRebuildRefusal = 
/** The recorded recipe is not a recipe this build understands (unknown version / not plain data). */
"malformed_recipe"
/** No current settings were declared, so nothing bounds the rebuild — see {@link foldAutoModeArming}. */
 | "current_settings_missing"
/** The redeeming deployment's OWN settings face is malformed — it cannot bound the rebuild, so nothing is built. */
 | "malformed_current_settings"
/** The redeeming build assembles a DIFFERENT classifier prompt than the arming recorded (asset skew). */
 | "prompt_assets_moved"
/** The prompt body moved between the recorded arming and the redeeming deployment's current settings. */
 | "settings_moved"
/** The two sides declare the SAME settings epoch while their bodies differ — a contradiction, not a fold. */
 | "epoch_inconsistent";
export type AutoModeArmingFold = {
    ok: true;
    /** The RECORDED recipe, canonical — the digest-bearing half (what the ancestor's row says). */
    recorded: AutoModeArmingRecipe;
    /** The criteria the rebuilt decider runs under: the agreed prompt body + the MINIMUM of each knob. */
    effective: AutoModeArmingRecipe;
    /** True iff a knob was actually pulled in by the current settings (informational, for a host log). */
    tightened: boolean;
} | {
    ok: false;
    reason: AutoModeRebuildRefusal;
    message: string;
};
/**
 * Safety bound ② — fold a RECORDED arming against the redeeming deployment's CURRENT settings,
 * tighten-only.
 *
 * · `current` ABSENT ⇒ REFUSED. The bound reads "the rebuilt arming is bounded by the settings in
 *   force", and a caller that declares none has not put anything in force — an unbounded rebuild is the
 *   single outcome this fold exists to prevent. A redeeming deployment that genuinely runs no auto-mode
 *   settings of its own says so by passing its own (possibly bare) face, which is a declaration and
 *   leaves a record; silence is not a declaration. This is also what keeps a CHAIN of redemptions
 *   honest: an intermediate leg's tightening is not carried in the row (the row keeps binding what the
 *   ANCESTOR recorded, which is what its digest authenticates), so every later redemption is bounded by
 *   ITS OWN declared settings rather than by whatever the previous redeemer happened to run under. A
 *   deployment that wants a tightening to be durable tightens its own settings, where it is auditable.
 * · bodies EQUAL ⇒ rebuild, with `timeoutMs` taking the MINIMUM of the two — fail-closed: a shorter
 *   timeout turns a slow classifier into `unavailable` sooner (the call goes on to a person where the
 *   station has an approval seat, and is refused with a sentence that says so where it has none), and can
 *   never widen a verdict. The denial-limit triple
 *   (#556) folds the same way through {@link tightenDenialLimit} — fewer blocks before a person looks,
 *   and no window ever lengthened or removed.
 * · bodies DIFFER ⇒ refuse. `settings_moved` normally; `epoch_inconsistent` when the two sides
 *   nevertheless declare the same epoch — that is a deployment telling us its settings did not move
 *   while its own data says otherwise, and picking either side would be a guess about which one is the
 *   policy in force.
 *
 * @contract auto_mode.arming.absent_timeout_reads_recorded_default — a parked arming's absent `timeoutMs` is
 * read as the default in force when it was recorded (`AUTO_MODE_RECORDED_ABSENT_TIMEOUT_MS`, 15_000 ms), not
 * the default in force when it is redeemed: persisted data is read under the contract it was written under,
 * so a default that moves can never widen an ancestor's bound at redemption; every recipe minted since
 * materialises the knob, so absence dates the recording.
 */
export declare function foldAutoModeArming(recorded: unknown, current: unknown): AutoModeArmingFold;
