/**
 * design/277 — the tool-registration MODEL GATE (CC 233 counterpart, first formalized there as a
 * family+version-floor table over the todo/task-board tool family).
 *
 * WHAT THE GATE IS: a DEFAULT-MOUNT trim, never a capability ban. A {@link ToolSpec.modelGate}
 * tag declares "this entry is a default-mounted scaffold of the named class"; when the task's
 * RESOLVED model id matches the class's rule below, that entry is dropped from the roster at
 * prepare (true unmount — the schema never reaches `tools[]`). An EXPLICITLY composed tool is
 * never tagged (the bundle only stamps its default arms), so "the user asked for it" is the
 * first restore channel by construction. Restore channels beyond that:
 * `TaskSpec.restoreGatedTools` (per task-tree), env `SEMA_TOOL_MODEL_GATE=off` (process),
 * `RunnerDeps.toolModelGate: false` (deployment).
 *
 * FAIL-OPEN, BY CONTRACT (not by accident): sema is BYOM — model ids are an OPEN set, and this
 * table encodes POSITIVE knowledge ("this family at this version and above manages multi-step
 * work without the scaffold"). An id the table knows nothing about (deepseek-* / gpt-* / qwen-* / any
 * custom name) is NOT gated: no knowledge ⇒ no trim ⇒ tool stays. The matcher's three pass-through
 * edges (family absent, id shape unmatched, version below floor) all point the same way, and it is
 * the only polarity compatible with BYOM — the reverse would trim every non-claude deployment's
 * default face. The engine does NOT guarantee a canonical id either: a deployment-authored
 * `Model.id` (a raw Model object, or a catalog key shadowing the tier expansion) that is not in
 * canonical `claude-<family>-<version>` shape simply falls off the regex and stays open — the
 * explicit-id discipline's deployment-side duty. To gate a non-claude strong model, write the
 * exact id into a {@link ToolModelGateRule.modelIds} row (the only non-claude channel; no prefix
 * or wildcard syntax exists).
 *
 * DIVERGENCE (registered, deliberate): CC judges the MAIN-LOOP canonical model once per process;
 * sema judges the PER-TASK resolved model on every prepare — BYOM per-task models are first-class,
 * so a delegated child on a different model re-judges under its own (strong parent / weak child ⇒
 * the child gets the scaffold back). And the env valve's polarity is reversed (CC: enable the
 * tools; sema: disable the GATE) because sema's class vocabulary is open — a per-family enable
 * env would have to grow with every class.
 */
import type { ToolSpec } from "./types.js";
/** The shared tail extractor (BYOM ids may carry provider prefixes — "openrouter/anthropic/claude-fable-5"):
 *  boundary-aware LAST path segment, case-folded. Single source for every model-family/model-id
 *  comparison site (`isFableFamilyModelId` consumes it too — the prompt-shape axis, semantically
 *  independent but sharing the one tail-extraction posture). */
export declare function modelIdTail(id: string): string;
/** One gate class's matching rule (both axes optional; a rule with BOTH axes empty gates nothing —
 *  the per-class OFF shape a deployment reaches by explicitly clearing the axes). */
export interface ToolModelGateRule {
    /** claude-syntax family+floor rows — meaningful only for ids whose TAIL SEGMENT matches
     *  `claude-<letters>-<digits(-digits)*>`. Version tuples compare positionally, missing positions
     *  read 0, tuple ≥ floor ⇒ gated (CC 233 comparator, verbatim semantics). Any other id shape
     *  falls through this axis entirely (fail-open). */
    floors?: ReadonlyArray<readonly [family: string, floor: ReadonlyArray<number>]>;
    /** Exact-id rows (explicit-id discipline): the resolved `Model.id`'s tail segment must equal the
     *  row case-insensitively. The ONLY channel by which a BYOM deployment gates its own non-claude
     *  strong model — deliberately no prefix/wildcard syntax (a substring guess against an open id
     *  set is how the `deepseek-chat` alias downgrade class of accident happens). */
    modelIds?: ReadonlyArray<string>;
}
/**
 * The built-in gate table: gate-class → rule. CC 233 z_S floors, verbatim (evidence D2; the
 * `mythos` row rides per the anchor). v1 vocabulary = ONE class, `"task-scaffold"` — the
 * TodoWrite/TaskCreate/TaskGet/TaskUpdate/TaskList default bundle (`assembleCodeTools`). One class
 * is evidence discipline, not mechanism limit: a family enters this table only with positive
 * "strong models don't need it" evidence (a CC anchor or deployment measurement); classes are an
 * open vocabulary and a deployment adds its own via `RunnerDeps.toolModelGate.classes`.
 */
