@sema-agent/core 7.17.7 → 7.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +140 -13
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +5 -0
  5. package/dist/agents/teacher.js +1 -0
  6. package/dist/agents/verify.js +3 -0
  7. package/dist/brain/route-adjudicator.d.ts +18 -0
  8. package/dist/brain/route-adjudicator.js +16 -0
  9. package/dist/core/agent-definition.d.ts +10 -0
  10. package/dist/core/artifact-host.d.ts +41 -4
  11. package/dist/core/artifact-host.js +18 -2
  12. package/dist/core/auto-mode.d.ts +141 -45
  13. package/dist/core/auto-mode.js +109 -3
  14. package/dist/core/checkpoint-store.d.ts +16 -2
  15. package/dist/core/engine-notice.d.ts +47 -1
  16. package/dist/core/engine-notice.js +7 -0
  17. package/dist/core/gate-lanes.js +2 -1
  18. package/dist/core/gate-outcome.d.ts +17 -2
  19. package/dist/core/gate-outcome.js +16 -0
  20. package/dist/core/governance-codes.d.ts +1 -1
  21. package/dist/core/governance-codes.js +8 -0
  22. package/dist/core/hook-seats.d.ts +66 -0
  23. package/dist/core/hook-seats.js +37 -0
  24. package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
  25. package/dist/core/runner/assemble-result.d.ts +2 -1
  26. package/dist/core/runner/assemble-result.js +1 -1
  27. package/dist/core/runner/classifier-ladder.d.ts +89 -0
  28. package/dist/core/runner/classifier-ladder.js +65 -0
  29. package/dist/core/runner/contracts.d.ts +69 -13
  30. package/dist/core/runner/decide-continuation.d.ts +1 -3
  31. package/dist/core/runner/decide-continuation.js +1 -7
  32. package/dist/core/runner/deferral-census.d.ts +52 -0
  33. package/dist/core/runner/deferral-census.js +44 -0
  34. package/dist/core/runner/derived-route-fallback.d.ts +8 -4
  35. package/dist/core/runner/derived-route-fallback.js +8 -3
  36. package/dist/core/runner/gate-exit.d.ts +12 -0
  37. package/dist/core/runner/gate-exit.js +6 -3
  38. package/dist/core/runner/prepare-artifact.d.ts +10 -2
  39. package/dist/core/runner/prepare-artifact.js +5 -4
  40. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  41. package/dist/core/runner/prepare-caps-and-workflow.js +30 -31
  42. package/dist/core/runner/prepare-config-doors.d.ts +1 -1
  43. package/dist/core/runner/prepare-config-doors.js +5 -3
  44. package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
  45. package/dist/core/runner/prepare-defer-classify.js +8 -3
  46. package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
  47. package/dist/core/runner/prepare-gate-stations.js +1 -1
  48. package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
  49. package/dist/core/runner/prepare-hands-readface.js +60 -24
  50. package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
  51. package/dist/core/runner/prepare-inherited-gate.js +10 -2
  52. package/dist/core/runner/prepare-lsp.d.ts +2 -2
  53. package/dist/core/runner/prepare-lsp.js +1 -1
  54. package/dist/core/runner/prepare-policy-chain.js +2 -0
  55. package/dist/core/runner/prepare-project-context.js +4 -2
  56. package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
  57. package/dist/core/runner/prepare-prompt-inputs.js +4 -1
  58. package/dist/core/runner/prepare-run-refs.d.ts +9 -1
  59. package/dist/core/runner/prepare-run-refs.js +3 -2
  60. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
  61. package/dist/core/runner/prepare-suspend-saga.js +5 -2
  62. package/dist/core/runner/prepare-task.js +18 -13
  63. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
  64. package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
  65. package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
  66. package/dist/core/runner/prepare-turn-wiring.js +3 -2
  67. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
  68. package/dist/core/runner/prepare-wiring-manifest.js +5 -2
  69. package/dist/core/runner/resume-checkpoint-screen.js +7 -0
  70. package/dist/core/runner/run-harness-handlers.js +4 -1
  71. package/dist/core/runner/run-identity-wiring.js +6 -2
  72. package/dist/core/runner/run-leg.d.ts +2 -0
  73. package/dist/core/runner/run-leg.js +35 -41
  74. package/dist/core/runner/run-notification-lane.js +55 -27
  75. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  76. package/dist/core/runner/run-terminal-adoption.js +16 -13
  77. package/dist/core/runner/run-turn-boundary.js +3 -1
  78. package/dist/core/runner/runtask.d.ts +6 -5
  79. package/dist/core/runner/runtask.js +26 -14
  80. package/dist/core/runner/skill-base-dir.d.ts +54 -0
  81. package/dist/core/runner/skill-base-dir.js +42 -0
  82. package/dist/core/runner/synthetic-tools.d.ts +16 -19
  83. package/dist/core/runner/synthetic-tools.js +18 -10
  84. package/dist/core/runner/tool-defer-gate.d.ts +22 -9
  85. package/dist/core/runner/tool-defer-gate.js +15 -5
  86. package/dist/core/runner/tool-disclosure.d.ts +5 -0
  87. package/dist/core/runner/tool-disclosure.js +2 -0
  88. package/dist/core/runner/tool-progress-lane.d.ts +42 -0
  89. package/dist/core/runner/tool-progress-lane.js +61 -0
  90. package/dist/core/runner-deps.d.ts +48 -7
  91. package/dist/core/swappable-deps.d.ts +9 -7
  92. package/dist/core/swappable-deps.js +7 -3
  93. package/dist/core/task-event.d.ts +96 -29
  94. package/dist/core/task-notification.d.ts +142 -16
  95. package/dist/core/task-notification.js +106 -0
  96. package/dist/core/task-result.d.ts +9 -1
  97. package/dist/core/task-spec.d.ts +20 -3
  98. package/dist/core/tighten-task-spec.js +7 -5
  99. package/dist/core/tool-model-gate.d.ts +1 -1
  100. package/dist/core/tool-search-setting.d.ts +78 -0
  101. package/dist/core/tool-search-setting.js +90 -0
  102. package/dist/core/tool-spec.d.ts +8 -0
  103. package/dist/core/types.d.ts +1 -1
  104. package/dist/core/types.js +1 -1
  105. package/dist/core/wiring-manifest.d.ts +46 -0
  106. package/dist/core/wiring-manifest.js +9 -1
  107. package/dist/engine/execution-env/node-execution-env.js +10 -4
  108. package/dist/engine/harness/agent-harness.d.ts +42 -0
  109. package/dist/engine/harness/agent-harness.js +61 -29
  110. package/dist/engine/loop/agent-loop.d.ts +68 -3
  111. package/dist/engine/loop/agent-loop.js +109 -16
  112. package/dist/index.d.ts +10 -5
  113. package/dist/index.js +9 -5
  114. package/dist/internal/harness.d.ts +1 -1
  115. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  116. package/dist/orchestration/run-workflow-tool.js +6 -1
  117. package/dist/orchestration/workflow-governance.d.ts +7 -1
  118. package/dist/orchestration/workflow-governance.js +9 -5
  119. package/dist/orchestration/workflow-primitives.d.ts +3 -0
  120. package/dist/orchestration/workflow-primitives.js +6 -1
  121. package/dist/orchestration/workflow-types.d.ts +48 -13
  122. package/dist/orchestration/workflow-types.js +1 -1
  123. package/dist/orchestration/workflow.js +45 -17
  124. package/dist/prompt-assembly/epoch.js +2 -0
  125. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  126. package/dist/prompt-assembly/section-usage.d.ts +26 -0
  127. package/dist/prompt-assembly/section-usage.js +10 -0
  128. package/dist/prompt-assembly/types.d.ts +8 -6
  129. package/dist/prompts/default.d.ts +13 -0
  130. package/dist/prompts/default.js +16 -8
  131. package/dist/tools/artifact/artifact-text.d.ts +10 -1
  132. package/dist/tools/artifact/artifact-text.js +6 -3
  133. package/dist/tools/artifact/artifact-tool.d.ts +12 -2
  134. package/dist/tools/artifact/artifact-tool.js +203 -70
  135. package/dist/tools/artifact/conformance.d.ts +56 -0
  136. package/dist/tools/artifact/conformance.js +349 -0
  137. package/dist/tools/artifact/local-stub.js +1 -1
  138. package/dist/tools/artifact/reject-breaker.d.ts +28 -0
  139. package/dist/tools/artifact/reject-breaker.js +42 -0
  140. package/dist/tools/fs/bash-lexer.d.ts +132 -0
  141. package/dist/tools/fs/bash-lexer.js +563 -0
  142. package/dist/tools/fs/bash-program-position.d.ts +142 -0
  143. package/dist/tools/fs/bash-program-position.js +125 -0
  144. package/dist/tools/fs/bash-readonly-classifier.d.ts +54 -16
  145. package/dist/tools/fs/bash-readonly-classifier.js +121 -552
  146. package/dist/tools/fs/containment-face.d.ts +89 -0
  147. package/dist/tools/fs/containment-face.js +81 -0
  148. package/dist/tools/fs/fs-bash.d.ts +46 -1
  149. package/dist/tools/fs/fs-bash.js +158 -22
  150. package/dist/tools/fs/fs-read.d.ts +1 -1
  151. package/dist/tools/fs/fs-search-tools.d.ts +3 -3
  152. package/dist/tools/fs/fs-write.d.ts +4 -3
  153. package/dist/tools/fs/fs-write.js +9 -9
  154. package/dist/tools/fs/index.d.ts +24 -5
  155. package/dist/tools/fs/index.js +18 -6
  156. package/dist/tools/fs/repo-map.d.ts +2 -2
  157. package/dist/tools/fs/safety.d.ts +3 -3
  158. package/dist/tools/fs/safety.js +4 -4
  159. package/package.json +1 -1
  160. package/test/export-surface.snapshot.json +85 -9
  161. package/wire-changes/core@7.17.7.json +49 -0
  162. package/dist/tools/fs/read-face.d.ts +0 -68
  163. package/dist/tools/fs/read-face.js +0 -47
