UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

1,444 lines • 108 kB
/**
 * design/179 §2 — the persisted permission-rule model and its matchers.
 *
 * A persisted rule is one person's standing instruction about a tool call, in one of THREE behaviors
 * ({@link RULE_BEHAVIORS}): an `allow` is the standing form of ONE recorded human approval (the person
 * confirmed a command once on an approval card and asked not to be asked again); a `deny` and an `ask`
 * are the standing forms of "never run this" and "ask me every time" — the same content-form rules
 * upstream reads out of its settings' deny/ask lists. ONE grammar, ONE canonical spelling and ONE
 * matcher family serve all three; the behavior is a FIELD beside the text, never part of it, so
 * `Bash(rm -r:*)` is one spelling whether it allows, denies or asks. This module owns the rule's SHAPE
 * (text grammar, canonical spelling, scope, behavior) and its MATCH semantics; who may mint one, where
 * it is stored, and where in the gate it is consumed live in `permission-rule-store.ts`,
 * `permission-rule-consent.ts` and `gate-lanes.ts` respectively.
 *
 * The two DIRECTIONS read a command differently, on purpose: an allow must cover the WHOLE call
 * ({@link ruleAdmitsCommand} — one recorded yes clears exactly what it spells), while a deny/ask
 * speaks about any PROGRAM RUN the call may perform ({@link programRunReachOf} — whole command or
 * any segment of a chain, read with a real shell lexer, `shell-lexer.ts`, and answering `unreadable`
 * for what it cannot read rather than silence). Precedence across behaviors is the upstream one,
 * deny > ask > allow ({@link RULE_BEHAVIOR_PRECEDENCE}, the one table every adjudication reads).
 *
 * ## The floor: what a rule may name, and what a rule may match (the ALLOW direction)
 *
 * Everything a shell can use to run a program the text does not NAME — substitution, subshells,
 * backgrounding, escapes, line breaks, and **redirection** — puts the command outside this lane
 * entirely: it falls back to the pre-existing chain and asks. Redirection specifically is a deliberate
 * registered divergence from upstream (upstream strips redirections before matching; with this repo's
 * write gate not covering the shell tool, stripping would turn a rule as innocuous as `Bash(ls)` into a
 * licence for `ls > ~/.ssh/authorized_keys`).
 *
 * CONNECTORS are the one construct the lane does speak for, in two forms. The EXACT form spells a
 * whole chain and admits exactly it: `./gradlew build && ./gradlew test` is one thing a person reads
 * and approves in one glance, and nothing is admitted that the rule text does not spell out end to
 * end. The COMPOUND-PREFIX form (`Bash(cd /tmp && adb pull:*)`) widens that by ONE seam and no other:
 * every leading segment — connectors included — is matched byte-for-byte like an exact rule, and only
 * the FINAL segment takes appended arguments, so the rule text still reads as the concrete combination
 * the person saw, with the last command's arguments free. A SINGLE-COMMAND prefix body stays
 * single-command on the match side (`Bash(npm:*)` does not admit `npm test && curl evil.example`) —
 * the matcher requires the command to read as exactly as many segments as the rule body names, which
 * is that historical line and the compound form's own containment in one predicate. The compound-prefix
 * rule TEXT is a registered divergence from upstream, which has no compound rule form at all: upstream
 * reaches compound coverage through a per-segment evaluation of its full decision chain, where a
 * segment DENY is returned strictly before any whole-string allow; this lane's equivalent is that the
 * deny/ask layer (`permission-rule-org.ts`) judges every segment and runs ahead of the allow lane at
 * the gate — a compound-prefix allow can therefore never outrun a deny on any segment it admits.
 *
 * design/375 adds upstream's OTHER half of that per-segment evaluation on the allow side: a compound
 * whose EVERY segment is admitted by some eligible rule is covered by the CONJUNCTION of those rules
 * (`findAdmittingRule` answers with the coverage SET). No single rule widens: the matcher predicates
 * above are untouched, and the set form is the decision layer's answer, fenced by the same
 * ahead-running per-segment deny/ask layer.
 *
 * The floor is `parseLeadingCommandName` + `splitShellCompoundSegments` — the one simple-command parser
 * and the one segmentation, both already shared with the read-only classifier, the reversibility probe,
 * the coarse command-name policy and the skill tool specifier. A second tokenizer would drift, and drift
 * on a LOOSENING face shows up as a circumvention rather than as a test failure. The tightening
 * direction's lexer is not that second tokenizer: it reads MORE than the floor (never less), and a
 * drift there costs an approval prompt, never a standing approval.
 *
 * argv[0] may carry a PATH prefix here (`./gradlew`, `/usr/bin/git`) — opt-in at the shared parser and
 * used by this lane alone. The bare-name requirement belongs to the argv[0]-NAME filters, which compare
 * one token against a name set; this lane compares the whole command line, so a path prefix is not a way
 * past anything — it IS the text that was approved. No basename folding follows from it: `./gradlew`
 * and `gradlew` stay two different commands to the matcher. The single place a basename is taken is the
 * interpreter refusal below, which must read `/usr/bin/node` as `node`.
 *
 * ## Normalization order is load-bearing
 *
 * Whitespace folding runs AFTER the shell-construct rejection, never before: folding first would
 * swallow a newline and glue two commands into one string that then reads as a single simple command.
 * Only space/tab runs fold; there is no case folding, no alias expansion (no shell alias view exists
 * here) and no leading-env-assignment stripping (`FOO=1 git status` simply does not match
 * `Bash(git status:*)`) — the last two are strict-side divergences from upstream, registered as such.
 */
import { type DenyFace } from "../tools/fs/read-deny.js";
import type { AssertAllKeysHandled, RuleOffersAbsence } from "./ask-origin.js";
import { type ToolCallLike } from "./tool-registry.js";
import { type PathDenialEvidence, type PathFamily, type PathIdentity } from "../tools/fs/safety.js";
import { type TargetViews } from "./path-rule-binding.js";
/**
 * The closed set of rule BEHAVIORS — what a matching rule says about the call. The upstream vocabulary
 * verbatim (`allow` / `deny` / `ask`, the three settings lists), in PRECEDENCE order: when rules of more
 * than one behavior speak for one call, the earliest member here decides ({@link RULE_BEHAVIOR_PRECEDENCE}
 * is that order as a table, and {@link adjudicatePersistedRules} is the one place that walks it).
 *
 * The type is derived FROM the array so a member added to either side is a compile error; a value read
 * off a wire or a file goes through {@link isRuleBehavior} (present but outside the set = refused, never
 * defaulted) or {@link readRowBehavior} (the ONE reading of a row that predates the field).
 */
export declare const RULE_BEHAVIORS: readonly ["deny", "ask", "allow"];
export type RuleBehavior = (typeof RULE_BEHAVIORS)[number];
/** Membership test for {@link RuleBehavior}. Exported because the word crosses process boundaries (a
 *  store row, a sync payload, a settings entry) and a host validating one must not hand-roll the set. */
export declare function isRuleBehavior(v: unknown): v is RuleBehavior;
/**
 * The precedence table: the rank at which each behavior decides a call when several rules speak (lower
 * decides first). Upstream's arbiter walks deny, then ask, then allow — an explicit deny is never
 * outvoted by an allow, and a standing "ask me each time" is never silenced by a remembered yes. Every
 * adjudication over a mixed rule table orders its walk by THIS table (never by a hand-written if-chain),
 * so the rank of a behavior is stated once.
 */
export declare const RULE_BEHAVIOR_PRECEDENCE: {
    readonly deny: 0;
    readonly ask: 1;
    readonly allow: 2;
};
/** Compile-time fence over the precedence table: `never` while every behavior has a rank. */
export type PrecedenceTableCoversEveryBehavior = AssertAllKeysHandled<Exclude<RuleBehavior, keyof typeof RULE_BEHAVIOR_PRECEDENCE>>;
/** The behaviors in precedence order — the walk every mixed-table adjudication takes. */
export declare const RULE_BEHAVIORS_BY_PRECEDENCE: readonly RuleBehavior[];
/**
 * The ONE reading of a persisted row's behavior at every seam that may meet a row written before the
 * field existed — the file store's schema migration, the sync wire's inbound picker, a checkpoint's
 * rehydrated session rows. An ABSENT behavior reads `"allow"`: every row an engine without the field
 * ever wrote was an allow, so that reading is the row's own meaning, not a default. A PRESENT value
 * outside the closed set is refused (`undefined`) — a word this engine cannot read must not become an
 * allow by falling through (the loud-bad-value doctrine), and must not become a deny either (a
 * fabricated tightening is still a fabrication).
 */
export declare function readRowBehavior(v: unknown): RuleBehavior | undefined;
/** The tools the rule lane speaks for — derived per grammar ({@link ruleToolGrammarOf}): the command word, every catalog
 *  path tool, the name tools, the domain tools.
 *
 *  design/276 §3.3 (doctrine, for whoever widens this set): a future `SendMessage(…)`-form rule MAY
 *  clear the peer-referral ask — that ask is classifier hesitation by construction (#144: allow rules
 *  silence the classifier's questions, never a mandated one), so a recorded human yes is exactly what
 *  clears it. The org/hook/matchedAskRule immunities stay: those conjuncts live in the gate's
 *  persisted-rule lane and do not loosen with this set. */
export type PersistedRuleTool = string;
/** The command grammar's ONE rule word — the spelling a shell command rule is written in (`Bash(<command>)`, upstream's).
 *  A rule word, not a tool list: the calls a command rule reaches are the SHELL FACE's — every catalog definition whose
 *  `command` the command grammar judges ({@link laneToolNamesOf}). */
export declare const COMMAND_RULE_TOOL = "Bash";
/** The read face's rule word: a `Read(<path>)` rule speaks for every call that reads a path ({@link PATH_RULE_FACES}). */
export declare const READ_RULE_TOOL = "Read";
/**
 * The path rules that speak for a FACE rather than for one tool — one table, one column per tightening behavior (the
 * reference implementation's reading: a Read rule covers every built-in tool that reads a file, an Edit rule every tool
 * that edits one — Write and NotebookEdit included, deny and ask alike — and a Read DENY also refuses writing the same path; a Read ASK asks for reads only). Such a rule
 * reaches any call whose declared path target is on one of its behavior's faces — the Read tool, the search tools'
 * roots, the write tools — judged on the path that call reads, searches or writes. A path rule on any other tool keeps
 * its tool's name as its reach (`Write(<path>)` is judged on the Write tool, a stricter reading the reference does not make).
 *
 * Both tightening columns are judged at the gate, on the call's own path target, before the call runs — by the rule
 * policy's lane and by the persisted lane alike, one column read for deny and for ask. A deny row is exported to the
 * engine's deny tables as well (`ToolPolicy.pathRules` bound where the policy is installed, the person's rows at
 * prepare), which do what the gate cannot: refuse inside the tool at execution time, descend into a search's results,
 * reach the shell's file commands' operands, and keep a refused target out of the out-of-root question. An ask does not
 * descend (a search rooted above the asked path is not asked, and its results are not filtered).
 *
 * The ALLOW column is the reference's too: an allow by path is read on these two tools only — a `Read(<path>)` allow speaks
 * for the read face, an `Edit(<path>)` allow for the write face and the read face (an edit allowed is a read allowed) — and
 * an allow by path on any other tool has no rule form. It is judged at the gate with the narrow reading
 * ({@link adjudicatePersistedPathRules}): every view of the call's target must lie where the rule names.
 */
export declare const PATH_RULE_FACES: Readonly<Record<string, Readonly<Partial<Record<RuleBehavior, readonly DenyFace[]>>>>>;
/** The face a call's protective path target is on: `read` for a read slot, `write` for every other access; none without
 *  one — the face a {@link PATH_RULE_FACES} rule is judged against, in a rule policy and in the persisted rule lane alike. */
export declare function callPathFaceOf(call: ToolCallLike): DenyFace | undefined;
/** Does some {@link PATH_RULE_FACES} tightening column (deny or ask) hold `face` — may a face-keyed row speak for a call on
 *  it, whatever the call's tool is called (a caller's tool declaring a read path target is on the read face too)? */
