import { type LexedCommand, type LexedSegment, type LexedWord, type ShellLex, type ShellSegmentConnector, type WordPiece } from "../../shell/lex.js";
import { type ProgramRow } from "./program-read-model.js";
/**
 * Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
 * The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
 * boundary**: {@link coarseReadonlyCheck} matches only the command NAME, so a listed command with a
 * writing flag (e.g. `find -delete`, `sort -o`, `tee`) would still write. Such commands are therefore
 * kept OFF this default — the list is curated to commands with NO write/mutation mode under ANY args, so
 * the `effect:"read"` declaration (relied on by wake/resume reconcile + the verifier read-only boundary)
 * AND the classifier's "reversible" promise both stay truthful. The deployment's tool-policy gate is the
 * authoritative control (council blocker #1); anything that can mutate state or run arbitrary
 * code belongs on the full `bash` (effect:write, gated), not here. A deployment may widen this list,
 * accepting that responsibility.
 *
 * Deliberately ABSENT: `date` (`-s`/`--set` → CLOCK_SETTIME), `hostname` (`<name>` → kernel
 * hostname), and `file` (`-C -m` → compiles/writes a magic file) were REMOVED — each is read-only by NAME
 * but state-MUTATING with args, which an argv[0]-only filter cannot tell apart. Leaving them in defeated
 * both the `effect:read` truthfulness here and the classifier's irreversibility promise (a `date -s` would
 * auto-allow an irreversible clock jump on the full shell face).
 *
 * Rows 3-4 are the CC-anchored expansion — the members of CC's safe-command
 * set (2.1.296 `v3e` (cli296.js:249645); `e6s` in the 223 corpus, identical in 220; the 220→245 campaign diff records
 * no change on this face) that satisfy THIS list's curation bar (no write/mutation mode under any args)
 * without new stdin-floor/option-value modeling: pure status/computation printers (`cal`…`sleep` — none
 * reads stdin when bare, none takes a path it READS except realpath/readlink, which stay boundary-judged)
 * plus the floor-2 content comparers `diff`/`cmp`/`comm` (their rows state the floor of 2 operands; diff's
 * recursive form was already a traversal row of the program read model). Still deliberately excluded, each for a
 * stated reason: `env` (an executor: `env FOO=1 cmd` runs cmd — CC excludes it too), `sort` (`-o` writes),
 * `uniq` (second positional is an OUTPUT file; CC allows a flags-only regex form this filter cannot
 * express), `printf` (builtin `-v var` assigns), `find` (CC allows only a regex excluding
 * `-delete`/`-exec`/…), and the bare-stdin text filters (`od`/`strings`/`nl`/`tsort`/`pr`/…) whose
 * value-taking options defeat the stdin-floor operand count without a per-verb option-value model
 * — those are a follow-on slice, not a silent drop.
 */
export type { ShellSegmentConnector } from "../../shell/lex.js";
export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
/**
 * The verbs the CLASSIFY face admits ON TOP of the shared list ({@link bashClassifyDefaultAllow}) —
 * admissible only where the compound classifier's per-verb judgment runs, and NOT on the shared list's
 * other consumer faces:
 *  · `find` — read-only only when no CC `aCy` dangerous predicate token is present (guard arm);
 *  · `sed`  — read-only only in CC's `_Ld` grammar (guard arm);
 *  · `cd`   — never touches the filesystem, but it MUTATES the persistent shell's committed cwd, so it
 *    must not reach the shared list's `isConcurrencySafe` consumer (a parallel-batch `cd` reorders
 *    every sibling's working directory) or the `effect:"read"` declaration face; the classify face's
 *    EXISTING cd boundary arm (glob/no-arg/`-` conservative, target judged against the roots) is what
 *    makes it admissible here.
 * The bash_readonly DECLARATION face checks names without argument vetting (its own header says so), so
 * none of the three may join {@link BASH_READONLY_DEFAULT_ALLOW}: `find . -delete` / `sed -i` under an
 * `effect:"read"` declaration would falsify it. Consumed as
 * {@link import("./fs-bash.js").bashReversibilityProbe}'s default when the caller passes NO allow list —
 * a caller-narrowed list is honored verbatim (naming these verbs there opts into the same guard/boundary
 * arms; omitting them keeps them refused), so the superset can never widen a narrowed deployment. The
 * poll-loop face inherits the guard arms for free: its body vetting delegates to the same compound
 * classifier.
 */
export declare const BASH_CLASSIFY_EXTRA_ALLOW: readonly string[];
/** A verb only the classify face admits. */
export type BashClassifyExtraVerb = (typeof BASH_CLASSIFY_EXTRA_ALLOW)[number];
/**
 * @contract shell.classify_allow.one_union_point — the classify face's default allow set is the union of
 * two SPELLED-OUT tables ({@link BASH_READONLY_DEFAULT_ALLOW} ∪ {@link BASH_CLASSIFY_EXTRA_ALLOW}) and
 * this function is its only assembly point; no call site composes its own. Both halves stay arrays of
 * string literals so a static reader can enumerate them: a vocabulary derived in place
 * (`[...TABLE, "extra"]`) is invisible to the release's wire-change manifest, so a verb added to or
 * dropped from this security-relevant set would move nothing a downstream reader could see.
 */
export declare function bashClassifyDefaultAllow(): readonly string[];
/**
 * G2 — options for {@link parseLeadingCommandName}. Omitting them keeps the parser byte-identical to
 * what every existing caller (the `bash_readonly` declaration face, the coarse command-name policy, the
 * permission-rule floor, the skill tool specifier) has always received.
 */
