/**
 * design/98 §2.5 / §E.2 (S8b, security core) — the DEFAULT-DENY governance for a sub-agent an LLM-authored
 * workflow script spawns. The child spec is BUILT (complete-by-construction) from the deployment's trusted
 * {@link WorkflowGovernanceBaseline} + a strict WHITELIST of safe work-fields the script may set — NEVER by
 * stripping a denylist off the untrusted spec (a missed control-plane field would leak).
 *
 * The script's spec is UNTRUSTED. Only {@link WHITELIST_KEYS} are ever read from it; every other field
 * (toolPolicy / onAsk / hooks / principal / tools / mcp / skills / lspManager / checkpointStore /
 * getApiKeyAndHeaders / promptProvider / sessionId / signal / …) is structurally never copied. "Never
 * copied" is a statement about the SCRIPT's spec — the child's control plane still arrives from the
 * TRUSTED side, and some of it inherits from the HOST run on trusted lanes of its own: `principal` / the
 * durable off-switch via `buildWorkflowPrimitives`' engine injections, `model` / `thinking` / the stated
 * subagent persona via the launch site's child seats (`withWorkflowChildSeats`), and the host's
 * effective approver via the run-workflow mount's base-slot fold (`RunWorkflowToolDeps.parentOnAsk` →
 * `baseline.base.onAsk`, backlog #342 — filled only when the deployment did not pin the seat). Those are
 * host-ctx bindings the engine writes onto the baseline/child, never a read of anything the script wrote:
 * a script-authored `onAsk` (or any other control-plane key) keeps being stripped + announced. The model is
 * chosen by NAME ONLY (resolved against an allowlist to a deploy-configured `Model` — the script never sees a
 * `Model` object, which carries `baseUrl`/`headers` = an exfil surface). Resource limits
 * are CLAMPED to the baseline + workflow ceilings (`tightenTaskSpec` only covers the safety knobs, not
 * cost/token caps).
 */
import type { Model } from "../internal/llm.js";
import type { EngineNotice, ImageInput, TaskSpec, ThinkingLevel, WorkflowGovernanceBaseline } from "../core/types.js";
import { type ReadDenyEntry } from "../tools/fs/read-deny.js";
/** Thrown when an LLM-authored script picks a `modelName` not in the workflow model allowlist (or no
 *  allowlist is configured). FAIL-CLOSED: a script can only ever name a model the deployment pre-approved. */
export declare class WorkflowModelNotAllowedError extends Error {
    readonly modelName: string;
    readonly code = "workflow.model_not_allowed";
    constructor(modelName: string, reason: string);
}
/**
 * The SAFE fields an LLM-authored workflow script may set per sub-agent — the WORK, never the control plane.
 * `model` is a NAME (string), NOT a `ModelRef`/`Model` object (a `Model` carries
 * `baseUrl`/`headers`/`extraBody` = endpoint + auth-header injection). No `modelRole`/`roles` (a `roles.select`
 * conditional selector is its own vector). The engine resolves `modelName` against the allowlist.
 */
