@sema-agent/core 7.22.0 → 7.23.1
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 +210 -1
- package/dist/core/auto-compaction.d.ts +2 -2
- package/dist/core/checkpoint-store.d.ts +1 -5
- package/dist/core/context-edit.js +1 -1
- package/dist/core/engine-notice.d.ts +13 -0
- package/dist/core/gate-bounded-await.d.ts +14 -4
- package/dist/core/gate-bounded-await.js +1 -1
- package/dist/core/gate-crash-trace.d.ts +7 -0
- package/dist/core/gate-crash-trace.js +3 -0
- package/dist/core/gate-fold.js +5 -4
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.js +2 -1
- package/dist/core/park-selfcheck.js +1 -1
- package/dist/core/path-identity.d.ts +11 -4
- package/dist/core/path-identity.js +5 -2
- package/dist/core/read-root-candidate.js +11 -1
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +25 -8
- package/dist/core/runner/contracts.d.ts +15 -15
- package/dist/core/runner/gate-exit.d.ts +0 -5
- package/dist/core/runner/gate-exit.js +0 -3
- package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
- package/dist/core/runner/inherited-ask-grants.js +1 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-context-lane.d.ts +43 -1
- package/dist/core/runner/prepare-context-lane.js +25 -4
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/prepare-turn-wiring.js +2 -2
- package/dist/core/runner/run-harness-handlers.js +4 -2
- package/dist/core/runner/run-recovery-lanes.js +6 -3
- package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -25
- package/dist/core/runner/write-protection-station.js +18 -0
- package/dist/core/task-registry.d.ts +0 -6
- package/dist/core/task-result.d.ts +28 -10
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +64 -12
- package/dist/core/write-protect.js +87 -34
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
- package/dist/engine/execution-env/node-execution-env.js +5 -1
- package/dist/index.d.ts +1 -1
- package/dist/tools/fs/bash-lexer.d.ts +12 -5
- package/dist/tools/fs/bash-lexer.js +5 -2
- package/dist/tools/fs/bash-program-position.d.ts +18 -0
- package/dist/tools/fs/bash-program-position.js +13 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +10 -3
- package/dist/tools/fs/bash-readonly-classifier.js +70 -41
- package/dist/tools/fs/fs-bash.d.ts +22 -0
- package/dist/tools/fs/fs-bash.js +19 -12
- package/dist/tools/fs/fs-read.js +2 -1
- package/dist/tools/fs/fs-shared.d.ts +33 -7
- package/dist/tools/fs/fs-shared.js +12 -1
- package/dist/tools/fs/safety.d.ts +37 -0
- package/dist/tools/fs/safety.js +31 -3
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +3 -1
- package/wire-changes/core@7.23.0.json +316 -0
- package/wire-changes/core@7.23.1.json +205 -0
package/dist/core/types.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditR
|
|
|
18
18
|
export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
|
|
19
19
|
export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
|
|
20
20
|
export type { TaskLimits } from "./task-limits.js";
|
|
21
|
-
export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult } from "./task-result.js";
|
|
21
|
+
export type { TaskStatus, RemoteEnvFailureNote, EffectiveMemoryScopes, TaskResult, HumanReviewGateRow } from "./task-result.js";
|
|
22
22
|
export type { TerminalCause, PausedCause } from "./terminal-cause.js";
|
|
23
23
|
export type { TaskEventIdentity, BrainStatusPhase, BrainRetryErrClass, BrainStatus, ToolActivity, HumanInputSource, HumanInputDelivery, DelegationTaskType, TaskEvent, HumanInputEvent, CompactOutcome } from "./task-event.js";
|
|
24
24
|
export type { TaskStream } from "./task-stream.js";
|
|
@@ -30,13 +30,26 @@ export interface WriteProtectedHit {
|
|
|
30
30
|
}
|
|
31
31
|
export declare const WRITE_PROTECTED_DEFAULT_TABLE: readonly WriteProtectedRow[];
|
|
32
32
|
/**
|
|
33
|
-
* The ONE
|
|
34
|
-
* compile, path segments at match)
|
|
35
|
-
* (U+0131 dotless ı → i, U+017F long ſ → s)
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* The ONE SEGMENT fold of this module, applied to BOTH sides of every comparison (table names at
|
|
34
|
+
* compile, path segments at match), in the reference implementation's own order:
|
|
35
|
+
* · `toLowerCase` plus the two confusable letters its fold maps (U+0131 dotless ı → i, U+017F long ſ → s);
|
|
36
|
+
* · the INVISIBLE FORMAT characters it strips — the zero-width joiners/non-joiners, the bidirectional
|
|
37
|
+
* overrides and the byte-order mark (U+200C–U+200F, U+202A–U+202E, U+206A–U+206F, U+FEFF), verbatim.
|
|
38
|
+
* A segment carrying one of these LOOKS like the row it is not, on both sides of every comparison, so
|
|
39
|
+
* the strip is a deception axis rather than a platform one: it applies to every reading below, not to
|
|
40
|
+
* the win32 one alone. It can only ADD matches, which on a TIGHTEN costs one extra question — and the
|
|
41
|
+
* data-root passthrough, where an added match would be a guard BYPASSED, deliberately does not use this
|
|
42
|
+
* fold at all (its comparison is byte-exact; see {@link relativeUnderDataRoot}).
|
|
43
|
+
* · …and a strip that would EMPTY the segment keeps the segment instead (the reference fold's own `|| n`):
|
|
44
|
+
* an all-invisible component names a real file, and folding it to "" would make it collide with nothing
|
|
45
|
+
* or with everything depending on the caller.
|
|
46
|
+
* Deliberately NOT full Unicode confusable folding: that would be a different, wider claim than the one made
|
|
47
|
+
* (the read deny set's ASCII-contract doc states the same boundary for its own fold). The NTFS data-stream
|
|
48
|
+
* suffix the reference fold also strips is a win32 NAMESPACE fact, not a comparison one, and lives with the
|
|
49
|
+
* other component normalization of those views ({@link ComponentNormalization}) so a POSIX file legally
|
|
50
|
+
* named `a:b` keeps its own identity on the POSIX reading.
|
|
38
51
|
*/
|
|
39
|
-
export declare function
|
|
52
|
+
export declare function foldWriteProtectSegment(s: string): string;
|
|
40
53
|
/**
|
|
41
54
|
* Resolve the ACTIVE table under the deployment seat (validated, loud — #123's bad-value states all
|
|
42
55
|
* throw and name the knob). `undefined` = the default table; `[]` = NO table (an explicit, legal
|
|
@@ -47,15 +60,32 @@ export declare function foldWriteProtectCase(s: string): string;
|
|
|
47
60
|
* candidate configuration with the engine's own rules.
|
|
48
61
|
*/
|
|
49
62
|
export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
|
|
63
|
+
/**
|
|
64
|
+
* WHY the ENVIRONMENT half of one judgment could not contribute — the canonicalizer refused the spelling,
|
|
65
|
+
* a link the tree could not follow, a namespace spelling it declines, an environment call that threw.
|
|
66
|
+
* Reported through {@link WriteProtectionCompileOptions.onViewAbsent}; it is a DISCLOSURE, never a verdict
|
|
67
|
+
* (see the absence law on {@link WriteProtectionMatcher.judge}).
|
|
68
|
+
*/
|
|
69
|
+
export interface WriteProtectionViewAbsence {
|
|
70
|
+
/** The environment's own sentence, verbatim — what it said when asked to resolve this target. */
|
|
71
|
+
readonly cause: string;
|
|
72
|
+
/** The failure was specifically a symlink whose real target is unknowable, as opposed to a spelling the
|
|
73
|
+
* tree refuses or a probe that errored. */
|
|
74
|
+
readonly unresolvedSymlink: boolean;
|
|
75
|
+
}
|
|
50
76
|
/**
|
|
51
77
|
* The judge's answer for ONE covered write ({@link WriteProtectionMatcher.judge}):
|
|
52
|
-
* · `clear` — no row on
|
|
78
|
+
* · `clear` — no row on any view that could answer: the write is not this layer's business;
|
|
53
79
|
* · `hit` — a row matched; `via` says which view named it: `"spelling"` (the anchored spelling, no
|
|
54
80
|
* filesystem consulted), `"chain"` (a spelling the leaf symlink chain passes through, judged as a
|
|
55
81
|
* spelling) or `"target"` (the resolved real target, the spelling being innocent);
|
|
56
|
-
* · `unresolvable` — the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
82
|
+
* · `unresolvable` — NO half of the judgment answered at all. The judge itself no longer mints this: an
|
|
83
|
+
* environment that cannot resolve the target has ANSWERED, and an answer of "I cannot resolve this"
|
|
84
|
+
* leaves the lexical half's judgment standing (the absence law below). What is left for this verdict is
|
|
85
|
+
* the judgment that never happened — the gate's bounded wait elapsing or the task ending before
|
|
86
|
+
* anything came back (`awaitWriteProtectionJudgment`, gate-bounded-await.ts) — where there is no
|
|
87
|
+
* lexical answer to stand on either, and the consumer REFUSES. `unresolvedSymlink` is that minter's
|
|
88
|
+
* `false`: a wait that produced nothing knows nothing about what it was waiting for.
|
|
59
89
|
*/
|
|
60
90
|
export type WriteProtectionVerdict = {
|
|
61
91
|
readonly kind: "clear";
|
|
@@ -115,8 +145,8 @@ export interface WriteProtectionMatcher {
|
|
|
115
145
|
* @contract write_protection.judges_the_real_target — the always-on write-protection table and the
|
|
116
146
|
* opt-in sensitive-path deny set differ in VOCABULARY (literal rows / segment globs) and SEVERITY
|
|
117
147
|
* (ask / deny), never in REACH: both judge the file the tool will write — the spelling anchored at the
|
|
118
|
-
* live cwd AND its resolved real target, through one canonicalizer
|
|
119
|
-
*
|
|
148
|
+
* live cwd AND its resolved real target, through one canonicalizer. Where they DO part: a target the write-protection
|
|
149
|
+
* judge cannot resolve leaves its resolved view ABSENT (the spelling still judges); the deny set still REFUSES whole. An environment-less judge has no target view and answers by the spelling alone —
|
|
120
150
|
* the same absence law the cwd follows (absent ⇒ the spelling is judged exactly as given). REACH is what
|
|
121
151
|
* this sentence promises, not a LOCK: the identity is resolved at judgment time and re-resolved by the
|
|
122
152
|
* tool at execute time (the module header states that window and the seat that would close it).
|
|
@@ -136,6 +166,17 @@ export interface WriteProtectionCompileOptions {
|
|
|
136
166
|
* path-family source when the call carries no cwd. Omitted ⇒ relative spellings resolve against the
|
|
137
167
|
* cwd, then the environment's own working directory. */
|
|
138
168
|
readonly rootPath?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Called at most ONCE per judgment, at the moment the ENVIRONMENT half is found unable to contribute
|
|
171
|
+
* ({@link WriteProtectionViewAbsence}) — before the judgment returns, and on the hit arm as well as the
|
|
172
|
+
* clear one, because the fact is about the ENVIRONMENT rather than about the answer it did not change.
|
|
173
|
+
* Omitted ⇒ the absence is silent, which is the honest default for a judge compiled outside a leg.
|
|
174
|
+
* A throwing sink is the caller's bug and is not caught here; the leg that wires one wraps it.
|
|
175
|
+
*
|
|
176
|
+
* Deliberately carries no call identity: what it reports is a deployment-level fact (this environment
|
|
177
|
+
* cannot answer path questions), and its consumer announces once per leg rather than once per write.
|
|
178
|
+
*/
|
|
179
|
+
readonly onViewAbsent?: (absence: WriteProtectionViewAbsence) => void;
|
|
139
180
|
}
|
|
140
181
|
/**
|
|
141
182
|
* Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
|
|
@@ -204,5 +245,16 @@ export interface WriteProtectionMount {
|
|
|
204
245
|
* environment cannot RESOLVE is the judge's own `unresolvable` verdict, not this null.
|
|
205
246
|
* Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
|
|
206
247
|
* `opts` are the judge's own seats, passed straight through ({@link compileWriteProtection}).
|
|
248
|
+
*
|
|
249
|
+
* @contract write_protection.membership_is_the_call — WHICH calls the always-on table judges is a
|
|
250
|
+
* PREDICATE OVER THE CALL and nothing else: the call's own face declares a path target whose access is not
|
|
251
|
+
* a read, so a custom writer joins this judge by DECLARING what it writes and a tool that declares nothing
|
|
252
|
+
* is not this layer's business under any name. There is deliberately NO name-list seat beside it, and the
|
|
253
|
+
* asymmetry with the opt-in deny policy — which does take one (`createSensitivePathPolicy({ tools })`, its
|
|
254
|
+
* own pre-declaration hatch) — is the intended shape rather than a gap: a name list on a default-ON floor
|
|
255
|
+
* is a way to narrow that floor silently, one tool name at a time, while the seat this layer gives a
|
|
256
|
+
* deployment for narrowing is the whole-table replacement, which says what it removes. A deployment that
|
|
257
|
+
* wants name-list membership composes the deny policy alongside; the two then answer under the shared
|
|
258
|
+
* `deny > ask > allow` fold, which is what makes the missing seat cost nothing.
|
|
207
259
|
*/
|
|
208
260
|
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMount | undefined;
|
|
@@ -57,8 +57,19 @@ export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
|
|
|
57
57
|
{ name: ".sema.json", kind: "basename" },
|
|
58
58
|
{ name: ".sema", kind: "segment" },
|
|
59
59
|
]);
|
|
60
|
-
export function
|
|
61
|
-
|
|
60
|
+
export function foldWriteProtectSegment(s) {
|
|
61
|
+
const cased = s.toLowerCase().replace(/ı/g, "i").replace(/ſ/g, "s");
|
|
62
|
+
return stripInvisibleFormat(cased) || cased;
|
|
63
|
+
}
|
|
64
|
+
function stripInvisibleFormat(s) {
|
|
65
|
+
return s.replace(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/g, "");
|
|
66
|
+
}
|
|
67
|
+
function stripWin32Component(raw, normalization) {
|
|
68
|
+
const base = normalization.invisibleFirst ? stripInvisibleFormat(raw) : raw;
|
|
69
|
+
return (normalization.stream ? base.replace(/:.*$/, "") : base).replace(/[. ]+$/, "");
|
|
70
|
+
}
|
|
71
|
+
function foldWriteProtectName(name) {
|
|
72
|
+
return name.split("/").map(foldWriteProtectSegment).join("/");
|
|
62
73
|
}
|
|
63
74
|
const KINDS = ["basename", "segment", "segment-run"];
|
|
64
75
|
function describeEntryValue(v) {
|
|
@@ -107,7 +118,7 @@ export function resolveWriteProtectedTable(entries) {
|
|
|
107
118
|
const seen = new Set();
|
|
108
119
|
for (const entry of entries) {
|
|
109
120
|
const row = normalizeWriteProtectedEntry(entry);
|
|
110
|
-
const key = `${row.kind}:${
|
|
121
|
+
const key = `${row.kind}:${foldWriteProtectName(row.name)}`;
|
|
111
122
|
if (seen.has(key))
|
|
112
123
|
continue;
|
|
113
124
|
seen.add(key);
|
|
@@ -115,8 +126,9 @@ export function resolveWriteProtectedTable(entries) {
|
|
|
115
126
|
}
|
|
116
127
|
return freezeTable(out);
|
|
117
128
|
}
|
|
118
|
-
function pathViewSegments(path,
|
|
129
|
+
function pathViewSegments(path, normalization, seps = BOTH_SEPARATOR_FAMILIES, anchorFloor = 0) {
|
|
119
130
|
const segs = [];
|
|
131
|
+
const win32 = normalization !== "literal";
|
|
120
132
|
for (const raw of path.split(seps)) {
|
|
121
133
|
if (raw.length === 0)
|
|
122
134
|
continue;
|
|
@@ -131,11 +143,17 @@ function pathViewSegments(path, win32, seps = BOTH_SEPARATOR_FAMILIES, anchorFlo
|
|
|
131
143
|
segs.push("..");
|
|
132
144
|
continue;
|
|
133
145
|
}
|
|
134
|
-
const stripped =
|
|
146
|
+
const stripped = normalization === "literal" ? raw : stripWin32Component(raw, normalization);
|
|
135
147
|
segs.push(stripped.length > 0 ? stripped : raw);
|
|
136
148
|
}
|
|
137
149
|
return segs;
|
|
138
150
|
}
|
|
151
|
+
const WIN32_NORMALIZATIONS = [
|
|
152
|
+
{ invisibleFirst: false, stream: false },
|
|
153
|
+
{ invisibleFirst: false, stream: true },
|
|
154
|
+
{ invisibleFirst: true, stream: false },
|
|
155
|
+
{ invisibleFirst: true, stream: true },
|
|
156
|
+
];
|
|
139
157
|
const BOTH_SEPARATOR_FAMILIES = /[\\/]/;
|
|
140
158
|
const POSIX_SEPARATOR_FAMILY = /\//;
|
|
141
159
|
function uncAnchorSegments(path) {
|
|
@@ -149,7 +167,7 @@ function relativeUnderDataRoot(path, roots) {
|
|
|
149
167
|
if (root.win ? !isWinFormPath(path) : isWinFormPath(path) || !path.startsWith("/"))
|
|
150
168
|
continue;
|
|
151
169
|
const seps = root.win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY;
|
|
152
|
-
const segs = pathViewSegments(path,
|
|
170
|
+
const segs = pathViewSegments(path, "literal", seps, uncAnchorSegments(path));
|
|
153
171
|
if (segs.length < root.segments.length)
|
|
154
172
|
continue;
|
|
155
173
|
if (root.segments.every((s, i) => segs[i] === s))
|
|
@@ -165,13 +183,13 @@ export function compileWriteProtection(entries, opts) {
|
|
|
165
183
|
const dataRootPaths = dataRootSpellings(opts?.dataRoot);
|
|
166
184
|
const rootViews = dataRootPaths.map((r) => {
|
|
167
185
|
const win = isWinFormPath(r);
|
|
168
|
-
return { win, segments: pathViewSegments(r,
|
|
186
|
+
return { win, segments: pathViewSegments(r, "literal", win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY, uncAnchorSegments(r)) };
|
|
169
187
|
});
|
|
170
188
|
const basenames = new Map();
|
|
171
189
|
const segments = new Map();
|
|
172
190
|
const runs = [];
|
|
173
191
|
for (const row of rows) {
|
|
174
|
-
const folded =
|
|
192
|
+
const folded = foldWriteProtectName(row.name);
|
|
175
193
|
if (row.kind === "basename") {
|
|
176
194
|
if (!basenames.has(folded))
|
|
177
195
|
basenames.set(folded, row.name);
|
|
@@ -187,7 +205,7 @@ export function compileWriteProtection(entries, opts) {
|
|
|
187
205
|
const judgeSegments = (raw) => {
|
|
188
206
|
if (raw.length === 0)
|
|
189
207
|
return null;
|
|
190
|
-
const segs = raw.map(
|
|
208
|
+
const segs = raw.map(foldWriteProtectSegment);
|
|
191
209
|
const base = basenames.get(segs[segs.length - 1] ?? "");
|
|
192
210
|
if (base !== undefined)
|
|
193
211
|
return { name: base, kind: "basename" };
|
|
@@ -205,9 +223,20 @@ export function compileWriteProtection(entries, opts) {
|
|
|
205
223
|
}
|
|
206
224
|
return null;
|
|
207
225
|
};
|
|
208
|
-
const judgeViews = (spelling) =>
|
|
209
|
-
judgeSegments(pathViewSegments(spelling,
|
|
210
|
-
|
|
226
|
+
const judgeViews = (spelling) => {
|
|
227
|
+
const posix = judgeSegments(pathViewSegments(spelling, "literal", POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling)));
|
|
228
|
+
if (posix !== null)
|
|
229
|
+
return posix;
|
|
230
|
+
const bothSeparators = judgeSegments(pathViewSegments(spelling, "literal"));
|
|
231
|
+
if (bothSeparators !== null)
|
|
232
|
+
return bothSeparators;
|
|
233
|
+
for (const normalization of WIN32_NORMALIZATIONS) {
|
|
234
|
+
const hit = judgeSegments(pathViewSegments(spelling, normalization));
|
|
235
|
+
if (hit !== null)
|
|
236
|
+
return hit;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
};
|
|
211
240
|
const judgeSpelled = (spelled) => judgeViews(relativeUnderDataRoot(spelled, rootViews) ?? spelled);
|
|
212
241
|
const judgeSpelling = (path, cwd) => {
|
|
213
242
|
const base = typeof cwd === "string" && cwd.length > 0 ? cwd : undefined;
|
|
@@ -231,31 +260,55 @@ export function compileWriteProtection(entries, opts) {
|
|
|
231
260
|
return { kind: "clear" };
|
|
232
261
|
const family = pathFamilyOf({ root: rootPath, cwd: ctx.cwd ?? env.cwd });
|
|
233
262
|
const base = ctx.base ?? ctx.cwd ?? rootPath;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
263
|
+
let noted = false;
|
|
264
|
+
const noteAbsence = (cause, unresolvedSymlink) => {
|
|
265
|
+
if (noted)
|
|
266
|
+
return;
|
|
267
|
+
noted = true;
|
|
268
|
+
opts?.onViewAbsent?.({ cause, unresolvedSymlink });
|
|
269
|
+
};
|
|
270
|
+
let canonKey;
|
|
271
|
+
try {
|
|
272
|
+
const canon = await canonicalizeTarget(env, path, ctx.signal, family, base);
|
|
273
|
+
if (canon.ok)
|
|
274
|
+
canonKey = canon.key;
|
|
275
|
+
else
|
|
276
|
+
noteAbsence(canon.message, canon.unresolvedSymlink === true);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
noteAbsence(err instanceof Error ? err.message : String(err), false);
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
if (canonKey !== undefined) {
|
|
283
|
+
const byTarget = judgeViews(canonKey);
|
|
284
|
+
if (byTarget !== null) {
|
|
285
|
+
for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
|
|
286
|
+
const relative = relativeUnderRoot(canonKey, rootKey);
|
|
287
|
+
if (relative === null)
|
|
288
|
+
continue;
|
|
289
|
+
if (relative === "")
|
|
290
|
+
return { kind: "clear" };
|
|
291
|
+
const remainder = judgeViews(relative);
|
|
292
|
+
return remainder === null ? { kind: "clear" } : { kind: "hit", hit: remainder, via: "target" };
|
|
293
|
+
}
|
|
294
|
+
return { kind: "hit", hit: byTarget, via: "target" };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
for (const hop of await symlinkChainHops(env, path, ctx.signal, family, base, (cause) => noteAbsence(cause, false))) {
|
|
298
|
+
const byHop = judgeSpelled(hop);
|
|
299
|
+
if (byHop === null)
|
|
242
300
|
continue;
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
301
|
+
if (canonKey !== undefined) {
|
|
302
|
+
for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
|
|
303
|
+
if (relativeUnderRoot(canonKey, rootKey) !== null)
|
|
304
|
+
return { kind: "clear" };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return { kind: "hit", hit: byHop, via: "chain" };
|
|
247
308
|
}
|
|
248
|
-
return { kind: "hit", hit: byTarget, via: "target" };
|
|
249
309
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (byHop === null)
|
|
253
|
-
continue;
|
|
254
|
-
for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
|
|
255
|
-
if (relativeUnderRoot(canon.key, rootKey) !== null)
|
|
256
|
-
return { kind: "clear" };
|
|
257
|
-
}
|
|
258
|
-
return { kind: "hit", hit: byHop, via: "chain" };
|
|
310
|
+
catch (err) {
|
|
311
|
+
noteAbsence(err instanceof Error ? err.message : String(err), false);
|
|
259
312
|
}
|
|
260
313
|
return { kind: "clear" };
|
|
261
314
|
},
|
|
@@ -7,6 +7,21 @@ import type { SchedulerCapability, ScheduledIntent, SchedulerContext, ScheduledT
|
|
|
7
7
|
import { SchedulerError } from "../../core/scheduler.js";
|
|
8
8
|
/** Convert user-facing timeout seconds into a positive, timer-safe millisecond delay. */
|
|
9
9
|
export declare function resolveExecTimeoutMs(timeoutSeconds: unknown): number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Anchor a RELATIVE symlink target at its link's own directory, UNFOLDED and with exactly one separator
|
|
12
|
+
* between the two. A directory that IS a root already ends in its own separator (`/`, `C:\`, `C:/`,
|
|
13
|
+
* `\\host\share\`, all of which `dirname` answers that way), and concatenating a second one mints a
|
|
14
|
+
* DOUBLE-separator spelling for a path that has one spelling — `/tmp -> private/tmp` came back as
|
|
15
|
+
* `//private/tmp`, which the judges that walk this chain then match against nothing, because a guarded row
|
|
16
|
+
* and a containment root are both written with single separators. Joining through `path.resolve`/`path.join`
|
|
17
|
+
* would fix the separator and fold `..` with it, which is the one thing this walk may not do.
|
|
18
|
+
*
|
|
19
|
+
* `separator` is the one the caller's `dirname` writes, passed rather than read from the module so the rule
|
|
20
|
+
* is a pure function of its inputs. A backslash counts as a terminator only where it IS a separator: on a
|
|
21
|
+
* POSIX tree a directory whose name ends in one (`/a/b\`) is an ordinary name, and reading it as terminated
|
|
22
|
+
* would rename `/a/b\/t` to `/a/b\t` — the same class of rewrite as folding `..` lexically.
|
|
23
|
+
*/
|
|
24
|
+
export declare function anchorAtLinkDir(linkDir: string, target: string, separator: string): string;
|
|
10
25
|
/** review finding (win32): `where bash.exe` can resolve to `System32\bash.exe` — the
|
|
11
26
|
* WSL LAUNCHER, not a native shell. Running it drops into a Linux subsystem where Windows cwd/paths
|
|
12
27
|
* do not exist; silently adopting it is exactly the "wrong shell, false green" failure D1 forbids.
|
|
@@ -171,8 +186,10 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
|
|
|
171
186
|
* target, until a hop is not a link (`EINVAL`), is absent (`ENOENT` / `ENOTDIR`), repeats, or the bound is
|
|
172
187
|
* reached. A relative target is ANCHORED at its link's directory and never folded — `..` is the filesystem's
|
|
173
188
|
* to resolve during the next `readlink`'s own lookup (a lexical fold reads `bridge/../next` as `next` and misses
|
|
174
|
-
* every link behind `bridge`).
|
|
175
|
-
*
|
|
189
|
+
* every link behind `bridge`). Anchoring puts EXACTLY ONE separator between the directory and the target
|
|
190
|
+
* ({@link anchorAtLinkDir}); a root directory already ends in its own. One syscall per hop, no `lstat`. On a
|
|
191
|
+
* win32 host a share-shaped hop is kept and ends the walk unprobed. Any other errno is an answer nobody can
|
|
192
|
+
* use (`err`), never a shortened chain.
|
|
176
193
|
*/
|
|
177
194
|
canonicalChain(path: string): Promise<Result<SymlinkChain, FileError>>;
|
|
178
195
|
exists(path: string): Promise<Result<boolean, FileError>>;
|
|
@@ -42,6 +42,10 @@ function fileKindFromStats(stats) {
|
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
|
44
44
|
const HAS_POSIX_MODE_FACE = process.platform !== "win32";
|
|
45
|
+
export function anchorAtLinkDir(linkDir, target, separator) {
|
|
46
|
+
const endsInSeparator = linkDir.endsWith(separator) || (separator === "\\" && linkDir.endsWith("/"));
|
|
47
|
+
return endsInSeparator ? `${linkDir}${target}` : `${linkDir}${separator}${target}`;
|
|
48
|
+
}
|
|
45
49
|
const SYMLINK_CHAIN_HOP_BOUND = 64;
|
|
46
50
|
function fileInfoFromStats(path, stats) {
|
|
47
51
|
const kind = fileKindFromStats(stats);
|
|
@@ -1043,7 +1047,7 @@ export class NodeExecutionEnv {
|
|
|
1043
1047
|
return ok({ hops });
|
|
1044
1048
|
return err(toFileError(error, cur));
|
|
1045
1049
|
}
|
|
1046
|
-
const next = isAbsolute(target) ? target :
|
|
1050
|
+
const next = isAbsolute(target) ? target : anchorAtLinkDir(dirname(cur), target, sep);
|
|
1047
1051
|
if (seen.has(next))
|
|
1048
1052
|
break;
|
|
1049
1053
|
hops.push(next);
|
package/dist/index.d.ts
CHANGED
|
@@ -105,7 +105,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, ty
|
|
|
105
105
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
106
106
|
export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
|
|
107
107
|
export { deploymentFaceClampNotice, resolveContainmentFace, type ContainmentFace, type ContainmentFaceInputs, type ContainmentFaceKind, type ContainmentFaceLayer, } from "./tools/fs/index.js";
|
|
108
|
-
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, WRITE_PROTECTION_TARGET_VIEWS, type WriteProtectionTargetView, type WriteProtectionMount, } from "./core/write-protect.js";
|
|
108
|
+
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionViewAbsence, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, WRITE_PROTECTION_TARGET_VIEWS, type WriteProtectionTargetView, type WriteProtectionMount, } from "./core/write-protect.js";
|
|
109
109
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
|
|
110
110
|
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type TerminalClaimIntent, type TerminalClaimOutcome, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, type CheckpointRow, realApprovalOrgFact, resolveCheckpointStore, checkpointStoreExecutionUndeclared, } from "./core/checkpoint-store.js";
|
|
111
111
|
export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, type ExecutionOutcomeRecordWord, type ExecutionRecordRowFacts, type ExecutionVerdict, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
|
|
@@ -92,20 +92,27 @@ export interface SegmentTokens {
|
|
|
92
92
|
/** What a substitution leaves in a word's FOLDED spelling: a marker no argv word can spell, so a consumer that
|
|
93
93
|
* judges words sees "this word carries text the shell computes" rather than the computing text itself. */
|
|
94
94
|
export declare const SUBSTITUTION_PLACEHOLDER = " subst";
|
|
95
|
+
export type QuoteOpen = '"' | "'" | undefined;
|
|
95
96
|
/**
|
|
96
|
-
* Which
|
|
97
|
-
* quote characters themselves
|
|
97
|
+
* Which QUOTE holds each character of `s`, by the same pairing rule the lexer walks with, and the boolean view
|
|
98
|
+
* of it. The pairing quote characters themselves belong to the region they bound — they are not operators
|
|
99
|
+
* either way — so `openedBy` names that region's quote character at their own index too.
|
|
100
|
+
*
|
|
101
|
+
* `quoted` is a VIEW of `openedBy`, minted in the same step: a consumer asking only "is this inside quotes"
|
|
102
|
+
* reads it, one asking WHICH quote (single quotes suppress every expansion, double quotes suppress neither `$`
|
|
103
|
+
* nor the backtick) reads `openedBy`, and the two cannot drift.
|
|
98
104
|
*
|
|
99
105
|
* `balanced` is false when a region is still open at the end of the string. Callers must then NOT use the mask:
|
|
100
106
|
* an unterminated quote is a string bash itself cannot parse, and every consumer falls back to the quote-BLIND
|
|
101
107
|
* reading, which is the stricter one.
|
|
102
108
|
*
|
|
103
109
|
* EXACTNESS. This models bash only for the residual character space the strict faces operate in, where backslash
|
|
104
|
-
* escapes
|
|
105
|
-
*
|
|
106
|
-
*
|
|
110
|
+
* escapes and subshells are rejected outright — there `'` and `"` pairing is the whole of quoting, so a region
|
|
111
|
+
* computed here is exactly a region bash quotes. A caller deciding about `$` or a backtick on this mask owes
|
|
112
|
+
* the argument in `bash-readonly-classifier.ts`. It is NOT a general shell quoting parser.
|
|
107
113
|
*/
|
|
108
114
|
export declare function quoteMask(s: string): {
|
|
115
|
+
openedBy: readonly QuoteOpen[];
|
|
109
116
|
quoted: readonly boolean[];
|
|
110
117
|
balanced: boolean;
|
|
111
118
|
};
|
|
@@ -5,14 +5,17 @@ function quoteStep(open, ch) {
|
|
|
5
5
|
return open === ch ? undefined : open;
|
|
6
6
|
}
|
|
7
7
|
export function quoteMask(s) {
|
|
8
|
+
const openedBy = new Array(s.length).fill(undefined);
|
|
8
9
|
const quoted = new Array(s.length).fill(false);
|
|
9
10
|
let open;
|
|
10
11
|
for (let i = 0; i < s.length; i++) {
|
|
11
12
|
const before = open;
|
|
12
13
|
open = quoteStep(open, s[i]);
|
|
13
|
-
|
|
14
|
+
const holds = before ?? open;
|
|
15
|
+
openedBy[i] = holds;
|
|
16
|
+
quoted[i] = holds !== undefined;
|
|
14
17
|
}
|
|
15
|
-
return { quoted, balanced: open === undefined };
|
|
18
|
+
return { openedBy, quoted, balanced: open === undefined };
|
|
16
19
|
}
|
|
17
20
|
export function literalTildeSpelling(word) {
|
|
18
21
|
return word.startsWith("~") ? `./${word}` : word;
|
|
@@ -114,6 +114,9 @@ export type ProgramPositionRefusal = {
|
|
|
114
114
|
readonly kind: "control_syntax_at_program_position";
|
|
115
115
|
readonly owner: string;
|
|
116
116
|
readonly word: string;
|
|
117
|
+
/** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL stands at the program
|
|
118
|
+
* position. Absent ⇒ genuine control syntax, which IS one. */
|
|
119
|
+
readonly runsShell?: true;
|
|
117
120
|
};
|
|
118
121
|
export interface ProgramPosition {
|
|
119
122
|
/** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
|
|
@@ -159,5 +162,20 @@ export declare function readProgramPosition(toks: SegmentTokens): ProgramPositio
|
|
|
159
162
|
* stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every
|
|
160
163
|
* consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
|
|
161
164
|
export declare function argvReentryUnjudged(pos: ProgramPosition, acceptedTargets: ReadonlySet<string>): boolean;
|
|
165
|
+
/**
|
|
166
|
+
* The two ways this reader DECLINES rather than judges — a CLOSED set, because which of them a seat reads as
|
|
167
|
+
* evidence is a table each seat declares against (`fs-bash.ts`, where the argument is), not a boolean buried
|
|
168
|
+
* in a caller. `unreadable_program_position`: the walk cannot name the program bash will run (`env -- ls`).
|
|
169
|
+
* `stream_fed_operands`: the operands arrive on a STREAM it never saw (`… | xargs cat`). Neither names a WORD.
|
|
170
|
+
*/
|
|
171
|
+
export declare const SHELL_SCAN_DECLINES: readonly ["unreadable_program_position", "stream_fed_operands"];
|
|
172
|
+
export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number];
|
|
173
|
+
/** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher
|
|
174
|
+
* ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings
|
|
175
|
+
* cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */
|
|
176
|
+
export declare function programPositionRunsShell(pos: ProgramPosition, shellProgram: RegExp): boolean;
|
|
177
|
+
/** Which decline, if any, this program position raises — ONE reading of the two facts above, so no consumer
|
|
178
|
+
* re-derives either. A refusal that says a SHELL runs is NOT one: it names what runs. */
|
|
179
|
+
export declare function shellScanDeclineOf(pos: ProgramPosition, acceptedStreamTargets: ReadonlySet<string>): ShellScanDecline | undefined;
|
|
162
180
|
/** ONE sentence per refusal kind, minted here and nowhere else. */
|
|
163
181
|
export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;
|
|
@@ -107,7 +107,7 @@ export function readProgramPosition(toks) {
|
|
|
107
107
|
}
|
|
108
108
|
const launchers = [{ index: i, name: basename }];
|
|
109
109
|
const owner = head;
|
|
110
|
-
const shellRefusal = () => ({ kind: "control_syntax_at_program_position", owner, word: "sh" });
|
|
110
|
+
const shellRefusal = () => ({ kind: "control_syntax_at_program_position", owner, word: "sh", runsShell: true });
|
|
111
111
|
let owedOperands = LAUNCHER_TABLE.get(basename)?.requiredOperands ?? 0;
|
|
112
112
|
let lastRow = LAUNCHER_TABLE.get(basename);
|
|
113
113
|
const chain0 = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin: false, runsDefaultProgram: false, program, refusal });
|
|
@@ -158,6 +158,18 @@ export function argvReentryUnjudged(pos, acceptedTargets) {
|
|
|
158
158
|
const runs = pos.program?.basename ?? (pos.runsDefaultProgram ? ARGV_REENTRY_DEFAULT_PROGRAM : undefined);
|
|
159
159
|
return runs === undefined || !acceptedTargets.has(runs);
|
|
160
160
|
}
|
|
161
|
+
export const SHELL_SCAN_DECLINES = ["unreadable_program_position", "stream_fed_operands"];
|
|
162
|
+
export function programPositionRunsShell(pos, shellProgram) {
|
|
163
|
+
if (pos.refusal !== undefined)
|
|
164
|
+
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true;
|
|
165
|
+
return pos.program !== undefined && shellProgram.test(pos.program.basename);
|
|
166
|
+
}
|
|
167
|
+
export function shellScanDeclineOf(pos, acceptedStreamTargets) {
|
|
168
|
+
if (pos.refusal !== undefined) {
|
|
169
|
+
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true ? undefined : "unreadable_program_position";
|
|
170
|
+
}
|
|
171
|
+
return argvReentryUnjudged(pos, acceptedStreamTargets) ? "stream_fed_operands" : undefined;
|
|
172
|
+
}
|
|
161
173
|
export function programPositionRefusalSentence(refusal) {
|
|
162
174
|
switch (refusal.kind) {
|
|
163
175
|
case "option_at_program_position":
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ShellSegmentConnector } from "./bash-lexer.js";
|
|
2
|
+
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
2
3
|
/**
|
|
3
4
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
4
5
|
* The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
|
|
@@ -31,7 +32,6 @@ import { type ShellSegmentConnector } from "./bash-lexer.js";
|
|
|
31
32
|
* value-taking options defeat the stdin-floor operand count without a per-verb option-value model
|
|
32
33
|
* — those are a follow-on slice, not a silent drop.
|
|
33
34
|
*/
|
|
34
|
-
export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
|
|
35
35
|
export type { ShellSegmentConnector } from "./bash-lexer.js";
|
|
36
36
|
export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
|
|
37
37
|
/**
|
|
@@ -174,7 +174,7 @@ export interface ShellCompoundSegments {
|
|
|
174
174
|
* drift in a segmentation is drift in what a deny rule is even looking at.
|
|
175
175
|
*
|
|
176
176
|
* Three steps, in this order and load-bearing:
|
|
177
|
-
* 1. WHOLE-STRING hard reject
|
|
177
|
+
* 1. WHOLE-STRING hard reject ({@link hardRejectReason}) — redirection, substitution,
|
|
178
178
|
* subshells, escapes, line breaks. This is what makes step 3's quote mask EXACT: with those
|
|
179
179
|
* characters gone, `'`/`"` pairing is the entirety of quoting, so the mask's regions are exactly
|
|
180
180
|
* bash's quoted regions and the unquoted connectors are exactly bash's command boundaries. Callers
|
|
@@ -524,7 +524,10 @@ export declare function resolveOperandSpellings(base: string | undefined, operan
|
|
|
524
524
|
* here-document, a here-string or a literal in-root stdin file; a directory change (which then makes every relative
|
|
525
525
|
* stdin literal in the command unjudged). A substitution body (`$(…)`, backticks, `<(…)`) is judged on its own: green
|
|
526
526
|
* under the compound walk, or exempt under this whitelist; a here-document body is text, not commands. */
|
|
527
|
-
export declare function refusedCommandMayRead(command: string, allow: ReadonlySet<string>, boundary
|
|
527
|
+
export declare function refusedCommandMayRead(command: string, allow: ReadonlySet<string>, boundary: BashReadonlyRootBoundary | undefined,
|
|
528
|
+
/** The {@link ShellScanDecline} members THIS SEAT reads as evidence (tables and argument: `fs-bash.ts`); one
|
|
529
|
+
* the table omits ends the scan with "exempt". */
|
|
530
|
+
declinesAreEvidence: ReadonlySet<ShellScanDecline>): boolean;
|
|
528
531
|
/**
|
|
529
532
|
* The home directory's VARIABLE spellings, SUBSTITUTED with the declared value before a read face segments the
|
|
530
533
|
* command: `$HOME` / `${HOME}` at the START of a word (an empty quote pair before it included), unquoted or
|
|
@@ -602,6 +605,10 @@ export interface OutOfRootReadGateVerdict {
|
|
|
602
605
|
* operand is placed against a base bash has already left, so no path can be named honestly. Read it
|
|
603
606
|
* as display/triage evidence; emptiness is not "not gated". */
|
|
604
607
|
readonly paths: readonly string[];
|
|
608
|
+
/** Did the deployment's DENY judge speak about an operand this command names? The other half of the same
|
|
609
|
+
* question, over the very same readers, in a second pass WITH the deny judge — which is what lets the
|
|
610
|
+
* containment answer above stay deny-free and keep its `paths` contract. Absent ⇔ it said nothing. */
|
|
611
|
+
readonly denied?: true;
|
|
605
612
|
}
|
|
606
613
|
/** Test seam for the lockstep pin that the launcher table (the ONE launcher truth, in the program-position
|
|
607
614
|
* reader's module) stays a SUBSET of the rule lane's curated `BARE_INTERPRETER_NAMES`. Exported for that one
|