export declare function faceRowsSpeakFor(face: DenyFace | undefined): boolean;
/**
 * The tools whose rule content NAMES THE VALUE of one parameter instead of a command or a path — ONE grammar (the name grammar,
 * match `name`), one row per tool, the row declaring the tool's SPELLING of it:
 *  · BARE — the reference's `Skill(<name>)` / `Skill(<prefix>:*)`: `param` is the argument the rule's content is read against,
 *    `params` every scalar argument the tool carries (the parameter spelling `Tool(<param>:<pattern>)` those names used to reach
 *    is retired for such a tool, see {@link parseNameRuleContent}). The Skill tool's real schema keys are pinned equal to
 *    `params` (test/backlog1318).
 *  · KEYED — `words` present: the reference's `Artifact(action:<word>)`, the content carrying the argument's own key. `params`
 *    is that one key; `words` is the closed set of values the tool itself declares for it (the row points at the tool's own
 *    set, never a copy), and a rule names exactly one of them — no prefix form, no `*`.
 * Whatever the spelling, a rule of this grammar says "the call's value of `param` is this value", read by the one subject
 * reader ({@link nameRuleSubjectOf}), the one predicate ({@link nameRuleAdmits}), the one precedence walk
 * ({@link adjudicatePersistedNameRules}) and the one offer ({@link suggestRuleForName}).
 */
export declare const NAME_RULE_FACES: Readonly<Record<string, {
    readonly param: string;
    readonly params: readonly string[];
    readonly words?: readonly string[];
}>>;
/**
 * Why a call of a KEYED value tool that names no value for the key cannot be read against that key's deny / ask rules, or
 * undefined (a bare-spelling tool, or no value tool). Every judge reads a call's arguments as the tool reads them
 * (`argsAsTheToolReads`: its argument preparation, then the schema's coercions); a call that STILL names no value for the key
 * runs a value no argument shows — a default filled in inside the tool's execution (a caller's tool mounted under the keyed
 * name, or answering to it, with no argument preparation of its own). Whether a rule by the key reaches such a call is
 * unknown, so every reader REFUSES it on the key's deny or ask rule — a refusal, never an ask a person could approve past a
 * deny, and no allow by the key reaches it. A tool whose reading always names the key (the built-in `Artifact`: an absent
 * action reads as `publish`) never meets this. A bare-spelling tool's call that names nothing is reached by nothing: no
 * default stands behind it.
 */
export declare function keyedValueAbsentReason(tool: string): string | undefined;
/**
 * Does the name grammar read this content of this tool? Every content of a bare-spelling tool; on a keyed tool only content
 * keyed by its own parameter (`action:…` on `Artifact`) — another key (`Artifact(url:…)`) is a rule on another argument,
 * which the persisted grammar has no form for and the caller policy reads with its generic parameter lane. Undefined grammar
 * ⇒ false.
 */
export declare function nameGrammarReads(tool: string, content: string): boolean;
/**
 * The tools whose rule content names the HOST of the URL one parameter carries — the reference's `WebFetch(domain:<host>)`
 * form, one row per tool: `param` is the URL argument the host is read from ({@link domainRuleSubjectOf}). Read by
 * {@link parseDomainRuleContent}; an allow only (see there).
 */
export declare const DOMAIN_RULE_FACES: Readonly<Record<string, {
    readonly param: string;
}>>;
/**
 * Which tools a rule may name — the model's tool set is DERIVED, never a hand-kept list: the command rule word
 * ({@link COMMAND_RULE_TOOL}) takes the COMMAND grammar — the other shell-face tools take none of their own, their command is
 * judged by the `Bash(…)` rules ({@link laneToolNamesOf}); every tool the catalog declares a path target for (Read / Edit / Write /
 * Glob / NotebookEdit / …, `pathTargetOf`) takes the PATH grammar; the tools of {@link NAME_RULE_FACES} take the NAME grammar
 * (each in its row's spelling: bare `Skill(<name>)`, keyed `Artifact(action:<word>)`); the tools of {@link DOMAIN_RULE_FACES} the
 * DOMAIN grammar.
 * A rule on any other tool has no content this model can judge and is refused (`unsupported.tool`). The tool set therefore
 * moves with the catalog's declarations, exactly as the protective fences' covered set does.
 */
export type RuleGrammar = "command" | "path" | "name" | "domain";
export declare function ruleToolGrammarOf(toolName: string): RuleGrammar | undefined;
/**
 * The HOST a domain-grammar tool's call fetches — the string every DOMAIN rule is read against — or undefined: not a domain
 * tool; no URL argument, or one that is not a string; a URL that does not parse, is not http(s), or carries credentials (the
 * fetch tool refuses those itself). The host is the URL parser's (lower case, IDN in its ASCII form, an IPv4 shorthand
 * expanded — the host the fetch connects to), its trailing dots read off.
 */
export declare function domainRuleSubjectOf(toolName: string, args: unknown): string | undefined;
/** The value a name-grammar tool's call carries in its name argument — the string every NAME rule is read against, the one a
 *  `Skill` call is resolved by — or undefined (not a name-grammar tool; no such argument; not a string). */
export declare function nameRuleSubjectOf(toolName: string, args: unknown): string | undefined;
/** The rule WORDS a call is judged under, once each: for every name the call answers to (the model's spelling, the wire
 *  name, the other aliases that resolve to it), the word its rules are written in — a shell-face name's is the command
 *  grammar's {@link COMMAND_RULE_TOOL} (a `Bash(<command>)` rule reaches `Monitor` as it reaches `Bash`), a path, name or
 *  domain tool's its own name — so a stored `Bash(…)` or `Edit(…)` row reaches a tool that answers to that name under another wire
 *  name, as a policy's rule does. Every lane that judges a call by a rule's word reads it here: the policy compiler, the
 *  person's stored rows, the org partition, the allow lists. Empty ⇒ no grammar speaks for the call. */
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
/**
 * Match forms, one family per grammar (`prefix` and `wildcard` are one predicate):
 *  · `exact` — admits the one string the rule spells.
 *  · `wildcard` — the TRAILING SPACE-STAR spelling (`Bash(npm run *)`): the body, or the body followed
 *    by a space and more, inside the body's own segment count. This is the spelling upstream itself
 *    mints for its prefix suggestions (its classifier compiles one trailing ` *` into an optional
 *    suffix `^body( .*)?$`), so it is the form this lane's SUGGESTION face mints too (#510).
 *  · `prefix` — the historical COLON-STAR spelling (`Bash(npm run:*)`), match-equivalent to `wildcard`
 *    on the single-command body. Kept as a fully supported COMPATIBILITY READ — stored rules are never
 *    migrated or rewritten — and it remains the ONLY spelling of the compound-prefix form
 *    (`Bash(cd /tmp && adb pull:*)`): the trailing-star form deliberately has no compound reading
 *    (see {@link parseRuleText}), because upstream's own trailing-star pattern is inert on a
 *    compound and importing/minting one as a chain licence would grant what nobody approved.
 *  · `subpath` — the directory form (design/382 §2.2): `Tool(//abs-dir/**)`, reaching every path under
 *    one absolute directory. Never admits any command; its one predicate is {@link directoryRuleAdmits}.
 *    As an ALLOW it exists on the two face tools ({@link PATH_RULE_FACES}); as a deny/ask on every
 *    path-targeting tool.
 *  · `path` — the PATH PATTERN form: upstream's file-rule spelling over a tool's path target
 *    (`Edit(~/.sema/settings.json)`, `Write(src/**)`, `Read(//etc/*)`), stored AS SPELLED and
 *    resolved at match time against the call's bases (`//` absolute, `~/` home, `/` project root,
 *    otherwise the working directory). Its predicate is {@link pathRuleReachOf}. As an ALLOW it exists
 *    on the two face tools, judged with the narrow reading (every view of the target inside the pattern).
 *  · `name` / `name_prefix` — the NAME grammar ({@link parseNameRuleContent}).
 *  · `host` / `subdomain` — the DOMAIN grammar ({@link parseDomainRuleContent}): one exact host, or every
 *    host below one domain.
 */
export type PersistedRuleMatch = "exact" | "prefix" | "wildcard" | "subpath" | "path" | "name" | "name_prefix" | "host" | "subdomain";
/**
 * Where a rule applies — design/382 §4.1, the THREE consent dimensions.
 *
 *  · `global` — the personal-global dimension: this OWNER's rules, everywhere (the bucket is already
 *    per-owner, {@link import("./permission-rule-store.js").RuleOwner}). The member keeps its historical
 *    spelling on purpose: renaming it `personal-global` would be a no-benefit migration of every stored
 *    row; the "personal" half lives in documentation and card wording, not in the wire byte.
 *  · `project` — applies ONLY inside `root`. `root` is expected to be already canonicalized (realpath)
 *    by whoever constructs the scope; the matcher compares with word-boundary containment, never a bare
 *    `startsWith` (`/a` must not reach `/ab`).
 *  · `session` — applies ONLY to calls of the ONE session it names, and lives in that session's own
 *    state (the host's session-rule overlay), NEVER in the persisted store: every durable entrance —
 *    the write deltas, sync in both directions, at-rest bytes — refuses or drops a session row
 *    (design/382 §4.3, the durable two-member face). `sessionId` uniqueness is the HOST's duty (the
 *    `principal` posture): reusing an id is a declaration that it is the same session. A session row
 *    cannot be individually deleted — it dies with its session (design/382 §10, ruled; the durable
 *    removal entry refuses it rather than answering a false no-op).
 *
 * ORG is deliberately NOT a member (design/382 §4.1): an approval card must be structurally unable to
 * spell an org-wide grant — org governance is a deny/ask snapshot published by an admin surface, with
 * no allow bucket at all. Unspellable beats runtime-refused; the consent validators refuse the spelling
 * anyway if a foreign writer plants it.
 *
 * TWO NAMED VALIDATION FACES read this union (design/382 §4.3): the CONSENT face (prepare candidates,
 * approval records, confirm/redeem, batch outcomes) admits all three members
 * ({@link import("./permission-rule-store.js").isValidConsentScope}); the DURABLE face (the persisted
 * store's write deltas, rule-sync in both directions, at-rest decode) admits only
 * `{global, project}` ({@link import("./permission-rule-store.js").isValidDurableScope}). One table
 * "for both" is exactly what would let a session row leak into the store silently.
 */
export type RuleScope = {
    kind: "global";
} | {
    kind: "project";
    root: string;
} | {
    kind: "session";
    sessionId: string;
};
/** The immutable causal identity of one add: a replica identity plus a monotonic counter. */
export interface RuleDot {
    actor: string;
    counter: number;
}
/** Where one add came from. Stored PER ADD — never folded across dots (a folded provenance cannot
 *  follow a dot that is later deleted on its own). */
export type RuleAddOrigin = "user" | "imported-cc" | "starter";
/** One add of one logical rule, carrying its own dot and its own provenance. */
export interface RuleAdd {
    dot: RuleDot;
    origin: RuleAddOrigin;
    createdAt: string;
}
/**
 * A persisted rule for one tool, in one of the three behaviors.
 *
 * The logical identity of a rule is the TRIPLE (behavior, rule text, scope): the same text may stand as
 * an allow and as a deny in one bucket (upstream keeps the three lists side by side and lets precedence
 * decide), so a tombstone, a quarantine row and a join group all key on the triple — a key on (text,
 * scope) alone would let a delete aimed at the allow take the deny with it.
 *
 * `adds` is a real set, not a scalar: two concurrently approved records for the same identity each
 * redeem their own dot, and a rule is live iff at least one of its adds survives the tombstones.
 * Organization rules are a separate partition (a server-published snapshot, no CRDT) and never enter
 * this type; the personal deny/ask rows here are the PERSON's own standing instructions (their settings'
 * deny/ask lists, imported through the same consent door the allow list takes).
 */
export interface PersistedRule {
    /** What a match of this rule says about the call — REQUIRED on every row this engine writes. A row
     *  read without one is a pre-field row and reads `allow` through {@link readRowBehavior}, at the
     *  reading seams only (the store's schema migration, the sync picker, a checkpoint's session rows). */
    behavior: RuleBehavior;
    /** Canonical rule text: `Bash(git status)` (exact) or `Bash(git status:*)` (prefix). A prefix body
     *  may itself be a connector chain (`Bash(cd /tmp && adb pull:*)`) — see the module note on
     *  connectors for what such a rule reaches. */
    rule: string;
    tool: PersistedRuleTool;
    match: PersistedRuleMatch;
    /** What the matcher compares against — parsed out of `rule`, stored so no consumer re-parses. */
    command: string;
    scope: RuleScope;
    adds: RuleAdd[];
}
/**
 * A delete intent, in observed-remove form: it carries EVERY add dot for its (rule, scope) that was
 * visible to the deleting replica at delete time. A single-dot tombstone cannot delete cleanly —
 * concurrent replicas add the same rule text under different dots, and a tombstone that names only one
 * of them lets the deleted rule come back. The merge criterion is dot IDENTITY, not a causal-order
 * comparison (single dots across replicas have no order to compare): an add is live iff its dot appears
 * in no tombstone for the same (rule, scope).
 */