export interface LeadingCommandNameOptions {
    /**
     * Read a shell operator that sits inside a QUOTED region as ordinary argument TEXT rather than as an
     * operator, so `grep -E "a|b" f.log` parses as the single simple command it is.
     *
     * OPT-IN, read off the one lexer's result: an operator character bash reads as text (inside quotes, or
     * behind an escape) is text here. Two callers set it — the compound classify face
     * ({@link classifyCompoundReadonlyDetailed}) and the persisted permission-RULE lane (`RULE_LANE_FLOOR`),
     * whose segments come out of the same residual predicate ({@link splitShellCompoundSegments}). The
     * `bash_readonly` DECLARATION face does NOT set it: its contract is "a single simple command with no
     * operator characters at all", and it runs on raw tool input with no whole-string gate in front of it.
     *
     * Fail-closed on a command the lexer cannot delimit: every operator character then counts, exactly as
     * without this option.
     */
    quotedOperatorsAreText?: boolean;
    /**
     * Read a PATH-PREFIXED `argv[0]` (`./gradlew`, `/usr/bin/git`, `bin/tool`) as an ordinary command
     * name instead of refusing it.
     *
     * OPT-IN, and used by ONE family of callers: the persisted permission-RULE lane
     * ({@link import("../../core/permission-rule-model.js").parseRuleText} and its matcher), where
     * both sides of the comparison are literal text a person read on an approval card. The refusal this
     * option lifts exists for the argv[0]-NAME filters (the read-only allowlist, the coarse
     * command-name policy, the skill specifier): those compare a bare token against a name set, and a
     * path prefix is how a caller reaches a program the set never vetted. A rule lane compares the whole
     * command line instead, so `./gradlew` there is not a way past a name set — it IS the name that was
     * approved, and refusing it made the single most ordinary build command in a repository unable to
     * carry a standing approval at all.
     *
     * What it does NOT lift: the argv[0] metacharacter refusal (quotes/braces/globs/`~` still make the
     * parsed token differ from the program bash runs), the leading env-assignment refusal, and every
     * shell operator. And it does not make a path-prefixed name equal its basename anywhere — the rule
     * lane matches on the text as written, so `./gradlew` and `gradlew` stay two different commands. The
     * one place a basename IS taken is the rule lane's own interpreter refusal, which must read
     * `/usr/bin/node` as `node` (see
     * {@link import("../../core/permission-rule-model.js").BARE_INTERPRETER_NAMES}).
     */
    pathPrefixedNameIsText?: boolean;
}
/**
 * The SINGLE fail-closed simple-command parser shared by `bash_readonly` ({@link coarseReadonlyCheck}), the
 * `bash` reversibility classifier ({@link import("./fs-bash.js").bashReversibilityProbe}), and the coarse command-name policy
 * ({@link import("../../core/tool-policy.js").createCoarseCommandNamePolicy}). It extracts the leading
 * `argv[0]` command NAME of a SINGLE simple command, rejecting anything that could chain past or escape an
 * argv[0]-name filter: shell operators (pipes / redirects / `;` / `&&` / `$(…)` / subshells / backticks /
 * newlines / backslash), a path-prefixed command (`/usr/bin/foo`), or a leading env-assignment (`FOO=bar cmd`).
 * The path-prefix refusal is the one arm a caller may lift, opt-in and for the rule lane only — see
 * {@link LeadingCommandNameOptions.pathPrefixedNameIsText} for why it is sound exactly there.
 *
 * Returns `{ name }` for a parseable single bare command, or `{ reject }` with a human reason otherwise. It
 * does NOT inspect ARGUMENTS for write flags or consult any allowlist — that is the caller's job (the
 * allowlist for `bash_readonly`, the allow/deny lists for the coarse policy). Keeping ONE parser is the whole
 * point: a second argv[0] parser would drift from this one and silently open a circumvention.
 */
export declare function parseLeadingCommandName(command: string, options?: LeadingCommandNameOptions): {
    name: string;
} | {
    reject: string;
};
/** Coarse pre-execution check for `bash_readonly`: the command must be a single bare allowlisted command
 *  with no shell operators. It validates the NAME only (+ blocks operators/path-prefix/env-assignment); it
 *  does NOT inspect arguments for write flags — keeping the allowlist free of write-capable commands and
 *  the policy gate are what make `effect:"read"` truthful. Returns a reason when rejected, else undefined.
 *  This `effect:"read"` DECLARATION face deliberately stays strict-single-command; only the
 *  classify face ({@link classifyCompoundReadonly} via {@link import("./fs-bash.js").bashReversibilityProbe}) segments compounds. */
export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<string>, options?: LeadingCommandNameOptions): string | undefined;
/**
 * Does this text carry a bare redirection operator — the ONE metacharacter family
 * {@link splitShellCompoundSegments} can be asked to tolerate at its whole-string gate
 * (`redirection: "tolerate"`)?
 *
 * Exported because the tolerating caller owes a PER-SEGMENT judgement in its place, and that judgement
 * must read the same character class this module tolerated — two hand-kept lists would drift, and a
 * drift here is a segment nobody judged. Says nothing about what a caller should DO about a hit: the
 * rule lane's offer face contributes no member for such a segment; the read-only classify face passes
 * only the two data-free spellings ({@link isDataFreeRedirection}).
 */