export interface WorkflowAgentSpec {
    objective: string;
    /**
     * The canonical spelling, and the only one this type expresses. At RUNTIME the spec ALSO accepts
     * `model` as an alias for this field (see {@link SPEC_KEY_ALIASES}) — the same axis is spelled
     * `model` by the Agent tool and by agents frontmatter, and a script author who reaches for the
     * spelling those two taught should not silently get the deployment's default model instead. The
     * alias carries a NAME exactly like this field does; a `Model` OBJECT is refused under either
     * spelling.
     */
    modelName?: string;
    thinking?: ThinkingLevel;
    systemPrompt?: string;
    images?: ImageInput[];
    /** Every sub-field must be a POSITIVE number — a non-positive value is a DISABLE sentinel downstream
     *  (or an exhausted budget), never a tighter ceiling, and is refused (see {@link NON_POSITIVE_LIMIT_MSG}). */
    limits?: {
        maxTurns?: number;
        maxWalltimeMs?: number;
        maxTokens?: number;
        maxCostUsd?: number;
    };
    /**
     * design/199 件A, TIGHTEN-ONLY: `"roots"` — the read-face containment judgment — is the ONLY value a
     * script may set. `"open"` is the WIDENING direction (it removes the containment step), so it is
     * stripped and announced rather than applied; the type states the asymmetry, and
     * {@link pickWhitelist} enforces it on the untrusted value.
     */
    readFace?: "roots";
    /**
     * TIGHTEN-ONLY, the write seat's twin of {@link readFace}: `"roots"` — the write-face containment
     * judgment — is the ONLY value a script may set. `"open"` is the WIDENING direction, so it is
     * stripped and announced rather than applied.
     */
    writeFace?: "roots";
    /**
     * design/199 件B, TIGHTEN-ONLY: additional read-deny entries. Add-only at every layer a governed
     * SCRIPT can reach: the baseline's entries and the deployment-ACTIVE built-in set are always in
     * force and a script cannot remove or replace them (`compileReadDeny` treats additions as
     * add-only; `tightenTaskSpec` unions). Which built-in tiers are active is a DEPLOYMENT seat
     * (#245: `readDenyBuiltinTiers`/`readDenyBuiltinExclude` on RunnerDeps) that no script-side key
     * reaches — so anything a script writes here can only ever narrow what the child may read.
     */
    readDenyPatterns?: readonly ReadDenyEntry[];
}
/**
 * The SINGLE source of truth for the whitelist (a test pins that it contains no control-plane key). `objective`
 * + `modelName` are handled explicitly in {@link buildGovernedChildSpec}; the rest map 1:1 onto `TaskSpec`.
 *
 * WHAT HAPPENS TO EVERY OTHER FIELD — {@link pickWhitelist} READS these keys and only these keys; it never
 * enumerates the script's fields to judge them, which is precisely the complete-by-construction property this
 * module is built on (nothing can leak by being forgotten in a denylist). The consequence is that an
 * unrecognized field is STRIPPED, not refused: the spawn proceeds on the baseline as if the field had never
 * been written. That silence is what {@link STRIPPED_KEYS_NOTICE_CODE} exists to close — one aggregated
 * notice per governed build names the fields that did not cross the seam, so a mistyped budget axis (a
 * top-level `maxCostUsd` that belongs inside `limits` since design/164, which would run the child unbounded
 * on exactly the axis the author tried to bound) or a rejected containment request is visible to the operator
 * instead of evaporating. Turning the strip into a LOUD REFUSAL that fails the spawn is a deliberate
 * NON-GOAL of this window: it would break every existing script carrying a harmless extra field, and is held
 * for a BREAKING window.
 */
export declare const WHITELIST_KEYS: readonly ["objective", "modelName", "thinking", "systemPrompt", "images", "limits", "readFace", "writeFace", "readDenyPatterns"];
/**
 * #505 ② — ACCEPTED ALIAS SPELLINGS on the spec: `alias → the {@link WHITELIST_KEYS} member it means`.
 *
 * One axis, three faces, two words: the Agent tool takes `model`, agents frontmatter takes `model`, and
 * this seam takes `modelName` (the name is not free to change here — `model` on a `TaskSpec` is the
 * resolved `Model` OBJECT, and the phases meta of the workflow tool already spends the word too). Renaming
 * across three surfaces buys uniformity at the price of a breaking churn in two of them; MUTUAL
 * RECOGNITION buys the same authoring outcome for the price of this map. So the alias is normalized here,
 * at the single door untrusted spec objects come through, and NOTHING downstream of {@link pickWhitelist}
 * ever sees the alias spelling — the rest of this module, `buildGovernedChildSpec` and `resolveModelName`
 * included, keeps reading exactly one field.
 *
 * An alias is NOT a whitelist key: `WHITELIST_KEYS` stays the closed set of fields that map onto `TaskSpec`,
 * so the "no control-plane key on the whitelist" property is untouched and a `Model` object written as
 * `model` is still structurally unable to reach the child (it is type-refused, not copied — see the
 * normalization at the foot of `pickWhitelist`).
 *
 * A test pins that this map has exactly ONE member, because the normalization arm below is written for that
 * one member by name: reading it generically would mean re-reading `s[canonical]` a second time, and the
 * single-read discipline every other field in this file follows is worth more than a loop over one entry.
 */
export declare const SPEC_KEY_ALIASES: ReadonlyMap<string, string>;
/**
 * One field of an untrusted agent spec that did NOT reach the child, and why.
 *  - `not_whitelisted` — the field is outside {@link WHITELIST_KEYS}: a control-plane field, a work field
 *    spelled at the wrong nesting level, or plain noise. It was never read.
 *  - `not_a_tightening_value` — the KEY is whitelisted but only its containment-TIGHTENING value may cross
 *    the seam, and the script wrote a different one. Today that is `readFace`, where `"roots"` tightens and
 *    anything else (`"open"`, or garbage) would either widen the child's read face or ask for a value the
 *    resolver does not define.
 */