export interface RuleTombstone {
    /** The identity's behavior member — a tombstone deletes adds of ONE (behavior, text, scope). */
    behavior: RuleBehavior;
    rule: string;
    scope: RuleScope;
    removedDots: RuleDot[];
    /** The delete operation's own dot — the operation's persistent identity. */
    deletedBy: RuleDot;
}
/** Why a rule text was refused. Codes are stable so an import report can group by them.
 *  `invalid.path_traversal` (design/382 §2.2, B5): a Read directory body carrying a `.` or `..`
 *  segment — the lexical-normal spelling is the ONLY spelling, so a traversal segment is not
 *  normalized away but refused. */
export type RuleRejectCode = "invalid.grammar" | "invalid.empty_command" | "invalid.not_simple_command" | "invalid.bare_interpreter_prefix" | "invalid.unbalanced_quotes" | "invalid.control_chars" | "invalid.too_long" | "invalid.path_traversal" | "unsupported.tool" | "unsupported.wildcard";
export interface RuleReject {
    code: RuleRejectCode;
    message: string;
}
/** The parsed, canonical shape of one rule text under one behavior — everything but the scope and the
 *  provenance. The behavior is the parser's INPUT (it decides which validator floor applies), carried
 *  out so a consumer holding the parse never re-derives which list the text came from. */
export interface ParsedRule {
    behavior: RuleBehavior;
    rule: string;
    tool: PersistedRuleTool;
    match: PersistedRuleMatch;
    command: string;
}
/** Upper bound on a rule text. Rules are human-authored one-liners; anything longer is a mistake or an
 *  attempt to make the store unreadable. */
export declare const MAX_RULE_TEXT_CHARS = 512;
/**
 * The most code points of a rule's ORIGINAL text a notice's machine-read slot carries: twice the grammar's own bound. A text past
 * {@link MAX_RULE_TEXT_CHARS} reads as no rule whatever follows its first characters, so the slot keeps room to show it as far as a person
 * needs to recognise it and no further; every text a rule can be is carried whole. Not exported from the package root: a consumer reads
 * the slot and never judges its length (it is not a relay that must refuse what the engine did not mint).
 */
export declare const NOTICE_RULE_TEXT_MAX: number;
/**
 * A rule's (or allow-list entry's) text as a notice's machine-read slot carries it: the original string, untouched, when it is at most
 * {@link NOTICE_RULE_TEXT_MAX} code points; a longer one is cut to that many and ends in the engine's one truncation mark
 * ({@link INLINE_TRUNCATION_MARK}), so the longest slot is `NOTICE_RULE_TEXT_MAX + INLINE_TRUNCATION_MARK_CODEPOINTS` (1 025) code points.
 * The ONE reading both `permission.allowed_tool_not_granted` (`detail.entries[].entry`) and `permission.rule_row_dropped`
 * (`detail.rows[].rule`) hand their rule text over through — what spells it for a person is the sentence, never this slot.
 */
export declare function noticeRuleText(text: string): string;
/**
 * Interpreter names whose PREFIX form is refused everywhere a rule can enter the store (minting,
 * import, file reload, every backend). `Bash(node:*)` reads as one narrow-looking rule and means "run
 * any program you like" — that is not a shape a single click can be understood to have authorized.
 *
 * The check is on argv[0] of the prefix, not on the prefix being exactly one word: `node -e:*` is the
 * same licence spelled longer. It is on argv[0]'s BASENAME, not its spelling: `/usr/bin/node:*` and
 * `./node:*` grant precisely what `node:*` grants, and a table consulted with the full path would be a
 * table any path prefix walks around (upstream takes the same basename before consulting its own
 * interpreter set). Deliberately strict-side — it costs the ability to persist a rule like
 * `python manage.py migrate:*` (which an import reports as skipped rather than dropping silently), and
 * an EXACT rule naming a whole interpreter command line stays legal, since it authorizes one command.
 *
 * TWO READERS, and they consult this table with DIFFERENT questions — do not read either as the other.
 * {@link parseRuleText} asks "is this rule TEXT's own head a row?" and compares the basename as
 * SPELLED, so an alias spelling (`SSH host:*`, `ssh.exe host:*`) is a different name to it and enters
 * the store like any other rule. {@link genericPrefixBody} asks "should the engine PROPOSE a body with
 * this head?" and normalizes first ({@link screenedHeadName}: case, trailing periods, executable
 * suffix), because a proposal is the engine's own act and must not hand a person an alias of a row.
 * The asymmetry is deliberate — normalizing at the validator would also refuse rule texts already
 * stored, imported or hand-authored — and it is the reason the residual paragraph below is about this
 * table's reach rather than about either reader.
 *
 * TWO groups, and the distinction matters when the table is next edited:
 *  · LANGUAGE interpreters (`node`, `python`, `ruby`, …) — the argument IS a program. This half is
 *    wider than upstream's own set, deliberately, and is the argument the paragraph above makes.
 *  · LAUNCHERS — a program whose argument is another program to run: the shells, the environment and
 *    privilege wrappers, the schedulers and resource wrappers, the tracers, and the shell BUILTINS
 *    that dispatch (`command`, `builtin`). This half is upstream's set, adopted verbatim rather than
 *    reasoned out row by row: it is the same question upstream answers with the same mechanism (its
 *    set is consulted on argv[0]'s basename too), and every row passes this table's own first axis —
 *    a body whose use is naming a program to execute is refused. They were missing, and each was one
 *    token in front of the licence the language half already refuses: `command node:*`, `timeout 9
 *    node:*` and `nice node:*` all grant exactly what `node:*` grants.
 *
 * KNOWN RESIDUAL, stated rather than hidden, and it is a property of the instrument rather than of this
 * particular list: a positive list of NAMES cannot be made complete, and two independent review rounds
 * enumerated escapes faster than rows could be added. Three shapes, none of which a longer list fixes:
 *  · a version- or distribution-suffixed spelling of a listed interpreter (`python3.12`, `nodejs`);
 *  · a launcher nobody listed — the set of programs that run another program has no boundary, and each
 *    round produced more of them;
 *  · a spelling that defeats the NAME question entirely: `/proc/self/exe` (basename `exe`, the running
 *    shell), a symlink or a renamed binary, a BusyBox applet, a dynamic loader invoked directly.
 * Matching a family would need a pattern rather than a set, with its own false-positive surface
 * (`node-gyp`, `timeout-monitor`), and no pattern addresses the third shape at all. What carries the
 * residue is therefore NOT this table: it is the three standing fences — the org deny/ask layer runs
 * ahead of this lane and cannot be silenced by it, a mandated ask is not rule-clearable, and the
 * narrower exact candidate (plus minting nothing) is always on the same card. This table's job is to
 * keep the OBVIOUS one-token licence off a card a person clicks once, not to be a boundary.
 */
export declare const BARE_INTERPRETER_NAMES: ReadonlySet<string>;
/**
 * Shell KEYWORDS, refused as a segment's `argv[0]` everywhere this lane reads a command.
 *
 * The floor this lane stands on extracts the first TOKEN of a segment and calls it the command name.
 * That identification is what every comparison here rests on — the interpreter refusal and a person's
 * reading of the rule text. For a control structure it is simply false: `for x in once; do curl
 * evil.example; done` splits into three segments whose first tokens are `for`, `do` and `done`; all
 * three are ordinary bare words, so the floor accepts each, and a rule text could name a keyword as
 * though it were a program.
 *
 * Refusing the keyword puts the whole command outside the ALLOW lane, which is the honest answer: no rule
 * can be minted for it and no allow matches it, so it asks. The DENY/ASK direction reads these shapes
 * with its own lexer (`shell-lexer.ts`: a prefixing keyword is stripped so `do curl evil.example` runs
 * `curl`; a `for`/`case` header, whose commands need a syntax tree, is `unreadable` and asks).
 *
 * `!` is here for the same reason with a sharper edge: it is a keyword that PREFIXES a real command, so
 * `! node -e …` is the `node` licence the interpreter table exists to refuse, wearing one extra token.
 * `{`/`}`/`[[`/`]]` are already refused by the floor's argv[0] metacharacter rule; listed anyway,
 * because that rule belongs to another module and is not this lane's to depend on.
 *
 * The words are the shell-word table's `rule_reserved` role (`src/shell/shell-words.ts`): the table is where a word's
 * grammar is stated once, and this lane only reads the projection.
 */
export declare const SHELL_RESERVED_WORDS: ReadonlySet<string>;
/**
 * design/185 §1 — the DEEPER-THAN-TWO-WORDS half of the prefix suggestion grammar.
 *
 * The prefix candidate itself is produced by {@link genericPrefixBody}: a general two-word rule that
 * covers every command whose second token reads as a subcommand. This table exists for the one thing
 * that rule cannot express — a body DEEPER than two words — so every row here makes a suggestion
 * NARROWER than it would otherwise be, and never wider.
 *
 * A flat set of BODIES — word sequences, each at least THREE words. A body takes the seat iff it is a
 * word-boundary prefix of the command's folded form, and the LONGEST hit wins; with no hit the
 * two-word generic body is used, and with neither there is no prefix candidate at all.
 *
 * Review criterion, one axis, and it is a PRECISION axis rather than a safety one: list a deeper body
 * when the two-word form names a NAMESPACE rather than an action. `gh pr`, `kubectl rollout`,
 * `docker compose`, `git stash`, `go mod`, `git submodule` and `yarn workspaces` are families of
 * verbs, and the verb is what a person reads as the thing they are approving. Listing a row costs
 * nothing and narrows one family; NOT listing one is not a refusal, since the generic two-word body
 * still fills the seat.
 *
 * WHAT THIS TABLE NO LONGER DOES, stated because two review axes used to live here and their removal
 * is a WIDENING no reader should have to reconstruct. Rows were once gated on "runs what it is told
 * to" (`docker run`, `npm exec`, `cargo install`, `gh extension`) and on "rewrites what others
 * execute" (`git config`, `kubectl config`, `npm config`, `go env`), and a head outside the table
 * produced NO prefix at all. Under the generic rule those bodies are produced like any other, which
 * is upstream's own behaviour and the adjudicated form: a positive table of two-word bodies cannot
 * cover the long tail of real CLIs, and the price of its closure was saying nothing at all for
 * everything outside nine families — the field report that drove this change was every `adb`,
 * `gradle` and `terraform` invocation asking forever, with an exact rule for one full command line as
 * the only offer and no reuse the moment an argument changed.
 *
 * What carries that residue is therefore NOT this table, and never was — it is the three standing
 * fences: the org deny/ask layer runs ahead of the rule lane and cannot be silenced by it; a mandated
 * ask (egress/irreversibility marks, shellGate:"always") is not rule-clearable either; and the
 * narrower exact candidate — plus minting no rule at all — is always on the same card. A prefix rule
 * also still admits ANY arguments after its body and never a second command, which is the property
 * that makes a body's width readable off its text.
 *
 * Maintenance: adding a row is a one-line narrowing of one family. The integrity pins (every body
 * ≥ 3 words, lowercase word shape, no interpreter heads, no duplicates) are enforced by this module's
 * test suite. This table and the read-only classifier's allowlists are DIFFERENT instruments and must
 * never be merged or cross-referenced: that one is a machine auto-allow face whose criterion is
 * "provably read-only"; this one is a human suggestion face whose criterion is "width a person can
 * read off the rule text". One guards against a machine loosening; the other against a person being
 * misled.
 */
