@sema-agent/core
Version:
Stateless, task-oriented AI agent core
801 lines • 54.6 kB
TypeScript
/**
* The ORIGIN of a surviving `ask` — WHICH AUTHORITY raised it, or took it over — as one word from a
* closed set; and, below it, the ask's CLASS SET, the mode × class arbitration table and the one predicate a
* permission mode is read through.
*
* Why an axis of its own. The tool gate has several automatic ask-resolution lanes (the auto-mode
* classifier today; the persisted-rule lane and the sandbox-admission leg are the same shape), and each
* one's eligibility used to be written as a growing conjunction of NEGATIVES — "not an org ask, not a
* hook ask, not a matched ask rule, not the classifier's own fallback, not the question tool, not a
* marked call…" — one more conjunct per kind of ask the engine learned to mint. Every conjunct was
* right; the FORM was the debt: a new kind of ask had to be remembered at every lane, and forgetting
* one was a silent loosening (the new ask fell into whichever lane nobody had excluded it from).
*
* The replacement: the gate derives the origin ONCE from the facts its layers established
* ({@link askOriginOf}) and stamps it on the surviving ask as the DISCLOSED word; the lanes read the ask's
* class SET ({@link classifyAsk}) folded onto one row of a table that MUST name every class
* ({@link MODE_ARBITRATION}, fenced by {@link AssertAllKeysHandled}). Adding a kind of ask is then: one
* class here, one arm in the derivation, one row in the table — and the compiler refuses the build until
* the row exists, so the default for an unconsidered class is a red build, never a silent allow.
*
* What is and is not an origin. An origin is WHO raised the question. Two facts that ride beside it
* are deliberately not origins: `requiresRealApproval` says what may CLEAR the question (a governance
* bit any origin can carry — a safety policy's own ask carries it with origin `policy`), and the
* gate's `autoMode` seat says whether a classifier is WIRED at all (a property of the session, not of
* the ask). Both stay where they are and are consumed beside the origin, not folded into it.
*/
import type { PermissionResult } from "./tool-policy.js";
/**
* The closed set, in AUTHORITY order — the order {@link askOriginOf} resolves a fold in when several
* facts hold on one call (a hook ask folded over a policy ask, an org stamp over either). The order
* decides only the WORD; every lane table below is total over the set, so it can never turn an ask
* one member would have excluded into one another member admits (the invariant is pinned).
*
* - `content_question` — the reserved question tool: a content-ROUTING signal, not a permission
* question; it leaves the gate through the content channel, never through a permission decider.
* - `ancestor_marked` — the call is MARKED by an ancestor (an inherited approver-unavailable float, or
* a durable mandate floated down because this task can park it): it must reach the park with no
* synchronous decision-maker in between. The word names WHO raised it (an ancestor's mark), like every
* other member; whether the ask can be resolved is the park's business, not the origin's.
* - `org_unavailable` — the deployment is org-governed and could not read its snapshot: the fail-closed
* tighten, cleared by a person only ("does the org forbid this?" is unanswered, and no automatic
* lane may answer it).
* - `org_rule` — an organization policy rule asked (or stamped an existing ask): "no configuration
* clears this, a person nods".
* - `rule_store_unavailable` — the person's rule store WAS read, but the CALL could not be read against
* their standing deny/ask rows (the tightening lexer's `unreadable` word: an expansion where a rule
* reads a word, an unterminated quote, a syntax error — the answer's `reason`): whether one of those
* rows forbids this call is unknown, so an allow tightens to an ask a person must clear and no automatic
* lane may answer. A store that could not be READ is not this word: its rows take no part in the
* decision (the call is judged as if the person had no persisted rows) and the run carries one
* `permission.rule_store_unavailable` notice instead.
* - `hook` — a deployment's PreToolUse screening face raised or co-raised the ask; the word is
* engine-stamped at the fold and is the deployment's own per-call adjudication.
* - `ask_rule` — an EXPLICIT `ask` permission rule matched: a person's standing "ask me each time",
* an instruction rather than hesitation.
* - `denial_limit_fallback` — the auto-mode classifier reached its denial bound and handed the call
* to a person; the classifier stands BEFORE this ask, never after it.
* - `shell_gate_tighten` — the gate's own irreversibility tighten raised the ask over a tier the COARSE
* shell seat installed (the shape tier on every full shell, or the `shellGate: "always"` floor), not an
* explicit per-tool mark. The gate records that its own tighten spoke; a policy stamping
* `decisionReason: "safety"` on its own ask does not become one.
* - `safety_tighten` — the gate's own post-fold tightens over an EXPLICIT fact of the call: the tool's
* egress mark, its irreversibility mark, a peer-message referral in auto mode, a write onto the
* write-protected table. Same recording rule as above.
* - `policy` — everything else: a deployment `ToolPolicy` ask or a mode-default ask. This is the
* hesitation family — the one an armed classifier exists to resolve. The two tighten words are
* classifier-eligible too (arming auto mode is the deployment's explicit choice to let the
* classifier be the explicit ask-resolution for those classes); they are separate words so a deny
* observer or an approval card can say WHICH engine layer raised the question, which one word
* could not.
*/
export declare const ASK_ORIGINS: readonly ["content_question", "ancestor_marked", "org_unavailable", "org_rule", "rule_store_unavailable", "hook", "ask_rule", "denial_limit_fallback", "shell_gate_tighten", "safety_tighten", "policy"];
export type AskOrigin = (typeof ASK_ORIGINS)[number];
/** Whether a value is a member of the closed origin set. Exported because the word crosses process
* boundaries: a host validating one it received must not hand-roll the set. */
export declare function isAskOrigin(v: unknown): v is AskOrigin;
/**
* A compile-time fence: `T` must be `never`. Instantiate it with `Exclude<<closed set>, keyof <table>>`
* beside a table that is meant to be TOTAL over the set — while every member has a row the argument is
* `never` and the build is green; add a member without its row and the argument is that member's
* literal, which does not satisfy the constraint, so the build is red AT THE FENCE with the missing
* member spelled out in the error. The table's own `satisfies Partial<Record<…>>` clause guards the
* other direction (a row for a word that is not in the set).
*/
export type AssertAllKeysHandled<T extends never> = T;
/** The facts about one call that only the gate holds — the derivation's inputs beyond the decision
* itself. Every member is a plain boolean/word read ONCE by the gate, so the derivation is a pure
* function that a test can drive arm by arm. */
export interface AskOriginFacts {
/** The call is on the reserved question tool. */
readonly contentQuestion: boolean;
/** The call is marked unresolvable by an ancestor (see the gate's `isMarkedUnresolvable` seat). */
readonly markedUnresolvable: boolean;
/** Which org state, if any, spoke on this call — the gate's own record of its org layer, never a
* read-back off the decision (a policy can put `decisionReason: "org_rule"` on its own ask; the
* gate's cell cannot be written by a policy). */
readonly org: "rule" | "unavailable" | undefined;
/** The gate's own record of its persisted-rule lane's READ: the store was read, but THIS CALL could not be read
* against the person's deny/ask rows (the lane's answer carried the lexer's reason) — the origin
* `rule_store_unavailable`. Absent: the lane read the call, or its store could not be read and its rows took no
* part. Written only by the gate. */
readonly ruleCallUnreadable?: boolean;
/** Which of the gate's OWN post-fold stations minted the standing ask, if one did — again the gate's
* record of its own layer (a station replaces an `allow` wholesale, so at most one fires per pass),
* never a read-back of `decisionReason: "safety"` off a decision a policy could have composed. `unstated` is
* the last station: the mode's answer for a call to a tool that states no permission of its own when no layer
* stated the allow — its origin is `policy` (no tighten word), its class `unstated`. */
readonly tightened: "shell_gate" | "safety" | "unstated" | undefined;
/** The MANDATE mark inputs of this call — the tool SEAT's marks plus the gate's own per-call records
* ({@link MandateMarks}), read from the same two places the lane's own derivation reads them. The
* per-call PROBE member is deliberately not here: it is engine-stamped on the DECISION, and
* {@link ruleClearanceOf} reads it there, so one fact cannot arrive twice with two values. An inherited
* station passes the tool SEAT's marks of the task it runs in (its per-call records are the gate's, absent
* there); a station holding no seat at all passes `{}` — an honest empty, since the decision's own
* members still answer. */
readonly mandateMarks: Omit<MandateMarks, "probeMandated">;
}
/**
* The ONE derivation of an ask's origin, from the surviving decision plus the gate's own facts. The
* arms are in {@link ASK_ORIGINS} order (strongest authority first); the members it reads off the
* decision are the three the engine already treats as tighten-only provenance (`decisionReason:
* "hook"` is engine-stamped at both fold arms; `matchedAskRule` and `denialLimitFallback` are stamped
* by their mint sites and, when self-declared by a policy, can only opt that policy's own ask OUT of
* automatic resolution — the safe direction). Nothing here reads a member that could loosen.
*/
export declare function askOriginOf(decision: Extract<PermissionResult, {
action: "ask";
}>, facts: AskOriginFacts): AskOrigin;
/**
* Stamp the ask's origin word — and withdraw the disclosures that speak for the TIGHTEN alone when some
* layer above it is the reason this call is being asked about. Today that is one member: the read
* boundary's directory offer, whose claim holds only while the tighten is the whole reason. Every higher
* rung is a layer with a reason of its own — a standing `ask` row, a call unreadable against the rows, an org
* verdict, a hook, the denial-limit fallback — which no directory grant answers, so the ask would stand
* after the grant and the offer would have promised a clearing it cannot deliver (measured: `cat
* <out-of-root>` under a standing `Bash(cat:*)` ask rule offered the directory and kept asking after it).
* It lives at the one derivation rather than the three stamp sites: the withdrawal's input IS the origin
* word, so a site that stamped without it would be a second, quieter answer to one question.
* The same stamp writes the ask's CLASS members (`askClass` / `classes`, {@link askClassMembersOf} over the
* `classification` the caller derived from this decision under these facts): the word and the class leave the
* gate on one object, and a value a policy put on either is replaced.
*/
export declare function stampAskOrigin<T extends Extract<PermissionResult, {
action: "ask";
}>>(decision: T, facts: AskOriginFacts, classification: AskClassification): T & {
origin: AskOrigin;
} & AskClassMembers;
/**
* CAN a persisted allow rule clear this ask — and, if not, which door closed? ONE derivation with TWO
* readers, which is the whole point of the shape: the approval card's `ruleOffersAbsence` word and the
* ask's own {@link import("./tool-policy.js").AskRequest.mandated} bit. They used to be two
* enumerations of one boundary, and the drift was live — the card's lane speaks for one tool, only
* where a rule store is wired and only on a task that can hold a rule, so the same mandated read asked
* as a mandated question on the shell tool and as an apparently ordinary one on the monitor tool.
*
* "Allow rules silence the classifier's questions, never a mandated one." The doors, as a SET:
* · the MARKS family — {@link persistedRuleMandateOf} over the tool seat's marks and the gate's own
* per-call records (the read station's structural demotion, an unanswered probe, an operator's
* per-call shell doctrine, the tool's own egress / irreversibility marks, a write the protection
* judge matched or could not resolve);
* · GOVERNANCE (`requiresRealApproval`) — an org verdict, a safety policy's own demand, the
* classifier's denial-limit fallback: only judgment clears it;
* · a HOOK-raised ask — the deployment's own per-call screening authority, which the lane's clearing
* conjuncts refuse;
* · ANCESTOR authority (`inheritedUnresolved` / `ancestorResolved`) — the ask belongs to an ancestor's
* approver and this task's rule lane never adjudicates it;
* · the bit already stamped on the ask, which no layer below the mint may clear (tightening-only).
*
* And the SHADOW doors, which are not mandates: an allow-layer member already matched and is outranked
* (`allowRuleShadowed`), or the person's explicit "ask me each time" rule matched
* (`matchedAskRule`). A rule minted from such a card silences nothing, so the lane offers none — but
* the reason is that a RULE is already speaking, not that the question is beyond rules, and a surface
* that auto-answers by posture is not crossing a mandate when it answers one.
*
* FOUR named readings of the one pass, because four callers need four different things and taking
* the wrong one is how this boundary drifts:
* · `word` — the DISCLOSED reason, first door wins, in the display order the card has always used
* (governance, the shadow, the hook, the ask rule, the two ancestor doors, then the marks). Display
* only. A structural door the offer factory owns (no lane wired, a tool it does not speak for, a
* task that cannot hold a rule) is decided BEFORE this and is deliberately silent;
* · `mandated` — the FACT the ask carries as its own member. Read off the whole mandate set, so which
* door is DISPLAYED cannot change it: an ask that is both shadowed and mandated is mandated;
* · `ruleMayClear` — the ENFORCEMENT reading the gate's clearing arms take. It is `mandated` plus the
* person's explicit `ask` rule, and deliberately NOT `word !== undefined`: `allowRuleShadowed`
* is an ACCOUNT of a refusal that already happened, display-only by contract, and reading it as a
* blocker would let a policy forwarding that metadata refuse a call nothing is blocking. A
* display word is never an authority;
* · `mandate` — WHICH table word the bit stands on, only when the MARKS family is its whole reason (no
* governance, hook or ancestor door, and no bit stamped without a word); absent, the bit's reason is
* not one word.
*/
export declare function ruleClearanceOf(ask: RuleClearanceFacts | undefined, marks: Omit<MandateMarks, "probeMandated">): {
word?: "mandated" | "shadowed";
mandated: boolean;
ruleMayClear: boolean;
mandate?: PersistedRuleMandate;
};
/** The facts one ask carries about whether a rule could clear it — the members {@link ruleClearanceOf}
* reads beside the tool seat's marks. Every one is either engine-stamped or, where a caller can
* self-declare it, tightening-only: a claim here can opt an ask OUT of automatic answering and never
* into it. */
export interface RuleClearanceFacts {
/** The bit a gate already stamped (see {@link import("./tool-policy.js").AskRequest.mandated}). */
mandated?: true;
/** The table word a gate stamped beside that bit (see {@link import("./tool-policy.js").AskRequest.mandate}). */
mandate?: PersistedRuleMandate;
requiresRealApproval?: boolean;
/** Allow-layer members matched and did not clear the ask (see {@link import("./allow-rule-shadow.js").AllowRuleShadow}). */
allowRuleShadowed?: readonly import("./allow-rule-shadow.js").AllowRuleShadow[];
decisionReason?: import("./tool-policy.js").DecisionReason;
/** An EXPLICIT `ask` permission rule matched this call (DSL, stamped at the rule policy's three
* matched-ask exits — bare, covering and param). */
matchedAskRule?: string;
/** The ask was minted by the deployment's own governance layer (see {@link import("./tool-policy.js").PermissionResult}). */
authoredBy?: "deployment";
/** The engine-stamped probe mandate (the reversibility probe declared this call's demotion
* structural — for the built-in shell probe, a read outside the session's allowed directories). */
probeMandated?: boolean;
/** An ancestor's approver answered "unavailable" for this call, or a durable mandate was floated here. */
inheritedUnresolved?: boolean;
/** The ask is being resolved at an ANCESTOR's frozen approver (the three inherited-lane mint sites
* pass it literally) — this task's rule lane never adjudicates it. */
ancestorResolved?: boolean;
/** The classes of the contributors the policy fold carried under this ask (see {@link import("./tool-policy.js").PermissionResult});
* a member whose row no rule may clear keeps the question a person's. */
foldedAskClasses?: readonly AskClass[];
}
/** The ask FACE's mandate members — the bit and its table word, spread at the four mint stations and the
* park row. The stations hand it the SAME facts object they hand the rule-offer factory, so the card's word
* and these members are readings of one derivation and cannot drift. The tool SEAT is not re-judged here:
* its marks were folded into the stamped bit and word at the gate, and a station minting a request from an
* ANCESTOR's decision holds no seat of this task's to judge. */
export declare function askMandateMember(ask: RuleClearanceFacts | undefined): {
mandated?: true;
mandate?: PersistedRuleMandate;
authoredBy?: "deployment";
};
/** The clearance facts of an ask the engine did not construct (a policy's verdict), each member read ONCE into
* an engine-owned value — for a station that keeps the answer past its own side effects, where the verdict's
* accessors must not run again. The members a verdict may carry that narrow what may clear it. */
export declare function ownedClearanceFacts(d: Extract<PermissionResult, {
action: "ask";
}>): RuleClearanceFacts;
/**
* Which origins IMPLY the `requiresRealApproval` bit, and WHERE that implication is verified — the
* consistency table {@link askOriginOf} reads. An ask of a must-carry origin exists so that no automatic
* lane clears it (the org's asks: "no configuration clears this, a person nods"; the classifier's
* fallback: no automatic lane may re-clear what the classifier already refused), so the bit must ride
* it. The bit is NOT derived from this table: the write sites each have their own reason, and a
* table-derived bit would have to either strip a safety policy's mandate or make every hook ask
* non-budgetable. The table only refuses the one shape that is a defect: a must-carry origin without
* the bit — and says where that refusal can honestly happen:
* - `"at_derivation"` — the origin is derived from a GATE fact only the engine writes (`facts.org`),
* so a missing bit is an engine defect and {@link askOriginOf} fails loud;
* - `"at_mint"` — the origin is derived from a decision MEMBER a policy may compose
* (`denialLimitFallback`: a self-declared one opts the policy's own ask out of automatic
* resolution, the safe direction, not a defect), so the invariant is pinned at the engine's two
* mint sites (the gate's fallback re-mint and the inherited station's) rather than thrown at
* derivation, where a policy's own object could trip it;
* - `false` — the origin says nothing about the bit: a `policy` ask may carry it (a safety policy's
* own mandate) or not, and both are legal.
*/
export declare const ORIGIN_IMPLIES_REAL_APPROVAL: {
readonly content_question: false;
readonly ancestor_marked: false;
readonly org_unavailable: "at_derivation";
readonly org_rule: "at_derivation";
readonly rule_store_unavailable: "at_derivation";
readonly hook: false;
readonly ask_rule: false;
readonly denial_limit_fallback: "at_mint";
readonly shell_gate_tighten: false;
readonly safety_tighten: false;
readonly policy: false;
};
/** The fence over the consistency table: `never` while every origin has a row. */
export type RealApprovalTableCoversEveryOrigin = AssertAllKeysHandled<Exclude<AskOrigin, keyof typeof ORIGIN_IMPLIES_REAL_APPROVAL>>;
/**
* The MANDATE family — the closed set of words for "this ask stands by STRUCTURE, so a standing allow
* rule may not retire it". A DISCLOSURE vocabulary: the word rides the approval card and the park row
* ({@link persistedRuleMandateOf} picks one), while what a live lane may do with the ask is decided by
* the ask's CLASS SET ({@link classifyAsk}, {@link MODE_ARBITRATION}) — never by this one word.
*
* - `operator_always` — the operator's `always` floor: per-call confirmation for shell commands.
* - `tool_marks` — the tool's own egress / irreversibility / interaction declaration.
* - `probe_mandate` — this call's reversibility probe declared the demotion STRUCTURAL.
* - `probe_unanswered` — the probe was consulted and did not answer: the boundary is UNKNOWN.
* - `write_protection` — the table covers this write's target. A write onto load-executed configuration
* re-authorizes the agent, so the question is a property of the TARGET, not of anyone's hesitation.
* - `write_protection_unresolved` — the judge could not RESOLVE the target: `probe_unanswered`'s
* doctrine one layer over.
*/
export declare const PERSISTED_RULE_MANDATES: readonly ["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "write_protection_unresolved"];
export type PersistedRuleMandate = (typeof PERSISTED_RULE_MANDATES)[number];
/** Membership test for {@link PersistedRuleMandate} — the screen a row reader applies to a stored word. */
export declare function isPersistedRuleMandate(v: unknown): v is PersistedRuleMandate;
/** WHY a matching persisted allow rule did not clear the ask, in the sentence a PERSON reads — one row per
* word (the ladder this replaces ended in a catch-all arm). Display prose; nothing reads it back to decide. */
declare const MANDATE_DISCLOSURE: {
readonly probe_mandate: "the reversibility check declared this call structurally gated (the built-in shell check raises this for a read outside the directories allowed for this session) — it is cleared by confirming this call, never by a standing allow rule";
readonly probe_unanswered: "the reversibility check did not answer for this call (it timed out or failed), so whether the call reads outside the directories allowed for this session is unknown — it is cleared by confirming this call, never by a standing allow rule";
readonly operator_always: "this deployment mandates per-call confirmation for shell commands (shellGate: always)";
readonly tool_marks: "this tool carries egress/irreversibility/interaction marks (a mandated confirmation a rule cannot clear)";
readonly write_protection: "this write's target is on the deployment's write-protection table (the agent's own configuration is guarded one look per write) — it is cleared by confirming this call, never by a standing allow rule";
readonly write_protection_unresolved: "the write-protection judge could not resolve this write's target, so whether it lands on the protected table is unknown — it is cleared by confirming this call: no standing rule and no automatic decider answers for it, though a wired approver seat can";
};
/** The fence over the disclosure table: `never` while every word has a row. */
export type DisclosureTableCoversEveryMandate = AssertAllKeysHandled<Exclude<PersistedRuleMandate, keyof typeof MANDATE_DISCLOSURE>>;
/** The disclosed reason for one word. `undefined` — no mandate — is the lane's OTHER refusal: a standing
* "ask me each time" rule matched, which outranks the remembered yes on its own. */
export declare function mandateDisclosureOf(mandate: PersistedRuleMandate | undefined): string;
/**
* The mandate provenance of one call, judged from the SAME mark inputs the gate is driven with —
* the single source for "could a persisted allow rule clear this ask?". Allow rules silence the
* classifier's questions, never a mandated one, and this predicate is the mandated-family half of
* that boundary (the real-approval/governance half rides the decision's own `requiresRealApproval`
* bit, which the org layer stamps):
* · `probeMandated` — this CALL's own reversibility probe declared its demotion STRUCTURAL
* (`"probe_mandate"`), judged FIRST because it is the only per-call member here: the three below
* are properties of the TOOL and are true of every call on the seat, so a mandate that is true of
* this one call must not be shadowed by the tier that happens to carry it (#502: the built-in
* shell probe raises it for a listed reader naming a path outside the session's roots — a boundary
* the deployment declared, which is exactly what the shape tier alone cannot say);
* · `egress` — the tool's own external-write mark, judged next: it is the tool's declaration even
* when the shell seat also installed a tier on the same name;
* · `shellGated` + tier `"always"` — the operator's `always` floor, a per-call confirmation
* (`"operator_always"`); without the floor the shell seat installs the shape tier's `"maybe"`,
* and THOSE asks stay the rule lane's home turf (`undefined`) — that is the don't-ask-again main case, and the per-call member
* above is deliberately the ONLY thing that carves a mandate out of it;
* · a tool's OWN `"always"`/`"maybe"` irreversibility tier without the doctrine (`"tool_marks"`).
*
* The two per-call write-protection seats sit BELOW the probe words and ABOVE the seat marks, on the
* ladder's rule: a word no lane may answer outranks the rest, and a fact about THIS call outranks a
* property of the tool. Judged HERE, not in {@link irreversibilityMandateOf} which {@link safetyAxisOf}
* shares: such a write is not an irreversible action, and its park stays budgetable.
*
* Two consumers, one derivation: the gate's silencing arm (a matching rule is disclosed as shadowed
* instead of clearing the ask) and the runner's suggestion factory (a mandated ask offers no
* "stop asking me this" option — a rule minted from it would never clear it). A drift between the
* two would let a card offer a rule the lane then refuses to honor. The per-call member reaches both
* the same way every other per-call fact does: the gate stamps it on the surviving ask, the
* synchronous sites read it off the decision they spread, and the park leg threads its own parameter.
*/
export declare function persistedRuleMandateOf(marks: MandateMarks): PersistedRuleMandate | undefined;
/** The mark inputs of the two mandate derivations — properties of the tool SEAT (`egress`, `shellGated`,
* `irreversibility`) beside the two per-call facts the gate stamps. */
export interface MandateMarks {
egress?: boolean;
/** The tool's server declared that a call must be put to a person
* ({@link import("../engine/loop/types.js").AgentTool.mcpRequiresUserInteraction}): a mark of the tool's own, like `egress` — no
* persisted allow rule or standing grant clears the ask, and the class `requires_user_interaction` rides every ask on the call. */
requiresUserInteraction?: boolean;
shellGated?: boolean;
irreversibility?: "never" | "maybe" | "always";
/** The surviving ask's engine-stamped `probeMandated` — see
* {@link import("./types.js").ReversibilityVerdict.mandated}. */
probeMandated?: boolean;
/** The gate's own record that the write-protection judge covered this call and its table MATCHED
* (`GatePass.writeProtected`, written at the fourth post-fold tighten for a write tool's target and by the read station
* for a shell command's written targets). A GATE fact like the org words: it rides the pass, not the decision, so no
* layer outside the engine declares it. */
writeProtected?: boolean;
/** The same record for the judge's other non-clear verdict — consulted, could not RESOLVE the target.
* The pair is the write face's twin of `probeMandated` / `probeUnanswered`, mutually exclusive. */
writeProtectionUnresolved?: boolean;
/** The pass's own record that the probe was consulted and did not answer (`GatePass.probeUnanswered`):
* the boundary is unknown, which no standing yes may stand in for. The runner's suggestion factory does
* not see the pass and never passes it — a card may offer a rule the lane then discloses as shadowed
* (loud, never a silent override), the same drift window the `matchedAskRule` conjunct already has. */
probeUnanswered?: boolean;
}
/**
* The SAFETY AXIS of one ask — what makes its durable park NON-BUDGETABLE (`irreversible_ask`), judged
* from the same mark inputs as the rule-lane mandate plus the gate's own record of which tighten minted the
* ask. `egress` is the tool's static external-write mark, as it always was. `irreversible` is a mandate of
* THIS call: the tool's own tier (`tool_marks` — the deployment declared the tool may be irreversible,
* and a policy or hook asking first does not un-declare it), the operator's per-call doctrine
* (`operator_always`), the probe's structural verdict (`probe_mandate` / `probe_unanswered`), OR — under a
* shell-seat-installed `"maybe"` tier — the shell gate's own tighten having minted this ask (the doctrine's
* probe judged THIS command not reversible). A policy ask on a shell-gated tool that the doctrine's probe
* did not tighten and did not mandate is a plain ask: the tier was the probe's instrument, not a risk
* label, and the park stays the budgetable kind. Before this derivation the static tier alone set the bit,
* so every policy ask on the full shell face parked non-budgetable.
*
* Deliberately NOT the rule-lane predicate verbatim: a shell tighten's ask is rule-clearable (a person's
* past yes to this command shape stands) yet non-budgetable (a budget's mechanical yes is not a person's).
* The two derivations share every other row through {@link irreversibilityMandateOf}.
*/
export declare function safetyAxisOf(marks: MandateMarks & {
readonly tightened: AskOriginFacts["tightened"];
}): {
egress: boolean;
irreversible: boolean;
shell?: boolean;
} | undefined;
/**
* The permission-mode closed set — the ONE key a caller sets (`TaskSpec.permissionMode`), spelled as the wire and
* the reference implementation spell it. A root task's absent key reads `default`; a child's absent key inherits
* its parent's effective mode ({@link clampPermissionMode}).
*/
export declare const PERMISSION_MODES: readonly ["default", "acceptEdits", "plan", "auto", "bypassPermissions", "dontAsk"];
export type PermissionMode = (typeof PERMISSION_MODES)[number];
/** Membership test for {@link PermissionMode} — the word crosses process boundaries (a request body, a checkpoint row). */
export declare function isPermissionMode(v: unknown): v is PermissionMode;
/**
* The reference implementation's mode RANK, used for one thing only: a child's declared mode may not outrank
* the ceiling its parent hands down (a declaration is adopted iff `rank[declared] <= rank[ceiling]`, and
* `acceptEdits` / `auto` are incomparable). It is a clamping relation, not a per-class strictness order —
* `default` and `dontAsk` share a rank, and whether a mode releases a given ask is {@link MODE_ARBITRATION}'s.
*/
export declare const PERMISSION_MODE_RANK: {
readonly plan: 0;
readonly default: 1;
readonly dontAsk: 1;
readonly acceptEdits: 2;
readonly auto: 3;
readonly bypassPermissions: 4;
};
/** The fence over the rank table: `never` while every mode has a rank. */
export type RankTableCoversEveryMode = AssertAllKeysHandled<Exclude<PermissionMode, keyof typeof PERMISSION_MODE_RANK>>;
/**
* Clamp a child's DECLARED mode under its parent's ceiling. An absent declaration inherits the ceiling; a
* declaration the rank table does not admit under the ceiling is folded back to the ceiling (`clamped: true`
* — the caller discloses it, never silently). `bypassPermissions` is adopted only under a `bypassPermissions`
* parent, and the incomparable pair (`acceptEdits` ↔ `auto`) is never adopted across.
*/
export declare function clampPermissionMode(declared: PermissionMode | undefined, ceiling: PermissionMode): {
effective: PermissionMode;
clamped: boolean;
};
/** WHO put a tool's egress / irreversibility mark on the seat — the deployment's own declaration (`spec.tools`),
* a protocol server's self-report (MCP annotations), or the A2A default (every peer skill is an external write). */
export type ToolMarkSource = "deployment" | "mcp_annotation" | "a2a_default";
/**
* The ask CLASS set — WHY a surviving ask exists, as the set of authorities that stand behind it. One ask can
* belong to several classes at once (a hook door and an org verdict on one call); a class is derived from the
* FACTS the gate's layers established ({@link classifyAsk}), never from the disclosed `origin` / `mandate`
* words, which are single-winner projections and hide the members behind them. The array ORDER decides one
* thing only: which member is the DISCLOSED word (`askClass`) when several hold — the door-before-word,
* most-immune-first order; every lane reads the folded row ({@link foldArbitrationRow}), never the word.
* `shell_floor` is the operator's `shellGate: "always"` floor on a shell-running seat — the question the `operator_always`
* mandate word discloses, as a class of its own so a set that holds another forced question still holds it.
* `requires_user_interaction` is the SERVER's declaration on the called tool (`_meta["anthropic/requiresUserInteraction"] === true`): a
* permission question, unlike `content_question` (the reserved question tool's content-routing signal) — it carries that class's arbitration
* row cell by cell, because the reference implementation judges the declaration ahead of its allow-rule lookup and its bypass release.
* `unstated` is the mode's answer for a call nobody stated: a tool whose permission seat is `passthrough` (it states no
* permission of its own — a remote peer's tool) reached the end of the fold with an allow no layer stated.
*/
export declare const ASK_CLASSES: readonly ["content_question", "hook", "ancestor", "unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "ask_rule", "denial_limit_fallback", "requires_user_interaction", "operator_forced", "shell_floor", "write_protection_unresolved", "probe", "write_protection", "tool_marks_mcp", "tool_marks_a2a", "policy_rra", "artifact_shared_read", "mode", "unstated", "ordinary"];
export type AskClass = (typeof ASK_CLASSES)[number];
/** Membership test for {@link AskClass} — the word crosses the wire on `tool_end.gate.disposition`. */
export declare function isAskClass(v: unknown): v is AskClass;
/** The four switches a class carries, one row per class ({@link MODE_ARBITRATION}). */
export interface ModeArbitrationRow {
/** The ask still reaches a person under `bypassPermissions` (the reference implementation's `bypassImmune`). */
readonly bypassImmune: boolean;
/** The auto-mode classifier may answer it. */
readonly classifierEligible: boolean;
/** The sandbox-admission leg may admit it (containment holds the effect). */
readonly sandboxAdmission: boolean;
/** A persisted allow rule may clear it. */
readonly ruleMayClear: boolean;
/** The reference implementation's rule / safety check answers `ask` for it, so a hook's ask beside it is not the hook's
* forced decision: the call runs the full permission pipeline (the reference's `hookAskFloor`) — under `dontAsk`, a
* refusal rather than a person's question. */
readonly hookAskFloor: boolean;
}
/**
* The mode × class table. `bypassImmune` is the one new column (nothing said it before); the classifier and
* sandbox columns are the fold of the two former per-word lane tables (only `probe` moved: the classifier may
* now answer it — the read boundary's unestablishable spelling is fed to it as a deny-table row instead), and
* `ruleMayClear` restates {@link ruleClearanceOf}'s conclusion per class (a real-approval bit is never rule-clearable).
* `mode` is the permission mode's OWN question (`plan`'s two sentences, `decisionReason: "mode"`): the reference
* implementation puts it to a person past every allow rule and never to an automatic lane, and its bypass conversion
* releases it like any other non-immune ask. `unstated` is the reference implementation's passthrough conversion: an allow
* rule or a bypass mode clears it, auto mode's classifier answers it, and a sandbox admits nothing on its behalf (the
* tool's effect lands at a remote peer, outside any containment the leg declares). `hookAskFloor` marks the classes the reference's rule / safety check answers
* `ask` for — an ask rule, a question the tool needs a person for, a dangerous removal, a protected or unresolvable write
* target, the session transcript, another's shared artifact.
*/
export declare const MODE_ARBITRATION: {
readonly content_question: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly hook: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly ancestor: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly unverifiable_delete: {
readonly bypassImmune: true;
readonly classifierEligible: true;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly transcript_write: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly org_rule: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly governance_unavailable: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly rule_unreadable: {
readonly bypassImmune: false;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly ask_rule: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly denial_limit_fallback: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly requires_user_interaction: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly operator_forced: {
readonly bypassImmune: true;
readonly classifierEligible: true;
readonly sandboxAdmission: true;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly shell_floor: {
readonly bypassImmune: true;
readonly classifierEligible: true;
readonly sandboxAdmission: true;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly write_protection_unresolved: {
readonly bypassImmune: true;
readonly classifierEligible: false;
readonly sandboxAdmission: true;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly probe: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly write_protection: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: true;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly tool_marks_mcp: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: true;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly tool_marks_a2a: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: true;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly policy_rra: {
readonly bypassImmune: true;
readonly classifierEligible: true;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly artifact_shared_read: {
readonly bypassImmune: false;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: true;
};
readonly mode: {
readonly bypassImmune: false;
readonly classifierEligible: false;
readonly sandboxAdmission: false;
readonly ruleMayClear: false;
readonly hookAskFloor: false;
};
readonly unstated: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: false;
readonly ruleMayClear: true;
readonly hookAskFloor: false;
};
readonly ordinary: {
readonly bypassImmune: false;
readonly classifierEligible: true;
readonly sandboxAdmission: true;
readonly ruleMayClear: true;
readonly hookAskFloor: false;
};
};
/** The fence over the arbitration table: `never` while every class has a row. */
export type ArbitrationTableCoversEveryClass = AssertAllKeysHandled<Exclude<AskClass, keyof typeof MODE_ARBITRATION>>;
/** The classes whose own mint puts the `requiresRealApproval` bit on the ask — a bit one of them explains is not
* the bare bit `policy_rra` names. The mark classes (probe / write protection / tool marks / operator forced / the
* shell floor) mint no bit and explain none — except `requires_user_interaction`, whose declaration the origin stamp
* turns into the bit on every ask of the tool. */
export declare const RRA_EXPLAINERS: ReadonlySet<AskClass>;
/** The classes a permission mode can RELEASE (the `bypassImmune: false` rows) — the only words that can appear on
* `GateDisposition.released.askClass`; every other class reaches a person under every mode. */
export declare const RELEASABLE_ASK_CLASSES: readonly AskClass[];
/**
* The closed set of `reason` words a mode release carries (`GateDisposition.released.reason`, the `released` arm of
* {@link Arbitration}): each is a releasable class the release record names on its own — `rule_unreadable`, a release
* over a person's rule the call could not be read against. A release carries the first member (array order) its class
* set holds, and none when it holds no member. {@link arbitrateByMode} and {@link isModeReleaseReason} read one copy
* of this list taken at load (a caller that writes into the exported array changes neither), so a new reason is one
* word here.
*/
export declare const MODE_RELEASE_REASONS: readonly ["rule_unreadable"];
export type ModeReleaseReason = (typeof MODE_RELEASE_REASONS)[number];
/** Membership test for {@link ModeReleaseReason} — the word crosses the wire on `tool_end.gate.disposition.released.reason`. */
export declare function isModeReleaseReason(v: unknown): v is ModeReleaseReason;
/**
* The facts the class derivation reads beside the decision: the origin facts the gate holds, plus the tool
* seat's mark PROVENANCE ({@link ToolMarkSource}; a mark with no source reads as the deployment's, fail-closed),
* the A2A isolation switch (a set switch turns the A2A default into an operator's mandate), and — at an
* ancestor station — the classes the ancestor's own ask carries.
*/
export interface AskClassFacts extends AskOriginFacts {
readonly toolMarkSources?: ReadonlySet<ToolMarkSource>;
readonly a2aIsolatePeers?: boolean;
readonly ancestorClasses?: ReadonlySet<AskClass>;
}
/** The classification the arbitration reads: the whole set, the disclosed word (the set's first member in
* {@link ASK_CLASSES} order) and the row folded across the set. */
export interface AskClassification {
readonly classes: ReadonlySet<AskClass>;
readonly askClass: AskClass;
readonly row: ModeArbitrationRow;
}
/** The two members an ask's classification puts on the approval request and the park row: the disclosed word and
* the whole set, the set as an array in {@link ASK_CLASSES} order (so `classes[0] === askClass`). */
export interface AskClassMembers {
readonly askClass: AskClass;
readonly classes: readonly AskClass[];
}
/** The ONE projection of a classification onto {@link AskClassMembers} — every station that carries an ask's class
* spells it through this, so the word and the set cannot be read from two derivations. The array is a fresh frozen
* copy per call: an approver that holds it cannot reach the park row's copy or the next card's. */
export declare function askClassMembersOf(c: AskClassification): AskClassMembers;
/** The screen a boundary applies before it writes the pair where a host reads it back (the park row): both or
* neither, and only a well-formed pair — every member of `classes` in the closed set, no repeat, and `askClass` its
* first member in {@link ASK_CLASSES} order. The copy returned is fresh and frozen, never the caller's array. */
export declare function screenAskClassMembers(askClass: unknown, classes: unknown): AskClassMembers | undefined;
/**
* The DECISION half of the class derivation — what ONE ask says about itself through its own members, read
* per contributing ask at the policy fold so a member the fold does not copy (a guard mark, the unreadable-rule
* fact) is not lost behind the ask that won the slot. The bare-bit rules are applied over THIS ask's own set:
* `requiresRealApproval` with no explaining class is `policy_rra`; a self-declared `mandated` with no other
* class is `operator_forced` (a deployment probe's own mandate, read conservatively). Returns the specific
* classes only — an empty set is "nothing but hesitation", which the caller reads as `ordinary`.
*/
export declare function decisionAskClassesOf(d: Extract<PermissionResult, {
action: "ask";
}>): Set<AskClass>;
/** Fold a class set onto one row: `bypassImmune` and `hookAskFloor` by OR (any immune member keeps the ask; any floored
* member floors a hook's ask), the three permissive switches by AND (one refusing member withholds the lane). An empty
* set is `ordinary`. */
export declare function foldArbitrationRow(classes: ReadonlySet<AskClass>): ModeArbitrationRow;
/**
* The ONE class derivation: the decision half ({@link decisionAskClassesOf}) joined with the gate's own facts —
* the question tool, an ancestor's mark, the org layer's two arms, the persisted lane's unreadable call, the tool
* seat's marks with their provenance, the read probe and the write-protection judge's records — then the bare
* real-approval rule re-read over the WHOLE set (an org verdict explains a bit the policy did not mint). The
* derivation reads facts and members, never the `origin` / `mandate` words.
*/
export declare function classifyAsk(d: Extract<PermissionResult, {
action: "ask";
}>, facts: AskClassFacts): AskClassification;
/** The refusal the model reads when an ask reaches a run with no approver: the reference implementation's sentence
* (2.1.280) — the call did NOT run because this session has no approval surface — with the run's own fact beside it
* and the ask's prompt as what was requested. The gate appends the caller-side remedy. */
export declare function headlessDenialMessage(toolName: string, askMessage: string): string;
/** The state of the approver seat a remedy is written for: `absent` — no approver is wired for the leg; `unavailable` — one is
* wired and answered that nobody is reachable for this ask. Absent in the input reads as `absent` (the posture the
* function was written for). */
export type HeadlessApproverSeat = "absent" | "unavailable";
/**
* The ONE sentence a headless auto-deny (an ask that reached its final deny because no approver is wired) adds
* after its refusal: the caller-side change that would have let this call run. Read off the effective mode, the
* ask's folded class row and whether the call is a covered write (the mode write gate's own question):
* · a class no permission mode releases (`bypassImmune`) — only a person can answer it: wire an approver;
* · the plan mode's own question (`mode`) — leave plan (steer the mode, or approve the plan) or wire an approver;
* · the write gate's own ask under `default` / `auto` — `acceptEdits` lets writes inside the working
* directories through without asking;
* · the same ask under `acceptEdits` — the target lies outside the working directories: add its directory to
* `additionalDirectories`;
* · any other releasable ask — `bypassPermissions` releases it (disclosed on the call).
* Every arm also names the approver, in the clause of the seat's own state ({@link HeadlessApproverSeat}): none wired ⇒
* wire one; one wired that answered `unavailable` for this ask ⇒ make a person reachable through it (a wired approver is
* never told to be wired). The refusal's tool text and the `delegation.ask_unresolvable` notice both carry this sentence —
* they call this function, neither spells it.
*/
export declare function headlessApprovalRemedy(input: {
readonly mode: PermissionMode | undefined;
readonly classification: AskClassification | undefined;
readonly coveredWrite: boolean;
readonly unplacedOptions?: {
readonly program: "cp" | "mv";
};
readonly approverSeat?: HeadlessApproverSeat;
}): string;
/** The reference implementation's sentence (2.1.280) for a `cp` / `mv` the mode will not answer because it carries option
* words — the approval card's message under `acceptEdits` and the head of the headless remedy. */
export declare function fileCommandOptionsSentence(program: "cp" | "mv"): string;
/** What the mode arbitration decided for one surviving ask. `ask` hands it on unchanged (park / approver); `accepted` is
* the mode answering an ordinary question itself (`acceptEdits` on one of its file commands — an allow of the mode's own
* policy, like its in-root write gate, and no release: nothing is disclosed as released);
* `released` is an allow the mode granted (disclosed on `tool_end.gate.disposition.released`); `denied_by_mode`
* is a refusal the mode made (`deniedBy: "mode"`). */
export type Arbitration = {
kind: "ask";
} | {
kind: "accepted";
mode: "acceptEdits";
} | {
kind: "released";
mode: PermissionMode;
askClass: AskClass;
classes: readonly AskClass[];
origin?: AskOrigin;
mandate?: PersistedRuleMandate;
reason?: ModeReleaseReason;
} | {
kind: "denied_by_mode";
mode: "bypassPermissions" | "dontAsk";
askClass: AskClass;
classes: readonly AskClass[];
};
/**
* The ONE mode read point. `dontAsk` refuses every ask — one value for every class — but a hook's that no member of the
* set floors (`hookAskFloor`: a hook's ask beside a rule's or a safety check's ask is refused, as the reference's full
* pipeline refuses it; a hook's ask alone is the reference's forced decision, which never reaches that conversion);
* `bypassPermissions` refuses a write whose protection could not be judged (a person cannot be asked, and
* releasing would be looser than the allow the judge already refuses), keeps every immune class and releases the
* rest; `acceptEdits` answers an ORDINARY ask itself on a call the caller vouches is one of the mode's file commands
* (`call.fileCommand`, `accepted`); `plan`, `default` and `auto` hand every ask on (plan's own questions were raised by
* its write gate; the reference implementation refuses nothing at this point under plan — auto's classifier reads the
* row at its own station). Reads the class SET and the folded row — never the disclosed word.
*/
export declare function arbitrateByMode(mode: PermissionMode, c: AskClassification, disclosure?: {
origin?: AskOrigin;
mandate?: PersistedRuleMandate;
}, call?: {
readonly fileCommand?: boolean;
}): Arbitration;
export {};