@@ -0,0 +1,142 @@
1
+ /**
2
+ * The ONE reader of a segment's PROGRAM POSITION: which argv word names the program bash runs, and what stands in
3
+ * front of it — bare control words with their declared options, environment assignments, a launcher chain.
4
+ *
5
+ * Every face that judges a command by its program (the compound walk, the single-command boundary face, the
6
+ * refused-command whitelist, the poll loop's base-move check) reads this ONE function. A face that needs the
7
+ * answer it cannot give reads {@link ProgramPosition.refusal}: a declarative record of why the program is not
8
+ * readable, minted into one sentence by {@link programPositionRefusalSentence}. Reading less than this reader
9
+ * does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
10
+ */
11
+ import type { SegmentTokens } from "./bash-lexer.js";
12
+ /** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
13
+ * they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
14
+ export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
15
+ /** argv[0] as the program bash will resolve it: `/usr/bin/cat` and `cat` are one name. */
16
+ export declare function programBasename(word: string): string;
17
+ /** A leading `NAME=value` word: an assignment bash applies to the command's environment rather than a
18
+ * program name. Same shape the exit-code reader in `fs-bash.ts` skips for the same reason. */
19
+ export declare const ASSIGNMENT_WORD: RegExp;
20
+ /** Shell control words that stand BEFORE a command: transparent for the boundary walk (the command behind them is
21
+ * judged). */
22
+ export declare const SHELL_CONTROL_PREFIXES: ReadonlySet<string>;
23
+ /** Shell block words that name no command of their own (a loop head, a block close, a case head). */
24
+ export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
25
+ /** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
26
+ * looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
27
+ export declare function isBareControlWord(toks: SegmentTokens, i: number, words: ReadonlySet<string>): boolean;
28
+ /**
29
+ * Programs that turn their STANDARD INPUT into the OPERANDS of another program, and EXEC that program directly.
30
+ *
31
+ * The same class as the shell re-entry programs, one level down: there the unseen text is COMMANDS; here the
32
+ * command's name IS visible and its argv is not — `echo <path> | xargs cat` opens a file no token of this walk
33
+ * ever named, because the operand arrives on a stream. The membership criterion is that behaviour and only that
34
+ * behaviour; a program that consumes stdin as DATA (`sort`, `wc`, a reader the deployment never listed) is NOT a
35
+ * member, because its stdin never becomes a filename. Spelled as a pattern — these are program names, never tool
36
+ * names.
37
+ *
38
+ * "EXECS DIRECTLY" is what separates this class from the shell re-entry class, and it is why GNU `parallel` is
39
+ * NOT a member: parallel BUILDS A COMMAND LINE and hands it to a shell, and with no command word at all it runs
40
+ * each input line as a command — so parallel belongs to the shell re-entry class and is refused whole with no
41
+ * exemption at all. A spelling this file has no name for (an aliased or wrapped `xargs`) is judged as any other
42
+ * unlisted program is — the name-table floor the re-entry class records for itself.
43
+ */
44
+ export declare const ARGV_REENTRY_PROGRAM_RE: RegExp;
45
+ /** The program an {@link ARGV_REENTRY_PROGRAM_RE} launcher runs when the chain names none: `xargs` defaults to
46
+ * `echo`, which is why a bare one reads nothing. */
47
+ export declare const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
48
+ /**
49
+ * What a launcher declares about its OWN options: each exact spelling with its ARITY — `0` for a switch, `1` for an
50
+ * option whose value is the next token. A fused `--name=value` and every spelling not in the map fall to the refusal
51
+ * (the conservative side, and the verdict those spellings always had); a `1` with no token left to take is not
52
+ * readable either.
53
+ */
54
+ export interface LauncherRow {
55
+ /** Consulted ONLY for the launcher the command line itself names — a bare keyword in front of the command
56
+ * (`time -p …`) or the written command word (`"time" -p …`, `X=1 time -p …`, `/usr/bin/time -p …`). A launcher
57
+ * nested behind another (`env time -p …`) is that launcher's operand, and its options refuse: resolving past
58
+ * them would turn a whole refusal into an ordinary walked segment — a widening, where every other read of this
59
+ * table only ever finds MORE program to judge. */
60
+ readonly optionArity: ReadonlyMap<string, 0 | 1>;
61
+ }
62
+ /**
63
+ * The ONE launcher truth: programs that RUN ANOTHER PROGRAM named in ordinary argv tokens, so a declared reader can
64
+ * sit behind one (`env cat <path>`, `sudo cat <path>`, `timeout 5 cat <path>`), each with the option grammar this
65
+ * file reads for it.
66
+ *
67
+ * Membership is the key set. A row with no declared options means every option-shaped word after that launcher
68
+ * refuses ("which program runs is not readable without the launcher's option table") — the answer every launcher
69
+ * but `time` has always had, now stated per row rather than by omission. Declaring a row's options is a behaviour
70
+ * decision with its own criteria; adding a launcher is a row.
71
+ *
72
+ * `time` carries both spellings a deployment can meet: the shell builtin takes `-p` alone, and the standalone
73
+ * program adds a format, an output file, and three switches.
74
+ *
75
+ * A deliberate SUBSET of the rule lane's own `BARE_INTERPRETER_NAMES`, mirrored rather than imported because that
76
+ * module imports the classifier — the dependency runs that way, and a cycle is a worse problem than a mirror whose
77
+ * subset relation is pinned by test. GNU `parallel` is absent for the reason the shells are absent: it does not run
78
+ * a program named in token-visible argv — it BUILDS a command line and hands it to a shell — so it is a shell
79
+ * re-entry member instead. Language interpreters and shells are excluded as a scope statement: their program
80
+ * argument is a script path or a quoted code string, not a token-visible program with token-visible operands.
81
+ */
82
+ export declare const LAUNCHER_TABLE: ReadonlyMap<string, LauncherRow>;
83
+ /** The launcher names — derived from the table, never kept beside it. */
84
+ export declare const COMMAND_LAUNCHERS: ReadonlySet<string>;
85
+ export declare const PROGRAM_POSITION_REFUSAL_KINDS: readonly ["option_at_program_position", "control_syntax_at_program_position"];
86
+ export type ProgramPositionRefusalKind = (typeof PROGRAM_POSITION_REFUSAL_KINDS)[number];
87
+ /** Why the program could not be read — a closed set, each member minted into one sentence. `owner` is the word
88
+ * that took the offending token: the control word or the first launcher of the chain. */
89
+ export type ProgramPositionRefusal = {
90
+ readonly kind: "option_at_program_position";
91
+ readonly owner: string;
92
+ readonly option: string;
93
+ } | {
94
+ readonly kind: "control_syntax_at_program_position";
95
+ readonly owner: string;
96
+ readonly word: string;
97
+ };
98
+ export interface ProgramPosition {
99
+ /** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
100
+ * are skipped with them. */
101
+ readonly controlWords: readonly number[];
102
+ /** The first index past the control words and their options — the WRITTEN command word: an assignment, a
103
+ * launcher, or the program itself. `undefined` when nothing stands there. */
104
+ readonly written: number | undefined;
105
+ /** The leading `NAME=value` words at `written`. */
106
+ readonly assignments: readonly number[];
107
+ /** The launcher chain past the assignments, in order (basenames); empty when the written word is not a launcher. */
108
+ readonly launchers: readonly {
109
+ readonly index: number;
110
+ readonly name: string;
111
+ }[];
112
+ /** Some chain member is an argv re-entry program: the program's operands arrive on a stream. */
113
+ readonly argvFromStdin: boolean;
114
+ /** The chain ends ON the re-entry program (nothing follows it), so it runs its default program. */
115
+ readonly runsDefaultProgram: boolean;
116
+ /** The program bash RUNS: the first word past the chain. `name` is the folded word as written, `basename` what
117
+ * bash resolves (`/usr/bin/cat` is `cat`). `undefined` when the chain names none or the read was refused. */
118
+ readonly program: {
119
+ readonly index: number;
120
+ readonly name: string;
121
+ readonly basename: string;
122
+ } | undefined;
123
+ /** Why no program could be read; `undefined` beside `program: undefined` means the chain simply names none. */
124
+ readonly refusal: ProgramPositionRefusal | undefined;
125
+ }
126
+ /**
127
+ * Read the program position of one segment's argv words.
128
+ *
129
+ * In order: bare control words and the options their launcher rows declare; the written word; its leading
130
+ * assignments; then either the program itself or a launcher chain — whose first member's declared options are read
131
+ * too (it is the launcher the line names), and inside which an assignment, a bare number, a nested launcher and an
132
+ * argv re-entry program are stepped over, an option-shaped word (a lone `-` included — `env - <program>` clears
133
+ * the environment and the table does not say so) refuses, and shell control syntax refuses. An option-shaped
134
+ * WRITTEN word (judged on its basename, as the program it names) refuses the same way: it is where the program
135
+ * would be, and the word in front took an option no row explains.
136
+ */
137
+ export declare function readProgramPosition(toks: SegmentTokens): ProgramPosition;
138
+ /** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
139
+ * stdin AND the program it runs is not declared no-path. ONE reading, shared by every consumer. */
140
+ export declare function argvReentryUnjudged(pos: ProgramPosition, noPathPrograms: ReadonlySet<string>): boolean;
141
+ /** ONE sentence per refusal kind, minted here and nowhere else. */
142
+ export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;
@@ -0,0 +1,125 @@
1
+ export const NOT_AUTO_ALLOWED = "— not auto-allowed";
2
+ export function programBasename(word) {
3
+ return word.includes("/") ? (word.split("/").pop() ?? word) : word;
4
+ }
5
+ export const ASSIGNMENT_WORD = /^[A-Za-z_][A-Za-z0-9_]*=/;
6
+ export const SHELL_CONTROL_PREFIXES = new Set(["!", "if", "then", "elif", "else", "while", "until", "do", "time"]);
7
+ export const SHELL_BLOCK_WORDS = new Set(["for", "select", "case", "esac", "fi", "done", "function", "coproc", "[[", "]]", "in"]);
8
+ export function isBareControlWord(toks, i, words) {
9
+ const word = toks.folded[i];
10
+ return word !== undefined && toks.raw[i] === word && words.has(word);
11
+ }
12
+ export const ARGV_REENTRY_PROGRAM_RE = /^xargs$/;
13
+ export const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
14
+ const NO_DECLARED_OPTIONS = { optionArity: new Map() };
15
+ export const LAUNCHER_TABLE = new Map([
16
+ ["env", NO_DECLARED_OPTIONS], ["command", NO_DECLARED_OPTIONS], ["builtin", NO_DECLARED_OPTIONS], ["exec", NO_DECLARED_OPTIONS],
17
+ ["noglob", NO_DECLARED_OPTIONS], ["xargs", NO_DECLARED_OPTIONS], ["busybox", NO_DECLARED_OPTIONS],
18
+ ["sudo", NO_DECLARED_OPTIONS], ["doas", NO_DECLARED_OPTIONS], ["pkexec", NO_DECLARED_OPTIONS], ["su", NO_DECLARED_OPTIONS],
19
+ ["runuser", NO_DECLARED_OPTIONS], ["chroot", NO_DECLARED_OPTIONS], ["setpriv", NO_DECLARED_OPTIONS],
20
+ ["nohup", NO_DECLARED_OPTIONS], ["nice", NO_DECLARED_OPTIONS], ["ionice", NO_DECLARED_OPTIONS], ["chrt", NO_DECLARED_OPTIONS],
21
+ ["taskset", NO_DECLARED_OPTIONS], ["stdbuf", NO_DECLARED_OPTIONS], ["timeout", NO_DECLARED_OPTIONS],
22
+ ["time", {
23
+ optionArity: new Map([
24
+ ["-p", 0], ["-a", 0], ["-v", 0], ["-q", 0], ["--portability", 0], ["--append", 0], ["--verbose", 0], ["--quiet", 0], ["--help", 0], ["--version", 0],
25
+ ["-f", 1], ["-o", 1], ["--format", 1], ["--output", 1],
26
+ ]),
27
+ }],
28
+ ["watch", NO_DECLARED_OPTIONS], ["setsid", NO_DECLARED_OPTIONS], ["flock", NO_DECLARED_OPTIONS], ["unshare", NO_DECLARED_OPTIONS],
29
+ ["nsenter", NO_DECLARED_OPTIONS], ["script", NO_DECLARED_OPTIONS], ["numactl", NO_DECLARED_OPTIONS], ["prlimit", NO_DECLARED_OPTIONS],
30
+ ["systemd-run", NO_DECLARED_OPTIONS],
31
+ ["strace", NO_DECLARED_OPTIONS], ["ltrace", NO_DECLARED_OPTIONS], ["valgrind", NO_DECLARED_OPTIONS], ["firejail", NO_DECLARED_OPTIONS],
32
+ ["bwrap", NO_DECLARED_OPTIONS],
33
+ ]);
34
+ export const COMMAND_LAUNCHERS = new Set(LAUNCHER_TABLE.keys());
35
+ function pastLauncherOptions(launcher, folded, i) {
36
+ const row = LAUNCHER_TABLE.get(launcher);
37
+ if (row === undefined)
38
+ return i;
39
+ for (;;) {
40
+ const tok = folded[i];
41
+ const arity = tok === undefined ? undefined : row.optionArity.get(tok);
42
+ if (arity === undefined)
43
+ return i;
44
+ if (arity === 1 && folded[i + 1] === undefined)
45
+ return i;
46
+ i += arity + 1;
47
+ }
48
+ }
49
+ const LAUNCHER_BARE_NUMBER = /^\d+[smhd]?$/;
50
+ export const PROGRAM_POSITION_REFUSAL_KINDS = ["option_at_program_position", "control_syntax_at_program_position"];
51
+ export function readProgramPosition(toks) {
52
+ const { folded } = toks;
53
+ const controlWords = [];
54
+ let i = 0;
55
+ while (i < folded.length && isBareControlWord(toks, i, SHELL_CONTROL_PREFIXES)) {
56
+ const controlWord = folded[i];
57
+ controlWords.push(i);
58
+ i = pastLauncherOptions(controlWord, folded, i + 1);
59
+ }
60
+ const none = (written, assignments, refusal) => ({ controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: undefined, refusal });
61
+ if (i >= folded.length)
62
+ return none(undefined, []);
63
+ const written = i;
64
+ const assignments = [];
65
+ while (i < folded.length && ASSIGNMENT_WORD.test(folded[i])) {
66
+ assignments.push(i);
67
+ i++;
68
+ }
69
+ const head = folded[i];
70
+ if (head === undefined)
71
+ return none(written, assignments);
72
+ const basename = programBasename(head);
73
+ if (basename.startsWith("-") && basename !== "-") {
74
+ const owner = controlWords.length > 0 ? folded[controlWords[controlWords.length - 1]] : head;
75
+ return none(written, assignments, { kind: "option_at_program_position", owner, option: head });
76
+ }
77
+ if (!COMMAND_LAUNCHERS.has(basename)) {
78
+ return { controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: { index: i, name: head, basename }, refusal: undefined };
79
+ }
80
+ const launchers = [{ index: i, name: basename }];
81
+ const owner = head;
82
+ let argvFromStdin = ARGV_REENTRY_PROGRAM_RE.test(basename);
83
+ let lastReentry = argvFromStdin ? i : -1;
84
+ const chain = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin, runsDefaultProgram: lastReentry === folded.length - 1, program, refusal });
85
+ for (let j = pastLauncherOptions(basename, folded, i + 1); j < folded.length; j++) {
86
+ const t = folded[j];
87
+ if (ASSIGNMENT_WORD.test(t) || LAUNCHER_BARE_NUMBER.test(t))
88
+ continue;
89
+ const wordBasename = programBasename(t);
90
+ if (ARGV_REENTRY_PROGRAM_RE.test(wordBasename)) {
91
+ argvFromStdin = true;
92
+ lastReentry = j;
93
+ continue;
94
+ }
95
+ if (COMMAND_LAUNCHERS.has(wordBasename)) {
96
+ launchers.push({ index: j, name: wordBasename });
97
+ continue;
98
+ }
99
+ if (t.startsWith("-"))
100
+ return chain(undefined, { kind: "option_at_program_position", owner, option: t });
101
+ if (SHELL_CONTROL_PREFIXES.has(wordBasename) || SHELL_BLOCK_WORDS.has(wordBasename)) {
102
+ return chain(undefined, { kind: "control_syntax_at_program_position", owner, word: wordBasename });
103
+ }
104
+ return chain({ index: j, name: t, basename: wordBasename });
105
+ }
106
+ return chain(undefined);
107
+ }
108
+ export function argvReentryUnjudged(pos, noPathPrograms) {
109
+ if (!pos.argvFromStdin)
110
+ return false;
111
+ const runs = pos.program?.basename ?? (pos.runsDefaultProgram ? ARGV_REENTRY_DEFAULT_PROGRAM : undefined);
112
+ return runs === undefined || !noPathPrograms.has(runs);
113
+ }
114
+ export function programPositionRefusalSentence(refusal) {
115
+ switch (refusal.kind) {
116
+ case "option_at_program_position":
117
+ return `"${refusal.owner}" is given the option "${refusal.option}" where the program it runs would be — which program runs is not readable without that word's option table ${NOT_AUTO_ALLOWED}`;
118
+ case "control_syntax_at_program_position":
119
+ return `"${refusal.owner}" is given shell control syntax ("${refusal.word}") where the program it runs would be ${NOT_AUTO_ALLOWED}`;
120
+ default: {
121
+ const exhaustive = refusal;
122
+ return exhaustive;
123
+ }
124
+ }
125
+ }
@@ -1,3 +1,4 @@
1
+ import { type ShellSegmentConnector } from "./bash-lexer.js";
1
2
  /**
2
3
  * Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
3
4
  * The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
@@ -30,9 +31,8 @@
30
31
  * value-taking options defeat the stdin-floor operand count without a per-verb option-value model
31
32
  * — those are a follow-on slice, not a silent drop.
32
33
  */