export interface StrippedSpecKeyNote {
    key: string;
    reason: "not_whitelisted" | "not_a_tightening_value";
}
/** The {@link EngineNotice} family for the strip announcement (see {@link WHITELIST_KEYS}). ONE notice per
 *  governed build, listing every field that did not cross the seam — a governed script can carry an
 *  arbitrary number of unrecognized fields, and a per-field notice would turn one authoring mistake into a
 *  flood. Not de-duplicated across builds: each spawn is a distinct fact about a distinct spec — and
 *  `detail.occurrenceId` (#495) is what makes that sentence checkable by a consumer, which otherwise saw
 *  two builds of one spec as one repeated notice with every key equal. */
export declare const STRIPPED_KEYS_NOTICE_CODE = "workflow.governance_key_stripped";
/**
 * #505 ① — the OPTIONS-side twin of {@link STRIPPED_KEYS_NOTICE_CODE}. `agent(spec, opts)`'s SECOND
 * argument crosses its own membrane (`safeAgentOptions` in workflow-primitives.ts), which copies a small
 * accepted set and dropped everything else in silence — a mistyped `lable`, an option that belongs on
 * another seam, or a known option carrying a value its type check rejects (`label: 42`) all vanished with
 * the call still succeeding.
 *
 * A SEPARATE code from the spec-side strip on purpose: the two membranes accept different key sets and the
 * message names the set, so an operator reading `workflow.governance_key_stripped` about an OPTIONS key
 * would look for it in the spec whitelist and correctly not find it. The severe half of this family — a
 * SPEC field written on the options object — is not announced at all but REFUSED at the membrane (a
 * silently ignored `model` runs the child on the deployment default, which is a different agent than the
 * author asked for); this notice covers only the residue that is genuinely just ignored.
 */
export declare const IGNORED_AGENT_OPTIONS_NOTICE_CODE = "workflow.agent_option_ignored";
/** The announcement budget, shared with the spec-side survey: one bound, one renderer, one place. Exported
 *  so the surveying caller can stop COLLECTING at the same number this emitter would render. */
export declare const MAX_ANNOUNCED_KEYS = 20;
/**
 * Announce the ignored option keys — one aggregated notice per `agent()` call, never one per key (an
 * options object's key count is the script's to choose). `sample` is expected pre-bounded by the caller to
 * {@link MAX_ANNOUNCED_KEYS}; `total` is exact. Key names are script-authored text landing on an operator
 * surface, so they cross {@link renderStrippedKey} — the same scrubber the spec-side line uses, never a
 * second one — and the caller quotes nothing itself.
 */
export declare function emitIgnoredAgentOptionsNotice(total: number, sample: readonly string[], accepted: readonly string[], onNotice?: (n: EngineNotice) => void): void;
/** Per-child workflow ceilings the engine forces onto every spawned agent (design/98 §D.6), independent of
 *  what the script asks for. The child's effective limits = min(script, baseline, these). */
export interface WorkflowChildCaps {
    /** design/164 — per-child wall-clock ceiling in MILLISECONDS (was `perAgentTimeoutSec`, seconds).
     *  No default: a deployment that wants a ceiling states it. */
    perAgentMaxWalltimeMs?: number;
    childMaxCostUsd?: number;
    childMaxTokens?: number;
    childMaxTurns?: number;
}
/**
 * RB-378 disclosure — one resource field whose EFFECTIVE (applied) value differs from what the script's spec
 * literally carried. `requested` is `undefined` when the script omitted the field entirely (a baseline/cap
 * ceiling was FORCED onto an unset field, not just a script value pushed down). Emitted so a caller can tell
 * "the script asked for/omitted X" apart from "the child actually ran under Y" — see
 * {@link buildGovernedChildSpec}'s `onResourceClamp` param.
 */