export declare const TOOL_MODEL_GATE_CLASSES: Readonly<Record<string, ToolModelGateRule>>;
/**
 * Pure matcher: is `modelId` gated under `rule`? Every unmatched shape returns `false` (the
 * fail-open contract in the module header). Exported for deployment pre-flight ("would my model
 * lose the scaffold?") — the engine's own decision point calls this same function.
 */
export declare function isModelGatedForClass(modelId: string, rule: ToolModelGateRule): boolean;
/**
 * #123 value screen for `TaskSpec.restoreGatedTools` (same posture as `assertContainmentFaceValue`: the
 * VALUE is screened at the door, unconditionally — a garbage restore list must refuse loudly on
 * every leg, never be silently read as "no restore" in the narrowing direction). Legal: absent,
 * literal `true` (restore every class), or an array of strings (wire names; unknown names are
 * inert by contract, like `excludeTools`).
 */
export declare function assertRestoreGatedToolsValue(value: unknown): asserts value is true | readonly string[] | undefined;
/** Doors-facing decision (built + applied inside `prepareConfigDoors`' synchronous stretch —
 *  decision and application share one atomic window, no drift gap). */
export interface ToolModelGateDecision<T extends ToolSpec = ToolSpec> {
    /** Present ⇔ at least one entry was removed: the fresh, PRIVATE survivor array `spec.tools` is
     *  rebound to (a caller mutating its original live array afterwards can neither re-add a removed
     *  entry nor displace anything — there are no indices left to misalign). `undefined` = no
     *  removal, the spec is left untouched. */
    survivors: T[] | undefined;
    /** gate-class → removed wire names (sorted, unique) — the removal-notice payload. */
    removedByClass: ReadonlyMap<string, readonly string[]>;
    /** Stamped classes with no row in the merged table — announce-once material (the loud half of
     *  fail-open: a tag typo must not silently become "never gated" with nobody told). */
    unknownClasses: readonly string[];
    /** An env value outside the closed set that was NOT in force (nothing this prepare would gate) —
     *  discarded-value announce material. In-force garbage never lands here: it throws. */
    discardedEnvRaw: string | undefined;
}
/**
 * The single decision point, called from `prepareConfigDoors` with the model id read ONCE into the
 * decision (the Model object is mutable; decision and later reads must not diverge) and the frozen
 * restore snapshot (never the live spec).
 *
 * Channel semantics (a UNION of loosening channels — any hit ⇒ don't trim; the order below is
 * evaluation order, not priority):
 *  - `depsSeat === false` — deployment kill switch; nothing is scanned.
 *  - env `SEMA_TOOL_MODEL_GATE` `off|0|false` (case-folded) — process kill switch; `on|1|true` is
 *    the explicit default (no-op). A value outside the closed set follows the loud-bad-value
 *    dialect split (#123, the SEMA_TOOL_MATERIALIZE_STRATEGY precedent): where the value is IN
 *    FORCE — its reading would change this prepare's outcome, i.e. after folding the other
 *    channels there remains a stamped entry the gate would remove — the prepare REFUSES
 *    (`config.tool_model_gate_env_invalid`; a mistyped `off` must not silently select the
 *    narrowing arm). Everywhere else it is a DISCARDED value: announced once per process, never a
 *    veto of the configuration that outranks it.
 *  - `restoreGated === true` — every class exempt for this task; an array exempts the WHOLE class
 *    of any stamped tool it names (CC "opting into any tool of the family restores the family",
 *    judged against the FULL stamp set — a name `excludeTools` will later remove still selects its
 *    class here, while the exclusion itself stands downstream; unknown names inert).
 *  - a stamped class with no merged row ⇒ kept + reported in `unknownClasses` (fail-open, loud).
 *  - a merged rule with both axes empty ⇒ kept, silent (the documented per-class OFF).
 *  - the exclusion valve (`excludeTools`, applied downstream at the roster splice) ALWAYS wins in
 *    the result: no restore channel resurrects an excluded name.
 */
export declare function applyToolModelGate<T extends ToolSpec>(input: {
    tools: ReadonlyArray<T> | undefined;
    modelId: string;
    depsSeat: unknown;
    restoreGated: true | readonly string[] | undefined;
    envRaw: string | undefined;
}): ToolModelGateDecision<T>;