33
- /** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
34
- * they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
35
- export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
34
+ export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
35
+ export type { ShellSegmentConnector } from "./bash-lexer.js";
36
36
  export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
37
37
  /**
38
38
  * The CLASSIFY face's default allow set: the shared list PLUS the verbs that are
@@ -140,11 +140,6 @@ export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<
140
140
  export declare function carriesShellRedirection(text: string): boolean;
141
141
  /** What {@link splitShellCompoundSegments} produces: the connector-delimited segments, plus which of
142
142
  * them a PIPE fed (the read-only classifier's stdin arm needs that distinction; a rule lane does not). */
143
- /** The connector bash ran a segment through — `"start"` for the first one. A closed set, because the read
144
- * boundary distinguishes all four: `&&` is the only one under which the previous segment is known to have
145
- * exited ZERO, `||` is the only one under which the previous segment may have been SKIPPED ENTIRELY, `|`
146
- * puts the segment in a subshell whose directory changes never reach the parent, and `;` is plain sequence. */
147
- export type ShellSegmentConnector = "start" | ";" | "&&" | "||" | "|";
148
143
  export interface ShellCompoundSegments {
149
144
  segments: string[];
150
145
  /** Parallel to {@link segments}: was this segment preceded by `|` (rather than `;`/`&&`/`||`/nothing)?
@@ -184,11 +179,11 @@ export interface ShellCompoundSegments {
184
179
  * own empty-command rule puts the whole string outside its lane — which is what keeps a statement
185
180
  * like "this rule form never matches a command containing a connector" literally true instead of
186
181
  * true-except-for-one-spelling.
187
- * 3. the quote-aware connector scan over `;` `&&` `||` `|`. A lone `&` is REFUSED rather than split
188
- * on: it backgrounds a process that outlives the command, which is not a combinator any face here
189
- * is willing to reason about. Unbalanced quoting disables the mask, which degrades to the
190
- * quote-blind scan — strictly MORE segments, each of which the caller must still vet, i.e. the
191
- * fail-closed direction.
182
+ * 3. the quote-aware connector scan over `;` `&&` `||` `|` — the strict mode of the ONE lexer
183
+ * ({@link lexShellText}). A lone `&` is REFUSED rather than split on: it backgrounds a process that
184
+ * outlives the command, which is not a combinator any face here is willing to reason about.
185
+ * Unbalanced quoting falls back to the quote-blind scan — strictly MORE segments, each of which the
186
+ * caller must still vet, i.e. the fail-closed direction.
192
187
  *
193
188
  * Empty segments are NOT refused here (`;;`, a leading `;`, a trailing `&&`) — they come back as empty
194
189
  * strings, and every caller's per-segment vetting rejects them through its own empty-command rule. The
@@ -200,6 +195,46 @@ export declare function splitShellCompoundSegments(source: string, options?: {
200
195
  }): ShellCompoundSegments | {
201
196
  reject: string;
202
197
  };
198
+ /**
199
+ * Compound read-only classification for the `bash` CLASSIFY face (shellGate:"classify").
200
+ * Splits a command on the connector set `;` `&&` `||` `|` and passes every segment through
201
+ * {@link coarseReadonlyCheck} (the single {@link parseLeadingCommandName} parser + bare-name allowlist).
202
+ * All segments green ⇒ read-only. Returns a reason when rejected, else undefined.
203
+ *
204
+ * G2 — the split CARRIES QUOTE STATE ({@link quoteMask}): a connector character inside a quoted region is
205
+ * argument text, not a boundary, so `grep -E "error:|BUILD FAILED" x.log | tail -10` is two segments
206
+ * (`grep …` and `tail -10`) rather than four, and the phantom head `BUILD` no longer decides the verdict.
207
+ * The whole compound face is what this fixes: a report of an every-command approval prompt on a read-only
208
+ * monitoring pipeline traced to exactly this — a quoted alternation in a `grep -E` pattern, which is the
209
+ * ordinary spelling of that idiom.
210
+ *
211
+ * Soundness — why the quote-aware split is NOT a relaxation. Backslash/`$`/backtick/subshells/redirection/
212
+ * line breaks are whole-string rejected first (the two neutral forms in {@link EXEMPT_REDIRECTION} are
213
+ * removed beforehand, and they move no data into any file), so in the residual space `'`/`"` pairing is
214
+ * 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:
215
+ * every program bash runs still starts at the head of some segment this classifier vetted, and its argv[0]
216
+ * is the head token (a token carrying a quote/brace/glob/tilde character is rejected by the parser's
217
+ * argv[0] metachar rule, and a clean token equals the executed program name). What changes versus the
218
+ * older quote-blind scan is only that PHANTOM segments — heads manufactured out of quoted text, which no
219
+ * program was ever going to run — are gone. Those could only ever ADD a rejection, so removing them
220
+ * removes false demotions and admits no additional program.
221
+ *
222
+ * UNBALANCED QUOTING falls back to the quote-blind scan (fail-closed). A string with an unterminated quote
223
+ * is one bash cannot parse into commands at all, so there is no "true" segmentation to be accurate about;
224
+ * the blind scan yields strictly MORE segments, each of which must still pass the allowlist.
225
+ *
226
+ * A LONE `&` is hard-rejected (not a connector): it backgrounds a process that survives the shell — the node
227
+ * execution env spools stdio and deliberately leaves descendants alive on completion — escaping TaskStop and
228
+ * background-lifecycle governance entirely. Backgrounding is not a read-only combinator, even for a listed
229
+ * reader (`tail -f x &` outlives the call). `|&` falls out of the same scan (its `&` reads as lone → reject).
230
+ * One TRAILING `;` is stripped (a no-op terminator); any other empty segment (`;;`, leading `;`, trailing
231
+ * `&&`/`|`) rejects via the parser's empty-command rule.
232
+ *
233
+ * NOT here by design: no git/subcommand allowlist — "zero-flag" vetting operates
234
+ * on pre-expansion text while the shell expands quotes/braces/globs into argv, and the candidate subcommands
235
+ * are not provably read-only under repo config anyway. Deployments needing those forms must widen via a
236
+ * dedicated widening decision, not this classifier.
237
+ */
203
238
  /**
204
239
  * The READ-BOUNDARY face of the read-only classification (opt-in).
205
240
  *
@@ -516,8 +551,11 @@ export interface OutOfRootReadGateVerdict {
516
551
  * as display/triage evidence; emptiness is not "not gated". */
517
552
  readonly paths: readonly string[];
518
553
  }