export declare function carriesShellRedirection(text: string): boolean;
/** What a caller asks the residual predicate to pass: see {@link strictResidualRefusal}. */
export interface StrictResidualOptions {
    /** `<`/`>` are not refused and a redirection is the segment's own business (the rule lane's offer reading). */
    readonly tolerateRedirection?: boolean;
    /** Only the two data-free spellings pass ({@link isDataFreeRedirection}); every other redirection refuses. */
    readonly dataFreeRedirections?: boolean;
    /** The declared home: a word that spells it ({@link homeExpansionOf}) is not an expansion to refuse — unless the
     *  command may rebind `HOME` ({@link textBindsHome}), which makes the declared home a guess. */
    readonly homeDir?: string;
}
/**
 * THE classify faces' whole-string gate, as a PREDICATE over the one lexer's result: the residual space those
 * faces live in is what this function does not refuse. Four passes, in the order the gate's sentences were
 * always minted, so a refused command is refused for the same stated reason:
 *  1. the quote-blind class ({@link SHELL_SEGMENT_QUOTE_BLIND_REJECT}) over every written spelling — a word, a
 *     redirection target under `tolerateRedirection` — plus every group, here-document, line-break connector
 *     and (unless tolerated or data-free) redirection token;
 *  2. the expansion character where bash expands it and a `~user` / `~+` / `~-` tilde word
 *     ({@link expandsBeyondText}), the home-directory word excepted under `homeDeclared`;
 *  3. a backgrounding `&` and a `|&`;
 *  4. a word whose value the text does not settle ({@link UNRESOLVED_SENTENCE}) — a compound array, an
 *     off-table ANSI-C escape, a subscript or parameter expansion this lexer does not read.
 * A command the lexer cannot delimit is refused for the class if its text carries one (the reading the gate
 * gave an unbalanced string), else for the parse failure. Returns the refusal REASON, or `undefined` to proceed.
 */
export declare function strictResidualRefusal(lexed: ShellLex, source: string, opts?: StrictResidualOptions): string | undefined;
/** What {@link splitShellCompoundSegments} produces: the connector-delimited segments, plus which of
 *  them a PIPE fed (the read-only classifier's stdin arm needs that distinction; a rule lane does not). */
export interface ShellCompoundSegments {
    segments: string[];
    /** Parallel to {@link segments}: was this segment preceded by `|` (rather than `;`/`&&`/`||`/nothing)?
     *  A VIEW of {@link connectors}, derived at the one place both are minted — two rows that could drift are
     *  two rows that will. */
    pipeFed: boolean[];
    /** Parallel to {@link segments}: the connector bash reached this segment through. Read by the read
     *  boundary, where it is the difference between a directory change the shell itself proved happened and
     *  one this walk cannot verify. A consumer that does not read it gets the unverified reading, which is
     *  the fail-closed one. */
    connectors: ShellSegmentConnector[];
    /** Parallel to {@link segments}: the segments as the lexer minted them — the words (with their facts) and
     *  the redirections of each. A consumer that tokenizes reads these; the text is for the sentences. */
    lexed: readonly LexedSegment[];
}
/**
 * Split a compound command into the segments bash would run, or refuse the whole string.
 *
 * THE one segmentation in this repository, and the one place the classify faces' residual predicate is
 * applied: the persisted permission-rule lane and the read-only classify face both read their segments
 * here, so a drift in what a deny rule is even looking at cannot open between them.
 *
 * Two steps: the trailing-terminator strip, then ONE lex ({@link lexShell}) judged by
 * {@link strictResidualRefusal}. The strip is OPTIONAL because it is the one step whose answer depends on
 * what the caller is asking. A face that asks "which programs does this run" wants it stripped (`ls;` runs
 * `ls`, nothing else). A face that asks "is this ONE command" must NOT strip it: `"keep"` makes `ls;` two
 * segments, the second empty, so the caller's own empty-command rule puts the whole string outside its lane
 * — which is what keeps a statement like "this rule form never matches a command containing a connector"
 * literally true instead of true-except-for-one-spelling. `redirection` picks which redirections the
 * predicate passes: none (the default, the incumbent whole-string refusal), every one (`"tolerate"` — the
 * refusal moves to the caller's per-segment vetting via {@link carriesShellRedirection}), or the two
 * data-free spellings (`"data-free"`, the read-only classify face). `homeDir` lets the word spelling the
 * declared home through the expansion refusal.
 *
 * Empty segments are NOT refused here (`;;`, a leading `;`, a trailing `&&`) — they come back as empty
 * strings, and every caller's per-segment vetting rejects them through its own empty-command rule. The
 * split does not judge segments; it says where they are.
 */
export declare function splitShellCompoundSegments(source: string, options?: {
    trailingTerminator?: "strip" | "keep";
    redirection?: "reject" | "tolerate" | "data-free";
    homeDir?: string;
}): ShellCompoundSegments | {
    reject: string;
};
/**
 * Compound read-only classification for the `bash` full-shell face (the shape tier).
 * Splits a command on the connector set `;` `&&` `||` `|` and passes every segment through
 * {@link coarseReadonlyCheck} (the single {@link parseLeadingCommandName} parser + bare-name allowlist).
 * All segments green ⇒ read-only. Returns a reason when rejected, else undefined.
 *
 * G2 — the split CARRIES QUOTE STATE (the one lexer): a connector character inside a quoted region is
 * argument text, not a boundary, so `grep -E "error:|BUILD FAILED" x.log | tail -10` is two segments
 * (`grep …` and `tail -10`) rather than four, and the phantom head `BUILD` no longer decides the verdict.
 * The whole compound face is what this fixes: a report of an every-command approval prompt on a read-only
 * monitoring pipeline traced to exactly this — a quoted alternation in a `grep -E` pattern, which is the
 * ordinary spelling of that idiom.
 *
 * Soundness — why the quote-aware split is NOT a relaxation. Backslash/`$`/backtick/subshells/redirection/
 * line breaks are refused first (the two data-free forms of {@link isDataFreeRedirection} pass, and they
 * move no data into any file), so in the residual space `'`/`"` pairing is
 * the ENTIRETY of quoting and the mask's regions are exactly bash's quoted regions. The unquoted connectors it splits on are therefore exactly bash's command boundaries:
 * every program bash runs still starts at the head of some segment this classifier vetted, and its argv[0]
 * is the head token (a token carrying a quote/brace/glob/tilde character is rejected by the parser's
 * argv[0] metachar rule, and a clean token equals the executed program name). What changes versus the
 * older quote-blind scan is only that PHANTOM segments — heads manufactured out of quoted text, which no
 * program was ever going to run — are gone. Those could only ever ADD a rejection, so removing them
 * removes false demotions and admits no additional program.
 *
 * UNBALANCED QUOTING is a parse failure, refused whole (fail-closed). A string with an unterminated quote
 * is one bash cannot parse into commands at all, so there is no "true" segmentation to be accurate about.
 *
 * A LONE `&` is hard-rejected (not a connector): it backgrounds a process that survives the shell — the node
 * execution env spools stdio and deliberately leaves descendants alive on completion — escaping TaskStop and
 * background-lifecycle governance entirely. Backgrounding is not a read-only combinator, even for a listed
 * reader (`tail -f x &` outlives the call). `|&` is refused by name.
 * One TRAILING `;` is stripped (a no-op terminator); any other empty segment (`;;`, leading `;`, trailing
 * `&&`/`|`) rejects via the parser's empty-command rule.
 *
 * NOT here by design: no git/subcommand allowlist — "zero-flag" vetting operates
 * on pre-expansion text while the shell expands quotes/braces/globs into argv, and the candidate subcommands
 * are not provably read-only under repo config anyway. Deployments needing those forms must widen via a
 * dedicated widening decision, not this classifier.
 */
