@sema-agent/core 7.45.0 → 7.47.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 +298 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +6 -4
- package/dist/core/allow-entry.js +6 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +24 -7
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +12 -4
- package/dist/core/gate-fold.js +5 -2
- package/dist/core/gate-lanes.js +11 -4
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +59 -24
- package/dist/core/permission-rule-model.js +48 -44
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +26 -45
- package/dist/core/permission-rules.js +77 -57
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +11 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +13 -5
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +12 -2
- package/dist/core/tool-face.d.ts +10 -1
- 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 +40 -17
- package/dist/core/tool-policy.js +11 -9
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +14 -3
- package/dist/core/write-protect.js +41 -10
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +22 -0
- package/dist/shell/lex.js +75 -13
- package/dist/tools/fs/bash-program-position.d.ts +14 -90
- package/dist/tools/fs/bash-program-position.js +41 -74
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +711 -0
- package/dist/tools/fs/program-read-model.js +1259 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +624 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +34 -24
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +86 -0
- package/dist/tools/fs/shell-read-argv.js +388 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +300 -0
- package/dist/tools/fs/shell-read-bindings.js +755 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +82 -8
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +33 -8
- package/dist/tools/fs/shell-read-plan.js +309 -407
- package/dist/tools/fs/shell-read-word.d.ts +60 -26
- package/dist/tools/fs/shell-read-word.js +47 -37
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +85 -137
- package/dist/tools/fs/shell-reentry-text.d.ts +136 -6
- package/dist/tools/fs/shell-reentry-text.js +203 -53
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +25 -5
- package/wire-changes/core@7.46.0.json +1522 -0
- package/wire-changes/core@7.47.0.json +412 -0
|
@@ -38,6 +38,24 @@ export declare function readDenyHomeLinksOf(env: ExecutionEnv, entries: readonly
|
|
|
38
38
|
readonly linked: Map<string, string[]>;
|
|
39
39
|
readonly whole: boolean;
|
|
40
40
|
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Where the rows that open with the configuration directory's name (`~/.sema/…`, {@link agentConfigDirTail}) lead when
|
|
43
|
+
* the host declares the directory's root (`RunnerDeps.agentConfigDir`) — the same `linked` answer
|
|
44
|
+
* {@link readDenyHomeLinksOf} gives, for the same rows: the row's literal head below the directory name is spelled under
|
|
45
|
+
* each spelling of the root and resolved the way the read faces mint their keys. A row is not anchored at the root by
|
|
46
|
+
* its own run (it is anchored at the homes), so every location here — the spelling itself and where it leads — is one the
|
|
47
|
+
* row is placed at in addition; the home spelling stays placed. The row's name in every refusal is unchanged.
|
|
48
|
+
* · a row whose run does not open with the directory's name (`~/.sema.*`, the external tools' files) adds nothing;
|
|
49
|
+
* · a row whose head ends at the directory name (`~/.sema/*.json`, `~/.sema`) is placed at the root itself, the rest of
|
|
50
|
+
* its run matched below it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readDenyConfigRootLinksOf(env: ExecutionEnv, entries: readonly {
|
|
53
|
+
readonly pattern: string;
|
|
54
|
+
readonly caseSensitive?: boolean;
|
|
55
|
+
}[], roots: readonly string[], signal?: AbortSignal, deadline?: number): Promise<{
|
|
56
|
+
readonly linked: Map<string, string[]>;
|
|
57
|
+
readonly whole: boolean;
|
|
58
|
+
}>;
|
|
41
59
|
/** Where an absolute location LEADS in one environment — resolved the way the read and write faces mint their keys
|
|
42
60
|
* (`canonicalizeTarget`: the longest existing prefix resolved, the rest rejoined). `undefined` when the environment
|
|
43
61
|
* does not answer (a refused or failed probe, one that throws). The one resolution a deny row's placement reads: a
|
|
@@ -122,7 +140,7 @@ export declare function carryHomeViews<T extends object>(judge: T, source: HomeV
|
|
|
122
140
|
* · each AUXILIARY home (see the module head) read once under {@link AUXILIARY_HOME_PROBE_MS} — its spellings, its
|
|
123
141
|
* chain, its rows' heads — at the same time; whatever of it did not answer costs that home alone and never `whole`.
|
|
124
142
|
* `compile` is the mount's own compile; the rows' patterns are read off its compile at no home. */
|
|
125
|
-
export declare function placeReadDenyAtHome(env: ExecutionEnv, compile: (homes: readonly string[], linked?: ReadonlyMap<string, readonly string[]>) => ReadDenyMatcher, homes?: readonly string[], signal?: AbortSignal): Promise<{
|
|
143
|
+
export declare function placeReadDenyAtHome(env: ExecutionEnv, compile: (homes: readonly string[], linked?: ReadonlyMap<string, readonly string[]>) => ReadDenyMatcher, homes?: readonly string[], signal?: AbortSignal, configRoot?: string): Promise<{
|
|
126
144
|
readonly matcher: PlacedReadDenyMatcher;
|
|
127
145
|
readonly homes: readonly string[];
|
|
128
146
|
readonly linked: ReadonlyMap<string, readonly string[]>;
|
|
@@ -2,6 +2,7 @@ import { userInfo } from "node:os";
|
|
|
2
2
|
import { canonicalizeTarget, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
|
|
3
3
|
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "../../core/gate-bounded-await.js";
|
|
4
4
|
import { thrownText } from "../../core/thrown-value.js";
|
|
5
|
+
import { agentConfigDirTail } from "../../core/agent-config-dir.js";
|
|
5
6
|
import { readDenyPatternRun, readDenyRunHead } from "./read-deny.js";
|
|
6
7
|
export const AUXILIARY_HOME_PROBE_MS = 2_000;
|
|
7
8
|
const said = new Set();
|
|
@@ -97,12 +98,15 @@ export async function readDenyHomesOf(env, signal) {
|
|
|
97
98
|
async function until(work, deadline, signal) {
|
|
98
99
|
return await withGateTimeout(work, Math.max(0, deadline - Date.now()), signal, "auxiliaryHome").catch(() => undefined);
|
|
99
100
|
}
|
|
100
|
-
function sayAuxiliary(what) {
|
|
101
|
+
function sayAuxiliary(what, subject = "auxiliary") {
|
|
102
|
+
if (subject === "config_root") {
|
|
103
|
+
sayOnce("config_root", `read-deny homes: the declared agent configuration root did not answer its probes within ${AUXILIARY_HOME_PROBE_MS} ms (${what}) — the rows that name the configuration directory sit at its spelling only, and a path through an alias of it is judged as spelled, until the next placement.`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
101
106
|
sayOnce("auxiliary", `read-deny homes: a home beside the environment's declared home did not answer its probes within ${AUXILIARY_HOME_PROBE_MS} ms (${what}) — its home-anchored read-deny rows sit at its spelling only, and a path through an alias of it is judged as spelled, until the next placement.`);
|
|
102
107
|
}
|
|
103
108
|
export async function readDenyHomeLinksOf(env, entries, homes, signal, deadline) {
|
|
104
109
|
const rows = [];
|
|
105
|
-
const familyOf = new Map();
|
|
106
110
|
for (const { pattern, caseSensitive } of entries) {
|
|
107
111
|
const { homeAnchored, run } = readDenyPatternRun(pattern);
|
|
108
112
|
if (!homeAnchored)
|
|
@@ -110,26 +114,51 @@ export async function readDenyHomeLinksOf(env, entries, homes, signal, deadline)
|
|
|
110
114
|
const head = run.split("/").filter(Boolean).slice(0, readDenyRunHead(run)).join("/");
|
|
111
115
|
if (head.length === 0)
|
|
112
116
|
continue;
|
|
113
|
-
const spelled = homes.map((home) => {
|
|
114
|
-
|
|
115
|
-
const at = normalizeAbsPathLexically(joinForFamily(family, home, head));
|
|
116
|
-
familyOf.set(at, family);
|
|
117
|
-
return at;
|
|
118
|
-
});
|
|
119
|
-
rows.push({ pattern, caseSensitive: caseSensitive === true, spelled });
|
|
117
|
+
const spelled = homes.map((home) => normalizeAbsPathLexically(joinForFamily(pathFamilyOf({ root: home }), home, head)));
|
|
118
|
+
rows.push({ pattern, caseSensitive: caseSensitive === true, spelled, anchored: spelled });
|
|
120
119
|
}
|
|
120
|
+
return await linkedLocationsOf(env, rows, signal, deadline);
|
|
121
|
+
}
|
|
122
|
+
export async function readDenyConfigRootLinksOf(env, entries, roots, signal, deadline) {
|
|
123
|
+
return await linkedLocationsOf(env, configRootRowsOf(entries, roots), signal, deadline);
|
|
124
|
+
}
|
|
125
|
+
function configRootRowsOf(entries, roots) {
|
|
126
|
+
const rows = [];
|
|
127
|
+
for (const { pattern, caseSensitive } of entries) {
|
|
128
|
+
const { homeAnchored, run } = readDenyPatternRun(pattern);
|
|
129
|
+
const tail = homeAnchored ? agentConfigDirTail(run, caseSensitive === true) : undefined;
|
|
130
|
+
if (tail === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
const head = tail.split("/").filter(Boolean).slice(0, readDenyRunHead(tail)).join("/");
|
|
133
|
+
const spelled = roots.map((root) => normalizeAbsPathLexically(head.length === 0 ? root : joinForFamily(pathFamilyOf({ root }), root, head)));
|
|
134
|
+
rows.push({ pattern, caseSensitive: caseSensitive === true, spelled, anchored: [] });
|
|
135
|
+
}
|
|
136
|
+
return rows;
|
|
137
|
+
}
|
|
138
|
+
function configRootSpelledLinks(entries, roots) {
|
|
139
|
+
const out = new Map();
|
|
140
|
+
for (const { pattern, spelled } of configRootRowsOf(entries, roots))
|
|
141
|
+
out.set(pattern, [...new Set(spelled)]);
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
async function linkedLocationsOf(env, rows, signal, deadline) {
|
|
145
|
+
const familyOf = new Map();
|
|
146
|
+
for (const { spelled } of rows)
|
|
147
|
+
for (const at of spelled)
|
|
148
|
+
familyOf.set(at, pathFamilyOf({ root: at }));
|
|
121
149
|
const leadOf = async (at, family) => (await whereItLeads(env, at, family, signal)) ?? ((await holdsNothingBelow(env, at, signal)) ? at : undefined);
|
|
122
150
|
const resolved = new Map(await Promise.all([...familyOf].map(async ([at, family]) => [at, deadline === undefined ? await leadOf(at, family) : await until(leadOf(at, family), deadline, signal)])));
|
|
123
151
|
const out = new Map();
|
|
124
152
|
const whole = [...resolved.values()].every((location) => location !== undefined);
|
|
125
|
-
for (const { pattern, caseSensitive, spelled } of rows) {
|
|
153
|
+
for (const { pattern, caseSensitive, spelled, anchored } of rows) {
|
|
126
154
|
const fold = (p) => (caseSensitive ? p : p.replace(/[A-Z]/g, (c) => c.toLowerCase()));
|
|
127
|
-
const own = new Set(
|
|
155
|
+
const own = new Set(anchored.map(fold));
|
|
128
156
|
const locations = [];
|
|
129
157
|
for (const at of spelled) {
|
|
130
|
-
const location
|
|
131
|
-
|
|
132
|
-
|
|
158
|
+
for (const location of [at, resolved.get(at)]) {
|
|
159
|
+
if (location !== undefined && !own.has(fold(location)) && !locations.includes(location))
|
|
160
|
+
locations.push(location);
|
|
161
|
+
}
|
|
133
162
|
}
|
|
134
163
|
if (locations.length > 0)
|
|
135
164
|
out.set(pattern, locations);
|
|
@@ -203,19 +232,24 @@ async function readAuxiliaryHome(env, home, opts, signal) {
|
|
|
203
232
|
let reading;
|
|
204
233
|
if (spelled?.canonical === undefined) {
|
|
205
234
|
const answered = spelled?.answered === true;
|
|
206
|
-
|
|
235
|
+
const linked = opts.configRoot === true && opts.entries !== undefined ? configRootSpelledLinks(opts.entries, [home]) : new Map();
|
|
236
|
+
reading = { place: [home], chain: answered ? { ancestors: [], whole: true } : NO_CHAIN, linked, answered };
|
|
207
237
|
}
|
|
208
238
|
else {
|
|
209
239
|
const place = spelled.spellings;
|
|
210
240
|
const identity = env.fileIdentity;
|
|
211
241
|
const [chain, heads] = await Promise.all([
|
|
212
242
|
typeof identity === "function" ? chainAbove(env, identity, spelled.canonical, signal, deadline) : Promise.resolve({ ancestors: [], whole: true }),
|
|
213
|
-
opts.entries
|
|
243
|
+
opts.entries === undefined
|
|
244
|
+
? Promise.resolve({ linked: new Map(), whole: true })
|
|
245
|
+
: opts.configRoot === true
|
|
246
|
+
? readDenyConfigRootLinksOf(env, opts.entries, place, signal, deadline)
|
|
247
|
+
: readDenyHomeLinksOf(env, opts.entries, place, signal, deadline),
|
|
214
248
|
]);
|
|
215
249
|
reading = { place, chain, linked: heads.linked, answered: chain.whole && heads.whole };
|
|
216
250
|
}
|
|
217
251
|
if (!reading.answered)
|
|
218
|
-
sayAuxiliary("a probe did not answer");
|
|
252
|
+
sayAuxiliary("a probe did not answer", opts.configRoot === true ? "config_root" : "auxiliary");
|
|
219
253
|
return reading;
|
|
220
254
|
}
|
|
221
255
|
function homeViewSourceOver(env, auxiliary) {
|
|
@@ -325,31 +359,35 @@ export function homeViewsOf(env, judge, signal) {
|
|
|
325
359
|
export function carryHomeViews(judge, source) {
|
|
326
360
|
return { ...judge, homeViews: source };
|
|
327
361
|
}
|
|
328
|
-
export async function placeReadDenyAtHome(env, compile, homes, signal) {
|
|
362
|
+
export async function placeReadDenyAtHome(env, compile, homes, signal, configRoot) {
|
|
329
363
|
const entries = compile([]).entries;
|
|
330
364
|
const declared = declaredHomeOf(env);
|
|
331
|
-
const
|
|
365
|
+
const relocated = configRoot === undefined ? undefined : readAuxiliaryHome(env, configRoot, { entries, configRoot: true }, signal);
|
|
366
|
+
const heldAnywhere = async (whole) => {
|
|
367
|
+
const linked = (await relocated)?.linked ?? new Map();
|
|
368
|
+
return placementOf(carryHomeViews(linked.size === 0 ? compile([]) : compile([], linked), homeViewSourceOver(env, relocated === undefined ? [] : [relocated])), [], linked, whole);
|
|
369
|
+
};
|
|
332
370
|
const listed = homes === undefined ? declaredReadDenyHomesAnswered(env) : { homes, whole: true };
|
|
333
371
|
if (!listed.whole)
|
|
334
|
-
return heldAnywhere(false);
|
|
372
|
+
return await heldAnywhere(false);
|
|
335
373
|
const own = listed.homes.filter((h) => isDeclared(declared, h));
|
|
336
374
|
let declaredAt = [];
|
|
337
375
|
if (own.length > 0) {
|
|
338
376
|
const ownSpellings = await withGateTimeout(homeSpellingsOf(env, own[0], signal), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "readDenyHomes");
|
|
339
377
|
if (!ownSpellings.answered)
|
|
340
|
-
return heldAnywhere(false);
|
|
378
|
+
return await heldAnywhere(false);
|
|
341
379
|
declaredAt = [...new Set([...own, ...ownSpellings.spellings])];
|
|
342
380
|
}
|
|
343
381
|
const auxiliary = readAuxiliaryHomes(env, listed.homes, { entries }, signal);
|
|
344
|
-
const [heads, readings] = await Promise.all([readDenyHomeLinksOf(env, entries, declaredAt, signal), Promise.all(auxiliary)]);
|
|
382
|
+
const [heads, readings, root] = await Promise.all([readDenyHomeLinksOf(env, entries, declaredAt, signal), Promise.all(auxiliary), relocated]);
|
|
345
383
|
const at = [...new Set([...declaredAt, ...readings.flatMap((r) => r.place)])];
|
|
346
384
|
const linked = new Map();
|
|
347
|
-
for (const map of [heads.linked, ...readings.map((r) => r.linked)]) {
|
|
385
|
+
for (const map of [heads.linked, ...readings.map((r) => r.linked), ...(root === undefined ? [] : [root.linked])]) {
|
|
348
386
|
for (const [pattern, locations] of map)
|
|
349
387
|
linked.set(pattern, [...new Set([...(linked.get(pattern) ?? []), ...locations])]);
|
|
350
388
|
}
|
|
351
389
|
const matcher = linked.size === 0 ? compile(at) : compile(at, linked);
|
|
352
|
-
return placementOf(carryHomeViews(matcher, homeViewSourceOver(env, auxiliary)), at, linked, heads.whole);
|
|
390
|
+
return placementOf(carryHomeViews(matcher, homeViewSourceOver(env, relocated === undefined ? auxiliary : [...auxiliary, relocated])), at, linked, heads.whole);
|
|
353
391
|
}
|
|
354
392
|
export function readDenyPlacedLater(unplaced, place) {
|
|
355
393
|
let current = unplaced;
|
|
@@ -70,26 +70,66 @@ export interface PathRuleDenyRow {
|
|
|
70
70
|
* row, or one whose head cannot be read: it is judged as spelled. */
|
|
71
71
|
readonly head?: string;
|
|
72
72
|
}
|
|
73
|
+
/** A row's reach over ONE absolute spelling, three-valued: `unreadable` = the row cannot be judged there (a base its anchor
|
|
74
|
+
* lacks) — a tightening judgment holds on it, an allow grants nothing on it. */
|
|
75
|
+
export type PathRowReach = {
|
|
76
|
+
readonly reach: "reached" | "not_reached";
|
|
77
|
+
} | {
|
|
78
|
+
readonly reach: "unreadable";
|
|
79
|
+
readonly reason: string;
|
|
80
|
+
};
|
|
81
|
+
/** A rule row as the one judgment reads it: the deny-table row, and — on a row a rule binding produced — its three-valued
|
|
82
|
+
* reach (a row built by hand answers through `reaches` alone: reached or not). */
|
|
83
|
+
export interface JudgedPathRow extends PathRuleDenyRow {
|
|
84
|
+
readonly reach?: (absolutePath: string) => PathRowReach;
|
|
85
|
+
}
|
|
86
|
+
/** A call's target as a judgment reads it: the absolute spellings of its views (lexical, canonical, the leaf chain's hops —
|
|
87
|
+
* any order the caller holds), or `unreadable` when the call names a target this reading cannot locate. */
|
|
88
|
+
export interface TargetViews {
|
|
89
|
+
readonly spellings: readonly string[];
|
|
90
|
+
readonly unreadable?: string;
|
|
91
|
+
}
|
|
92
|
+
/** What {@link pathRowsReach} found: the row, the view it met there (absent on an unreadable hold), and — when nothing was
|
|
93
|
+
* reached and some row could not be judged — the reason of the first such row. */
|
|
94
|
+
export interface PathRowsHit<R> {
|
|
95
|
+
readonly row: R;
|
|
96
|
+
readonly view?: string;
|
|
97
|
+
readonly unreadable?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* THE one judgment of path rule rows over a call's target, for the gate's rule lanes and the tool's deny table alike: rows
|
|
101
|
+
* outer, views inner — the first row (in the caller's order) that some view reaches is the answer, so the rule a refusal
|
|
102
|
+
* names does not depend on which view happened to meet it. `tighten` (deny / ask): any view reaching the row reaches it; a
|
|
103
|
+
* row that cannot be judged (or a target that cannot be read) is held and answered only when no row reaches. `allow`: a
|
|
104
|
+
* row admits the call only when it admits EVERY view (a link out of an allowed directory is not admitted through its
|
|
105
|
+
* spelling); an unreadable target is admitted by nothing.
|
|
106
|
+
*/
|
|
107
|
+
export declare function pathRowsReach<R extends JudgedPathRow>(rows: readonly R[], views: TargetViews, direction: "tighten" | "allow"): PathRowsHit<R> | undefined;
|
|
108
|
+
/** The locations a row is also judged at (each leading part of its HEAD: the anchor's children down to the head itself,
|
|
109
|
+
* a share's root for a UNC head) — what a leg resolves once, for every row whose head it holds. */
|
|
110
|
+
export declare function ruleHeadParts(rows: readonly Pick<PathRuleDenyRow, "head">[]): string[];
|
|
73
111
|
/**
|
|
74
112
|
* The rule rows as a leg judges them: each row as spelled, and — for every location on the way to the row's HEAD (each
|
|
75
|
-
* leading part of it
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* deny / ask rule's literal prefix (cli280.js `ei` 9766583, compiled in `mt` 9767083). `leadsTo` is the leg's resolution,
|
|
85
|
-
* the one the faces mint their keys with (`undefined` = could not resolve: no twin there); each location is resolved
|
|
86
|
-
* once, and a part that only extends its parent's resolution adds no row.
|
|
113
|
+
* leading part of it) that the environment resolves to another spelling (a link or firmlink: `/tmp` → `/private/tmp`,
|
|
114
|
+
* `/var` → `/private/var`, a project root, a home entry or a directory inside the rule reached through a symlink) — the
|
|
115
|
+
* same row at that spelling, right after it: a path at or below the resolved location is judged as its spelling at or
|
|
116
|
+
* below that part. Every part, not the head alone: a link inside the rule's literal prefix that is re-pointed after the
|
|
117
|
+
* rows were placed still meets the row through the parts above it. One rule for every row form and every judge (the
|
|
118
|
+
* gate's lanes and the deny tables): the reference compiles the same "physical twin" of every deny / ask rule's literal
|
|
119
|
+
* prefix (cli280.js `ei` 9766583, compiled in `mt` 9767083). `leads` is the leg's resolution of each part (absent or
|
|
120
|
+
* `undefined` = no twin there); a part that only extends its parent's resolution adds no row. A twin is the row's own
|
|
121
|
+
* kind (it keeps every member but the head), so a judgment reports the rule the twin stands for.
|
|
87
122
|
*/
|
|
88
|
-
export declare function
|
|
123
|
+
export declare function rowsWhereTheyLead<R extends JudgedPathRow>(rows: readonly R[], leads: ReadonlyMap<string, string | undefined>): R[];
|
|
124
|
+
/** Where the location `path` itself leads in a leg's resolution (`leads`, keyed by each leading part — the parts
|
|
125
|
+
* {@link ruleHeadParts} names): `undefined` when the leg did not resolve it or it leads to its own spelling. */
|
|
126
|
+
export declare function locationLeadsTo(path: string, leads: ReadonlyMap<string, string | undefined>): string | undefined;
|
|
127
|
+
/** {@link rowsWhereTheyLead} with each part resolved by `leadsTo` (the leg's resolution; each part once). */
|
|
128
|
+
export declare function withRuleRowsWhereTheyLead<R extends JudgedPathRow>(rows: readonly R[], leadsTo: (head: string) => Promise<string | undefined>): Promise<R[]>;
|
|
89
129
|
/**
|
|
90
130
|
* The WRITE face of the deny table: the rule rows whose faces include `write` — the judge the shell station reads for a
|
|
91
131
|
* command's write targets (a redirection, `tee`, `sed -i`, `cp` / `mv` / `touch` / `mkdir` / `rm` operands). Same row
|
|
92
|
-
* type, same judgment (
|
|
132
|
+
* type, same judgment ({@link pathRowsReach}) as the read face's rule rows; a hit names the row by its rule text.
|
|
93
133
|
*/
|
|
94
134
|
export interface WriteDenyMatcher {
|
|
95
135
|
/** Which face this judge is — the write tools' path resolution reads it to word and code the refusal as a write. */
|
|
@@ -12,32 +12,62 @@ export function readDenyRunHead(run) {
|
|
|
12
12
|
return wild === -1 ? segments.length : wild;
|
|
13
13
|
}
|
|
14
14
|
export const DENY_FACES = Object.freeze(["read", "write"]);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (const
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
const REACHED = { reach: "reached" };
|
|
16
|
+
const NOT_REACHED = { reach: "not_reached" };
|
|
17
|
+
const reachOf = (row, spelled) => row.reach?.(spelled) ?? (row.reaches(spelled) ? REACHED : NOT_REACHED);
|
|
18
|
+
export function pathRowsReach(rows, views, direction) {
|
|
19
|
+
if (direction === "allow") {
|
|
20
|
+
if (views.unreadable !== undefined || views.spellings.length === 0)
|
|
21
|
+
return undefined;
|
|
22
|
+
for (const row of rows)
|
|
23
|
+
if (views.spellings.every((v) => reachOf(row, v).reach === "reached"))
|
|
24
|
+
return { row };
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
let held;
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
if (views.unreadable !== undefined)
|
|
30
|
+
held ??= { row, unreadable: views.unreadable };
|
|
31
|
+
for (const view of views.spellings) {
|
|
32
|
+
const r = reachOf(row, view);
|
|
33
|
+
if (r.reach === "reached")
|
|
34
|
+
return { row, view };
|
|
35
|
+
if (r.reach === "unreadable")
|
|
36
|
+
held ??= { row, unreadable: r.reason };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return held;
|
|
40
|
+
}
|
|
41
|
+
export function ruleHeadParts(rows) {
|
|
42
|
+
const out = new Set();
|
|
43
|
+
for (const row of rows)
|
|
44
|
+
if (row.head !== undefined)
|
|
45
|
+
for (const part of partsOf(row.head))
|
|
46
|
+
out.add(part);
|
|
47
|
+
return [...out];
|
|
48
|
+
}
|
|
49
|
+
function partsOf(head) {
|
|
50
|
+
const family = pathFamilyOf({ root: head });
|
|
51
|
+
const id = pathIdentityOf(head, family, head);
|
|
52
|
+
if (!id.ok)
|
|
53
|
+
return [head];
|
|
54
|
+
const unc = id.identity.startsWith("//");
|
|
55
|
+
const segments = id.identity.split("/").filter((seg) => seg !== "");
|
|
56
|
+
const parts = [];
|
|
57
|
+
for (let k = unc ? 2 : 1; k <= segments.length; k++)
|
|
58
|
+
parts.push(spellingOfIdentity(`${unc ? "//" : "/"}${segments.slice(0, k).join("/")}`, family));
|
|
59
|
+
return parts.length > 0 ? parts : [head];
|
|
60
|
+
}
|
|
61
|
+
export function rowsWhereTheyLead(rows, leads) {
|
|
34
62
|
const out = [];
|
|
35
|
-
for (const
|
|
63
|
+
for (const row of rows) {
|
|
36
64
|
out.push(row);
|
|
65
|
+
if (row.head === undefined || leads.size === 0)
|
|
66
|
+
continue;
|
|
37
67
|
let parentLed;
|
|
38
68
|
let parentPart;
|
|
39
|
-
for (const part of
|
|
40
|
-
const led =
|
|
69
|
+
for (const part of partsOf(row.head)) {
|
|
70
|
+
const led = leads.get(part);
|
|
41
71
|
const extendsParent = led !== undefined && parentLed !== undefined && parentPart !== undefined && sameLocation(led, `${parentLed.replace(/[\\/]+$/, "")}/${part.slice(parentPart.replace(/[\\/]+$/, "").length).replace(/^[\\/]+/, "")}`);
|
|
42
72
|
const twin = extendsParent ? undefined : rowLedTo(row, part, led);
|
|
43
73
|
if (twin !== undefined)
|
|
@@ -48,6 +78,15 @@ export async function withRuleRowsWhereTheyLead(rows, leadsTo) {
|
|
|
48
78
|
}
|
|
49
79
|
return out;
|
|
50
80
|
}
|
|
81
|
+
export function locationLeadsTo(path, leads) {
|
|
82
|
+
const parts = partsOf(path);
|
|
83
|
+
return leads.get(parts[parts.length - 1]);
|
|
84
|
+
}
|
|
85
|
+
export async function withRuleRowsWhereTheyLead(rows, leadsTo) {
|
|
86
|
+
const parts = ruleHeadParts(rows);
|
|
87
|
+
const led = await Promise.all(parts.map((part) => leadsTo(part).catch(() => undefined)));
|
|
88
|
+
return rowsWhereTheyLead(rows, new Map(parts.map((part, i) => [part, led[i]])));
|
|
89
|
+
}
|
|
51
90
|
function sameLocation(a, b) {
|
|
52
91
|
const family = pathFamilyOf({ root: a });
|
|
53
92
|
const x = pathIdentityOf(a, family, a);
|
|
@@ -65,50 +104,60 @@ function rowLedTo(row, head, at) {
|
|
|
65
104
|
const base = from.identity;
|
|
66
105
|
const under = base.endsWith("/") ? base : `${base}/`;
|
|
67
106
|
const onto = to.identity.endsWith("/") ? to.identity.slice(0, -1) : to.identity;
|
|
107
|
+
const mapped = (absolutePath) => {
|
|
108
|
+
const id = pathIdentityOf(absolutePath, family, head);
|
|
109
|
+
if (!id.ok)
|
|
110
|
+
return undefined;
|
|
111
|
+
if (id.identity === base)
|
|
112
|
+
return to.identity;
|
|
113
|
+
return id.identity.startsWith(under) ? `${onto}/${id.identity.slice(under.length)}` : undefined;
|
|
114
|
+
};
|
|
115
|
+
const { head: _head, ...rest } = row;
|
|
116
|
+
const reach = row.reach;
|
|
68
117
|
return {
|
|
69
|
-
|
|
70
|
-
tool: row.tool,
|
|
71
|
-
pattern: row.pattern,
|
|
72
|
-
faces: row.faces,
|
|
118
|
+
...rest,
|
|
73
119
|
reaches: (absolutePath) => {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
return false;
|
|
77
|
-
if (id.identity === base)
|
|
78
|
-
return row.reaches(to.identity);
|
|
79
|
-
return id.identity.startsWith(under) && row.reaches(`${onto}/${id.identity.slice(under.length)}`);
|
|
120
|
+
const m = mapped(absolutePath);
|
|
121
|
+
return m !== undefined && row.reaches(m);
|
|
80
122
|
},
|
|
123
|
+
...(reach !== undefined
|
|
124
|
+
? {
|
|
125
|
+
reach: (absolutePath) => {
|
|
126
|
+
const m = mapped(absolutePath);
|
|
127
|
+
return m === undefined ? NOT_REACHED : reach(m);
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
: {}),
|
|
81
131
|
};
|
|
82
132
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return r;
|
|
87
|
-
return undefined;
|
|
133
|
+
function ruleRowsHit(rows, face, spellings) {
|
|
134
|
+
const onFace = rows.filter((r) => r.faces.includes(face));
|
|
135
|
+
return onFace.length === 0 ? undefined : pathRowsReach(onFace, { spellings }, "tighten");
|
|
88
136
|
}
|
|
89
137
|
export function compileWriteDeny(rules = []) {
|
|
90
138
|
const ruleRows = rules.filter((r) => r.faces.includes("write"));
|
|
91
139
|
const matchPath = (path) => {
|
|
92
|
-
const hit =
|
|
93
|
-
return hit === undefined ? null : { pattern: hit.rule };
|
|
140
|
+
const hit = ruleRowsHit(ruleRows, "write", [path]);
|
|
141
|
+
return hit === undefined ? null : { pattern: hit.row.rule };
|
|
94
142
|
};
|
|
95
|
-
return { face: "write", ruleRows, matchPath, matchTarget: (canonicalKey, lexicalView, opts) => matchTargetOver(
|
|
143
|
+
return { face: "write", ruleRows, matchPath, matchTarget: (canonicalKey, lexicalView, opts) => matchTargetOver(() => null, ruleRows, "write", canonicalKey, lexicalView, opts) };
|
|
96
144
|
}
|
|
97
|
-
function matchTargetOver(
|
|
98
|
-
const canonical =
|
|
145
|
+
function matchTargetOver(segmentPath, ruleRows, face, canonicalKey, lexicalView, opts) {
|
|
146
|
+
const canonical = segmentPath(canonicalKey, opts);
|
|
99
147
|
if (canonical !== null)
|
|
100
148
|
return { ...canonical, matchedView: canonicalKey };
|
|
101
149
|
if (lexicalView !== undefined) {
|
|
102
|
-
const lexical =
|
|
150
|
+
const lexical = segmentPath(lexicalView);
|
|
103
151
|
if (lexical !== null)
|
|
104
152
|
return { ...lexical, matchedView: lexicalView };
|
|
105
153
|
}
|
|
106
154
|
for (const hop of opts?.hops ?? []) {
|
|
107
|
-
const viaHop =
|
|
155
|
+
const viaHop = segmentPath(hop);
|
|
108
156
|
if (viaHop !== null)
|
|
109
157
|
return { ...viaHop, matchedView: hop };
|
|
110
158
|
}
|
|
111
|
-
|
|
159
|
+
const hit = ruleRowsHit(ruleRows, face, [canonicalKey, ...(lexicalView !== undefined ? [lexicalView] : []), ...(opts?.hops ?? [])]);
|
|
160
|
+
return hit === undefined ? null : { pattern: hit.row.rule, matchedView: hit.view ?? canonicalKey };
|
|
112
161
|
}
|
|
113
162
|
export function readDenySeat(matcher) {
|
|
114
163
|
const entries = [...(matcher?.entries ?? []).map((e) => e.pattern), ...(matcher?.ruleRows ?? []).map((r) => r.rule)];
|
|
@@ -195,6 +244,7 @@ export const READ_FACE_BUILTIN_DENY_TABLE = [
|
|
|
195
244
|
{ pattern: "~/.mcp.json", tier: "agent-config" },
|
|
196
245
|
{ pattern: "~/.ai-agent/.env", tier: "agent-config" },
|
|
197
246
|
{ pattern: "~/.sema/engine-data/.env", tier: "agent-config" },
|
|
247
|
+
{ pattern: "~/.sema/config-lkg-*.json", tier: "agent-config" },
|
|
198
248
|
{ pattern: "~/.codex/config.toml", tier: "agent-config" },
|
|
199
249
|
{ pattern: "~/.cursor/mcp.json", tier: "agent-config" },
|
|
200
250
|
{ pattern: "~/.continue/config.json", tier: "agent-config" },
|
|
@@ -461,18 +511,22 @@ export function compileReadDeny(additions = [], layer = "additions", builtin, ru
|
|
|
461
511
|
return { pattern: hit };
|
|
462
512
|
return ruleRows.length === 0 ? null : { pattern: ruleRows[0].rule };
|
|
463
513
|
};
|
|
464
|
-
const
|
|
514
|
+
const segmentPath = (path, opts) => {
|
|
465
515
|
const hit = matchSegmentPatterns(path, judged, opts);
|
|
516
|
+
return hit === null ? null : { pattern: hit };
|
|
517
|
+
};
|
|
518
|
+
const matchPath = (path, opts) => {
|
|
519
|
+
const hit = segmentPath(path, opts);
|
|
466
520
|
if (hit !== null)
|
|
467
|
-
return
|
|
468
|
-
const rule = ruleRows.length === 0 || opts?.walked === true ? undefined :
|
|
469
|
-
return rule === undefined ? null : { pattern: rule.rule };
|
|
521
|
+
return hit;
|
|
522
|
+
const rule = ruleRows.length === 0 || opts?.walked === true ? undefined : ruleRowsHit(ruleRows, "read", [path]);
|
|
523
|
+
return rule === undefined ? null : { pattern: rule.row.rule };
|
|
470
524
|
};
|
|
471
525
|
return {
|
|
472
526
|
entries: normalized,
|
|
473
527
|
...(ruleRows.length > 0 ? { ruleRows } : {}),
|
|
474
528
|
matchPath,
|
|
475
|
-
matchTarget: (canonicalKey, lexicalView, opts) => matchTargetOver(
|
|
529
|
+
matchTarget: (canonicalKey, lexicalView, opts) => matchTargetOver(segmentPath, ruleRows, "read", canonicalKey, lexicalView, opts),
|
|
476
530
|
rgExclusionGlobs,
|
|
477
531
|
rgProbeGlobs,
|
|
478
532
|
unexcludableUnder,
|
|
@@ -43,12 +43,13 @@ export type RemovalFinding = (DangerousRemoval & {
|
|
|
43
43
|
/**
|
|
44
44
|
* The removal findings of an established plan — ONE walk, nested plans included (a `bash -c` / `sh -c` text, a substitution
|
|
45
45
|
* body), two words. For every `rm` / `rmdir` segment, each of its OPERANDS (every write target that is not one of its
|
|
46
|
-
* option words
|
|
47
|
-
* program unlinks, and the deny-floor shadow of a home word the plan did
|
|
48
|
-
* an operand the plan could not place that expands a name the command
|
|
49
|
-
* bindings give it ({@link boundSpellingsOf}), and one with a binding this
|
|
50
|
-
*
|
|
51
|
-
*
|
|
46
|
+
* option words: a word some implementation's reading of the program's row writes, {@link writeReadings}) as the plan
|
|
47
|
+
* established it on the WRITE face (the entry the program unlinks, and the deny-floor shadow of a home word the plan did
|
|
48
|
+
* not place) is judged by {@link dangerousRemovalOf}; an operand the plan could not place that expands a name the command
|
|
49
|
+
* itself binds is judged in every spelling those bindings give it ({@link boundSpellingsOf}), and one with a binding this
|
|
50
|
+
* reading cannot read is an unverifiable target (a recursive forced removal, {@link recursiveForce}). A shell handed a
|
|
51
|
+
* script built only when the command runs is a dangerous removal of its own ({@link runtimeScriptOf}). The read-face copy
|
|
52
|
+
* of an operand is not judged: it follows a link the removal does not follow.
|
|
52
53
|
*/
|
|
53
54
|
export declare function removalFindingsOf(established: EstablishedPlan, seat: RemovalSeat): RemovalFinding[];
|
|
54
55
|
/** Does any segment of this plan (nested plans included) run a removal program, or hand a shell a script built when it
|