export interface ResourceClampNote {
    field: "maxCostUsd" | "maxTokens" | "maxWalltimeMs" | "maxTurns";
    /** What the script's spec literally set for this field, or `undefined` if it never set it. */
    requested: number | undefined;
    /** The effective value the child actually runs under (min of script/baseline/caps, or a forced ceiling). */
    applied: number;
}
/**
 * The worktree-overlay merge — `{ ...base, ...worktreeBase }` with ONE amendment: an overlay key
 * whose value is OWN NULLISH (`undefined` OR `null` — both plain-JS deployment shapes) never
 * overrides `base`; it reads as the absent key its value spells ("an absent overlay field inherits
 * from base", the documented semantics). The GENERIC successor of the per-key protections that grew
 * one field at a time (three face arrays, then `onAsk` — the mount-side deletes, which remain for
 * their base-slot readings) while every OTHER same-shaped key stayed destructive: a surviving own
 * nullish overlay key erased the base value — `toolPolicy` / `shellGate` / `limits` / `hooks` /
 * `restoreGatedTools` / `readFace` / … — for exactly the children an untrusted script can route to
 * the overlay (`isolation: "worktree"`), own-`undefined` keys included (a spread copies them; the
 * old `=== null` face drop missed that half). One rule at the one merge point: a new baseline key
 * is safe with no list to extend, and there is no exception list — the one key that used to give
 * `null` a meaning of its own (`checkpointStore`, the durable off switch) now spells it as the word
 * `"disabled"`, an ordinary non-nullish value the rule already keeps. Explicit non-nullish overlay
 * values keep winning wholesale — the documented deployment override.
 *
 * ACCEPTED COST, stated (adversarial-review round 2): the rule is direction-blind — a CAPABILITY
 * key's out-of-type overlay `null` used to WITHHOLD the base value by the same spread accident
 * (e.g. `worktreeBase: { getApiKeyAndHeaders: null }` overwrote the base resolver AND defeated the
 * parent-credential `=== undefined` fill in workflow.ts — silently blocking the documented
 * inheritance, the same seam-defeat disease the onAsk r3 ruling deleted null for), and such a key
 * now inherits per the documented contract. Deployments withhold capabilities from isolated
 * children with IN-TYPE spellings (`tools: []`, a deny `toolPolicy`, `handsReadOnly: true`);
 * `getApiKeyAndHeaders` currently has no in-type disable value — that expressiveness gap is a
 * baseline-contract question, deliberately not solved by resurrecting undocumented null semantics.
 */
export declare function overlayWorktreeBaseline(base: WorkflowGovernanceBaseline["base"], worktreeBase: WorkflowGovernanceBaseline["base"]): WorkflowGovernanceBaseline["base"];
/** Resolve an LLM-supplied model NAME to a deploy-configured `Model`, FAIL-CLOSED against the allowlist.
 *  Throws (never falls open to the whole catalog) when no allowlist is configured or the name is not on it. */
export declare function resolveModelName(name: string, allowlist: string[] | undefined, models: Record<string, Model> | undefined): Model;
/**
 * Build a TRUSTED child `TaskSpec` for an LLM-authored sub-agent (design/98 §2.5). Complete-by-construction:
 *   1. read ONLY the whitelist work-fields off the untrusted script spec (type-checked);
 *   2. resolve `modelName` → a deploy `Model` against the allowlist (fail-closed);
 *   3. clamp resource limits to the baseline + workflow ceilings;
 *   4. `tightenTaskSpec(baseline.base, safe)` → the child spec (baseline governance FIRST, safe work-fields
 *      overlay; safety fields can only equal-or-tighten — belt-and-suspenders over the whitelist).
 * Every governance/control-plane field of the result comes from `baseline.base` (the deployment's trusted
 * config), never from the script.
 *
 * RB-378: `onResourceClamp`, when given, receives the non-empty list of {@link ResourceClampNote}s produced by
 * step 3 — i.e. it fires exactly when the child's effective maxCostUsd/maxTokens/maxWalltimeMs/maxTurns diverges
 * from what the script's spec literally asked for (never called when nothing was clamped/forced). The clamp
 * VALUES and the fact that it is always-tighten-never-raise are UNCHANGED by this param — it is a pure
 * disclosure hook the caller can wire to a log/event channel (see `buildWorkflowPrimitives`, which logs it
 * onto the run's log stream so the LLM-authored script's caller can see requested→applied per field instead
 * of the child silently running under different limits than the script wrote).
 *
 * `onNotice` is the deployment's structured notice sink (`RunnerDeps.onNotice`): step 1's strip is announced
 * through it as one aggregated {@link STRIPPED_KEYS_NOTICE_CODE} notice (see {@link WHITELIST_KEYS}). An
 * absent sink keeps the historic loudness (`console.warn`); the delivery itself is swallow-guarded, so no
 * sink can turn an announcement into a failed spawn.
 */
export declare function buildGovernedChildSpec(scriptSpec: unknown, baseline: WorkflowGovernanceBaseline, models: Record<string, Model> | undefined, caps?: WorkflowChildCaps, onResourceClamp?: (notes: ResourceClampNote[]) => void, onNotice?: (n: EngineNotice) => void): TaskSpec;