/**
 * The READ-BOUNDARY face of the read-only classification (opt-in).
 *
 * The allowlist vets `argv[0]` only, so until this face existed every listed reader could name ANY
 * path: `ls /etc` and `cat /etc/passwd` classified read-only, and a deployment whose manual-mode shell
 * gate auto-allows the read-only verdict served the whole filesystem with no approval prompt. CC 2.1.220
 * demotes exactly these shapes (its per-command path extractor + working-directory containment check,
 * pretty220 @457568) and renders an approval option naming the directory (@905642 (cli250.js:66007)).
 *
 * Supplying this object turns the check ON; omitting it leaves the classifier byte-identical to its
 * name-only behaviour, so a consumer pinned to an older calling convention is unaffected.
 */
export interface BashReadonlyRootBoundary {
    /** Canonical containment roots — the primary root plus any additional allowed directories. An EMPTY
     *  array means "no directory is allowed", so every path-shaped operand demotes (a bare `cat a.txt`
     *  still does not: a name with no separator can only reach the working directory, which is the
     *  caller's to govern). A caller that wants the check OFF omits the whole object instead. */
    roots: readonly string[];
    /** Base for resolving relative operands. Defaults to `roots[0]`. Callers tracking an observable `cd`
     *  should pass the LIVE working directory, or a `..` operand will be judged against the wrong base. */
    cwd?: string;
    /** Expansion for a leading `~` — and the value the read faces substitute for the `$HOME` / `${HOME}` word
     *  spellings ({@link homeExpansionOf}). Without it a `~`-rooted operand is unresolvable
     *  and demotes (with no out-of-root signal — the classifier does not know where it points, which is a
     *  different verdict from knowing it points outside). */
    homeDir?: string;
    /** The sensitive-path read deny judge over LEXICALLY RESOLVED operands: a hit demotes the command (ask,
     *  never auto-allow), independently of the roots — in-root operands are judged too. Returns the matched
     *  pattern, or null. TWO named residuals, both inherited from this classifier's declared purity
     *  (synchronous, zero I/O — the same scope stated for the containment half): ① operand TARGET matching
     *  only, no ancestor intersection: the judge sees the operand's own resolved spelling, never its subtree.
     *  The RECURSIVE-reach half of ① is narrowed — a listed recursive verb's path operand under this wired
     *  seat rides {@link CompoundReadonlyVerdict.recursiveReadPaths} (⊂ undecidedPaths), so `grep -r x ~/` no
     *  longer auto-allows; what REMAINS is the traversal rows' stated open set (the KNOWN OPEN SET note at
     *  the program read model's traversal rows: a traversal verb absent there that a deployment allowlists classifies by
     *  its ordinary form and can auto-run a traversal unjudged). ② LEXICAL only HERE — an in-root symlink
     *  whose target is guarded reads as its innocent spelling at this seat, as it does for the containment
     *  half, because the classify auto-allow lane has no I/O seat by contract; the CANONICAL view is judged by
     *  an execute-time recheck that asks THIS judge (the same function object, forwarded) where each candidate
     *  really lands, on a `checkedPaths` literal and a pattern's expansions alike.
     *  EVERY read face wires this one seat, the `bash_readonly` shell included, at BOTH of its judging
     *  moments — roots answer WHERE a command may read and the allowlist WHICH verb runs, neither answers
     *  WHICH FILES are off the table. What this seat is and is not is stated once, at
     *  `shell.read_boundary.friction_floor`. */
    denyMatch?: (resolvedPath: string) => string | null;
    /** The row a directory NAMES or contains, or `{ unplaced }` — a home-anchored row no home places, which any root may hold
     *  ({@link import("./read-deny.js").ReadDenyMatcher.rowUnderRoot}), or null — read for a recursive read's root, the
     *  one question the table answers about a tree without enumerating it. PRESENT-IFF {@link denyMatch} is. */
    denyUnder?: (resolvedPath: string) => import("./read-deny.js").ReadDenyRootAnswer;
    /** What the wired {@link denyMatch} judge would apply — the ACTIVE deny patterns. Read for ONE question:
     *  is there anything for a traversal to defeat? EMPTY ⇒ the judge refuses nothing, so a walk under it
     *  bypasses nothing and {@link CompoundReadonlyVerdict.recursiveReadPaths} is not minted. ABSENT ⇒ the
     *  caller did not say, which is not "none": the walk is reported, the reading every caller had before
     *  this seat existed. It is deliberately NOT the reference implementation's question at the same place —
     *  its deny rules are ROOTED, so "is the guarded place inside this traversal?" is lexically answerable
     *  there; these patterns match a segment run ANYWHERE in a path ({@link
     *  import("./read-deny.js").ReadDenyEntry}), so only emptiness is. */
    denyEntries?: readonly string[];
    /** The resolved read-face containment state. Under "open" the CONTAINMENT half of
     *  this boundary is structurally satisfied (an out-of-roots operand is not a demotion; it reports
     *  as a checked candidate instead), while the deny half above keeps judging in both faces.
     *  Absent ⇒ "roots" (byte-compat). The `bash_readonly` face never passes this seat — its
     *  containment is load-bearing and never opens. */
    face?: "open" | "roots";
}
/**
 * The structured verdict of {@link classifyCompoundReadonlyDetailed}. `reason === undefined`
 * is the read-only verdict; everything else is a demotion.
 */