export declare const SUGGESTION_LEXICON: readonly string[];
/**
 * Render a peer- or file-controlled value for a DISCLOSURE line (a warning, a refusal message, an
 * operator log). Takes `unknown` on purpose: most of these values are typed but arrive off a wire or a
 * file, so the runtime value can be anything, and a signature that demanded a string would push a bare
 * `String(x)` to every call site — the exact step that gets forgotten. Rule texts are the motivating case; the same treatment is owed to every untrusted
 * field a line interpolates (a wire `reason`, an actor id), since the hazard is the character class,
 * not which field carries it.
 *
 * Quoting a refused text verbatim would carry the exact sequence the refusal exists to keep off a
 * display surface, and would print the two texts a reader has to tell apart — `Bash(echo hi)` and
 * `Bash(echo<ZWSP>hi)` — identically, so the report could not name WHICH rule it means. Every character
 * {@link CONTROL_CHARS_RE} covers is therefore printed as its `\uXXXX` escape (`\u{XXXXX}` above the
 * BMP — the TAG block U+E0020–U+E007F is a `\p{Cf}` family that lives there); everything else passes
 * through, so an ordinary rule text reads normally. The result is length-bounded.
 */
export declare function escapeForDisclosure(value: unknown): string;
/**
 * The zero-width half of {@link renderUntrustedCommandText} on its own: every `\p{Cf}` character
 * REMOVED, nothing else touched. Exposed for a seat that must run a PATTERN pass (a secret scanner)
 * before the display render — a credential the text splits with a format character is invisible to
 * the scanner while it runs, and the render's own strip would then glue it back together in clear
 * AFTER the scanner missed it. Strip first, scan, then render: the render's strip is idempotent over
 * text this already stripped, so the final bytes are the same as a single render, minus the leak.
 *
 * It knows nothing of the engine's envelope defusal and needs to know nothing: a defused tag or
 * fence sentinel (untrusted-text.ts) is spelled with printable ASCII, so this pass — and any other
 * format-character cleaning a downstream layer applies to a defused string — leaves it as it was.
 */
export declare function stripFormatCharacters(text: string): string;
/**
 * Render raw, untrusted COMMAND text for a display surface — the minimal safe baseline for the seats
 * that carry post-rewrite command bytes verbatim ({@link SegmentRuleSuggestion.segment} is the
 * motivating one; a card's own `command`/`args` echo has the same hazard).
 *
 * WHAT IT DOES, and why the two character families are not handled the same way:
 *  · `\p{Cf}` (the bidirectional controls U+061C/U+200E/U+200F/U+202A–U+202E/U+2066–U+2069 and the
 *    zero-width formatters ZWSP/ZWNJ/ZWJ/WORD JOINER/SOFT HYPHEN/BOM) is REMOVED. These occupy no
 *    width, so deleting them is what makes the rendered line agree with what a reader believes they
 *    are reading — a `rm -rf x` that DISPLAYS as something benign is exactly the deception the seat
 *    would otherwise hand on. This is the same family
 *    {@link import("./tool-policy.js").AskRequest.hasBidiControls} raises its warning bit for; the
 *    bit says "the raw bytes carry them", this function is what a surface renders instead.
 *  · Controls and line/paragraph separators (C0 incl. TAB and NEWLINE, DEL, C1, U+2028/U+2029) are
 *    FOLDED TO A SPACE with runs collapsed, not removed: they occupy width (or a whole line), and
 *    deleting them would glue two tokens into one word that reads as a different command. TAB is not
 *    excepted here — a one-line display value has no use for one, and a rule text's foldable-tab
 *    exception belongs to the VALIDATOR class, not to this one.
 * The result is then capped by code point (never splitting a surrogate pair) with a `…` marker, and
 * carries the shared authority-envelope neutralization every untrusted inline value gets — this text
 * can land on a model-visible line as easily as on a human one.
 *
 * WHAT IT IS NOT. A DISPLAY baseline, never an identity channel and never adjudication input: the
 * transform is LOSSY and NOT injective (two distinct commands can render identically — that is the
 * price of a readable line). Correlate, match, store and adjudicate on the RAW bytes, which the
 * carrying seats keep unchanged: this function neither changes what those seats contain nor what any
 * of them means. A caller that must instead tell two adversarial texts APART wants
 * {@link escapeForDisclosure} (which escapes rather than folds, so nothing collides) or
 * {@link hasUnrenderableCharacters} (refuse instead of render).
 *
 * @param text the raw untrusted text (a `String(x)` is applied to anything that is not one).
 * @param maxLen code-point cap for the rendered result; defaults to the disclosure-line budget.
 */
export declare function renderUntrustedCommandText(text: unknown, maxLen?: number): string;
/**
 * Does this text carry a character of the class {@link escapeForDisclosure} exists for — the class
 * {@link CONTROL_CHARS_RE} names? Asked by a caller that must REFUSE such a value rather than render
 * it: the two answers to "these bytes cannot honestly ride a display line" are escape it or do not
 * take it, and a call site with nothing to display picks the second. Reads the same source regex as
 * the escape pass, so the screen and the escape cannot drift into two character classes.
 *
 * Not part of the package's public surface — an internal screen for lane entry points.
 */
export declare function hasUnrenderableCharacters(text: string): boolean;
/**
 * Parse one rule text into its canonical shape, or refuse it with a reason.
 *
 * This is the ONE validator every entry point shares — minting a suggestion, importing another
 * product's settings, reloading a store file, and any future backend. A shape refused here cannot enter
 * the store through any door.
 *
 * `behavior` is REQUIRED — there is no default, because the two directions have different floors and
 * a defaulted allow is exactly the silent-widening shape a validator must not have: the
 * bare-interpreter-prefix refusal is an ALLOW-side argument (one approval click cannot be read as
 * authorizing arbitrary programs), while a deny/ask has the opposite polarity — `Bash(node:*)` as a DENY
 * is a legitimately wide tightening, and refusing it would silently disarm exactly the rule a person
 * most wants. The tightening behaviors therefore admit the interpreter-headed prefix form and change
 * nothing else; the allow floor is unchanged.
 */
export declare function parseRuleText(text: string, behavior: RuleBehavior): {
    rule: ParsedRule;
} | {
    reject: RuleReject;
};
/** The canonical spelling of a rule for a (already folded) command and match form on `tool`. For
 *  `subpath` the command is the lexical-normal absolute directory (one leading slash), re-spelled in the
 *  double-slash directory form; for `path` it is the pattern as spelled. */
export declare function formatRuleText(command: string, match: PersistedRuleMatch, tool: PersistedRuleTool): string;
/** Does a name rule reach this name? `name`: equal; `name_prefix`: begins with it (no boundary — the reference's `startsWith`).
 *  A rule of any other family reaches no name. */
export declare function nameRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, name: string): boolean;
/** Does a domain rule reach this host (as {@link domainRuleSubjectOf} reads it)? `host`: equal; `subdomain`: a name ending in
 *  `.<domain>` with at least one label before it and no empty label. A rule of any other family reaches no host. */
export declare function domainRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, host: string): boolean;
/**
 * The closed set of BASES a path-form pattern resolves against — facts of the CALL (its execution
 * environment and its task), never of the rule:
 *  - `cwd`  — a relative pattern (`dist/**`, `./x`, a bare `x` read at any depth): the live tracked working directory; when no tracker
 *             moved (or the caller keeps none) the task root stands in for it — that stand-in is the WORD's
 *             own meaning (the same reading `TOOL_PATH_BASES.cwd` gives a tool's relative slot), not a
 *             fallback to a different base;
 *  - `root` — a root-relative pattern (`/dist/**`): the task's project root;
 *  - `home` — a home-relative pattern (`~/.ssh/**`): the home directory OF THE EXECUTION ENVIRONMENT the
 *             call runs in (`ExecutionEnv.homeDir`, declared by the adapter). NEVER the engine process's
 *             own home: on a remote or sandboxed leg the two differ, and a `~/` deny resolved against the
 *             engine host's home silently guarded the wrong directory (#644).
 * A pattern whose base is ABSENT (or not an absolute path) cannot be judged: the reach is `unreadable`
 * (fail-closed — a standing deny/ask the lane cannot read becomes an ask a person clears), never
 * silence and never a guess. `//abs` patterns need no base. {@link ruleBasesNeeded} tells a host BEFORE
 * prepare which base a rule will need, so it can declare it (or expect the ask).
 */
export declare const PATH_RULE_BASES: readonly ["cwd", "root", "home"];
export type PathRuleBase = (typeof PATH_RULE_BASES)[number];
/** The bases a call supplies, keyed by {@link PathRuleBase}; every member optional (absent = not supplied). */
export type PathRuleBases = {
    readonly [K in PathRuleBase]?: string;
};
/** The noun a refusal or an unreadable reason names each base by — the disposition table over the set
 *  (a base without a row does not compile: {@link PathRuleBaseLabelCoversEveryBase}). */
export declare const PATH_RULE_BASE_LABEL: {
    readonly cwd: "the working directory";
    readonly root: "the project root";
    readonly home: "the home directory of the execution environment";
};
/** The fence over the label table: `never` while every base has a row. */
export type PathRuleBaseLabelCoversEveryBase = AssertAllKeysHandled<Exclude<PathRuleBase, keyof typeof PATH_RULE_BASE_LABEL>>;
/**
 * WHICH base a rule's pattern resolves against — the one classification `resolvePathPattern`, the rule
 * compiler's compile-time check and a host's pre-prepare "can this environment judge this rule?" all read.
 * Empty for a rule that needs none: a `//abs` pattern, a `subpath` (directory) rule, a command rule. The
 * relative form names `cwd`, whose own meaning includes "the root stands in when no tracker moved" — a
 * host that supplies `root` has therefore supplied `cwd`'s stand-in too.
 */
export declare function ruleBasesNeeded(rule: Pick<PersistedRule, "match" | "command">): readonly PathRuleBase[];
/** Is this base usable by {@link resolvePathPattern} — an absolute path with a lexical normal form? A rule
 *  compiler asks this at compile time so a pattern is refused rather than compiled inert. */
export declare function isUsablePathBase(base: string | undefined, family: PathFamily | undefined): boolean;
/**
 * The PATH family's tightening reach — three-valued like the command family's ({@link programRunReachOf}):
 * does this deny/ask rule reach the call's target path? The target is the caller's ALREADY-RESOLVED
 * lexical-normal absolute path (the same identity the read/write fences judge with). A `subpath` rule
 * reaches the directory and everything under it ({@link directoryRuleAdmits}); a `path` rule resolves its
 * spelling against the call's bases ({@link resolvePathPattern} — a cwd-relative bare name reads at any
 * depth, a trailing `/**` peeled first and, for that peeled form alone, by the rule's BEHAVIOR:
 * `path_rule.bare_name`) and matches segment-wise (`*` within a segment, `**` across
 * segments), reaching the path it names and everything under a directory it names. A command-family rule
 * reaches no path. `unreadable` (#644): the pattern needs a base the call did not supply (or supplied as
 * a non-absolute spelling) — the rule CANNOT be judged, and the answer says which base; a lane that must
 * fail closed on it turns it into the ask a person clears (the persisted lane's unreadable answer, the
 * content lane's unreadable ask), exactly as an unreadable command is treated. Silence here was the #644
 * defect (a `~/` deny falling back to the engine host's home guarded the wrong directory; a `/…` deny with
 * no root reached nothing at all).
 */
export declare function pathRuleReachOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, target: PathIdentity, bases: PathRuleBases, treeFamily?: PathFamily | undefined): ProgramRunReachOutcome;
/**
 * Where a path rule's reach STARTS under the call's bases — its HEAD: the location the resolved pattern names before
 * its first segment carrying `*` (the whole path when none does), or a directory rule's directory. Everything the rule
 * reaches lies at or below the head, so a spelling of the head that the environment resolves elsewhere (a link, a
 * firmlink, `/var` → `/private/var`) is a second spelling of everything the rule reaches — the deny tables judge the
 * rule at both ({@link import("../tools/fs/read-deny.js").withRuleRowsWhereTheyLead}; the reference's "physical twin"
 * of a rule prefix, cli280.js 9766583). The head may be the bare anchor (`/`, a drive or share root — `Read(/**)` at a
 * drive root): an environment can resolve a drive or share root elsewhere. `undefined`: the pattern needs a base the
 * call does not supply (the rule then reads `unreadable`, a reach, everywhere), or a directory rule names the bare
 * anchor (it admits nothing).
 */
