UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

206 lines (205 loc) • 15.2 kB
/** * 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;