export interface CompoundReadonlyVerdict {
    /** Demotion reason (the same string {@link classifyCompoundReadonly} returns), or undefined: the
     *  command's SHAPE is not one the shape tier auto-allows. WHERE the command reads is not this
     *  verdict's business — that is the gate's shell read station (`shell-read-check.ts`). */
    reason?: string;
}
/**
 * The single minting point for the out-of-root-read approval option text, so a gate rendering
 * the prompt and any other consumer cannot drift apart. Shape follows CC: the option
 * shows the LAST segment of the grantable directory followed by a separator, not the full path.
 *
 * `directory` is the directory to be granted — a caller holding a filesystem (this classifier does not)
 * resolves a file operand to its parent first; CC does the same with a stat-then-dirname step.
 * The separator is always `/`: this classifier's path handling is POSIX-lexical throughout, where CC
 * uses the host's native separator.
 */
export declare function formatOutOfRootReadApprovalOption(directory: string): string;
/** True for a token that can denote a filesystem location OUTSIDE the working directory. A bare word
 *  (`foo`, `a.txt`, a grep pattern, an awk program) cannot: with no separator and no `..` it always
 *  resolves under the cwd, which the caller's cwd governance already covers.
 *
 *  The one spelling that would break that argument is a win-form absolute written with
 *  backslashes only (`\\host\share\secret`, `C:\other\secret`): no `/`, so not path-shaped, yet it names
 *  something the cwd does not contain. Two separate facts, once written as one:
 *   · on the COMPOUND faces it is indeed unreachable — {@link SHELL_SEGMENT_QUOTE_BLIND_REJECT} refuses the
 *     whole command as soon as it contains a backslash, wherever it stands, so no such token reaches this scan;
 *   · on {@link classifySimpleCommandReadBoundary} — one command, no compound grammar, and therefore no
 *     such gate — it DOES reach it. Measured on a win32-rooted boundary, before the lane split below: the operand
 *     resolved to nothing, was dropped, and the verdict was empty, i.e. a read outside every root
 *     AUTO-ALLOWED on the one face whose doctrine is hard refusal in exactly that direction.
 *  The second fact is why the lane split in
 *  {@link collectSegmentBoundaryFindings} tests {@link isAbsoluteForFamily} (an address's own family) BESIDE this predicate,
 *  which is what the option-payload arm always did; the two now read one rule rather than two. */
export declare function isPathShapedToken(tok: string): boolean;
/** Resolve one operand against the boundary's base, WITHOUT touching the filesystem. Returns undefined
 *  when the token cannot be resolved statically — the fail-closed direction, reported as its own
 *  demotion rather than silently treated as in-root.
 *
 *  The resolution is TEXTUAL, and its limitation runs in BOTH directions (only the first shows up as
 *  a refusal, so only that half tends to get reported):
 *   · false REFUSAL — a non-canonical spelling of an in-root path (a symlinked `/tmp` → `/private/tmp`
 *     prefix) does not textually match the canonical roots and is refused;
 *   · false ALLOW — a symlink INSIDE a root that points outside it (`<root>/vendor` → `/etc`) resolves
 *     to a textual prefix match, so `cat vendor/passwd` is judged in-root and the real command follows
 *     the link. This face is therefore lexical containment, NOT filesystem containment: it is weaker
 *     than the structured Read/Grep tools, which canonicalize through `realpath` before comparing
 *     (see safety.ts's `canonicalizeAndEnforceRoot`). Closing it means resolving each candidate against
 *     the live filesystem, which this function deliberately cannot do (it is synchronous, pure, and
 *     shared with the approval-gate face); the seam for it is the ENFORCING caller, which holds the
 *     ExecutionEnv.
 *
 *  The verdict now carries {@link CompoundReadonlyVerdict.checkedPaths} (the
 *  candidates that landed inside), and `createBashReadonlyTool` canonicalizes them on a LOCAL env
 *  before running — closing BOTH directions above for that face (an escaping symlink is refused; a
 *  non-canonical spelling that resolves back inside is no longer refused). Two residuals stay here by
 *  construction, and belong to the caller rather than to this function: the compound/approval-gate face
 *  has no filesystem and stays lexical, and a REMOTE env keeps the lexical behaviour (its
 *  `canonicalPath` is an RPC per candidate). */