519
- /** Test seam for the lockstep pin that this mirror stays a SUBSET of the rule lane's curated table.
520
- * Exported for that one assertion — production code reads the constant directly. */
554
+ /** Test seam for the lockstep pin that the launcher table (the ONE launcher truth, in the program-position
555
+ * reader's module) stays a SUBSET of the rule lane's curated `BARE_INTERPRETER_NAMES`. Exported for that one
556
+ * assertion — production code reads the table directly. The containment face ({@link classifyOutOfRootReadGate})
557
+ * does NOT look through a launcher: there every segment whose program is not a listed reader is judged whole,
558
+ * launcher or not, so looking through one would only narrow what it reads. */
521
559
  export declare const COMMAND_LAUNCHERS_FOR_TEST: ReadonlySet<string>;
522
560
  /**
523
561
  * The OUT-OF-ROOT READ GATE asked as its OWN question: does any listed reader in this command
@@ -672,7 +710,7 @@ export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
672
710
  * · STDIN-BLOCKING BODY (an availability residual, not a safety one). CLOSED for the shape this note
673
711
  * used to record: a quoted multiword operand like `grep "x y"` read, to the then whitespace-blind
674
712
  * tokenizer, as two operands meeting grep's file floor, while bash passes ONE pattern and no file and
675
- * grep blocks on stdin. {@link splitWordsQuoteAware} now counts the operand bash actually passes, so
713
+ * grep blocks on stdin. the lexer now counts the operand bash actually passes, so
676
714
  * that command demotes on both this arm and the plain face. What REMAINS residual is the general
677
715
  * statement: this arm inherits whatever the shared availability arms can see, they are best-effort
678
716
  * (an operand that only turns out to be unreadable at run time is not in the text), and a hang is