export declare function pathRuleHeadOf(rule: Pick<PersistedRule, "match" | "command" | "behavior">, bases: PathRuleBases): PathIdentity | undefined;
/**
 * Does this rule's command pattern admit `command`?
 *
 * `command` is the raw tool argument: the floor and the folding happen HERE, in that order, so no
 * caller can match a command this lane has not read. Returns false for every redirection, substitution,
 * subshell, backgrounded or escaped form.
 *
 * **A prefix rule reaches past its body only inside its FINAL segment.** This is the single
 * load-bearing line of the connector widening, and it is checked on the MATCH side rather than left to
 * the mint side: the command must read as exactly as many segments as the rule's own body. For the
 * single-command body that is the historical contract verbatim — `Bash(npm:*)` is an ordinary,
 * legitimately mintable rule, and if the "does this command start with `npm `" arm were allowed to see
 * a compound at all, that rule would admit `npm test && curl evil.example` — one stored yes to a build
 * command turned into a standing yes to whatever is chained behind it. For a compound-prefix body the
 * same count equality is the containment: `Bash(a && b:*)` admits `a && b x`, never `a && b x && c`.
 * An EXACT rule has no such reach by construction: it admits one string, the one it spells.
 */
export declare function ruleAdmitsCommand(rule: Pick<PersistedRule, "match" | "command">, command: string): boolean;
/**
 * What one TIGHTENING (deny/ask) rule can say about a call — the closed three-word answer of
 * {@link programRunReachOf}:
 *  - `reached` — the text PROVABLY performs a run the rule names (the whole chain, or any one segment);
 *  - `not_reached` — the text provably does not (every compared word is literal and differs, or the
 *    command is shorter than the rule's body);
 *  - `unreadable` — the text MAY perform it but cannot be read: an expansion sits where the rule reads a
 *    word, a quote or substitution never closes, a syntax error, a control structure whose commands need
 *    a syntax tree, a wrapper handed its command as one string.
 * The three lanes that consume it (the org partition, the personal deny/ask rows, the rule DSL's
 * content-form rules) dispose of the words identically: `reached` speaks with the rule's behavior,
 * `not_reached` is silence, and `unreadable` is a FAIL-CLOSED ask — never silence, which is what once let
 * `rm -r x; echo "$HOME"` walk past a deny on `rm -r` because one expansion took the whole line out of
 * the reader. Registered in docs/CLOSED-SETS.md.
 */
export declare const PROGRAM_RUN_REACHES: readonly ["reached", "not_reached", "unreadable"];
export type ProgramRunReach = (typeof PROGRAM_RUN_REACHES)[number];
/** The answer of {@link programRunReachOf}: the word, and for `unreadable` the reason a surface can show. */
export type ProgramRunReachOutcome = {
    readonly reach: "reached" | "not_reached";
} | {
    readonly reach: "unreadable";
    readonly reason: string;
};
/**
 * Does one TIGHTENING (deny/ask) rule speak about this call — as a whole, or about any single program
 * run in it — or can the call not be read against it at all? The ONE reach predicate of the tightening
 * direction, shared by the org partition, the personal deny/ask rows and the rule DSL's content-form
 * deny/ask rules, so no two of them can disagree about what a deny reaches.
 *
 * The allow lane asks a different question and gets a different answer, deliberately. An allow rule is
 * one recorded human yes and must cover the WHOLE call to clear it ({@link ruleAdmitsCommand}:
 * `Bash(npm test)` does not clear `npm test && curl evil.example`, because nobody said yes to the second
 * half), and it refuses every shell construct outright. A deny/ask rule is a standing statement about a
 * program run, and a call that MAY perform that run is a call the rule speaks about — so this direction
 * reads the command with a real lexer ({@link readShellCommand}): redirections are stripped at the
 * token level, quotes are removed on BOTH sides before words are compared (`rm "-r" x` runs `rm -r x`),
 * wrappers are peeled (`sudo env X=1 nohup rm -r x` runs `rm -r x`), every connector — `&` and a newline
 * included — bounds a segment, and a comment is not a run. Deliberately an OVER-approximation, in the
 * tightening direction: the segments are the runs the text can reach, not the runs that will happen, so
 * a short-circuited branch (`true || curl evil.example`) is judged as though it ran. Answering "does this
 * definitely execute" needs evaluation, not parsing; over-firing costs an approval prompt, and the
 * opposite error costs the rule.
 *
 * Words are compared ARGV-wise, position by position, against the rule body read by the same lexer —
 * over every run CANDIDATE a segment carries: as spelled, then each peeled layer (`sudo rm -r x` is a run
 * of `sudo …` AND of `rm -r x`; a deny on either reaches; a rule body is never peeled, so
 * `Bash(sudo rm -r x)` denies exactly that). A literal word that differs settles `not_reached`; a word
 * carrying an expansion at a position the rule reads settles `unreadable` (the text could spell the
 * rule's word there — unless it is the SAME glob/brace spelling the body wrote); a prefix body whose
 * every word is matched is `reached` whatever follows; an exact body needs equal length — a longer
 * command is `not_reached` unless an extra word is an UNQUOTED expansion (or `"$@"` / an array
 * expansion), which may expand to nothing. The commands inside a substitution and an unquoted heredoc
 * body are segments of their own (`echo "$(rm -r x)"` runs `rm`). A compound
 * body (`Bash(echo ok && rm -r:*)`) reaches only a chain of the same shape — same segment count, same
 * connectors, every leading segment equal, the final one by the body's match kind; a single-segment
 * body reaches any segment. A segment the lexer could not read makes the answer `unreadable` when no
 * other segment already reached.
 *
 * A rule body the lexer cannot read (foreign store bytes; every entrance validates against exactly
 * this) reaches nothing. A rule of another family (path: `subpath` / `path`; name: `name` / `name_prefix`) reaches no
 * command: its predicates are {@link directoryRuleAdmits}, {@link pathRuleReachOf} and {@link nameRuleAdmits}.
 */
export declare function programRunReachOf(rule: Pick<PersistedRule, "match" | "command">, command: string): ProgramRunReachOutcome;
/** The boolean face of {@link programRunReachOf}: `reached` only. The shadow view and the pins that
 *  assert a reach read this; a lane that must fail closed on `unreadable` reads the outcome. */
export declare function ruleReachesProgramRun(rule: Pick<PersistedRule, "match" | "command">, command: string): boolean;
/** design/382 §3.3-5 — one BREADTH note about a rule a person is about to persist: which wide shape
 *  it has, said in words a surface can show beside the input line. A warning is never a refusal —
 *  the closed refusal set is untouched — and never adjudication input. */
export interface EditedRuleBreadthWarning {
    readonly code: "broad_prefix" | "compound_prefix";
    /** The baseline sentence (a surface may reword it, never invert it — the same contract as the
     *  §3.5 reason baselines). */
    readonly message: string;
}
/**
 * design/382 §3.3-5 — the breadth classifier behind the card-edit face's warning seat: does this
 * (accepted) rule carry a width worth saying out loud before a person persists it?
 *
 *  · `broad_prefix` — a prefix rule whose body is a SINGLE token (a bare verb, `Bash(mkdir:*)`):
 *    it admits every argument form of that command, not just the one on the card. This is the
 *    legal wide grant of §3.4 — the offer face deliberately never proposes it, the edit /
 *    hand-written channels legally accept it, and this sentence is what makes that acceptance an
 *    INFORMED one.
 *  · `compound_prefix` — a prefix rule whose body is a connector CHAIN (`Bash(cd /tmp && npm:*)`):
 *    one rule admitting several program runs, with anything extending its final segment.
 *
 * Judged on the CANONICAL rule (post-normalization, the spelling that would persist), with the
 * lane's own instruments — the same shape reader the matcher uses, never a second grammar. A body
 * this lane cannot read yields no warning: every such spelling is already the validator's refusal,
 * and a warning beside a refusal would be noise about a rule that is not going to exist.
 *
 * An EXACT rule never warns (it admits one string, the one it spells — there is no width to name),
 * and a SUBPATH rule is not this classifier's subject (no command reach at all). The two
 * reach-bearing forms take the same two questions: `prefix` (colon-star) and `wildcard`
 * (trailing space-star, #510 — match-equivalent reach on a single-command body; its grammar has no
 * chain reading, so only the single-token question can fire for it).
 * MAINTENANCE DUTY, stated where it binds: every match form with PREFIX REACH belongs here — a
 * new reach-bearing member added to {@link PersistedRuleMatch} owes this classifier its arm, judged
 * by the same "single token / chain body" questions (an exact-only form never warns).
 * The upstream editor validates edited text but shows no breadth evidence at all; this seat is a
 * superset disclosure (registered divergence, design/382 §6-4).
 */
export declare function ruleBreadthWarningsOf(rule: Pick<PersistedRule, "match" | "command">): readonly EditedRuleBreadthWarning[];
/**
 * Is `path` inside (or equal to) `root`? Word-boundary containment on the path separator, so `/a` does
 * not contain `/ab`. Both sides are expected to be canonical already.
 */
export declare function pathWithinRoot(path: string, root: string): boolean;
/**
 * The LEXICAL NORMALIZER for a CALL's target path (design/382 §2.2 — the rule family's one path
 * identity is the lexical normal form, zero IO): collapse slash runs and `.` segments, resolve `..`
 * segments textually (a `..` at the root stays at the root, as POSIX resolves it), and answer the
 * one-leading-slash absolute spelling — or `undefined` for a relative path. RULE
 * bodies never come through here (their canonical spelling is unique and non-normal spellings are
 * refused at the validator); this is for the comparison INPUT a caller derives from a live call.
 */
export declare function lexicalNormalAbsolutePathOf(path: string, family: PathFamily | undefined, anchor?: string): PathIdentity | undefined;
/**
 * design/382 §2.5 — the ONE admission predicate of the Read directory rule family, beside
 * {@link ruleAdmitsCommand} as its path-lane sibling. Four consumers share it (the minting round
 * trip, the coverage table, the gate conjunction arm, and the gate's Read lane) so there is no
 * second fact source anywhere.
 *
 * `path` is the caller's ALREADY-RESOLVED lexical-normal absolute path — the same path identity the
 * deployment's read adjudication judged the call with (for the Read tool face), or the cd resolver's
 * output (for the Bash cd-segment face). This predicate NORMALIZES NOTHING: a non-normal spelling
 * (a `.`/`..` segment, a doubled slash, a relative path) is refused outright, fail-closed, so a
 * caller cannot smuggle a second path identity through the comparison. Containment is the
 * word-boundary one ({@link pathWithinRoot}: `/a` never reaches `/ab`).
 *
 * What it never does, stated as loudly as what it does (§2.5): as an ALLOW it admits READS ONLY — the
 * TOOL conjunct is the caller's (every consumer judges eligibility with the call's tool, and
 * {@link adjudicatePersistedPathRules} takes the allow arm for the Read tool alone), so a directory allow
 * never admits a write, never a whole Bash command, never a non-cd segment — and it clears ASKS only,
 * never a filesystem fence: workspace containment, the sensitive-read deny face and every violation
 * refusal judge exactly as they would without the rule (a fence refusal is structural, not an ask, so
 * there is nothing here for it to clear). As a DENY/ASK the same containment refuses or questions every
 * path under the directory, on whichever path tool the rule names.
 */
export declare function directoryRuleAdmits(rule: Pick<PersistedRule, "match" | "command">, path: PathIdentity, bases: PathRuleBases, treeFamily?: PathFamily | undefined): boolean;
/** The identity of a directory SPELLING (the cd resolver's `/`-rooted output) under the call's tree, for the
 *  consumers that hand {@link directoryRuleAdmits} a spelling; a rule minted from it stores this form. */
export declare function directoryIdentityOf(spelling: string, bases: PathRuleBases): PathIdentity | undefined;
/** Does a rule's scope cover a task running in `cwd` (and, for the session dimension, in the session
 *  named by `sessionId`)? A project rule needs a cwd to compare against; without one it covers nothing
 *  (fail-closed). A session rule (design/382 §4.3) covers a call iff the call's `sessionId` equals the
 *  scope's — and a call that carries NO session identity is covered by no session rule, the same
 *  fail-closed arm as project-without-cwd: an axis the caller did not thread is an axis the rule
 *  cannot speak on. `sessionId` is threaded from the ORIGINAL call context by the caller (the 375
 *  r3-② cwd posture), never inferred from the process. ONE function on purpose: this is the scope
 *  conjunct of the single eligibility predicate ({@link eligibleContext}), and a sister predicate
 *  would be the second fact source the 375 §5.1 discipline exists to forbid. */