export declare function resolveOperandLexically(base: string | undefined, operand: string, homeDir: string | undefined): string | undefined;
/** One operand's two absolute spellings. `folded` has its `..` collapsed in the TEXT and is the
 *  containment answer — every root comparison, deny judgment and verdict field here is built from it.
 *  `spelled` keeps those segments, the walk the KERNEL performs: each prefix resolves (symlinks included)
 *  BEFORE a parent step applies, so `<root>/link/../x` names `dirname(realpath(link))/x`. Present ONLY
 *  when the operand traverses `..`; a filesystem-resolving caller judges it BESIDE the folded form. */
export interface OperandSpellings {
    /** The `..`-collapsed absolute path — what containment is decided on. */
    folded: string;
    /** The uncollapsed absolute path, when the operand traverses `..`. */
    spelled?: string;
}
/** Resolve one operand into {@link OperandSpellings}, or `undefined` on the same unresolvable operands
 *  {@link resolveOperandLexically} reports (it is this function's folded half). */
export declare function resolveOperandSpellings(base: string | undefined, operand: string, homeDir: string | undefined): OperandSpellings | undefined;
/**
 * Does the shell REWRITE this word before the program sees it — a brace list, a parameter, command, process or
 * arithmetic expansion, or a `$` / backtick / brace character where bash reads it (unquoted; `$` and a backtick
 * also inside double quotes)? Read off the lexer's pieces. Brace expansion runs BEFORE tilde expansion and
 * before the word ever reaches the program, so `cat {<abs>,}` hands the program an absolute path no spelling
 * shows, and `ls {~,}` a home directory the `~` arm of {@link resolveOperandLexically} never got to refuse —
 * both were real out-of-root reads. Rather than emulate expansion (the classifier resolves paths lexically and
 * has no filesystem), such a word is declared unresolvable — the direction the whole file errs in.
 *
 * Deliberately NOT here: the GLOB characters ({@link globInWord}) — a glob only ever names entries of a
 * directory that is already on the command line — and a tilde, which the resolver reads. A bracket expression whose
 * body carries an expansion (`[$x]`, `[$(…)]`: bash expands the members before it matches) IS here.
 */
export declare function expansionInWord(w: {
    readonly pieces: readonly WordPiece[];
}): boolean;
/**
 * Does this word carry PATHNAME expansion (`*`, `?`, `[`), unquoted? Split from {@link expansionInWord} because
 * the two have different consequences, not different detection: a brace/variable/command expansion can produce
 * ANY word, so it is unresolvable; a glob only ever names entries of a directory that is already on the command
 * line, so a caller holding a filesystem can decide it. Both quoting forms suppress globbing in bash.
 */
export declare function globInWord(w: {
    readonly pieces: readonly WordPiece[];
}): boolean;
/**
 * Read ONE sed script and answer whether it can name a file or run a command.
 *
 * sed's command letters are a CLOSED set, which is what makes this a small recognizer over a known
 * grammar rather than a second hand-written parser over an open one: address → optional negation →
 * command letter → the argument that letter takes. Anything the walk cannot finish reading — an
 * unbalanced `s` delimiter, an unclosed `{`, a letter outside the set — answers `unparsed`, which
 * its caller treats exactly as it treats `names`: sed itself rejects those scripts, so the cost of the
 * conservative answer is one approval on a script that was not going to run.
 */
/** What one sed script NAMES: the files its `r`/`R` commands read, the files its `w`/`W` commands (and `s///w`)
 *  write, the command lines its `e` commands run, and whether an `s///e` flag executes the pattern space. */
export interface SedScriptNames {
    readonly reads: readonly string[];
    readonly writes: readonly string[];
    readonly commands: readonly string[];
    readonly executesPatternSpace: boolean;
    /** Why the script's names are NOT all here, when they are not: the script is supplied from a FILE whose text is not
     *  in the command (one reading), or no reading could parse it to its end. Absent when every name is listed. */
    readonly unread?: string;
}
/** Every file and command line the program text of a read-language row names, under EVERY implementation's grammar
 *  (the union, deduplicated): what a candidate extraction hands the read judge (`reads`), the write face (`writes`) and
 *  a nested plan (`commands`). `undefined` for a row whose program text the station does not read. A reading that saw
 *  no text names nothing here — that fact rides the verdict face. */
export declare function programTextSlotFiles(row: ProgramRow, args: readonly string[]): SedScriptNames | undefined;
/** The program the shell will RUN here and where its OWN argv begins — the two seats
 *  {@link HeadedSegment.argvProgram} / {@link HeadedSegment.argvFrom} carry, from one reader so they cannot
 *  disagree. The LEADING assignments (both spellings) are the program-position reader's own step, so this
 *  seat no longer strips them a second time: a second strip read the quoted `'A+=x'` as an assignment the
 *  one reader calls a program name, which is two readers with two answers. */
export declare function argvProgramOf(words: readonly LexedWord[]): {
    program: string;
    from: number;
} | undefined;
export declare function attachedOptionPayloads(tok: string): string[];
/**
 * The home directory's VARIABLE spelling as a word FACT: `$HOME` / `${HOME}` as the word's FIRST piece,
 * unquoted or double-quoted, followed by `/`, by a double-quoted piece, or by nothing (unquoted), or — inside
 * the quotes — by `/` or the closing quote; every later piece literal. Those are exactly the words whose expansion
 * is the DECLARED home and nothing else, so `spelled` is what bash hands the program: `<home><rest>`. Everything
 * else is left to be the expansion it is: a single-quoted spelling does not expand, a mid-word or suffixed one
 * (`x$HOME`, `$HOME.bak`, `$HOMES`) is a substitution the classify faces refuse and the read station cannot
 * place, and an unquoted spelling of a home bash would split or glob ({@link HOME_EXPANDS_TO_ONE_WORD}) is not
 * one word. The caller reads it only against a declared home (with none declared the child's `$HOME` may differ
 * from what `~` falls back to) and only when the command cannot rebind `HOME` ({@link textBindsHome}).
 */
