import type { A2aServerSpec, McpServerSpec, RunnerDeps, TaskSpec } from "./types.js";
import { type ToolAxesSurface } from "./mcp-server-spec.js";
import type { ToolPolicy } from "./tool-policy.js";
/** The closed set of administratively lockable configuration keys. Adding a member is a deliberate
 *  edit HERE (tsc forces the registry row), never a free-form string. */
export type LockedKey = "mcp" | "toolPolicy" | "compliancePosture" | "retentionPolicy";
/**
 * The closed locked-key registry: each key names the exact `TaskSpec` fields it guards. Keys whose
 * field set is EMPTY guard deployment-only surfaces (`compliancePosture` rides
 * `RunnerDeps.compliancePostureResolver`, `retentionPolicy` rides `RunnerDeps.retentionPolicy`) —
 * there is structurally no untrusted spec field to refuse, so for those the lock is a declaration
 * consumed elsewhere (the retention startup validation keys off `retentionPolicy` being locked; the
 * server control plane reads the declaration for its own sync gate). `satisfies` keeps the row set
 * and the {@link LockedKey} union in lockstep — tsc refuses a key without a row and a row without a
 * key.
 */
export declare const LOCKED_KEY_REGISTRY: {
    readonly mcp: {
        readonly specFields: readonly ["mcp"];
    };
    readonly toolPolicy: {
        readonly specFields: readonly ["toolPolicy", "basePolicyForResumeEdit"];
    };
    readonly compliancePosture: {
        readonly specFields: readonly [];
    };
    readonly retentionPolicy: {
        readonly specFields: readonly [];
    };
};
/** A deployment's lock declaration ({@link RunnerDeps.lockedConfig}). Config-time, trusted. */
export interface LockedConfig {
    /** The keys this deployment locks. Wire-validated against {@link LOCKED_KEY_REGISTRY} at the
     *  preflight door — an unknown member is a loud `config.locked_registry` refusal, never a no-op. */
    keys: readonly LockedKey[];
}
/** The preflight's resolved snapshot — the ONLY thing downstream task assembly reads for the
 *  guarded slots (never the raw `spec.mcp` / `spec.a2a` / `spec.toolPolicy` fields). */
export interface LockedPreflight {
    /** The validated lock set (empty when the deployment declares none). */
    lockedKeys: ReadonlySet<LockedKey>;
    /** Effective MCP server list for this task: a validated, frozen SNAPSHOT of `spec.mcp` when the
     *  key is unlocked (see {@link snapshotAndValidateMcpEntries} — the content-class and `toolAxes` declarations
     *  are read once and judged here, so no mint site downstream can be handed a different value than the door
     *  approved); with `mcp` locked a spec-supplied value has already been refused, so this is always
     *  `undefined` (core has no deployment-level MCP seat — a locked deployment mounts no
     *  task-supplied servers). */
    mcp: TaskSpec["mcp"];
    /** The task's A2A peers: a frozen SNAPSHOT of `spec.a2a` whose `toolAxes` were judged here by the same reader and with the same
     *  disposition as an MCP server's (a declaration that does not fit refused the whole prepare), each peer carrying the reader's plain
     *  copy; `undefined` when the spec declares no peer (absent or empty). Read once, here: the peers dialed are the peers judged. */
    a2a: TaskSpec["a2a"];
    /** Effective caller tool policy: the same `spec.toolPolicy ?? deps.toolPolicy` slot the gate has
     *  always enforced, resolved ONCE here. With `toolPolicy` locked, the deployment's own
     *  `deps.toolPolicy` is the whole answer (a spec-supplied policy has already been refused). */
    toolPolicy: ToolPolicy | undefined;
    /** F-012: the effective resume-edit re-adjudication OVERRIDE, resolved lock-aware in
     *  the SAME slot as {@link toolPolicy}. With `toolPolicy` locked, a spec-supplied
     *  `basePolicyForResumeEdit` has already been refused wholesale, so this is the deployment's own
     *  `deps.basePolicyForResumeEdit` (or undefined — the caller-policy fallback then applies at the use
     *  site). Unlocked: `spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit`. */
    basePolicyForResumeEdit: ToolPolicy | undefined;
}
/**
 * How one configuration door is entered: `seat` names the input that carries the value the door judges — the task's configuration or the
 * deployment's deps. A prepare enters each door as it stands ({@link ENTER_AS_IS}: the door's own coded throw); a resume's screen before the
 * claim enters the same door through the resume's refusal channel, which names the seat (`asResumeConfigRefusal`). One door, one function,
 * two entrances: the seat is written once, where the door is called.
 */