export declare function scopeCoversCwd(scope: RuleScope, cwd: string | undefined, sessionId?: string, identity?: ScopeRootIdentity): boolean;
/**
 * A leg's root as an IDENTITY for project coverage: its canonical spelling, and the leg's resolution of a project row's
 * scope root to the location it leads to. A DENY or ASK project row written under one spelling of a directory (a link the
 * host spelled, a firmlink) covers a leg rooted at another spelling of the same directory: the row applies by where the two
 * roots ARE, not only by how they are spelled. An allow row never reads it ({@link spelledScope}). A caller that holds no
 * resolution (no leg, no environment) compares the spellings alone.
 */
export interface ScopeRootIdentity {
    readonly canonicalRoot: string;
    readonly canonicalOf: (root: string) => string | undefined;
}
/** Is this rule live — i.e. does it still carry at least one add? Deleted adds are removed by the store
 *  when tombstones are applied, so a rule with an empty `adds` is a rule that no longer exists. */
export declare function isRuleLive(rule: PersistedRule): boolean;
/**
 * design/375 §5.1 — MAY this rule speak about this call's CONTEXT? Tool identity plus scope coverage,
 * and nothing about the rule's liveness: the CONTEXT question is the half a not-yet-persisted rule (a
 * card's proposed candidate, which has no add dots yet) can already answer.
 *
 * The tool conjunct is not decoration: more than one tool takes a shell command string, and matching on
 * the command alone would let an approval given on one surface resolve a call on another. The scope
 * conjunct is the project fence — project A's approval is not project B's, and a project rule with no
 * cwd to compare against covers nothing (fail-closed, {@link scopeCoversCwd}).
 *
 * `scope` rides beside the rule rather than inside it for the PROPOSED half's sake: a
 * {@link ParsedRule} carries no scope, so a proposal is judged against the scope it would LAND in.
 *
 * design/382 §4.3 — the call context carries a THIRD axis, `sessionId` (optional: an existing caller
 * that threads only `{tool, cwd}` still compiles and still means what it meant — no session rule can
 * cover its calls, the fail-closed direction). It is the session dimension's whole eligibility story:
 * every consumer of this ONE predicate (the gate's conjunction arm, the coverage table, the consent
 * prepare) inherits it without a second judge existing anywhere.
 */
export declare function eligibleContext(rule: {
    tool: string;
    scope: RuleScope;
}, call: {
    tool: string;
    cwd: string | undefined;
    sessionId?: string;
    scopeIdentity?: ScopeRootIdentity;
}): boolean;
/**
 * design/375 §5.1 — MAY this PERSISTED rule participate in admitting this call at all? The liveness
 * layer on top of {@link eligibleContext}: a tombstoned rule (zero surviving adds) is a rule that no
 * longer exists and must not count as coverage anywhere.
 *
 * THE single eligibility predicate for every consumer that reads the persisted store — the gate's
 * conjunction arm and its coverage-set selection here, the suggestion face's coverage judgement and the
 * consent prepare's ({@link segmentCoverageOf} is the shared instrument). One predicate, so a rule that
 * one consumer refuses cannot be a rule another counts: the historically inlined trio of checks
 * (live / tool / scope) is this function, verbatim — plus the BEHAVIOR conjunct: a mixed table holds
 * allow, deny and ask rows, and a consumer asking "which allow rules cover this" must not be answered
 * by a deny row that happens to spell the same text. The behavior is a REQUIRED argument for the same
 * reason the parser's is: a defaulted allow here is a silent widening.
 */
export declare function eligiblePersisted(rule: PersistedRule, call: {
    tool: string;
    cwd: string | undefined;
    sessionId?: string;
    scopeIdentity?: ScopeRootIdentity;
}, behavior: RuleBehavior): boolean;
/**
 * The call context an ALLOW row's scope is judged in: the spellings alone. A deny or ask row applies wherever its project's
 * directory is (its scope root resolved where it leads — more rows, only ever more protection); an allow row applies only
 * where its scope root is SPELLED, so a link someone can re-point never carries a standing approval to another directory.
 */
export declare function spelledScope<C extends {
    scopeIdentity?: ScopeRootIdentity;
}>(call: C): Omit<C, "scopeIdentity">;
/**
 * Find the rules that admit this command for a task in `cwd`, or `undefined`.
 *
 * THE ANSWER IS A COVERAGE SET (design/375 §5.1, the upstream per-segment form): a single rule that
 * admits the whole command is a one-element set — the historical arm, table order preserved verbatim —
 * and a COMPOUND whose every segment is admitted by some eligible rule is covered by the CONJUNCTION of
 * those rules, returned as the set that covered it. The judging ladder, in order:
 *  1. the fail-closed floor (a shape the lane cannot read matches nothing), evaluated once;
 *  2. the single-rule arm — whole-string exact, single/compound-prefix — exactly the historical loop:
 *     first table-order match wins and is what the decision names. A single rule naming the command is
 *     found BEFORE any conjunction: reporting one rule is narrower than reporting a set;
 *  3. the conjunction arm, compounds only: every segment must be admitted by some eligible rule under
 *     the MATCH reading (the allow side's own under-approximating instrument — never the deny side's
 *     wider PROGRAM_RUNS reading), or be a read-only segment that allows itself ({@link readsOnlyAsSegment});
 *     at least one rule must speak. Per segment the FIRST table-order admitting rule is selected, and
 *     the coverage set is those selections in segment order, de-duplicated — the same command against
 *     the same rule table always names the same set (reporting-order determinism, the historical
 *     "order is defined only for reporting" contract extended to sets).
 *
 * What the conjunction arm deliberately does NOT change: {@link ruleAdmitsCommand} itself. A
 * single-command prefix body still never admits a chain — `Bash(npm:*)` does not admit
 * `npm test && curl evil.example` — but the chain is now admitted when EVERY segment has a standing
 * yes, which is the upstream decision form (per-segment allow, any-segment deny decided strictly
 * earlier at the gate's org layer). A trailing-terminator spelling (`a && b ;`) keeps the conservative
 * answer by construction: under the MATCH reading its tail is an empty segment the floor refuses, so
 * the whole command is outside the lane and asks.
 *
 * Order is defined only for reporting (what the decision names); no rule is stronger than another —
 * they are all the same one answer, "the person already said yes to this".
 *
 * ALLOW rows only: this is the allow direction's question, and a deny/ask row in the same table is not
 * coverage (the eligibility predicate's behavior conjunct). The mixed-table walk that orders the three
 * behaviors is {@link adjudicatePersistedRules}.
 */
export declare function findAdmittingRule(rules: readonly PersistedRule[], call: AdmissionCall): readonly PersistedRule[] | undefined;
/** Whose admission a list's allow rows answer: a GRANT (a person's recorded yes, a list that releases a call — the reference's
 *  per-segment form, where a read-only segment of a compound allows itself) or a NARROWING (a list outside of which nothing
 *  runs — every segment must be named by a row; nothing allows itself). The posture is the list's, never the call's. */
export type AdmissionPosture = "grant" | "narrowing";
/** The call {@link findAdmittingRule} / {@link commandCoverageOf} judge. */
export interface AdmissionCall {
    tool: string;
    command: string;
    cwd: string | undefined;
    sessionId?: string;
    /** The directory RELATIVE cd targets resolve against — the live TRACKED working directory of the
     *  execution seat (adversarial-review P1: after an earlier observable `cd`, the shell resolves
     *  `./x` against the moved cursor, not the task root). Defaults to `cwd`; the scope/eligibility
     *  axis stays `cwd` either way. */
    execCwd?: string;
    /** {@link AdmissionPosture}; absent = `grant`. */
    posture?: AdmissionPosture;
}
/** {@link findAdmittingRule}'s answer with its miss named: the rules that cover the command, or the first segment no row covers
 *  (`undefined` when the command cannot be read at all — the fail-closed floor). One walk; the miss is for a sentence only. */
export declare function commandCoverageOf(rules: readonly PersistedRule[], call: AdmissionCall): {
    readonly covering: readonly PersistedRule[];
} | {
    readonly uncovered: string | undefined;
};
/** The answer of {@link adjudicatePersistedRules}: which behavior decided, and the rule(s) it stands
 *  on — a one-element set for a deny/ask (the first table-order rule of the deciding behavior that
 *  reaches the call; for a keyed tool's call naming no value for its key, a DENY standing on the first deny — else ask —
 *  row of that key, which the call cannot be read against: {@link adjudicatePersistedNameRules}) and the allow
 *  direction's coverage SET ({@link findAdmittingRule}). */
export interface PersistedRuleVerdict {
    readonly behavior: RuleBehavior;
    readonly rules: readonly PersistedRule[];
    /** The call could not be READ against the person's deny/ask rows (see {@link programRunReachOf}):
     *  `behavior` is `ask`, `rules` names the first tightening row that could not be judged, and this is
     *  the lexer's reason. The lane projects it as an UNREADABLE answer carrying that reason (it fails closed)
     *  — never as a matched ask rule. */
    readonly unreadable?: string;
    /** A path DENY's coordinates: the view of the target the reaching row met and the face the call is on. Minted here,
     *  with the verdict; the gate's exits mint the refusal's card from it and judge nothing a second time. */
    readonly denial?: PathDenialEvidence;
}
/**
 * The ONE adjudication over a MIXED persisted-rule table for a shell call: walk the behaviors in
 * {@link RULE_BEHAVIOR_PRECEDENCE} order and answer with the first behavior whose eligible rules speak
 * for the call — deny before ask before allow, the upstream arbiter's order across its three lists.
 *
 * Each behavior is judged with ITS direction's instrument: a deny/ask row through
 * {@link programRunReachOf} (the wide tightening reading: whole command or any program run in it,
 * three-valued), an allow through {@link findAdmittingRule} (the narrow reading: the whole call, or
 * every segment of it, covered by recorded yeses). The asymmetry is the point and runs one way only: a
 * deny reads MORE of a command than an allow does, so an exact allow naming a whole chain can never
 * outrun a deny on one of its parts. A call the lexer cannot read against a standing deny/ask row is an
 * UNREADABLE verdict (`behavior: "ask"` + `unreadable`), returned before any allow is consulted: the
 * question "does one of my rules forbid this?" is unanswered, and a recorded yes must not answer it.
 * Eligibility (live, this tool, scope covers the call, THIS behavior) is the one predicate every
 * consumer shares. `undefined` ⇒ no rule of any behavior speaks.
 */
export declare function adjudicatePersistedRules(rules: readonly PersistedRule[], call: {
    tool: string;
    command: string;
    cwd: string | undefined;
    sessionId?: string;
    execCwd?: string;
    scopeIdentity?: ScopeRootIdentity;
}): PersistedRuleVerdict | undefined;
/**
 * The path-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules
 * of one path-targeting tool, judged on the call's target views by the one judgment every path rule meets
 * (`judgePathRows`: rows outer, views inner).
 *
 * TWO readings, because the two directions read the call's path slot differently (design/388 B6/B17): the
 * tightening arms judge the PROTECTIVE reading's views (`views.tighten` — the object face's declaration, the catalog's
 * for the name as the floor: reading the catalog for an undeclared same-name tool can only tighten a deny), every row
 * also where its head leads (`leads`, the judging leg's resolution); the allow arm judges the DECLARED reading's views
 * (`views.allow` — the face's OWN declaration only: a same-name tool that declares no path target is unconfirmable, and
 * no grant admits it) — the NARROW reading: a row speaks for a call on a face its {@link PATH_RULE_FACES} allow column
 * holds, and admits it only when it admits EVERY view (a link out of the named place is not admitted through its
 * spelling, a case twin is another spelling, no row is judged where its head leads, an unreadable view is admitted by
 * nothing). Either reading may be absent (no resolvable path in that reading); the arm that needs it then speaks for nothing.
 *
 * A row is anchored by `anchorsOf` (the person's rows: a project row at its scope root, the rest at the chain's top leg).
 *
 * `call.face` is the face the call's protective target is on (`read` / `write`): a tightening row (deny or ask) speaks for
 * the call when it is the call's tool's row, or when {@link PATH_RULE_FACES} puts that face in the row's column of THAT
 * behavior (a `Read(<path>)` deny row refuses, a `Read(<path>)` ask row asks for, a Grep / Glob / RepoMap / caller's reader
 * searching or reading under the path) — the one sentence the rule policy's lane speaks too. Both directions judge the call's
 * own target here; the run's deny tables are a different job (a search's results, a shell command's operands, the
 * execution-time re-judgment).
 */