export declare function homeExpansionOf(w: LexedWord, home: string): {
    readonly spelled: string;
} | undefined;
/** An `?(` `*(` `+(` `@(` `!(` extended-glob opener spelled UNQUOTED, over an already-lexed text: a word whose last piece
 *  stands unquoted and whose written text ends in one of `? * + @ !`, with the source `(` right after it (across `\<newline>`
 *  continuations only). A here-document delimiter counts too: the lexer ends
 *  it at the `(` (`<<@(x)` reads as the delimiter `@` and a group), where bash with `extglob` on reads the one word
 *  `@(x)` — a different delimiter, so the body the lexer read as data is not the body bash reads. The token keeps no
 *  per-character quoting, so a delimiter quoted anywhere (`<<'E'@(X)`) counts as well: a quoted opener is bash's
 *  syntax error, where reading it as one costs a card and runs nothing. */
export declare function extglobOpenerIn(lexed: LexedCommand, text: string): boolean;
/**
 * {@link classifyCompoundReadonly} with the structured verdict. The name/operator/blocking arms
 * run FIRST and unchanged, so an existing demotion keeps its exact reason and never acquires the
 * out-of-root signal; the boundary scan below only ever converts a would-be READ-ONLY verdict into a
 * demotion, which is why omitting `boundary` reproduces the name-only behaviour exactly.
 */
export declare function classifyCompoundReadonlyDetailed(command: string, allow: ReadonlySet<string>, 
/** The environment's declared home, so the `$HOME` / `${HOME}` word spellings fold the way the read station
 *  folds them before the shape is read. Nothing else is read off it: WHERE a command reads is the station's. */
boundary?: {
    readonly homeDir?: string;
}): CompoundReadonlyVerdict;
/**
 * Compound read-only classification, reason-only face. Returns the demotion reason, or
 * undefined when the command classifies read-only. ⚠️ `undefined` is NOT "safe to auto-execute":
 * the detailed verdict may still carry `undecidedPaths` (operands whose unexpanded spelling — a
 * glob — is what got checked), and this face discards that field. An auto-allow decision must read
 * {@link classifyCompoundReadonlyDetailed} and treat a non-empty `undecidedPaths` as ask — the
 * engine's own probe does exactly that (fs-bash.ts). The optional `boundary`: with it,
 * an allowlisted reader whose path arguments leave the allowed directories is demoted too (use the
 * detailed face when the caller wants to know WHY, e.g. to offer the narrow "allow reading from
 * <dir>" approval); without it the verdict is exactly what it always was.
 */
export declare function classifyCompoundReadonly(command: string, allow: ReadonlySet<string>, boundary?: {
    readonly homeDir?: string;
}): string | undefined;
/**
 * Iteration cap for {@link classifyBoundedReadonlyPollLoop}. The bound must be a LITERAL in the
 * command text (that is what makes "bounded" provable without executing anything); this cap keeps a
 * literal-but-huge bound (`$(seq 1 999999)`) from turning "bounded" into "unbounded in practice".
 * 120 beats × the sleep cap below ≈ 20h worst case — the walltime axis governs actual runtime; this
 * arm's job is only that termination is structurally guaranteed.
 */
export declare const POLL_LOOP_MAX_BEATS = 120;
/**
 * Per-`sleep` cap (seconds) for {@link classifyBoundedReadonlyPollLoop}. A literal `sleep 99999999`
 * is technically bounded but practically a hang — the same hazard class the availability arms
 * demote (a green verdict that blocks until the tool timeout is green in name only).
 */
