@sema-agent/core 7.46.0 → 7.48.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.
- package/CHANGELOG.md +106 -0
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +4 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +23 -6
- package/dist/core/checkpoint-store.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +7 -4
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +1 -1
- package/dist/core/permission-rule-model.d.ts +12 -6
- package/dist/core/permission-rule-model.js +10 -2
- package/dist/core/permission-rules.d.ts +4 -5
- package/dist/core/permission-rules.js +28 -15
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/park-commit.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +5 -5
- package/dist/core/runner/prepare-policy-chain.js +2 -1
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/safety-merge-corpus.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +6 -3
- package/dist/core/session-policy-store.js +28 -10
- package/dist/core/shell-wrapper-projection.js +1 -1
- package/dist/core/shell-wrapper-table.d.ts +8 -8
- package/dist/core/shell-wrapper-table.js +3 -3
- package/dist/core/tool-catalog-entries.js +3 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-aliases.d.ts +9 -8
- package/dist/core/tool-name-aliases.js +2 -0
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +10 -9
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/write-protect.d.ts +10 -3
- package/dist/core/write-protect.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/types.d.ts +1 -1
- package/dist/shell/lex.d.ts +15 -0
- package/dist/shell/lex.js +86 -15
- package/dist/tools/fs/bash-program-position.d.ts +16 -6
- package/dist/tools/fs/bash-program-position.js +18 -12
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/program-read-model.d.ts +84 -12
- package/dist/tools/fs/program-read-model.js +105 -34
- package/dist/tools/fs/program-read-model.rows.js +26 -7
- package/dist/tools/fs/read-deny-home.d.ts +20 -1
- package/dist/tools/fs/read-deny-home.js +28 -7
- package/dist/tools/fs/read-deny.d.ts +13 -6
- package/dist/tools/fs/read-deny.js +12 -3
- package/dist/tools/fs/removal-targets.js +24 -5
- package/dist/tools/fs/shell-read-argv.d.ts +6 -8
- package/dist/tools/fs/shell-read-argv.js +5 -5
- package/dist/tools/fs/shell-read-bindings.d.ts +71 -19
- package/dist/tools/fs/shell-read-bindings.js +190 -75
- package/dist/tools/fs/shell-read-establish.js +6 -7
- package/dist/tools/fs/shell-read-plan.d.ts +3 -15
- package/dist/tools/fs/shell-read-plan.js +126 -129
- package/dist/tools/fs/shell-read-start.d.ts +57 -0
- package/dist/tools/fs/shell-read-start.js +92 -0
- package/dist/tools/fs/shell-read-word.d.ts +68 -8
- package/dist/tools/fs/shell-read-word.js +106 -12
- package/dist/tools/fs/shell-read-write-targets.js +4 -3
- package/dist/tools/fs/shell-reentry-text.d.ts +103 -3
- package/dist/tools/fs/shell-reentry-text.js +122 -4
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -3
- package/wire-changes/core@7.47.0.json +412 -0
- package/wire-changes/core@7.48.0.json +252 -0
|
@@ -5,34 +5,94 @@
|
|
|
5
5
|
* does to the word before the program sees it.
|
|
6
6
|
*/
|
|
7
7
|
import { type LexedCommand, type LexedWord, type WordPiece } from "../../shell/lex.js";
|
|
8
|
-
import { type BindingTable, type BoundValue } from "./shell-read-bindings.js";
|
|
8
|
+
import { type BindingTable, type BoundValue, type FloorValue } from "./shell-read-bindings.js";
|
|
9
9
|
/** `"glob"` = an unquoted pathname-expansion character; `"unresolvable"` = the shell rewrites the word (a brace,
|
|
10
10
|
* variable or command substitution) so its text is not the path. The read candidate's own field, spelled here. */
|
|
11
11
|
export type WordExpands = false | "glob" | "unresolvable";
|
|
12
|
-
/** A word as the candidate readers name it: the lexer's word, the spelling
|
|
13
|
-
* refers to read through the binding table ({@link wordValues}: `"$F"` after `F=/x` is `/x
|
|
14
|
-
* it before the program sees it.
|
|
12
|
+
/** A word as the candidate readers name it: the lexer's word, the spelling a reader names it by — the names the word
|
|
13
|
+
* refers to read through the binding table ({@link wordValues}: `"$F"` after `F=/x` is `/x`; {@link spellingOf}) — and
|
|
14
|
+
* what the shell does to it before the program sees it. A text handed to a shell that re-parses it is the program's
|
|
15
|
+
* ({@link programTextOf}), never this one. */
|
|
15
16
|
export interface Read {
|
|
16
17
|
readonly word: LexedWord;
|
|
17
18
|
readonly raw: string;
|
|
18
19
|
readonly folded: string;
|
|
20
|
+
/** A pattern's names are matched by this spelling where it is not {@link folded} (a class the judge widens, read as
|
|
21
|
+
* any one character — {@link LexedWord.judged}, {@link judgedSpellingOf}); `folded` stays the name opened where none match. */
|
|
22
|
+
readonly pattern?: string;
|
|
19
23
|
readonly expands: WordExpands;
|
|
20
24
|
/** The names the word refers to were read through the table: its expansion IS the spelling above (and, where the
|
|
21
25
|
* names may hold several values, {@link values}). */
|
|
22
26
|
readonly settled?: true;
|
|
23
|
-
/** Every
|
|
24
|
-
|
|
27
|
+
/** Every value the word may expand to (the first spelled {@link folded}, {@link spellingOf}; matched by {@link pattern}),
|
|
28
|
+
* each with the parts of it the shell expands as a pattern ({@link BoundValue}); present wherever the names were read
|
|
29
|
+
* through the table ({@link settled}). */
|
|
30
|
+
readonly values?: readonly BoundValue[];
|
|
25
31
|
/** The word refers to `HOME` and `HOME` may hold something other than the declared home here: the spelling the
|
|
26
32
|
* word has with the declared home, for the deny judge alone. */
|
|
27
33
|
readonly homeFloor?: string;
|
|
28
34
|
/** The word's names are unknown here, but their bindings left spellings for the deny judge (an array's elements):
|
|
29
35
|
* what the word may then spell, for the deny judge alone. */
|
|
30
|
-
readonly floorValues?: readonly
|
|
36
|
+
readonly floorValues?: readonly FloorValue[];
|
|
31
37
|
}
|
|
32
38
|
/** The reads of a segment's words against the binding table at that segment (`homeDir` = the declared home, the
|
|
33
39
|
* state `HOME` starts in). A word that refers to no name, or only to a `HOME` the text has not touched (`~/x`, placed
|
|
34
40
|
* by the establishment at the declared home), reads as written. */
|
|
35
|
-
export declare function readsOf(words: readonly LexedWord[], table: BindingTable | undefined, homeDir: string | undefined): Read[];
|
|
41
|
+
export declare function readsOf(words: readonly LexedWord[], table: BindingTable | undefined, homeDir: string | undefined, split?: boolean): Read[];
|
|
42
|
+
/** One reading of a segment: the binding table with each name its words read held to ONE value (or, past
|
|
43
|
+
* {@link READINGS_MAX}, made unknown — every value it held stays a deny-floor spelling, {@link BindingTable.floorOf}),
|
|
44
|
+
* the reads of its argv against it ({@link readsOf}), and the argv the shell hands the program in it — a word whose
|
|
45
|
+
* names settle into one value that is no pattern is that value, as a literal word (a reader of the program position,
|
|
46
|
+
* of a launcher's fields, of a program's options reads it as it reads a literal word); any other word as written, its
|
|
47
|
+
* names' deny floors in `floors` (a shell that re-reads the word — `sh -c`, `eval`, a launcher's shell — starts with
|
|
48
|
+
* them: the word names in that text what it names here). */
|
|
49
|
+
export interface SegmentReading {
|
|
50
|
+
readonly table: BindingTable;
|
|
51
|
+
readonly reads: readonly Read[];
|
|
52
|
+
readonly argv: readonly LexedWord[];
|
|
53
|
+
readonly floors: ReadonlyMap<string, readonly FloorValue[]>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The readings of one segment's words (its argv and its redirection targets): the names they read through the table that
|
|
57
|
+
* may hold several values here, in the order the words read them (the program position first), each held to one value
|
|
58
|
+
* per reading — every combination while the readings stay within {@link READINGS_MAX}, so the program position, a
|
|
59
|
+
* launcher's fields, a re-entered text and an operand are spelled from the same values (`$P` read twice takes one value
|
|
60
|
+
* in a reading: the shell expands both alike); a name past that bound is unknown in every reading — its word as written,
|
|
61
|
+
* every value it held a deny floor — never a sample of its combinations. A segment whose names hold one value each has
|
|
62
|
+
* one reading.
|
|
63
|
+
*/
|
|
64
|
+
export declare function segmentReadingsOf(argv: readonly LexedWord[], targets: readonly LexedWord[], table: BindingTable, homeDir: string | undefined): SegmentReading[];
|
|
65
|
+
/** The spelling a reader names a value by, read directly as a path — for a pattern, the name the program opens where it
|
|
66
|
+
* matches nothing: its text, a leading `~` spelled as the literal tilde it is ({@link literalTildeSpelling}, the lexer's
|
|
67
|
+
* spelling of a written one) — the shell never tilde-expands what a quoted piece or an expansion produced, so a value that
|
|
68
|
+
* starts with `~` starts with a literal one (`X=/w; rm \~$X/*` reads `./~/w/*`, as `rm \~/w/*` does). A shell that
|
|
69
|
+
* re-parses the value reads {@link programTextOf} instead. */
|
|
70
|
+
export declare const spellingOf: (v: BoundValue) => string;
|
|
71
|
+
/** The pattern a PART of a pattern word is matched by (an option value attached to its option word, `--file=["!"a]*`; an
|
|
72
|
+
* assignment-shaped word's value, `if=["!"a]*`): the word's own judged spelling (`pattern`) past the text in front of
|
|
73
|
+
* the part — bash expands the WHOLE word, so the names the part may take are those the word's widened spelling reaches
|
|
74
|
+
* (a superset: a name is judged whether or not the word in front of it matches too). `undefined` where the word carries
|
|
75
|
+
* none, the part is not the end of the word's spelling, or the text in front holds a widened class itself (its judged
|
|
76
|
+
* spelling no longer lines up). */
|
|
77
|
+
export declare function partPattern(word: {
|
|
78
|
+
readonly folded: string;
|
|
79
|
+
readonly pattern?: string;
|
|
80
|
+
}, part: string): string | undefined;
|
|
81
|
+
/** A candidate under another name (a value of several, a backup, a copy's child): the word's `pattern` matched the word,
|
|
82
|
+
* not that name — the name is matched by its own text. */
|
|
83
|
+
export declare function renamed<C extends {
|
|
84
|
+
readonly spelled: string;
|
|
85
|
+
readonly pattern?: string;
|
|
86
|
+
}>(c: C, spelled: string): Omit<C, "pattern">;
|
|
87
|
+
/** The text a read's word hands a program, as a shell re-parsing it reads it (`eval`, `sh -c`, a launcher's line): its
|
|
88
|
+
* first established value as the program receives it, or the lexer's folded spelling — never {@link spellingOf}'s path
|
|
89
|
+
* spelling (`X='~'; eval rm -rf "$X"` re-parses `rm -rf ~`, the home). */
|
|
90
|
+
export declare const programTextOf: (r: Read) => string;
|
|
91
|
+
/** What a read's word hands the program, where the reader knows it: each value its names settled into — one field each
|
|
92
|
+
* (an unquoted expansion whose value is not one word is never settled, {@link wordValues}), or, where the word is a
|
|
93
|
+
* pattern, the pattern whose matches are the fields, its parts telling pattern characters from literal ones;
|
|
94
|
+
* `undefined` for a word the table did not settle (or that names nothing). */
|
|
95
|
+
export declare function settledFieldsOf(r: Read): readonly BoundValue[] | undefined;
|
|
36
96
|
/** The shell REWRITES this read's word before the program sees it (a parameter, command, process, arithmetic or
|
|
37
97
|
* brace expansion, or the character of one) — a word whose names the table settled excepted: its expansion is its
|
|
38
98
|
* spelling. */
|
|
@@ -1,33 +1,127 @@
|
|
|
1
|
-
import { argvOf, lexShell, UNRESOLVED_SENTENCE } from "../../shell/lex.js";
|
|
1
|
+
import { argvOf, lexShell, literalTildeSpelling, UNRESOLVED_SENTENCE } from "../../shell/lex.js";
|
|
2
2
|
import { expansionInWord, extglobOpenerIn, globInWord, homeExpansionOf } from "./bash-readonly-classifier.js";
|
|
3
|
-
import { wordFloorValues, wordValues } from "./shell-read-bindings.js";
|
|
4
|
-
export function readsOf(words, table, homeDir) {
|
|
3
|
+
import { floorOf, judgedSpellingOf, namesIn, wordFloorValues, wordValues } from "./shell-read-bindings.js";
|
|
4
|
+
export function readsOf(words, table, homeDir, split = false) {
|
|
5
5
|
return words.map((word) => {
|
|
6
|
-
const refersToName = word.pieces.some((p) => p.expansion?.kind === "parameter");
|
|
6
|
+
const refersToName = word.pieces.some((p) => p.expansion?.kind === "parameter") || namesIn(word).length > 0;
|
|
7
|
+
const asWritten = { word, raw: word.raw, folded: word.folded, ...(word.judged !== undefined ? { pattern: word.judged } : {}) };
|
|
7
8
|
if (!refersToName && table?.homeTouched !== true)
|
|
8
|
-
return {
|
|
9
|
-
const values = wordValues(word, table, false);
|
|
9
|
+
return { ...asWritten, expands: expandsOf(word) };
|
|
10
|
+
const values = wordValues(word, table, false, undefined, split);
|
|
10
11
|
if (values === undefined)
|
|
11
|
-
return {
|
|
12
|
+
return { ...asWritten, expands: expandsOf(word) };
|
|
12
13
|
if (values === "unknown") {
|
|
13
14
|
const floors = refersToName ? wordFloorValues(word, table) : [];
|
|
14
|
-
return {
|
|
15
|
+
return { ...asWritten, expands: refersToName ? "unresolvable" : expandsOf(word), ...(floors.length > 0 ? { floorValues: floors } : {}) };
|
|
15
16
|
}
|
|
16
17
|
const glob = values.some((v) => v.glob === true) || globInWord(word);
|
|
17
18
|
const refersToHome = word.pieces.some((p) => p.expansion?.kind === "tilde" || (p.expansion?.kind === "parameter" && p.expansion.name === "HOME"));
|
|
18
19
|
const declared = refersToHome && homeDir !== undefined && table?.homeTouched === true ? wordValues(word, table, false, new Map([["HOME", [{ spelled: homeDir }]]])) : undefined;
|
|
19
|
-
const floor = declared !== undefined && declared !== "unknown" ? declared[0]
|
|
20
|
+
const floor = declared !== undefined && declared !== "unknown" && declared[0] !== undefined ? spellingOf(declared[0]) : undefined;
|
|
21
|
+
const pattern = judgedSpellingOf(values[0]);
|
|
20
22
|
return {
|
|
21
23
|
word,
|
|
22
24
|
raw: word.raw,
|
|
23
|
-
folded: values[0]
|
|
25
|
+
folded: spellingOf(values[0]),
|
|
26
|
+
...(pattern !== undefined ? { pattern } : {}),
|
|
24
27
|
expands: glob ? "glob" : false,
|
|
25
28
|
settled: true,
|
|
26
|
-
|
|
27
|
-
...(floor !== undefined && !values.every((v) => v
|
|
29
|
+
values,
|
|
30
|
+
...(floor !== undefined && !values.every((v) => spellingOf(v) === floor) ? { homeFloor: floor } : {}),
|
|
28
31
|
};
|
|
29
32
|
});
|
|
30
33
|
}
|
|
34
|
+
const READINGS_MAX = 64;
|
|
35
|
+
export function segmentReadingsOf(argv, targets, table, homeDir) {
|
|
36
|
+
const several = new Map();
|
|
37
|
+
for (const word of [...argv, ...targets]) {
|
|
38
|
+
for (const name of namesReadIn(word, table)) {
|
|
39
|
+
const state = table.stateOf(name);
|
|
40
|
+
if (state !== "unknown" && state.length > 1)
|
|
41
|
+
several.set(name, state);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let overlays = [new Map()];
|
|
45
|
+
const past = [];
|
|
46
|
+
for (const [name, values] of several) {
|
|
47
|
+
if (past.length > 0 || overlays.length * values.length > READINGS_MAX) {
|
|
48
|
+
past.push(name);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
overlays = overlays.flatMap((o) => values.map((v) => new Map(o).set(name, [v])));
|
|
52
|
+
}
|
|
53
|
+
for (const o of overlays)
|
|
54
|
+
for (const name of past)
|
|
55
|
+
o.set(name, "unknown");
|
|
56
|
+
return overlays.map((overlay) => {
|
|
57
|
+
const reading = overlay.size === 0 ? table : overlaidTable(table, overlay);
|
|
58
|
+
const fielded = readsOf(argv, reading, homeDir, true).flatMap((r) => {
|
|
59
|
+
const fields = r.values?.length === 1 ? r.values[0].fields : undefined;
|
|
60
|
+
return fields === undefined ? [r] : fields.map((f) => ({ word: argumentWordOf(f, f) ?? r.word, raw: f, folded: spellingOf({ spelled: f }), expands: false, settled: true, values: [{ spelled: f }] }));
|
|
61
|
+
});
|
|
62
|
+
const floors = new Map();
|
|
63
|
+
for (const r of fielded) {
|
|
64
|
+
if (r.settled === true)
|
|
65
|
+
continue;
|
|
66
|
+
for (const p of r.word.pieces) {
|
|
67
|
+
const name = p.expansion?.kind === "parameter" ? p.expansion.name : undefined;
|
|
68
|
+
if (name === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
const held = reading.stateOf(name);
|
|
71
|
+
const floor = [...(held === "unknown" ? [] : held.map(floorOf)), ...reading.floorOf(name)];
|
|
72
|
+
if (floor.length > 0)
|
|
73
|
+
floors.set(name, floor);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { table: reading, reads: fielded, argv: fielded.map((r) => valuedWord(r.word, r)), floors };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function namesReadIn(word, table) {
|
|
80
|
+
return [...namesIn(word), ...(table.homeTouched && word.pieces.some((p) => p.expansion?.kind === "tilde" && p.expansion.body === "~") ? ["HOME"] : [])];
|
|
81
|
+
}
|
|
82
|
+
function overlaidTable(table, overlay) {
|
|
83
|
+
return {
|
|
84
|
+
stateOf: (name) => overlay.get(name) ?? table.stateOf(name),
|
|
85
|
+
isInteger: (name) => table.isInteger(name),
|
|
86
|
+
ifsTouched: table.ifsTouched,
|
|
87
|
+
homeTouched: table.homeTouched,
|
|
88
|
+
floorOf: (name) => {
|
|
89
|
+
const held = table.stateOf(name);
|
|
90
|
+
return overlay.get(name) !== "unknown" || held === "unknown" ? table.floorOf(name) : [...table.floorOf(name), ...held.map(floorOf)];
|
|
91
|
+
},
|
|
92
|
+
inherited: table.inherited,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function valuedWord(word, r) {
|
|
96
|
+
const v = r.values?.length === 1 ? r.values[0] : undefined;
|
|
97
|
+
if (word.assignment !== undefined || v === undefined || v.glob === true || v.spelled === word.folded)
|
|
98
|
+
return word;
|
|
99
|
+
return argumentWordOf(v.spelled, r.raw) ?? word;
|
|
100
|
+
}
|
|
101
|
+
function argumentWordOf(text, raw) {
|
|
102
|
+
const quoted = text.replace(/[^A-Za-z0-9_./+@:%=,-]/g, (c) => (c === "'" ? "\\'" : `'${c}'`));
|
|
103
|
+
const lexed = lexShell(`: ${quoted === "" ? "''" : quoted}`);
|
|
104
|
+
const words = "parseFailure" in lexed || lexed.segments.length !== 1 ? undefined : argvOf(lexed.segments[0]);
|
|
105
|
+
return words?.length === 2 ? { ...words[1], raw } : undefined;
|
|
106
|
+
}
|
|
107
|
+
export const spellingOf = (v) => literalTildeSpelling(v.spelled);
|
|
108
|
+
export function partPattern(word, part) {
|
|
109
|
+
if (word.pattern === undefined || part === word.folded || !word.folded.endsWith(part))
|
|
110
|
+
return undefined;
|
|
111
|
+
const front = word.folded.slice(0, word.folded.length - part.length);
|
|
112
|
+
if (!word.pattern.startsWith(front))
|
|
113
|
+
return undefined;
|
|
114
|
+
const judged = word.pattern.slice(front.length);
|
|
115
|
+
return judged === part ? undefined : judged;
|
|
116
|
+
}
|
|
117
|
+
export function renamed(c, spelled) {
|
|
118
|
+
const { pattern: _matched, ...rest } = c;
|
|
119
|
+
return { ...rest, spelled };
|
|
120
|
+
}
|
|
121
|
+
export const programTextOf = (r) => r.values?.[0]?.spelled ?? r.word.folded;
|
|
122
|
+
export function settledFieldsOf(r) {
|
|
123
|
+
return r.settled === true ? r.values : undefined;
|
|
124
|
+
}
|
|
31
125
|
export const rewritten = (r) => r.settled !== true && expansionInWord(r.word);
|
|
32
126
|
export function readOfText(text) {
|
|
33
127
|
const l = lexShell(text);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { denyFloorShadowsOf } from "./shell-read-judge.js";
|
|
2
|
+
import { renamed } from "./shell-read-word.js";
|
|
2
3
|
import { implementationsOf, programRow, selectedOptions, writeReadings } from "./program-read-model.js";
|
|
3
4
|
import { programBasename, readProgramPosition } from "./bash-program-position.js";
|
|
4
5
|
export function programArgs(segment) {
|
|
@@ -85,8 +86,8 @@ function backupTargetsOf(edited, readings, override) {
|
|
|
85
86
|
const suffixes = [...new Set(readings.flatMap((r) => r.suffixes))];
|
|
86
87
|
const follows = override !== undefined && !override.includes("entry");
|
|
87
88
|
return edited.flatMap(({ candidate: f, onlyIfExists }) => suffixes.flatMap((suffix) => follows
|
|
88
|
-
? [{ candidate: { ...f,
|
|
89
|
-
: sedBackupNames(f.spelled, suffix).map((spelled) => ({ candidate: { ...f, spelled, face: "write" }, seats: ["entry"], ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }))));
|
|
89
|
+
? [{ candidate: { ...renamed(f, `${f.spelled}${suffix}`), face: "write" }, seats: ["entry"], followed: { operand: f, suffix }, ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }]
|
|
90
|
+
: sedBackupNames(f.spelled, suffix).map((spelled) => ({ candidate: { ...renamed(f, spelled), face: "write" }, seats: ["entry"], ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }))));
|
|
90
91
|
}
|
|
91
92
|
function copyDestinationChildren(segment, words, readings) {
|
|
92
93
|
const candidateOf = (raw) => segment.reads.find((c) => c.raw === raw && WORD_PROVENANCES.has(c.provenance));
|
|
@@ -107,7 +108,7 @@ function copyDestinationChildren(segment, words, readings) {
|
|
|
107
108
|
const child = `${dest.spelled.replace(/\/+$/, "")}/${src.spelled.replace(/\/+$/, "").split("/").pop() ?? ""}`;
|
|
108
109
|
if (child.endsWith("/") || children.has(child))
|
|
109
110
|
continue;
|
|
110
|
-
children.set(child, destCandidate !== undefined && source !== undefined ?
|
|
111
|
+
children.set(child, destCandidate !== undefined && source !== undefined ? renamed(destCandidate, child) : { spelled: child, raw: src.raw, provenance: "unlisted_word", expands: "unresolvable", bases: [] });
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
return [...children.values()];
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* re-entry (`shell-read-plan.ts`); this module is the one reader of which operand — or which input — that text is.
|
|
6
6
|
*/
|
|
7
7
|
import { type LexedSegment, type LexedWord } from "../../shell/lex.js";
|
|
8
|
+
import { type ProgramPosition } from "./bash-program-position.js";
|
|
9
|
+
import { type TargetUser } from "./program-read-model.js";
|
|
10
|
+
import type { BoundValue } from "./shell-read-bindings.js";
|
|
8
11
|
/**
|
|
9
12
|
* The code bodies a builtin keeps among its operands, one per body; empty for every other program. The program read
|
|
10
13
|
* model's row says where the body is: the action of a `trap` (its first operand after the options), the value of an
|
|
@@ -24,6 +27,102 @@ export declare function codeBodies(basename: string, args: readonly string[]): r
|
|
|
24
27
|
* the argv names no text and the program runs its standard input ({@link scriptSourceOf}: the row runs it unnamed, or
|
|
25
28
|
* the script file named is standard input), the text is `stdin` — one text that may arrive there, as the caller spells it. */
|
|
26
29
|
export declare function reentryText(basename: string, args: readonly string[], stdin?: string): string | undefined;
|
|
30
|
+
/** One argument of the argv a launcher hands the SHELL it runs: a word of the command line passed on as it stands (`at`,
|
|
31
|
+
* its argv index; `from` — only the word's characters from there on, an option value attached to its option word), a word
|
|
32
|
+
* of the launcher's own (`fixed`: the `-c` in front of a text it builds), or a text it builds from several words
|
|
33
|
+
* (`joined`: their argv indices, joined by a space; `escaped` — every character of a word's value but a letter, a digit,
|
|
34
|
+
* `_`, `-` and `$` escaped with a backslash, as `sudo -s` builds its shell's `-c` text). */
|
|
35
|
+
export type LaunchedArg = {
|
|
36
|
+
readonly at: number;
|
|
37
|
+
readonly from?: number;
|
|
38
|
+
} | {
|
|
39
|
+
readonly fixed: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly joined: readonly number[];
|
|
42
|
+
readonly escaped?: true;
|
|
43
|
+
};
|
|
44
|
+
/** What a launcher that runs a program named by no word of its own runs: `program` — absent, a SHELL named by no word
|
|
45
|
+
* (sudo's, chroot's, the user's login shell `su` runs); `at` — the word naming it (`su -s PROGRAM`, from its `from`-th
|
|
46
|
+
* character where the value is attached to its option word); `spelled` — a program the launcher takes from elsewhere,
|
|
47
|
+
* as a shell spells it (`su -m`: the caller's `"$SHELL"`) — and the argv it hands that program. */
|
|
48
|
+
export interface LaunchedCommand {
|
|
49
|
+
readonly program?: {
|
|
50
|
+
readonly at: number;
|
|
51
|
+
readonly from?: number;
|
|
52
|
+
} | {
|
|
53
|
+
readonly spelled: string;
|
|
54
|
+
};
|
|
55
|
+
readonly args: readonly LaunchedArg[];
|
|
56
|
+
/** The user the launcher runs it as, where its row says it runs another ({@link targetUserOf}). */
|
|
57
|
+
readonly as?: TargetUser;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* What a launcher that runs a program named by no word of its own runs ({@link readProgramPosition}'s `runsShell`: the
|
|
61
|
+
* launcher, and the first word it hands on), one command per reading of the launcher's row where its implementations
|
|
62
|
+
* read it differently; empty when no launcher of the segment runs one. A shell's argv is read by that shell's row
|
|
63
|
+
* ({@link reentryText}, {@link scriptFileAt}): its `-c` text, its script file, else its standard input. By the launcher
|
|
64
|
+
* row (`LauncherSpec`, program-read-model.ts):
|
|
65
|
+
* · no word handed on (`sudo -s`, `chroot DIR`, a bare `su`): the shell is handed nothing;
|
|
66
|
+
* · `shellCommand: "escaped"` (`sudo -s CMD…`): `-c` and the words, escaped and joined;
|
|
67
|
+
* · `shellText: "joined"` (`watch CMD…`): `-c` and the non-option words, joined — and `-c` with each shell-text value
|
|
68
|
+
* attached to its option word (`script -c'CMD'`);
|
|
69
|
+
* · `shellText: "user"` (`su [-] [USER [ARG…]]`): per implementation — its first operand past a lone `-` names the user;
|
|
70
|
+
* one that reads its options anywhere hands `-c TEXT` for each shell-text value of its own (a command each) and its
|
|
71
|
+
* operands after the user, one that stops at the user hands every word after the user. The program is the value of
|
|
72
|
+
* its option of role `command` (`-s PROGRAM`), else the program a selected `names-program` switch takes from the
|
|
73
|
+
* caller's environment (`-m`: `"$SHELL"`), else the user's login shell.
|
|
74
|
+
*/
|
|
75
|
+
export declare function launchedCommands(argv: readonly LexedWord[], pos?: ProgramPosition): readonly LaunchedCommand[];
|
|
76
|
+
/** How a launched command is read: as a SHELL's argv (`shell`: the shell whose row reads it — a program that runs its
|
|
77
|
+
* `-c` text, `sh`, `bash`, `fish`, …, or `sh` for the shell named by no word), as the command line it is (`line`: a
|
|
78
|
+
* program that is no shell, `su -s /bin/rm …`, its words as written, so its expansions are the command line's), or
|
|
79
|
+
* both — a program the text does not spell (`su -s "$X"`, `su -m`: the caller's `"$SHELL"`) may be any program, a shell
|
|
80
|
+
* among them. */
|
|
81
|
+
export declare function launchedReadings(argv: readonly LexedWord[], command: LaunchedCommand): {
|
|
82
|
+
readonly shell?: string;
|
|
83
|
+
readonly line?: string;
|
|
84
|
+
};
|
|
85
|
+
/** What a launcher runs ({@link launchedCommands}, {@link launchedReadings}), as a reader of the command line reads it in
|
|
86
|
+
* one reading of its names: each shell's argv in two spellings — `texts` (a word's literal value, else `spell.text`) and
|
|
87
|
+
* `args` (`spell.folded`) — with the argv index each position stands for (`words`), its words' fields read off
|
|
88
|
+
* `spell.values` ({@link spellLaunched}) and the user it runs as (`as`); the word naming a shell (`su -s /bin/bash`,
|
|
89
|
+
* `programWords`); and each command line a program that is no shell runs (`lines`, once each). */
|
|
90
|
+
export declare function launchedInput(argv: readonly LexedWord[], pos: ProgramPosition, spell: {
|
|
91
|
+
readonly text: (k: number) => string;
|
|
92
|
+
readonly folded: (k: number) => string;
|
|
93
|
+
readonly values: (k: number) => readonly BoundValue[] | undefined;
|
|
94
|
+
}): {
|
|
95
|
+
readonly shells: readonly {
|
|
96
|
+
readonly shell: string;
|
|
97
|
+
readonly texts: readonly string[];
|
|
98
|
+
readonly args: readonly string[];
|
|
99
|
+
readonly words: readonly (number | undefined)[];
|
|
100
|
+
readonly as?: TargetUser;
|
|
101
|
+
}[];
|
|
102
|
+
readonly programWords: readonly number[];
|
|
103
|
+
readonly lines: readonly string[];
|
|
104
|
+
};
|
|
105
|
+
/** How a reader spells the words of a command line it hands a launched shell ({@link spellLaunched}): `word` — a word as
|
|
106
|
+
* it stands, from its `from`-th character; `values` — for a word the shell rewrites before the launcher sees it, the
|
|
107
|
+
* values it may take where the reader knows them (each one field, or a pattern whose matches are the fields — its parts
|
|
108
|
+
* say which characters the shell expands), `undefined` where it does not; absent — the reader knows none. */
|
|
109
|
+
export interface LaunchedSpelling {
|
|
110
|
+
readonly word: (at: number, from: number) => string;
|
|
111
|
+
readonly values?: (at: number) => readonly BoundValue[] | undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The argv a launched shell is handed ({@link launchedCommands}), spelled as `spell` spells the command line's words in ONE
|
|
115
|
+
* reading of their names (the plan reads a segment once per combination of its names' values, `segmentReadingsOf` in
|
|
116
|
+
* shell-read-word.ts — a word's value here is its value in that reading). A word an escaping launcher joins (`sudo -s
|
|
117
|
+
* CMD…`) is handed as the launcher hands it: the FIELD the command line's shell gives the launcher, escaped
|
|
118
|
+
* ({@link sudoEscaped}) — the field a literal word spells, or the field the shell established for a word it rewrites first
|
|
119
|
+
* (`X='#'; sudo -s rm -r "$X" d` hands `rm -r \# d`: the value is an operand, never a comment), a pattern's literal
|
|
120
|
+
* characters escaped and its pattern characters bare ({@link launchedSpellingOf}). A word whose field the reader does not
|
|
121
|
+
* know is handed AS WRITTEN: the launched shell re-reads the expansion with the command line's quoting, so the word stays
|
|
122
|
+
* one argument (an unknown there) and its neighbours stay theirs (`"#$Y" d` is never `#$Y d`). Any other launcher hands a
|
|
123
|
+
* word's text as `spell.word` spells it. One argv, as a list of the one.
|
|
124
|
+
*/
|
|
125
|
+
export declare function spellLaunched(argv: readonly LexedWord[], args: readonly LaunchedArg[], spell: LaunchedSpelling): string[][];
|
|
27
126
|
/** The argv index of the SCRIPT FILE a shell runs where its argv carries no text ({@link scriptSourceOf}). */
|
|
28
127
|
export declare function scriptFileAt(basename: string, args: readonly string[]): number | undefined;
|
|
29
128
|
/** One text that may arrive on a segment's standard input: `from` = its source as written (a here-document's body,
|
|
@@ -34,9 +133,10 @@ export interface StdinText {
|
|
|
34
133
|
}
|
|
35
134
|
/** What may arrive on a segment's standard input: each here-document (its descriptor is not read: every one counts) —
|
|
36
135
|
* its body, literal when quoted or when nothing in it expands — each here-string (literal when its word is), `<` file
|
|
37
|
-
* or `<&` descriptor written to no other descriptor, and the pipe it is reached through
|
|
38
|
-
*
|
|
39
|
-
|
|
136
|
+
* or `<&` descriptor written to no other descriptor, and what it inherits (`fed`: the pipe it is reached through, the
|
|
137
|
+
* pipe or the input of the group it stands in, the input of the shell running its text) unless one of those three
|
|
138
|
+
* replaces it. */
|
|
139
|
+
export declare function stdinTextsOf(segment: LexedSegment, fed: readonly StdinText[]): StdinText[];
|
|
40
140
|
/** What one segment (its argv) writes into the pipe after it: the text a printer spells ({@link printedText}) when its
|
|
41
141
|
* program word and every argument are literal, else only the segment as written. */
|
|
42
142
|
export declare function pipedTextOf(segment: LexedSegment, argv: readonly LexedWord[]): StdinText;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { literalHeredocBody, literalTextOf } from "../../shell/lex.js";
|
|
2
|
-
import { readProgramPosition } from "./bash-program-position.js";
|
|
3
|
-
import { printedText, programRow, readArgRoles, selectedOptions } from "./program-read-model.js";
|
|
2
|
+
import { programBasename, readProgramPosition } from "./bash-program-position.js";
|
|
3
|
+
import { implementationRejects, implementationsOf, printedText, programRow, readArgRoles, selectedOptions, targetUserOf } from "./program-read-model.js";
|
|
4
4
|
import { namesStandardInput } from "./safety.js";
|
|
5
5
|
export function codeBodies(basename, args) {
|
|
6
6
|
const row = programRow(basename);
|
|
@@ -70,6 +70,124 @@ function argvText(row, at, args) {
|
|
|
70
70
|
}
|
|
71
71
|
return undefined;
|
|
72
72
|
}
|
|
73
|
+
export function launchedCommands(argv, pos = readProgramPosition(argv)) {
|
|
74
|
+
const runs = pos.refusal?.kind === "control_syntax_at_program_position" ? pos.refusal.runsShell : undefined;
|
|
75
|
+
if (runs === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
const row = programRow(programBasename(argv[runs.launcher].folded));
|
|
78
|
+
const words = argv.slice(runs.from).map((w) => w.folded);
|
|
79
|
+
const at = (k) => runs.from + k;
|
|
80
|
+
if (row?.launcher?.shellText === "user")
|
|
81
|
+
return userCommands(row, words, at);
|
|
82
|
+
const user = row === undefined ? undefined : targetUserOf(row, argv.slice(runs.launcher + 1, runs.from).map((w) => w.folded));
|
|
83
|
+
const as = user !== undefined ? { as: user } : {};
|
|
84
|
+
if (words.length === 0 || row?.launcher === undefined)
|
|
85
|
+
return [{ args: [], ...as }];
|
|
86
|
+
if (row.launcher.shellText === "joined") {
|
|
87
|
+
const texts = new Map();
|
|
88
|
+
for (const view of implementationsOf(row)) {
|
|
89
|
+
const events = readArgRoles(view, words);
|
|
90
|
+
const first = events.find((e) => e.kind === "operand" || e.kind === "dash-word-operand")?.at ?? words.length;
|
|
91
|
+
if (implementationRejects(row, view, words.slice(0, first)))
|
|
92
|
+
continue;
|
|
93
|
+
const joined = words.slice(first).map((_, k) => at(first + k));
|
|
94
|
+
const values = events.flatMap((e) => (e.kind === "option" || (e.kind === "value" && e.at < first) ? roleValue(e, "shell-text", words, at) ?? [] : []));
|
|
95
|
+
for (const t of [...(joined.length === 0 ? [] : [{ joined }]), ...values])
|
|
96
|
+
texts.set(JSON.stringify(t), t);
|
|
97
|
+
}
|
|
98
|
+
return texts.size === 0 ? [{ args: [], ...as }] : [...texts.values()].map((t) => ({ args: [{ fixed: "-c" }, t], ...as }));
|
|
99
|
+
}
|
|
100
|
+
return [{ args: [{ fixed: "-c" }, { joined: words.map((_, k) => at(k)), escaped: true }], ...as }];
|
|
101
|
+
}
|
|
102
|
+
function roleValue(e, kind, words, at) {
|
|
103
|
+
if (e.kind === "value" && e.role?.kind === kind)
|
|
104
|
+
return { at: at(e.at) };
|
|
105
|
+
if (e.kind === "option" && typeof e.reading === "object" && e.reading.role?.kind === kind)
|
|
106
|
+
return { at: at(e.at), from: words[e.at].length - e.reading.attached.length };
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
function userCommands(row, words, at) {
|
|
110
|
+
const out = [];
|
|
111
|
+
const valued = (e, kind) => roleValue(e, kind, words, at);
|
|
112
|
+
for (const view of implementationsOf(row)) {
|
|
113
|
+
const events = readArgRoles(view, words);
|
|
114
|
+
const operands = events.filter((e) => e.kind === "operand");
|
|
115
|
+
const user = (operands[0]?.dash !== undefined ? operands[1] : operands[0])?.at;
|
|
116
|
+
const end = user ?? words.length;
|
|
117
|
+
const stops = view.grammars[0]?.afterFirstOperand === "operands";
|
|
118
|
+
const own = stops ? events.filter((e) => e.at < end) : events;
|
|
119
|
+
const named = own.flatMap((e) => valued(e, "command") ?? []).at(-1);
|
|
120
|
+
const fromEnvironment = selectedOptions(view, stops ? words.slice(0, end) : words, "one-implementation").some((o) => o.mode === "names-program");
|
|
121
|
+
const program = named !== undefined ? { program: named } : fromEnvironment ? { program: { spelled: "\"$SHELL\"" } } : {};
|
|
122
|
+
const target = targetUserOf(view, stops ? words.slice(0, end) : words.filter((_, k) => k !== user), { name: user === undefined ? "root" : words[user], dash: operands[0]?.dash !== undefined });
|
|
123
|
+
const runs = { ...program, ...(target !== undefined ? { as: target } : {}) };
|
|
124
|
+
if (stops) {
|
|
125
|
+
out.push({ ...runs, args: user === undefined ? [] : words.slice(user + 1).map((_, k) => ({ at: at(user + 1 + k) })) });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const rest = operands.filter((e) => user !== undefined && e.at > user).map((e) => ({ at: at(e.at) }));
|
|
129
|
+
const texts = own.flatMap((e) => valued(e, "shell-text") ?? []);
|
|
130
|
+
if (texts.length === 0)
|
|
131
|
+
out.push({ ...runs, args: rest });
|
|
132
|
+
for (const text of texts)
|
|
133
|
+
out.push({ ...runs, args: [{ fixed: "-c" }, text, ...rest] });
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
export function launchedReadings(argv, command) {
|
|
138
|
+
const p = command.program;
|
|
139
|
+
if (p === undefined)
|
|
140
|
+
return { shell: "sh" };
|
|
141
|
+
const raw = (k, from = 0) => {
|
|
142
|
+
if (from === 0)
|
|
143
|
+
return argv[k].raw;
|
|
144
|
+
const literal = literalTextOf(argv[k].pieces);
|
|
145
|
+
return literal === undefined ? argv[k].folded.slice(from) : `'${literal.slice(from).replace(/'/g, "'\\''")}'`;
|
|
146
|
+
};
|
|
147
|
+
const line = [("at" in p ? raw(p.at, p.from) : p.spelled), ...command.args.map((a) => ("fixed" in a ? a.fixed : "at" in a ? raw(a.at, a.from) : a.joined.map((k) => raw(k)).join(" ")))].join(" ");
|
|
148
|
+
const literal = "at" in p ? literalTextOf(argv[p.at].pieces)?.slice(p.from ?? 0) : undefined;
|
|
149
|
+
if (literal === undefined)
|
|
150
|
+
return { shell: "sh", line };
|
|
151
|
+
const basename = programBasename(literal);
|
|
152
|
+
const reentry = programRow(basename)?.reentry;
|
|
153
|
+
return reentry?.kind === "shell" && reentry.text === "c-option" ? { shell: basename } : { line };
|
|
154
|
+
}
|
|
155
|
+
export function launchedInput(argv, pos, spell) {
|
|
156
|
+
const spelled = (word) => (args) => spellLaunched(argv, args, { word: (k, from) => word(k).slice(from), values: spell.values });
|
|
157
|
+
const asTexts = spelled(spell.text);
|
|
158
|
+
const asArgs = spelled(spell.folded);
|
|
159
|
+
const lines = new Set();
|
|
160
|
+
const programWords = [];
|
|
161
|
+
const shells = launchedCommands(argv, pos).flatMap((c) => {
|
|
162
|
+
const { shell, line } = launchedReadings(argv, c);
|
|
163
|
+
if (line !== undefined)
|
|
164
|
+
lines.add(line);
|
|
165
|
+
if (shell === undefined)
|
|
166
|
+
return [];
|
|
167
|
+
if (line === undefined && c.program !== undefined && "at" in c.program && c.program.from === undefined)
|
|
168
|
+
programWords.push(c.program.at);
|
|
169
|
+
const words = c.args.map((x) => ("at" in x && x.from === undefined ? x.at : undefined));
|
|
170
|
+
const args = asArgs(c.args);
|
|
171
|
+
return asTexts(c.args).map((texts, i) => ({ shell, texts, args: args[i], words, ...(c.as !== undefined ? { as: c.as } : {}) }));
|
|
172
|
+
});
|
|
173
|
+
return { shells, programWords, lines: [...lines] };
|
|
174
|
+
}
|
|
175
|
+
function sudoEscaped(field) {
|
|
176
|
+
return field.replace(/[^A-Za-z0-9_$-]/gu, (c) => `\\${c}`);
|
|
177
|
+
}
|
|
178
|
+
function launchedSpellingOf(v) {
|
|
179
|
+
return v.glob === true ? v.parts.map((p) => (p.pattern ? p.text : sudoEscaped(p.text))).join("") : sudoEscaped(v.spelled);
|
|
180
|
+
}
|
|
181
|
+
export function spellLaunched(argv, args, spell) {
|
|
182
|
+
const field = (k) => {
|
|
183
|
+
const literal = literalTextOf(argv[k].pieces);
|
|
184
|
+
if (literal !== undefined)
|
|
185
|
+
return sudoEscaped(literal);
|
|
186
|
+
const values = spell.values?.(k);
|
|
187
|
+
return values?.length === 1 ? launchedSpellingOf(values[0]) : argv[k].raw;
|
|
188
|
+
};
|
|
189
|
+
return [args.map((a) => ("fixed" in a ? a.fixed : "at" in a ? spell.word(a.at, a.from ?? 0) : a.joined.map((k) => (a.escaped === true ? field(k) : spell.word(k, 0))).join(" ")))];
|
|
190
|
+
}
|
|
73
191
|
export function scriptFileAt(basename, args) {
|
|
74
192
|
const row = programRow(basename);
|
|
75
193
|
return row?.reentry?.kind === "shell" && argvText(row, row.reentry.text, args) === undefined ? scriptSourceOf(row, args).file : undefined;
|
|
@@ -85,7 +203,7 @@ function scriptSourceOf(row, args) {
|
|
|
85
203
|
return { stdin: true };
|
|
86
204
|
return { file: first.at, stdin: options.some((e) => e.kind === "option" && e.reading === "speculative") };
|
|
87
205
|
}
|
|
88
|
-
export function stdinTextsOf(segment,
|
|
206
|
+
export function stdinTextsOf(segment, fed) {
|
|
89
207
|
const out = [];
|
|
90
208
|
let replacesPipe = false;
|
|
91
209
|
for (const t of segment.tokens) {
|
|
@@ -99,7 +217,7 @@ export function stdinTextsOf(segment, piped) {
|
|
|
99
217
|
replacesPipe = true;
|
|
100
218
|
}
|
|
101
219
|
}
|
|
102
|
-
return
|
|
220
|
+
return replacesPipe ? out : [...fed, ...out];
|
|
103
221
|
}
|
|
104
222
|
export function pipedTextOf(segment, argv) {
|
|
105
223
|
const from = `${segment.text.trim()} |`;
|
package/dist/tools/monitor.d.ts
CHANGED
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
* The handle is a first-class m* registry task: TaskOutput serves the re-readable spool, TaskStop stops the
|
|
29
29
|
* watch (design/134 stoppedBy attribution applies automatically).
|
|
30
30
|
*
|
|
31
|
-
* Security posture: `command` is an arbitrary shell script ⇒ effect:"write", same as Bash
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Security posture: `command` is an arbitrary shell script ⇒ effect:"write", same as Bash; the catalog puts Monitor on the
|
|
32
|
+
* SHELL FACE, so the `Bash(<command>)` rules, the shell seat, the read station and the removal guard judge its `command`
|
|
33
|
+
* as they judge Bash's — Monitor is never a policy side-door around a gated Bash.
|
|
34
34
|
*
|
|
35
35
|
* 深对比残差 M17 CLOSED (2026-07-09 live-CC probe, docs/cc-probe-198/live-schema-monitor-worktree-2026-07-09.json):
|
|
36
36
|
* the deep-dive bundle showed a `command? + ws?:{url,protocols}` dual-mode schema; the LIVE current-CC schema is
|