@sema-agent/core
Version:
Stateless, task-oriented AI agent core
206 lines (205 loc) • 15.2 kB
TypeScript
/**
* The ONE reader of a segment's PROGRAM POSITION: which argv word names the program bash runs, and what stands in
* front of it — bare control words with their declared options, environment assignments, a launcher chain.
*
* Every face that judges a command by its program (the compound walk, the single-command boundary face, the
* refused-command whitelist, the poll loop's base-move check) reads this ONE function. A face that needs the
* answer it cannot give reads {@link ProgramPosition.refusal}: a declarative record of why the program is not
* readable, minted into one sentence by {@link programPositionRefusalSentence}. Reading less than this reader
* does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
*/
import { SHELL_CONTROL_PREFIXES, type LexedWord } from "../../shell/lex.js";
/** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
* they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
/** argv[0] as the program bash will resolve it: `/usr/bin/cat` and `cat` are one name. */
export declare function programBasename(word: string): string;
/**
* The `NAME=` / `NAME+=` prefix as WRITTEN in a whitespace-split token. This is NOT the position reader's grammar
* — the reader reads the lexer's {@link LexedWord.assignment} fact — but the two readers that still walk the
* command TEXT rather than its words (the unverifiable-delete policy and the activity summary) read it, and
* they read it here so that they read one spelling. It retires with them.
*/
export declare const ASSIGNMENT_PREFIX: RegExp;
/** The prefix a whitespace-split token BEGINS with, or `undefined` — the text readers' view of
* {@link ASSIGNMENT_PREFIX}; `name` is the variable before any subscript, `settlesWholeName` false for an
* append and for a subscript. */
export declare function writtenAssignmentPrefix(token: string): {
readonly prefix: string;
readonly name: string;
readonly appends: boolean;
readonly settlesWholeName: boolean;
} | undefined;
export { SHELL_CONTROL_PREFIXES };
/** Shell block words that name no command of their own (a loop head, a block close, a case head). */
export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
/** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
* looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
export declare function isBareControlWord(words: readonly LexedWord[], i: number, set: ReadonlySet<string>): boolean;
/**
* A word bash strips as an ASSIGNMENT before it chooses the program: the lexer's assignment fact, written BARE
* (the name, the brackets and the operator unquoted — a quoted VALUE still assigns, a quoted operator is a
* program name), read where bash accepts one (the lexer's own command-position state, which this reader's walk
* over control words and earlier assignments re-traces). One fact, two readers that must agree.
*/
export declare function isLeadingAssignmentWord(words: readonly LexedWord[], i: number): boolean;
/**
* Programs that turn their STANDARD INPUT into the OPERANDS of another program, and EXEC that program directly.
*
* The same class as the shell re-entry programs, one level down: there the unseen text is COMMANDS; here the
* command's name IS visible and its argv is not — `echo <path> | xargs cat` opens a file no token of this walk
* ever named, because the operand arrives on a stream. The membership criterion is that behaviour and only that
* behaviour; a program that consumes stdin as DATA (`sort`, `wc`, a reader the deployment never listed) is NOT a
* member, because its stdin never becomes a filename. Spelled as a pattern — these are program names, never tool
* names.
*
* "EXECS DIRECTLY" is what separates this class from the shell re-entry class, and it is why GNU `parallel` is
* NOT a member: parallel BUILDS A COMMAND LINE and hands it to a shell, and with no command word at all it runs
* each input line as a command — so parallel belongs to the shell re-entry class and is refused whole with no
* exemption at all. A spelling this file has no name for (an aliased or wrapped `xargs`) is judged as any other
* unlisted program is — the name-table floor the re-entry class records for itself.
*/
export declare const ARGV_REENTRY_PROGRAM_RE: RegExp;
/** The program an {@link ARGV_REENTRY_PROGRAM_RE} launcher runs when the chain names none: `xargs` defaults to
* `echo`, which is why a bare one reads nothing. */
export declare const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
/**
* What a launcher declares about its OWN options: each exact spelling with its ARITY — `0` for a switch, `1` for an
* option whose value is the next token. Every spelling not in the map falls to the refusal (the conservative side,
* and the verdict those spellings always had); a `1` with no token left to take is not readable either.
*
* THREE SPELLINGS OF ONE DECLARATION, because getopt has three: an arity-1 entry is honoured separated (`-k 5`),
* fused to its long name (`--signal=TERM`) and fused to its short letter (`-k5`), and an arity-0 short entry inside
* a cluster of arity-0 letters (`-pv`). Reading only the exact spelling made one program two.
*
* WHAT A ROW MAY NOT GET WRONG: declaring an arity-1 option as arity 0 steps onto its VALUE and reads the value as
* the program — the one direction that turns a refusal into a wrong answer rather than a lost one. A row carries
* only options whose arity every supported implementation agrees on; a flag no row declares still refuses.
*/
export interface LauncherRow {
/** Consulted ONLY for the launcher the command line itself names — a bare keyword in front of the command
* (`time -p …`) or the written command word (`"time" -p …`, `X=1 time -p …`, `/usr/bin/time -p …`). A launcher
* nested behind another (`env time -p …`) is that launcher's operand, and its options refuse: resolving past
* them would turn a whole refusal into an ordinary walked segment — a widening, where every other read of this
* table only ever finds MORE program to judge. */
readonly optionArity: ReadonlyMap<string, 0 | 1>;
/** A bare NEGATIVE integer in this launcher's option section is its OWN argument (`nice -5 <cmd>`), not a flag
* the table cannot read. Per row: elsewhere a dash-leading token is an option, and reading one as a number
* would step over a flag whose meaning this table does not know. */
readonly negativeIntegerIsOwnArgument?: true;
/** Positional operands the launcher takes BEFORE the program, consumed whatever they spell (`timeout DURATION
* COMMAND`); a spelling-based reading under-accepted, and every spelling it missed became the program word. */
readonly requiredOperands?: number;
/** The launcher reads NAME=VALUE operands with ITS OWN name grammar (`env 1=x …`), wider than the shell's. */
readonly assignmentOperands?: true;
/** The launcher hands its operands to a SHELL as text (`watch "cat /x"` runs `sh -c`): no program word to read. */
readonly shellText?: true;
/** With no program word the launcher runs a SHELL: piped text would reach it, so an empty program position refuses. */
readonly whenNoProgram?: "shell";
/** Options whose value is a WHOLE COMMAND LINE the launcher splits and runs (`env -S "cmd arg"`,
* `--split-string=…`). Not an arity this reader steps over — the program is inside the value, so the
* program-position walk still refuses here — but the candidate extraction reads the value as nested shell
* text instead of as one opaque word. */
readonly commandString?: readonly string[];
}
/**
* The ONE launcher truth: programs that RUN ANOTHER PROGRAM named in ordinary argv tokens, so a declared reader can
* sit behind one (`env cat <path>`, `sudo cat <path>`, `timeout 5 cat <path>`), each with the option grammar this
* file reads for it.
*
* Membership is the key set. A row with no declared options means every option-shaped word after that launcher
* refuses ("which program runs is not readable without the launcher's option table") — the answer every launcher
* but `time` has always had, now stated per row rather than by omission. Declaring a row's options is a behaviour
* decision with its own criteria; adding a launcher is a row.
*
* `time` carries both spellings a deployment can meet: the shell builtin takes `-p` alone, and the standalone
* program adds a format, an output file, and three switches.
*
* A deliberate SUBSET of the rule lane's own `BARE_INTERPRETER_NAMES`, mirrored rather than imported because that
* module imports the classifier — the dependency runs that way, and a cycle is a worse problem than a mirror whose
* subset relation is pinned by test. GNU `parallel` is absent for the reason the shells are absent: it does not run
* a program named in token-visible argv — it BUILDS a command line and hands it to a shell — so it is a shell
* re-entry member instead. Language interpreters and shells are excluded as a scope statement: their program
* argument is a script path or a quoted code string, not a token-visible program with token-visible operands.
*/
export declare const LAUNCHER_TABLE: ReadonlyMap<string, LauncherRow>;
/** The launcher names — derived from the table, never kept beside it. */
export declare const COMMAND_LAUNCHERS: ReadonlySet<string>;
export declare const PROGRAM_POSITION_REFUSAL_KINDS: readonly ["option_at_program_position", "control_syntax_at_program_position"];
export type ProgramPositionRefusalKind = (typeof PROGRAM_POSITION_REFUSAL_KINDS)[number];
/** Why the program could not be read — a closed set, each member minted into one sentence. `owner` is the word
* that took the offending token: the control word or the first launcher of the chain. */
export type ProgramPositionRefusal = {
readonly kind: "option_at_program_position";
readonly owner: string;
readonly option: string;
} | {
readonly kind: "control_syntax_at_program_position";
readonly owner: string;
readonly word: string;
/** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL stands at the program
* position. Absent ⇒ genuine control syntax, which IS one. */
readonly runsShell?: true;
};
export interface ProgramPosition {
/** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
* are skipped with them. */
readonly controlWords: readonly number[];
/** The first index past the control words and their options — the WRITTEN command word: an assignment, a
* launcher, or the program itself. `undefined` when nothing stands there. */
readonly written: number | undefined;
/** The leading `NAME=value` words at `written`. */
readonly assignments: readonly number[];
/** The launcher chain past the assignments, in order (basenames); empty when the written word is not a launcher. */
readonly launchers: readonly {
readonly index: number;
readonly name: string;
}[];
/** Some chain member is an argv re-entry program: the program's operands arrive on a stream. */
readonly argvFromStdin: boolean;
/** The chain ends ON the re-entry program (nothing follows it), so it runs its default program. */
readonly runsDefaultProgram: boolean;
/** The program bash RUNS: the first word past the chain. `name` is the folded word as written, `basename` what
* bash resolves (`/usr/bin/cat` is `cat`). `undefined` when the chain names none or the read was refused. */
readonly program: {
readonly index: number;
readonly name: string;
readonly basename: string;
} | undefined;
/** Why no program could be read; `undefined` beside `program: undefined` means the chain simply names none. */
readonly refusal: ProgramPositionRefusal | undefined;
}
/**
* Read the program position of one segment's argv words.
*
* In order: bare control words and the options their launcher rows declare; the written word; its leading
* assignments; then either the program itself or a launcher chain — whose first member's declared options are read
* too (it is the launcher the line names), and inside which an assignment, a bare number, a nested launcher and an
* argv re-entry program are stepped over, an option-shaped word (a lone `-` included — `env - <program>` clears
* the environment and the table does not say so) refuses, and shell control syntax refuses. An option-shaped
* WRITTEN word (judged on its basename, as the program it names) refuses the same way: it is where the program
* would be, and the word in front took an option no row explains.
*/
export declare function readProgramPosition(words: readonly LexedWord[]): ProgramPosition;
/** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
* stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every
* consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
export declare function argvReentryUnjudged(pos: ProgramPosition, acceptedTargets: ReadonlySet<string>): boolean;
/**
* The two ways this reader DECLINES rather than judges — a CLOSED set (`docs/CLOSED-SETS.md`), because what a
* seat does with a declined segment is a ROW of the shell read decision table (`shell-read-judge.ts`, the
* `declined` row: the one row the seats disagree on), not a boolean buried in a caller.
* `unreadable_program_position`: the walk cannot name the program bash will run (`env -- ls`).
* `stream_fed_operands`: the operands arrive on a STREAM it never saw (`… | xargs cat`). Neither names a WORD.
*/
export declare const SHELL_SCAN_DECLINES: readonly ["unreadable_program_position", "stream_fed_operands"];
export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number];
/** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher
* ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings
* cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */
export declare function programPositionRunsShell(pos: ProgramPosition, shellProgram: RegExp): boolean;
/** Which decline, if any, this program position raises — ONE reading of the two facts above, so no consumer
* re-derives either. A refusal that says a SHELL runs is NOT one: it names what runs. */
export declare function shellScanDeclineOf(pos: ProgramPosition, acceptedStreamTargets: ReadonlySet<string>): ShellScanDecline | undefined;
/** ONE sentence per refusal kind, minted here and nowhere else. */
export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;