export type ConfigDoorEntry = <T>(seat: "taskConfig" | "deps", door: () => T) => T;
export declare const ENTER_AS_IS: ConfigDoorEntry;
/**
 * Validate a deployment's lock declaration against the closed registry. Fail-loud on any unknown
 * key (`config.locked_registry`): a security control that doesn't parse must refuse to arm, not
 * degrade to "that key silently unlocked". Exported for deployments that want the check at their
 * own startup; the task-preparation preflight runs it on every leg regardless (a mis-declared lock
 * then fails the first prepare instead of silently guarding nothing — and, on a resume, the screen before
 * the claim, which calls the same preflight).
 *
 * Total over the declaration: anything but `undefined` (no locks) or plain data whose `keys` is a list of registry names — `null`, a
 * list, a primitive, a `keys` that is absent or not a list, a declaration that is not plain data (a Proxy, a `Set` of keys, a class
 * instance, a getter that throws), an element that is not a string, a string that is not a registry name — is the same coded refusal
 * naming `RunnerDeps.lockedConfig` and the member (an element by its index), never a TypeError and never "no locks". The declaration is
 * read once, by the one declaration reading.
 */
export declare function resolveLockedKeys(config: LockedConfig | undefined): ReadonlySet<LockedKey>;
/**
 * design/378 — turn a caller-owned MCP entry list into FROZEN PLAIN DATA, reading every own property
 * exactly once.
 *
 * WHY A COPY. `McpServerSpec.contentOrigin` decides whether a server's tools mark the session's
 * memory, and the entry objects travel a long way: the preparation door validates them, then
 * `materializeMcpTools` connects them, then every mid-task refresh re-reads the same object. A value
 * read off a LIVE object is only true for as long as the object holds still, and the fail-open
 * direction is the reachable one — so both boundaries consume data instead.
 *
 * The read discipline is the load-bearing part, and it is why the copy is made in the pass that reads
 * rather than "read the property, then copy the object": a getter answering ALTERNATING LEGAL values would
 * satisfy any number of re-reads (`"external"` to a validator, `"local"` to a mint site), and no
 * vocabulary guard anywhere has purchase on a legal value. One pass evaluates every member EXACTLY ONCE
 * and materializes the result as plain data; every later reader — validator, connect, refresh — then sees
 * the same byte. Frozen so nothing downstream rewrites it.
 *
 * Plain data, stated: an entry IS plain data, to its whole depth, or it is refused whole. The entry is read
 * by the one declaration reading ({@link receiveDeclaration} — the engine's one receipt of a caller's object,
 * in its declaration mode): every own member read once off its captured descriptor, every level copied, every
 * copied container frozen; an object the entry reaches through two members is read once, and both hold the one
 * copy. So a server's `transport` with its `args` / `env` / `headers`, `allowTools`, `toolFaces`, `toolAxes`
 * and a peer's `headers` and `allowSkills` are the copy's own, and a host that edits its own nested object after
 * the door (a header value, an `allowSkills.push`) reaches nothing the engine holds: the dial, a refresh and a
 * re-dial all work from this copy, on both surfaces. The axes are then judged on that copy by their own reader
 * ({@link judgeEntryToolAxes}).
 *
 * There is no third outcome between "copied whole" and "refused": a value the copy cannot carry whole is a
 * declaration the engine cannot read — a Proxy at any level, an object whose prototype is neither
 * `Object.prototype` nor null (a class instance, a map, `process.env`, a record built on a prototype or in another
 * realm, whose members a copy would drop or could not tell from its own), a function, a structure that contains
 * itself, a getter that throws (whatever it throws), a nesting too deep to read, a list element that is not a
 * string, a member named `__proto__`, an entry that is not an object. Each is refused as `config.invalid_argument`
 * naming the declarer and the member where reading stopped (`toolFaces.write_file`, `allowTools[2]`), never as the
 * raw error the host's code threw, and never by handing the host's object on: a member some of whose getters have
 * already run would be read again downstream, and could answer differently. The door refuses the whole prepare and
 * a library entrance the whole call, with this one sentence either way.
 *
 * Exported because the SECOND boundary needs the identical guarantee: `materializeMcpTools` is public
 * and a library caller reaches it without passing any door, so it snapshots its own input through
 * this same function (idempotent over an already-frozen entry). One spelling, both entrances.
 */