export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
/**
 * The BOUNDED READ-ONLY POLL LOOP arm of the classify face. The compound classifier above rejects any
 * control structure (`for` is not an allowlisted name), which makes the shape tier behave like
 * the `"always"` floor for a monitoring consumer whose whole idiom is `for i in $(seq 1 8); do tail -n 5 x.log;
 * sleep 2; done`. This arm accepts EXACTLY that grammar and nothing else. Returns the rejection
 * reason, or undefined when the command matches the grammar (⇒ safe to treat as reversible).
 *
 * It is ADDITIVE and consulted only after {@link classifyCompoundReadonly} rejected (see
 * {@link import("./fs-bash.js").bashReversibilityProbe}): it can never change a verdict the plain face
 * already gives, and every unparseable shape falls through to a rejection — the default path is
 * untouched.
 *
 * SAFETY ARGUMENT — why this allowance does not increase irreversibility/egress risk. Each pillar
 * maps to a test group in test/poll-loop-classifier.test.ts (G1-G6):
 *  G1 BOUNDED TERMINATION. The loop head must be one of three LITERAL forms with a computable
 *     iteration count ≤ {@link POLL_LOOP_MAX_BEATS} ({@link pollLoopBeatsOf}); `while`/`until` never
 *     match the skeleton. Every body command terminates: non-`sleep` segments inherit the availability/
 *     108/153 availability arms via {@link classifyCompoundReadonlyDetailed} (blocking stdin, `tail
 *     -f`, unbounded devices all reject), and `sleep` must be a literal ≤
 *     {@link POLL_LOOP_MAX_SLEEP_SECONDS}. Bounded beats × terminating body ⇒ the loop halts.
 *  G2 NO WRITE OR EGRESS CHANNEL. Every non-`sleep` body segment must pass the SAME
 *     {@link coarseReadonlyCheck} parser + allowlist as the plain face (one parser, no drift);
 *     `sleep` touches neither filesystem nor network. No redirection (`>` `<`), no `$`/backtick
 *     substitution, no subshell exists anywhere: the body bans them wholesale
 *     ({@link POLL_BODY_HARD_REJECT}), and the head's single `$(seq <int> <int>)` exemption is an
 *     exact-spelling match whose two arguments are literal digits.
 *  G3 CONNECTOR CLOSURE. `;` is the only connector in the body; `|`/`&`/`&&`/`||` are banned there
 *     outright, and a nested control structure surfaces as a `for`/`do`/`done` head token that fails
 *     the allowlist. Nothing can chain past the vetted segment heads.
 *  G4 READ BOUNDARY UNCHANGED. The reassembled body goes through the boundary scan of
 *     {@link classifyCompoundReadonlyDetailed} with the caller's boundary — out-of-root operands
 *     reject exactly as on the plain face (not relaxed by one rule). STRICTER here: a
 *     verdict carrying {@link CompoundReadonlyVerdict.undecidedPaths} (an unexpanded glob) rejects,
 *     because the probe face is reason-only and a repeated read of a runtime-decided target must not
 *     auto-allow.
 *  G5 STRUCTURE SOUNDNESS (the parser sees what bash executes). Any non-ASCII / control whitespace
 *     ({@link NON_ASCII_WHITESPACE}) rejects the whole command up front, the reference implementation's
 *     form. The skeleton is then read off the ONE lexer's segments and words ({@link pollLoopShapeOf}) — the
 *     same reading bash gives the text: an unterminated quote is a parse failure (reject), a `done` hidden in
 *     quotes is a word that is not `done`, and the var/iterable words must be written bare. The body's
 *     segments are the lexer's, so a connector inside quotes is text, never a boundary.
 *  G6 ENVIRONMENT INTEGRITY. The loop variable must be a SINGLE LOWERCASE LETTER: the `for`
 *     assignment mutates a shell variable, and a name like `PATH`/`IFS`/`LD_PRELOAD`/`BASH_ENV`
 *     would change how the body's commands RESOLVE (`for PATH in dir; do cat x; done` would run
 *     `dir/cat` — arbitrary code under an auto-allow). No known resolution-affecting variable is a
 *     single lowercase letter, and the body cannot read the variable anyway (`$` is banned there),
 *     so the counter is write-only and inert.
 *
 * ACCEPTED-COST NOTES (the residuals, recorded rather than silently tolerated — same posture as the
 * `head` note above):
 *  · `$(seq …)` RESOLVES A COMMAND. This is the ONE command-substitution form the ruling exempts, and
 *    it does run whatever `seq` resolves to on PATH (or a shell function/alias of that name). Two
 *    facts bound the exposure. FIRST, the trust assumption is ISOMORPHIC to the allowlist itself:
 *    `ls`/`cat`/`grep` are equally PATH-resolved, so a party that can shadow `seq` can already shadow
 *    `cat` — this arm adds no trust assumption the plain face does not already make, and the file's
 *    standing posture is that the tool-policy gate is the authoritative control and command-name
 *    resolution to the real coreutils binaries is assumed (see {@link BASH_READONLY_DEFAULT_ALLOW}).
 *    SECOND, the beat count is taken from the LITERAL digits, not from seq's output, so a shadowed
 *    `seq` emitting more items only loosens the ITERATION bound (never the read-only / no-write /
 *    no-egress envelope — every beat still runs allowlisted readers + literal `sleep`), and the
 *    walltime axis cascades a hard kill regardless. A deployment wanting NO command-substitution at
 *    all can prefer `{<lo>..<hi>}` or a literal list, which bash expands internally with no command
 *    run. (The substitution is a flagged, high-severity shape kept per the ruling that names
 *    `$(seq <int> <int>)` as the one exemption — recorded for a
 *    tightening decision, since the brace form is a drop-in equivalent.)
 *  · STDIN-BLOCKING BODY (an availability residual, not a safety one). What REMAINS residual is the general
 *    statement: this arm inherits whatever the shared availability arms can see, they are best-effort
 *    (an operand that only turns out to be unreadable at run time is not in the text), and a hang is
 *    bounded by the tool timeout — outside the irreversibility / egress envelope this arm governs. The
 *    G1 termination guarantee is therefore stated for the NON-stdin-blocking body.
 *
 * NON-RESIDUAL (a divergence this arm CLOSES rather than accepts): the JS-`\s`-vs-bash-`$IFS` gap.
 * Because the shared parser splits on JS `\s` (which includes NBSP/U+2000-block/U+3000/form-feed/
 * vertical-tab) but bash's default `$IFS` does not, `ls x` reads as head `ls` here yet is ONE
 * command word to bash. Rather than reason about the (harmless — nonexistent program, exit 127)
 * consequence case by case, the whole command is rejected up front if it carries any such character
 * ({@link NON_ASCII_WHITESPACE}), so the grammar's tokenization and bash's stay identical.
 */
export declare function classifyBoundedReadonlyPollLoop(command: string, allow: ReadonlySet<string>, boundary?: {
    readonly homeDir?: string;
}): string | undefined;
/**
 * The poll-loop face's STRUCTURED verdict — the same {@link CompoundReadonlyVerdict} the compound face
 * returns, so a consumer reads the boundary's structural findings (deny row, containment, recursive or
 * undecided operands) off the loop body exactly as off a plain compound, beside whatever sentence the
 * loop grammar or the body's shape produced. {@link classifyBoundedReadonlyPollLoop} is its `reason`
 * projection. A refusal of the LOOP GRAMMAR itself (not a bounded loop, an unlisted body program, an
 * unvetted sleep) carries no structure: no reader segment was walked.
 */
export declare function classifyBoundedReadonlyPollLoopDetailed(command: string, allow: ReadonlySet<string>, boundary?: {
    readonly homeDir?: string;
}): CompoundReadonlyVerdict;