export declare function adjudicatePersistedPathRules(rules: readonly PersistedRule[], call: {
    tool: string;
    cwd: string | undefined;
    sessionId?: string;
    face?: DenyFace | undefined;
    scopeIdentity?: ScopeRootIdentity;
}, views: {
    readonly tighten?: TargetViews;
    readonly allow?: TargetViews;
}, anchorsOf: (rule: PersistedRule) => PathRuleBases, leads: ReadonlyMap<string, string | undefined>): PersistedRuleVerdict | undefined;
/**
 * The name-family sibling of {@link adjudicatePersistedRules}: the same precedence walk over the rules of one name tool
 * ({@link NAME_RULE_FACES}), judged on the one string the call names (`nameRuleSubjectOf`). A rule of the tool speaks for the call
 * when it is live, of this behavior, in scope ({@link eligiblePersisted}, the one predicate) and {@link nameRuleAdmits} the name; the
 * first behavior in {@link RULE_BEHAVIORS_BY_PRECEDENCE} whose rules speak decides, the first table-order rule of it is the answer.
 * Every behavior is judged with the one predicate — a name has no "program run" the tightening direction could read wider.
 * A call that names nothing (read as the tool reads it): on a bare-spelling tool it is reached by no rule; on a KEYED tool it
 * cannot be read against the key's rules ({@link keyedValueAbsentReason}) and is REFUSED — a deny verdict standing on the
 * first eligible deny row of the key, else its first ask row (an ask a person could approve would let a deny be approved
 * past) — and no allow row reaches it.
 */
export declare function adjudicatePersistedNameRules(rules: readonly PersistedRule[], call: {
    tool: string;
    name: string | undefined;
    cwd: string | undefined;
    sessionId?: string;
    scopeIdentity?: ScopeRootIdentity;
}): PersistedRuleVerdict | undefined;
/**
 * The domain-family sibling of {@link adjudicatePersistedNameRules}: the rules of one domain tool ({@link DOMAIN_RULE_FACES})
 * judged on the host the call fetches ({@link domainRuleSubjectOf}) by {@link domainRuleAdmits}; the first table-order rule
 * that is eligible and admits the host is the answer. The grammar has an allow form only, so only an allow can speak.
 */
export declare function adjudicatePersistedDomainRules(rules: readonly PersistedRule[], call: {
    tool: string;
    host: string | undefined;
    cwd: string | undefined;
    sessionId?: string;
    scopeIdentity?: ScopeRootIdentity;
}): PersistedRuleVerdict | undefined;
/**
 * The ONE offer a name tool's approval card carries: the exact rule for the name the call carries (`Skill(<name>)`, the reference's
 * "don't ask again for <skill>" row), or undefined when no rule spells exactly that name (the round-trip law: the canonical text,
 * parsed back as an allow, must be the exact name rule over the very name — a leading slash, an interior star or an unreadable
 * spelling offers nothing rather than a rule that would not clear the ask it was minted on). The reference's second row, the first
 * word as a prefix, is offered only for a name containing a space; it is not minted here (it would widen past the name shown).
 */
export declare function suggestRuleForName(tool: string, name: string): Extract<RuleOffer, {
    kind: "single";
}> | undefined;
/**
 * design/375 §5.2 — the per-segment coverage table for `command`: which segments an eligible rule
 * already admits, judged with the SAME split and the same eligibility predicates as the gate's
 * conjunction arm — one instrument, so the suggestion face and the consent prepare can never call a
 * segment covered that the gate would ask about (or vice versa).
 *
 * The split is the OFFER reading ({@link OFFER_READING}, #490 修①), which is the MATCH reading on
 * every axis but where a redirection is refused — this table exists to align, row for row, with
 * {@link suggestRulesForCommand}'s own segmentation, and a table that stopped one command short of the
 * face it aligns with is a table that face discards (its positional belt), leaving every segment
 * reading UNCOVERED. Each ROW's verdict is still the matcher's, unchanged: `covered` is
 * {@link ruleAdmitsCommand} under the MATCH reading, so a segment carrying a redirection is admitted
 * by nothing and reports `false`. The gate's own conjunction arm keeps the whole-string floor and is
 * not consulted here: for a redirection-bearing compound it has no segments at all, and this table
 * saying "an eligible rule admits `npm test`" is a true statement about that segment either way — it
 * is read only to keep an already-covered segment out of a batch offer, never as an admission.
 *
 * TWO SOURCES, two predicates, and the seat labels are the contract:
 *  · `persisted` — rules already in the store, judged {@link eligiblePersisted} ∧ admit: a tombstoned
 *    rule, another project's rule and another tool's rule are uniformly NOT coverage;
 *  · `proposed` — rules of the CURRENT batch that are not yet persisted (no add dots yet), judged
 *    {@link eligibleContext} ∧ admit against the scope each would land in. Judging these with the
 *    persisted predicate would make "this batch, once redeemed, covers the command" permanently false
 *    — the liveness layer is the one thing a proposal legitimately lacks.
 *
 * Returns `undefined` for a command the lane cannot read at all (the fail-closed floor — the caller's
 * honest answer is then "no coverage table", which every consumer must treat as all-uncovered). The
 * reported `segment` texts are the FOLDED spellings, trimmed — display/correlation seats, never
 * adjudication inputs.
 */
export declare function segmentCoverageOf(command: string, rules: {
    persisted: readonly PersistedRule[];
    proposed?: readonly {
        rule: ParsedRule;
        scope: RuleScope;
    }[];
}, call: {
    tool: string;
    cwd: string | undefined;
    sessionId?: string;
    /** The relative-cd resolution base (see {@link findAdmittingRule}'s member of the same name) —
     *  defaults to `cwd`; scope/eligibility stays `cwd`. */
    execCwd?: string;
    /** The leg's root as an identity for project coverage ({@link ScopeRootIdentity}). */
    scopeIdentity?: ScopeRootIdentity;
}): readonly SegmentCoverage[] | undefined;
/** One row of a per-segment coverage table (see {@link segmentCoverageOf}): the FOLDED, trimmed
 *  segment text (a display/correlation seat, never adjudication input) and whether an eligible rule
 *  already admits it. */
export interface SegmentCoverage {
    readonly segment: string;
    readonly covered: boolean;
}
/** One member of a {@link RuleOffer} batch: a rule the engine offers to mint for ONE segment of a
 *  compound command. `segment` is the FOLDED segment text the member was minted from — a rendering
 *  seat, never adjudication input. */
export interface SegmentRuleSuggestion {
    /** The canonical rule text the host would redeem. */
    readonly rule: string;
    readonly match: PersistedRuleMatch;
    /** The command pattern in the rule, for a host that wants to render it without re-parsing. */
    readonly command: string;
    /** The (folded) segment this suggestion came from. Raw post-rewrite command bytes — a substring
     *  of the same call's `args`/`command` values riding beside it on every surface that carries
     *  this seat — so render it with contextual escaping, exactly like those.
     *
     *  RAW is the contract here and does not change: this seat carries the bytes, because the bytes are
     *  what a rule would be minted from and what a correlation keys on. The rendering side of that
     *  contract is {@link renderUntrustedCommandText} — the shipped display baseline (bidi and
     *  zero-width controls removed, controls/line separators folded, length capped, envelope-neutralized)
     *  a surface can adopt instead of writing its own, since raw command bytes displayed unprocessed can
     *  show a reader something other than what they authorize. */
    readonly segment: string;
}
/**
 * design/382 §2.3 (B3, BREAKING) — one member of a batch offer, a DISCRIMINATED union keyed by
 * `kind`:
 *
 *  · `kind: "command"` — the historical shape: a per-segment Bash rule
 *    ({@link SegmentRuleSuggestion}'s four seats, plus the discriminant).
 *  · `kind: "directoryRead"` — a DIRECTORY READ AUTHORIZATION minted from a cd segment (the §2.4
 *    floor): `rule` is the canonical `Read(//dir/**)` text, `directory` the lexical-normal absolute
 *    directory (a render seat, spared a re-parse), `segment` the folded cd segment it came from (a
 *    rendering-context seat like its command sibling's — never adjudication input).
 *
 * CONSUMER DEGRADE ARM (normative, design/382 §2.3): a batch member whose `kind` is outside this set
 * drops the WHOLE batch offer — never the single member (a conjunction batch silently one member
 * short renders "yes to N" as "yes to N−1", worse than not rendering), and never the whole card (the
 * exact single beside it is intact and honest). Selection indices stay keyed on the ORIGINAL wire
 * offer order.
 */
export type RuleOfferBatchMember = (SegmentRuleSuggestion & {
    readonly kind: "command";
}) | {
    readonly kind: "directoryRead";
    /** The canonical rule text the host would redeem — `Read(//a/b/**)`. */
    readonly rule: string;
    /** The lexical-normal absolute directory the rule names — a render seat, no re-parse needed. */
    readonly directory: string;
    /** The (folded) cd segment this authorization came from — rendering context, never adjudication input. */
    readonly segment: string;
};
/**
 * design/375 §3.1 — ONE "don't ask again" option on an approval card. A CLOSED discriminated union
 * (`kind`); consumers parse it per element, and an element whose `kind` they do not know is dropped
 * (single-row degrade) while keeping the ORIGINAL wire index for every element they keep — selection
 * and redemption are keyed on original indices, so a consumer that cannot preserve the mapping must
 * suppress its persistence actions entirely (fail toward asking).
 *
 *  · `kind: "single"` — one rule covering the WHOLE command (the exact spelling, or a broader
 *    reviewed prefix form for a simple command).
 *  · `kind: "batch"` — a CONJUNCTION batch of per-segment rules for a compound command. Choosing it
 *    is one yes to ALL of `rules` at once — there is no per-member selection inside a batch; a
 *    consumer that wants a narrower answer picks the single offer or approves once without a rule.
 *    `rules` has 1..5 members (the card-lane minting cap), in segment order, deduplicated by rule
 *    text, each a {@link RuleOfferBatchMember} (design/382 B3: the member form is a discriminated
 *    union — a cd segment of a project-scope card mints a `directoryRead` member, one accept lands
 *    commands and directory authorization together, the upstream single-line merged-consent form).
 *    `uncoveredSegments` is the honest surplus disclosure of the cap: judged at MINT time
 *    against the coverage snapshot the card was drawn from ∪ this batch's FINAL (deduplicated,
 *    capped) rules, it counts the violating segments still admitted by neither — 0 means "once this
 *    batch is redeemed, this compound is fully covered by that snapshot's lights". It is a statement
 *    about the minting snapshot, not a standing guarantee: a concurrent rule removal can date it.
 *
 * Array contract (`ruleOffers`, the durable park row, and the consent record's projection while
 * the card is PENDING all share it): at most 2 offers; the whole-string exact single is index 0
 * whenever present; the batch offer — at most one — is always last. It is a draw-time contract,
 * not a settled-record invariant: confirming an EDITED candidate appends a third single offer
 * AFTER the batch in the same write that settles the record, so a SETTLED record's projection may
 * carry it. ADVISORY display metadata, never adjudication input, and never a
 * rule by itself: minting is a separate act that goes through the approval-record protocol, so a
 * surface that ignores offers entirely changes nothing about what is allowed. The authoritative
 * render source for a card is the PREPARED record's own offers (record-is-what-you-see); the ask-time
 * projection here may drift from it across a coverage change, and the drift direction is harmless
 * (the record is what gets confirmed).
 */