export declare function snapshotMcpEntries(entries: readonly McpServerSpec[]): McpServerSpec[];
/**
 * The refusal of a declaration the engine cannot read (`config.invalid_argument`). `declarer` is the entry's own name as the engine captured
 * it ({@link capturedName}) — absent when there was none to capture — so a consumer that holds the refused entry by a name (the A2A
 * entrance's warning, its collision check) takes it from here and never reads the caller's object again.
 */
export type DeclarationRefusal = Error & {
    code: "config.invalid_argument";
    declarer?: string;
};
/**
 * The name a refused entry is held under by a consumer that holds it by a name (the A2A entrance): the refusal's own `declarer`, read as
 * own data — never through anything the refusal's prototype carries — or `undefined` (the consumer names the entry by its place).
 */
export declare function refusedDeclarer(refusal: unknown): string | undefined;
/**
 * One entry of EITHER surface that declares `toolAxes` (an MCP server, an A2A peer) as frozen plain data, read by the discipline
 * {@link snapshotMcpEntries} states: the entry and everything in it read once by the one declaration reading and copied to its whole
 * depth, or the entry refused ({@link DeclarationRefusal}), naming the member where reading stopped. The A2A peer's door and its
 * materialization snapshot through this same function, so the two surfaces' declarations reach the one judge ({@link judgeEntryToolAxes})
 * read the same way; `at` is the surface the entry sits on, which only decides what the refusal calls the declarer.
 */
export declare function snapshotDeclaredEntry<E extends McpServerSpec | A2aServerSpec>(entry: E, at: ToolAxesSurface): E;
/**
 * The `toolAxes` question for ONE snapshot entry of either surface (`at`: an MCP server, an A2A peer) — asked by the preparation door (a
 * refusal ends the prepare) and by each materialization entrance (a refusal fails that server's or that peer's dial), so there is one
 * reading, one copy and one sentence whichever surface and whichever time it is asked. `ok: true` carries the entry with the reader's
 * PLAIN COPY of the axes in place of the caller's object: what was judged is the very value the connect and every refresh read, not a
 * live object that could answer differently later. The copy is frozen with the entry — a tool that shallow-copies a server
 * (`mountedMcp()`) to customise its axes must not rewrite what the parent reads. `ok: false` carries the coded refusal
 * (`config.invalid_argument`) naming the declarer, the tool (or skill) and the member, never a value.
 */
export declare function judgeEntryToolAxes<E extends McpServerSpec | A2aServerSpec>(copy: E, at: ToolAxesSurface): {
    ok: true;
    entry: E;
} | {
    ok: false;
    error: Error & {
        code: string;
    };
};
/**
 * The single preflight resolver: validate the lock declaration, refuse a spec that tries to occupy
 * a locked slot (`config.locked_key`, two-state — the whole prepare is rejected, nothing is
 * silently dropped or merged), and produce the resolved snapshot downstream assembly consumes.
 * Runs BEFORE any side effect of task preparation — and, on a resume, before the claim: the resume's
 * screen calls this same function (`enter` naming each refusal's seat) and hands the leg the frozen
 * entries it judged, so the leg's prepare judges engine-owned plain data and dials what was judged.
 */
export declare function preflightLockedConfig(spec: Pick<TaskSpec, "mcp" | "a2a" | "toolPolicy" | "basePolicyForResumeEdit">, deps: RunnerDeps, enter?: ConfigDoorEntry): LockedPreflight;
/**
 * The two caller policies a prepare resolves to: the task's own seat over the deployment's, or the deployment's alone
 * under a `toolPolicy` lock — one resolution, read by the preflight above (which a resume enters before its claim).
 */
export declare function callerPolicySeats(spec: Pick<TaskSpec, "toolPolicy" | "basePolicyForResumeEdit">, deps: Pick<RunnerDeps, "toolPolicy" | "basePolicyForResumeEdit">, toolPolicyLocked: boolean): Pick<LockedPreflight, "toolPolicy" | "basePolicyForResumeEdit">;