export type RuleOffer = {
    readonly kind: "single";
    /** The canonical rule text the host would redeem. */
    readonly rule: string;
    readonly match: PersistedRuleMatch;
    /** The command pattern in the rule, for a host that wants to render it without re-parsing. */
    readonly command: string;
} | {
    readonly kind: "batch";
    /** 1..5 per-segment members, segment order, deduplicated by rule text (design/382 B3: the
     *  member form is the {@link RuleOfferBatchMember} discriminated union). */
    readonly rules: readonly RuleOfferBatchMember[];
    /** Mint-time honest surplus: violating segments admitted by neither the coverage snapshot nor
     *  this batch's final rules (see the union doc above for the exact arithmetic). */
    readonly uncoveredSegments: number;
    /**
     * design/382 §3.5 — WHY each of those segments stays uncovered, one row per counted segment,
     * segment order (same text repeated = two rows; the correspondence is positional, never a raw
     * text dedup). Minted in the SAME pass as `uncoveredSegments` off the same snapshot, so
     * `uncoveredDetail.length === uncoveredSegments` whenever the seat is present; the count seat
     * stays the truth source, and ABSENCE (an older minter, a degraded consumer's re-emit) is not
     * a claim. `segment` is the folded, trimmed segment text — a display/correlation seat carrying
     * raw command bytes (render like its siblings, {@link renderUntrustedCommandText}); `reason`
     * is the closed set {@link UncoveredSegmentDetail} documents. Additive on the wire (an unknown
     * key inside a known kind — old consumers read the batch exactly as before); on the CONSENT
     * RECORD's card batch the seat is REQUIRED (schema: 3) — see the record's own contract.
     */
    readonly uncoveredDetail?: readonly UncoveredSegmentDetail[];
};
/** design/382 §3.5 — the closed reason set for one uncovered segment of a batch offer.
 *
 *  · `"redirection"` — the segment carries a shell redirection: refused by discipline (#490 修①,
 *    §3.2), PERMANENTLY per-ask — no rule text can cover this spelling; the honest outlet is a rule
 *    for its redirection-free form, if the person trusts that form.
 *  · `"no_rule_form"` — neither the segment's prefix nor its exact form survives the mint round
 *    trip (a glob argument, an over-length line): nothing coverable exists for these bytes, and
 *    guidance deliberately does NOT point at hand-writing a rule — the same grammar refuses the
 *    same text on every door.
 *  · `"cap_overflow"` — the segment minted a rule, but the batch's dedup+cap left it out and no
 *    surviving member admits it: covered next time it comes up, asked about until then.
 *
 * These are WIRE-STRUCTURED facts, and that is the whole §3.6 point (recorded here so the design's
 * half-radius is visible in the code): the reader is not only a person. A model or harness that
 * keeps meeting `redirection` on a spelling it retries can re-run the redirection-free form —
 * which HAS a coverable rule — with zero new mechanism on this side; a structured honest refusal
 * is programmable, exactly like a loud bad-value error. The baseline person-facing sentences are
 * {@link UNCOVERED_SEGMENT_REASON_BASELINE}. */
export interface UncoveredSegmentDetail {
    /** The folded, trimmed segment text — raw post-rewrite command bytes, same contract as
     *  {@link SegmentRuleSuggestion.segment}. */
    readonly segment: string;
    readonly reason: "redirection" | "no_rule_form" | "cap_overflow";
}
/**
 * design/382 §3.5-2 — the BASELINE "why + outlet" sentence per uncovered-segment reason: the
 * one-source copy a surface renders beside the §3.5 detail rows. A surface may reword these, never
 * invert them (the same contract as {@link renderUntrustedCommandText}: adopt the baseline, or own
 * an equivalent-strength rewrite). Load-bearing asymmetries, deliberate:
 *  · `redirection` points at the redirection-FREE form's outlets (that form is coverable);
 *  · `no_rule_form` points at NOTHING but per-use approval — hand-writing is not suggested,
 *    because the same grammar refuses the same text on every door, and pointing a person at a rule
 *    that can never take effect is the exact defect this surface exists to remove;
 *  · `cap_overflow` promises only honesty: not covered by this batch, asked again when met.
 */
export declare const UNCOVERED_SEGMENT_REASON_BASELINE: Readonly<Record<UncoveredSegmentDetail["reason"], string>>;
/**
 * design/382 §3.5-2 — the BASELINE sentence per card-level offer-ABSENCE reason (`ruleOffersAbsence` on the ask and its
 * durable park twin; the closed set is {@link import("./ask-origin.js").RULE_OFFERS_ABSENCES}). Same reword-never-invert
 * contract as the table above. The load-bearing asymmetries: `mandated` and `allow_outranked` must NOT point at writing
 * rules — on a mandated ask no rule is consulted, an outranked allow shows another allow would be outranked the same way, and
 * directing a person to mint one that can never take effect is the #457④ anti-shape; the other two reasons name their real
 * outlets.
 */
export declare const RULE_OFFERS_ABSENCE_BASELINE: {
    readonly mandated: "This approval is required by the deployment's policy each time; a rule cannot waive it.";
    readonly allow_outranked: "An allow rule already matches this call, and this question outranks it — another allow rule would be outranked the same way; it is approved per use.";
    readonly ask_rule_matched: "An ask-each-time rule matches this call and keeps asking — a new allow rule changes nothing while it stands; to stop being asked, review the existing ask rules.";
    readonly lane_cannot_speak: "This card has no rule to offer for this exact command; it can be approved per use. A coverable form of what you trust can still get a rule — through a card drawn for that form, or settings.";
};
/** The fence over the baseline table: `never` while every word has a sentence. */
export type OfferAbsenceBaselineCoversEveryWord = AssertAllKeysHandled<Exclude<RuleOffersAbsence, keyof typeof RULE_OFFERS_ABSENCE_BASELINE>>;
/**
 * The offers presented on an approval card for `command` (design/375 §5.2).
 *
 * **Array order is a documented CONTRACT, not an implementation accident** (see {@link RuleOffer}):
 * the whole-string EXACT single is index 0 whenever present; a SIMPLE command may carry a broader
 * reviewed PREFIX single after it; a COMPOUND carries at most one BATCH offer, always last.
 * Selection indices are keyed against this order, so consumers may rely on it; redemption TICKETS
 * are NOT — they key on the consent record's flat CANDIDATE list, and a chosen offer is redeemed
 * via `redeemRuleBatch` (see the consent record's own doc for the two index spaces).
 *
 * The prefix single is the LONGEST body two sources agree to offer: {@link SUGGESTION_LEXICON}'s
 * deeper-than-two-words rows first, and otherwise {@link genericPrefixBody}'s general two-word form
 * (`argv[0]` + a token shaped like a subcommand). Read those two for the grammar, its adopted origin,
 * and the operand false positive it accepts; the short version is that a bare verb (`git`), an
 * interpreter or wrapper head (`sudo apt`, `node build.js`), a flag in second position
 * (`git -C /repo status`) and a quoted or dotted token all still yield NO prefix. Naive spacing note:
 * `folded` keeps quoted whitespace, so splitting on single spaces can shear a quoted segment —
 * harmless in this direction, because the sheared pieces carry quote characters and can therefore
 * match neither a lexicon word nor the generic subcommand shape; every suspicious form lands on
 * "no prefix".
 *
 * Every produced candidate must survive the round trip — parse as a rule, come back as the match form
 * its seat is offering, AND admit the very text it was minted from (the whole command for a single,
 * its own segment for a batch member). Enforced on EVERY seat, fail-closed: a candidate that would
 * not round-trip is silently not offered, since offering an option redemption would refuse is worse
 * than offering one fewer.
 *
 * The FORM half of that check is not decoration. A command may end in the rule grammar's own prefix
 * marker — `rm :*` is a legal thing to type — and wrapping it as an exact rule produces the text
 * `Bash(rm :*)`, which the validator reads back as the PREFIX rule `Bash(rm:*)` over the command `rm`.
 * Trusting the exact seat's own intent and labelling that result `match: "exact"` put a blanket
 * every-`rm` rule on the card under the narrowest option's name, one click from being persisted (and,
 * for a lexicon command like `git status :*`, emitted twice — once mislabelled, once as the real prefix
 * candidate). The seat therefore believes the PARSER about what it got back, never its own request.
 *
 * A COMPOUND (`./gradlew build && ./gradlew test`) fills the exact single — the offered rule spells
 * the whole chain and admits exactly it — and a BATCH offer besides: every segment NOT already
 * covered (per `ctx.coverage`) yields its own single-segment best form — a prefix when the
 * single-command grammar grants one for that segment's tokens (the interception screens apply
 * unchanged), otherwise the segment's exact — deduplicated by rule text in segment order and capped
 * at FIVE (upstream's own batch cap; the overflow is disclosed via `uncoveredSegments` rather than
 * silently truncated away). A segment whose every form the floor refuses simply contributes no
 * member (and stays counted as uncovered — the honest gap). The COMPOUND-PREFIX candidate this seat
 * used to mint here is deliberately NO LONGER OFFERED (design/375 §5.3): the batch takes its card
 * position, while the compound-prefix GRAMMAR remains a fully supported rule family — the matcher,
 * the hand-written/edited/import channels and every persisted such rule are untouched.
 *
 * `ctx.coverage` is the per-segment coverage table minted by the SAME store read that adjudicated
 * the call (see {@link segmentCoverageOf} and the gate's engine-stamped seat) — positional over this
 * function's own folded segments, belt-checked by text: a table whose length or segment texts do not
 * line up is discarded whole, and an absent or discarded table reads as ALL-UNCOVERED (fail toward
 * over-offering; every offered rule still passes the validator at redemption, so an over-offer widens
 * nothing).
 *
 * Returns an empty array for anything the rule lane cannot speak for (substitutions, subshells,
 * backgrounding, an unreadable segment) — the card then simply carries no "don't ask again" option,
 * which is the honest answer.
 *
 * A REDIRECTION is the one member of that list judged per SEGMENT rather than over the whole string
 * (#490 修①, {@link OFFER_READING}): `a && b 2>/dev/null && c` offers a batch for `a` and `c` with
 * `uncoveredSegments: 1`, exactly as the same compound dirtied by a glob always has. A SIMPLE command
 * carrying one still offers nothing — its only segment is the dirty one — and no offer this function
 * mints ever admits the redirection-bearing text: the minting grammar and the matcher keep the
 * whole-string floor, so the compound stays un-admitted and the honest `uncoveredSegments` count says
 * so (design/382 §3.2: neither a sanitized rule text nor a widened matcher is on the table).
 */
export declare function suggestRulesForCommand(command: string, ctx?: {
    readonly coverage?: readonly SegmentCoverage[];
    /**
     * design/382 §2.4-7 — the SCOPE the card's candidates would land in, the directoryRead mint
     * gate's first input: a cd segment mints a directory member ONLY when this scope is a PROJECT
     * or SESSION scope that also COVERS the adjudicated call (the second conjunct, through the one
     * scope predicate — a scope that does not cover the deciding call would mint a member that is
     * never eligible where it was minted, the offer-that-never-works shape). The SESSION arm is the
     * §8-Q3 re-ruling (opened at final sign-off: the upstream session destination is exactly this
     * grant's home, and a session-scope member is a pure narrowing of the project one). GLOBAL
     * stays closed — a directory grant is place-and-time consent, and the everywhere spelling is
     * the explicit channels' business. Absent ⇒ the gate is closed and a cd segment falls back to
     * its command member (the pre-382 behaviour, byte-for-byte).
     */
    readonly scope?: RuleScope;
    /**
     * The adjudicated call's working directory (threaded from the original call context, never the
     * process — the same value the coverage read and the gate judged with). Two consumers: the
     * `./`-relative cd resolution, and the scope gate's coverage conjunct. Absent ⇒ no relative cd
     * resolves and a project-scope gate is closed.
     */
    readonly cwd?: string;
    /**
     * The relative-cd RESOLUTION base — the live tracked working directory of the execution seat
     * (adversarial-review P1: after an earlier observable `cd`, the shell resolves `./x` against the
     * moved cursor, not the task root — a member minted off the wrong base names a directory the cd
     * never enters). Defaults to `cwd`; the scope gate's coverage conjunct stays on `cwd`.
     */
    readonly execCwd?: string;
    /**
     * The adjudicated call's SESSION identity (threaded like `cwd`, never inferred) — the session
     * arm's coverage conjunct: a session-scope card mints a directory member only when its scope
     * names THIS session. Absent ⇒ no session scope covers anything (fail-closed, the shared
     * predicate's own arm).
     */
    readonly sessionId?: string;
    /**
     * design/382 §2.4-6 — the deployment's sensitive-read negative control, SAME-SOURCE with its
     * compiled read-deny judge (`ReadDenyMatcher.matchPath !== null` is the canonical wiring): the
     * engine never PROPOSES a directory authorization for a directory this judge names. Absent ⇒
     * {@link defaultDeniesDirectoryRead} stands in (refusal-only; every hands leg passes its OWN judge). Explicit-consent doors (hand-written,
     * import) are deliberately NOT behind this — they have their own preview and validation.
     */
    readonly deniesDirectoryRead?: (directory: string) => boolean;
}): RuleOffer[];