@sema-agent/core 7.33.8 → 7.35.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 +462 -3
- package/dist/agents/cross-session-judge.d.ts +16 -7
- package/dist/agents/cross-session-judge.js +18 -15
- package/dist/agents/peer-session-drain.d.ts +4 -0
- package/dist/agents/peer-session-drain.js +5 -2
- package/dist/agents/repair-loop.d.ts +1 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +12 -3
- package/dist/brain/anthropic.js +1 -0
- package/dist/brain/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/open-responses.js +1 -0
- package/dist/brain/openai.js +1 -0
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-engine.d.ts +3 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/a2a.d.ts +3 -1
- package/dist/core/a2a.js +1 -1
- package/dist/core/ask-origin.d.ts +379 -154
- package/dist/core/ask-origin.js +224 -45
- package/dist/core/ask-question.js +2 -1
- package/dist/core/ask-unresolvable-notice.d.ts +5 -2
- package/dist/core/ask-unresolvable-notice.js +4 -3
- package/dist/core/auto-mode-arming.d.ts +17 -4
- package/dist/core/auto-mode-arming.js +31 -6
- package/dist/core/auto-mode-defaults.d.ts +16 -7
- package/dist/core/auto-mode-defaults.js +2 -1
- package/dist/core/auto-mode-directive.d.ts +18 -30
- package/dist/core/auto-mode-directive.js +9 -18
- package/dist/core/auto-mode-prompt.d.ts +62 -1
- package/dist/core/auto-mode-prompt.js +42 -4
- package/dist/core/auto-mode-rebuild.d.ts +2 -1
- package/dist/core/auto-mode-rebuild.js +1 -0
- package/dist/core/auto-mode.d.ts +32 -3
- package/dist/core/auto-mode.js +80 -41
- package/dist/core/checkpoint-store.d.ts +54 -35
- package/dist/core/checkpoint-store.js +58 -3
- package/dist/core/child-clamp-seats.js +1 -0
- package/dist/core/classifier-unavailable-arm.d.ts +57 -0
- package/dist/core/classifier-unavailable-arm.js +17 -0
- package/dist/core/effective-path-target.d.ts +12 -0
- package/dist/core/effective-path-target.js +18 -7
- package/dist/core/engine-notice.d.ts +53 -2
- package/dist/core/fs-write-gate-policy.d.ts +5 -1
- package/dist/core/fs-write-gate-policy.js +4 -1
- package/dist/core/gate-fold.js +19 -3
- package/dist/core/gate-lanes.js +116 -50
- package/dist/core/gate-outcome.d.ts +40 -7
- package/dist/core/gate-outcome.js +26 -4
- package/dist/core/governance-codes.d.ts +18 -2
- package/dist/core/governance-codes.js +23 -1
- package/dist/core/hooks.d.ts +53 -29
- package/dist/core/hooks.js +1 -3
- package/dist/core/mcp.js +1 -1
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +185 -0
- package/dist/core/mode-policy.js +184 -0
- package/dist/core/oracle-isolation.d.ts +3 -3
- package/dist/core/permission-rule-model.d.ts +20 -3
- package/dist/core/permission-rule-model.js +40 -3
- package/dist/core/permission-rule-provider.d.ts +22 -11
- package/dist/core/permission-rule-provider.js +17 -7
- package/dist/core/permission-rules.d.ts +23 -1
- package/dist/core/permission-rules.js +56 -9
- package/dist/core/persisted-rule-arms.d.ts +14 -10
- package/dist/core/persisted-rule-arms.js +13 -6
- package/dist/core/present-plan-tool.d.ts +1 -1
- package/dist/core/present-plan-tool.js +3 -3
- package/dist/core/read-only-shell.d.ts +19 -19
- package/dist/core/read-only-shell.js +38 -12
- package/dist/core/retired-keys.d.ts +9 -3
- package/dist/core/retired-keys.js +5 -1
- package/dist/core/runner/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- package/dist/core/runner/classifier-ladder.d.ts +43 -1
- package/dist/core/runner/classifier-ladder.js +29 -0
- package/dist/core/runner/contracts.d.ts +62 -38
- package/dist/core/runner/denial-limit-arms.d.ts +35 -7
- package/dist/core/runner/denial-limit-arms.js +15 -9
- package/dist/core/runner/env-capability-census.js +1 -1
- package/dist/core/runner/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- package/dist/core/runner/gate-exit.d.ts +30 -4
- package/dist/core/runner/gate-exit.js +28 -7
- package/dist/core/runner/permission-rule-lanes.d.ts +11 -6
- package/dist/core/runner/permission-rule-lanes.js +55 -48
- package/dist/core/runner/persisted-path-rows.d.ts +64 -0
- package/dist/core/runner/persisted-path-rows.js +89 -0
- package/dist/core/runner/prepare-artifact.d.ts +5 -1
- package/dist/core/runner/prepare-artifact.js +9 -3
- package/dist/core/runner/prepare-ask-lane.d.ts +6 -4
- package/dist/core/runner/prepare-ask-lane.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
- package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
- package/dist/core/runner/prepare-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +37 -9
- package/dist/core/runner/prepare-gate-stations.js +46 -29
- package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
- package/dist/core/runner/prepare-hands-readface.js +64 -53
- package/dist/core/runner/prepare-inherited-gate.d.ts +30 -23
- package/dist/core/runner/prepare-inherited-gate.js +45 -25
- package/dist/core/runner/prepare-park-ask.d.ts +6 -4
- package/dist/core/runner/prepare-park-ask.js +7 -5
- package/dist/core/runner/prepare-policy-chain.d.ts +25 -4
- package/dist/core/runner/prepare-policy-chain.js +203 -180
- package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
- package/dist/core/runner/prepare-protocol-tools.js +13 -3
- package/dist/core/runner/prepare-run-refs.d.ts +6 -6
- package/dist/core/runner/prepare-run-refs.js +9 -4
- package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
- package/dist/core/runner/prepare-safety-scan.js +19 -3
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
- package/dist/core/runner/prepare-suspend-saga.js +6 -6
- package/dist/core/runner/prepare-task.js +56 -24
- package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
- package/dist/core/runner/prepare-wiring-manifest.js +5 -3
- package/dist/core/runner/read-root-grant.d.ts +9 -0
- package/dist/core/runner/read-root-grant.js +12 -0
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-brain-sinks.js +3 -1
- package/dist/core/runner/run-identity-wiring.js +1 -1
- package/dist/core/runner/run-leg.js +29 -18
- package/dist/core/runner/run-notification-lane.d.ts +3 -0
- package/dist/core/runner/run-notification-lane.js +3 -1
- package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +8 -11
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- package/dist/core/runner/stream-mode-verb.d.ts +27 -0
- package/dist/core/runner/stream-mode-verb.js +19 -0
- package/dist/core/runner/turn-attachments.d.ts +3 -3
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runner/write-protection-station.d.ts +7 -1
- package/dist/core/runner/write-protection-station.js +6 -3
- package/dist/core/runner-deps.d.ts +31 -34
- package/dist/core/runtime-caps.d.ts +12 -3
- package/dist/core/safety-axis-vocab.d.ts +2 -2
- package/dist/core/safety-axis-vocab.js +1 -1
- package/dist/core/secret-env.d.ts +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.d.ts +4 -2
- package/dist/core/sensitive-path-policy.js +41 -31
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/task-event.d.ts +1 -1
- package/dist/core/task-result.d.ts +1 -1
- package/dist/core/task-spec.d.ts +41 -89
- package/dist/core/task-stream.d.ts +16 -0
- package/dist/core/terminal-cause.js +1 -0
- package/dist/core/tighten-task-spec.d.ts +2 -2
- package/dist/core/tighten-task-spec.js +12 -14
- package/dist/core/tool-catalog-entries.js +4 -3
- package/dist/core/tool-policy.d.ts +84 -30
- package/dist/core/tool-policy.js +76 -16
- package/dist/core/tool-registry.d.ts +38 -0
- package/dist/core/tool-registry.js +25 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/tool-spec.d.ts +8 -9
- package/dist/core/trace.d.ts +18 -6
- package/dist/core/types.d.ts +2 -1
- package/dist/core/wiring-manifest.d.ts +31 -5
- package/dist/core/wiring-manifest.js +28 -1
- package/dist/core/write-protect.d.ts +11 -1
- package/dist/core/write-protect.js +26 -15
- package/dist/engine/execution-env/node-execution-env.d.ts +6 -1
- package/dist/engine/execution-env/node-execution-env.js +16 -3
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/harness/types.d.ts +6 -7
- package/dist/engine/llm/types.d.ts +2 -4
- package/dist/engine/loop/types.d.ts +7 -0
- package/dist/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +18 -8
- package/dist/index.js +15 -6
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-size-guideline.js +2 -2
- package/dist/stores/file/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- package/dist/tools/artifact/artifact-tool.d.ts +17 -0
- package/dist/tools/artifact/artifact-tool.js +16 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
- package/dist/tools/fs/bash-readonly-classifier.js +88 -0
- package/dist/tools/fs/containment-face.d.ts +9 -3
- package/dist/tools/fs/containment-face.js +2 -0
- package/dist/tools/fs/file-read-check.d.ts +50 -0
- package/dist/tools/fs/file-read-check.js +79 -0
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-read.d.ts +1 -0
- package/dist/tools/fs/fs-search-tools.d.ts +2 -2
- package/dist/tools/fs/fs-search-tools.js +4 -4
- package/dist/tools/fs/fs-shared.js +20 -6
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +39 -3
- package/dist/tools/fs/index.js +38 -15
- package/dist/tools/fs/read-deny-home.d.ts +93 -0
- package/dist/tools/fs/read-deny-home.js +225 -0
- package/dist/tools/fs/read-deny.d.ts +176 -34
- package/dist/tools/fs/read-deny.js +268 -61
- package/dist/tools/fs/safety.d.ts +6 -1
- package/dist/tools/fs/safety.js +18 -5
- package/dist/tools/fs/search.d.ts +3 -0
- package/dist/tools/fs/search.js +47 -9
- package/dist/tools/fs/shell-read-check.d.ts +52 -13
- package/dist/tools/fs/shell-read-check.js +199 -20
- package/dist/tools/fs/shell-read-establish.d.ts +7 -3
- package/dist/tools/fs/shell-read-establish.js +134 -21
- package/dist/tools/fs/shell-read-identity.d.ts +7 -0
- package/dist/tools/fs/shell-read-identity.js +21 -0
- package/dist/tools/fs/shell-read-judge.d.ts +92 -32
- package/dist/tools/fs/shell-read-judge.js +43 -18
- package/dist/tools/fs/shell-read-plan.d.ts +4 -0
- package/dist/tools/fs/shell-read-plan.js +26 -4
- package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
- package/dist/tools/fs/shell-read-write-targets.js +173 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +117 -11
- package/wire-changes/core@7.34.0.json +2621 -0
- package/wire-changes/core@7.35.0.json +584 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
|
|
1
|
+
import { isAbsoluteForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
|
|
2
2
|
import { boundaryIsTheSandbox, canonicalPathIsAuthoritative } from "../../core/remote-env.js";
|
|
3
3
|
import { resolveOperandSpellings } from "./bash-readonly-classifier.js";
|
|
4
|
-
import {
|
|
5
|
-
import { movedReads } from "./shell-read-identity.js";
|
|
4
|
+
import { judgedCandidatesOf, sedBackupNames } from "./shell-read-write-targets.js";
|
|
5
|
+
import { movedReads, withHomeViews } from "./shell-read-identity.js";
|
|
6
6
|
const GLOB_METACHAR_RE = /[*?[]/;
|
|
7
7
|
export const MAX_GLOB_DIRS = 256;
|
|
8
8
|
export const MAX_GLOB_MATCHES = 8192;
|
|
9
|
+
const WRITE_LINK_HOPS = 32;
|
|
9
10
|
function compileGlobSegment(pattern) {
|
|
10
11
|
if (pattern.includes("\\"))
|
|
11
12
|
return undefined;
|
|
@@ -131,15 +132,25 @@ function lexicalReadsOf(candidate, boundary) {
|
|
|
131
132
|
}
|
|
132
133
|
return out;
|
|
133
134
|
}
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
return [
|
|
135
|
+
function unfollowedBackupReads(j, boundary, cause) {
|
|
136
|
+
const spellings = sedBackupNames(j.followed.operand.spelled, j.followed.suffix).flatMap((name) => lexicalReadsOf({ ...j.candidate, spelled: name }, boundary).flatMap((r) => [...(r.target !== undefined ? [r.target] : []), ...r.spellings]));
|
|
137
|
+
return [{ candidate: j.candidate, established: "unestablishable", spellings: [...new Set(spellings)], cause }];
|
|
138
|
+
}
|
|
139
|
+
function writtenAt(read, j, seat) {
|
|
140
|
+
if (read.candidate.face !== "write" || !(j.written ?? j.seats ?? ["landing"]).includes(seat))
|
|
141
|
+
return read;
|
|
142
|
+
return { ...read, written: { seat, ...(j.child === true ? { child: true } : {}) } };
|
|
137
143
|
}
|
|
138
144
|
export function establishShellReadsLexically(plan, boundary) {
|
|
139
145
|
const reads = new Map();
|
|
140
146
|
const nested = new Map();
|
|
141
147
|
for (const segment of plan.segments) {
|
|
142
|
-
reads.set(segment, judgedCandidatesOf(segment).flatMap((
|
|
148
|
+
reads.set(segment, judgedCandidatesOf(segment).flatMap((j) => {
|
|
149
|
+
if (j.followed !== undefined)
|
|
150
|
+
return unfollowedBackupReads({ ...j, followed: j.followed }, boundary, `"${j.followed.operand.raw}" names its backup through a link chain only the filesystem can follow`);
|
|
151
|
+
const seat = (j.written ?? j.seats ?? ["landing"]).includes("landing") ? "landing" : "entry";
|
|
152
|
+
return lexicalReadsOf(j.candidate, boundary).map((r) => writtenAt(r, j, seat));
|
|
153
|
+
}));
|
|
143
154
|
for (const n of segment.nested)
|
|
144
155
|
nested.set(n, establishShellReadsLexically(n, boundary));
|
|
145
156
|
}
|
|
@@ -164,12 +175,23 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
164
175
|
})();
|
|
165
176
|
return await rootsOnce;
|
|
166
177
|
};
|
|
167
|
-
const resolveWithoutParentSteps = async (p, signal) => {
|
|
178
|
+
const resolveWithoutParentSteps = async (p, signal, writeTarget = false, linkHops = WRITE_LINK_HOPS) => {
|
|
168
179
|
const parts = p.split(SEP);
|
|
169
180
|
const tail = [];
|
|
170
181
|
let allAbsent = true;
|
|
171
182
|
while (parts.length > 0) {
|
|
172
|
-
const
|
|
183
|
+
const at = parts.join("/") || "/";
|
|
184
|
+
const canon = await env.canonicalPath(at, signal);
|
|
185
|
+
if (!canon.ok && canon.error.code === "not_found" && writeTarget && allAbsent) {
|
|
186
|
+
const entry = await env.fileInfo(at, signal);
|
|
187
|
+
const link = entry.ok && entry.value.kind === "symlink" && linkHops > 0 ? await env.readLink?.(at, signal) : undefined;
|
|
188
|
+
if (link?.ok === true) {
|
|
189
|
+
const landing = isAbsoluteForFamily(family, link.value) ? link.value : joinUnder(parts.slice(0, -1).join("/") || "/", link.value);
|
|
190
|
+
return await resolve([landing, ...[...tail].reverse()].join("/"), signal, true, linkHops - 1);
|
|
191
|
+
}
|
|
192
|
+
if (entry.ok || entry.error.code !== "not_found")
|
|
193
|
+
allAbsent = false;
|
|
194
|
+
}
|
|
173
195
|
if (canon.ok) {
|
|
174
196
|
if (tail.length === 0)
|
|
175
197
|
return { value: canon.value, exact: true };
|
|
@@ -182,20 +204,20 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
182
204
|
return undefined;
|
|
183
205
|
};
|
|
184
206
|
const parentOf = (canonical) => normalizeAbsPathLexically(`${canonical}/..`);
|
|
185
|
-
const resolve = async (p, signal) => {
|
|
207
|
+
const resolve = async (p, signal, writeTarget = false, linkHops = WRITE_LINK_HOPS) => {
|
|
186
208
|
const parts = p.split(SEP);
|
|
187
209
|
const first = parts.findIndex((seg) => seg === "..");
|
|
188
210
|
if (first < 0)
|
|
189
|
-
return await resolveWithoutParentSteps(p, signal);
|
|
211
|
+
return await resolveWithoutParentSteps(p, signal, writeTarget, linkHops);
|
|
190
212
|
const headEnd = parts.slice(0, first).reduce((n, seg) => n + seg.length + 1, 0);
|
|
191
|
-
let head = await resolveWithoutParentSteps(p.slice(0, headEnd) || "/", signal);
|
|
213
|
+
let head = await resolveWithoutParentSteps(p.slice(0, headEnd) || "/", signal, writeTarget, linkHops);
|
|
192
214
|
if (head === undefined)
|
|
193
215
|
return undefined;
|
|
194
216
|
let pending = [];
|
|
195
217
|
const flush = async () => {
|
|
196
218
|
if (pending.length === 0)
|
|
197
219
|
return true;
|
|
198
|
-
const next = await resolveWithoutParentSteps([head.value.replace(TRAILING_SEP, ""), ...pending].join("/"), signal);
|
|
220
|
+
const next = await resolveWithoutParentSteps([head.value.replace(TRAILING_SEP, ""), ...pending].join("/"), signal, writeTarget, linkHops);
|
|
199
221
|
pending = [];
|
|
200
222
|
if (next === undefined)
|
|
201
223
|
return false;
|
|
@@ -342,8 +364,18 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
342
364
|
const members = found.map(({ path, canonical, symlink }) => ({ path, ...(canonical !== undefined ? { canonical } : {}), node: (symlink && canonical !== undefined && listedDirs.has(canonical) ? "directory" : "file") }));
|
|
343
365
|
return { ...(root !== undefined ? { root } : {}), members };
|
|
344
366
|
};
|
|
345
|
-
const
|
|
346
|
-
const
|
|
367
|
+
const entryOf = async (spelling, signal) => {
|
|
368
|
+
const parts = spelling.split(SEP);
|
|
369
|
+
const leaf = parts.pop() ?? "";
|
|
370
|
+
if (leaf === "" || leaf === "." || leaf === ".." || parts.length === 0)
|
|
371
|
+
return await resolve(spelling, signal, true);
|
|
372
|
+
const parent = await resolve(parts.join("/") || "/", signal, true);
|
|
373
|
+
if (parent === undefined)
|
|
374
|
+
return undefined;
|
|
375
|
+
return { value: joinUnder(parent.value, leaf), exact: parent.exact, ...(parent.missing !== undefined ? { missing: parent.missing } : {}) };
|
|
376
|
+
};
|
|
377
|
+
const canonicalReadOf = async (candidate, spelling, spellings, established, origin, canonicalKnown, signal, seat = "landing") => {
|
|
378
|
+
const real = canonicalKnown !== undefined ? { value: canonicalKnown, exact: true } : seat === "entry" ? await entryOf(spelling, signal) : await resolve(spelling, signal, candidate.face === "write");
|
|
347
379
|
if (real === undefined) {
|
|
348
380
|
if (resolverIsAuthoritative)
|
|
349
381
|
return { candidate, established: "unestablishable", spellings, ...(origin !== undefined ? { origin } : {}), cause: `"${spelling}" cannot be resolved to verify where it reads` };
|
|
@@ -354,10 +386,88 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
354
386
|
}
|
|
355
387
|
return { candidate, established, target: real.value, spellings, ...(origin !== undefined ? { origin } : {}) };
|
|
356
388
|
};
|
|
389
|
+
const followedName = async (spelled, base, homeDir, signal) => {
|
|
390
|
+
let name = spelled;
|
|
391
|
+
for (let hop = 0; hop <= WRITE_LINK_HOPS; hop++) {
|
|
392
|
+
const at = resolveOperandSpellings(base === "" ? undefined : base, name, homeDir);
|
|
393
|
+
if (at === undefined)
|
|
394
|
+
return undefined;
|
|
395
|
+
const walked = at.spelled ?? at.folded;
|
|
396
|
+
if (TRAILING_SEP.test(walked))
|
|
397
|
+
return name;
|
|
398
|
+
const parts = walked.split(SEP);
|
|
399
|
+
const leaf = parts.pop() ?? "";
|
|
400
|
+
if (leaf === "" || leaf === "." || leaf === "..")
|
|
401
|
+
return name;
|
|
402
|
+
const parent = await resolve(parts.join("/") || "/", signal);
|
|
403
|
+
if (parent === undefined)
|
|
404
|
+
return undefined;
|
|
405
|
+
if (!parent.exact)
|
|
406
|
+
return parent.missing === true ? name : undefined;
|
|
407
|
+
const entry = await env.fileInfo(joinUnder(parent.value, leaf), signal);
|
|
408
|
+
if (!entry.ok)
|
|
409
|
+
return entry.error.code === "not_found" || entry.error.code === "not_directory" ? name : undefined;
|
|
410
|
+
if (entry.value.kind !== "symlink")
|
|
411
|
+
return name;
|
|
412
|
+
const link = await env.readLink?.(joinUnder(parent.value, leaf), signal);
|
|
413
|
+
if (link?.ok !== true)
|
|
414
|
+
return undefined;
|
|
415
|
+
const dir = name.slice(0, name.search(/[^/]*$/));
|
|
416
|
+
name = isAbsoluteForFamily(family, link.value) ? link.value : `${dir}${link.value}`;
|
|
417
|
+
}
|
|
418
|
+
return undefined;
|
|
419
|
+
};
|
|
420
|
+
const followedBackupReads = async (j, boundary, signal) => {
|
|
421
|
+
const { operand, suffix } = j.followed;
|
|
422
|
+
const bases = basesFor(operand, boundary);
|
|
423
|
+
const unfollowed = (cause) => unfollowedBackupReads(j, boundary, cause);
|
|
424
|
+
if (bases === "unmodelled")
|
|
425
|
+
return unfollowed(`"${operand.raw}" is relative to a working directory this command's text does not settle`);
|
|
426
|
+
const out = [];
|
|
427
|
+
for (const base of bases) {
|
|
428
|
+
const name = await followedName(operand.spelled, base, boundary.homeDir, signal);
|
|
429
|
+
if (name === undefined) {
|
|
430
|
+
out.push(...unfollowed(`"${operand.raw}" names its backup through a link chain that could not be read`));
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
for (const backup of sedBackupNames(name, suffix)) {
|
|
434
|
+
const at = resolveOperandSpellings(base === "" ? undefined : base, backup, boundary.homeDir);
|
|
435
|
+
if (at === undefined) {
|
|
436
|
+
out.push(...unfollowed(`"${backup}" cannot be placed against "${base}"`));
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const spellings = at.spelled !== undefined ? [at.folded, at.spelled] : [at.folded];
|
|
440
|
+
for (const seat of j.seats ?? ["landing"])
|
|
441
|
+
out.push(writtenAt(await canonicalReadOf(j.candidate, at.spelled ?? at.folded, spellings, "canonical", undefined, undefined, signal, seat), j, seat));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return out;
|
|
445
|
+
};
|
|
357
446
|
const establishSegment = async (segment, boundary, roots, signal) => {
|
|
358
447
|
const out = [];
|
|
359
|
-
for (const
|
|
448
|
+
for (const judged of judgedCandidatesOf(segment)) {
|
|
449
|
+
if (judged.followed !== undefined) {
|
|
450
|
+
out.push(...(await followedBackupReads({ ...judged, followed: judged.followed }, boundary, signal)));
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const candidate = judged.candidate;
|
|
360
454
|
const traversal = candidate.provenance === "traversal_root";
|
|
455
|
+
const resolvesItself = TRAILING_SEP.test(candidate.spelled) || [".", ".."].includes(candidate.spelled.split(SEP).pop() ?? "");
|
|
456
|
+
const landsToo = (ss) => (resolvesItself && ss.length > 0 && !ss.includes("landing") ? [...ss, "landing"] : ss);
|
|
457
|
+
const seats = judged.seats === undefined ? ["landing"] : landsToo(judged.seats);
|
|
458
|
+
const writtenSeats = { ...judged, written: judged.written === undefined ? seats : landsToo(judged.written) };
|
|
459
|
+
const atSeats = async (spelling, spellings, established, origin, canonicalKnown) => {
|
|
460
|
+
const reads = [];
|
|
461
|
+
for (const seat of seats) {
|
|
462
|
+
const read = writtenAt(await canonicalReadOf(candidate, spelling, spellings, established, origin, canonicalKnown, signal, seat), writtenSeats, seat);
|
|
463
|
+
const same = reads.findIndex((r) => r.established === read.established && r.target === read.target);
|
|
464
|
+
if (same < 0)
|
|
465
|
+
reads.push(read);
|
|
466
|
+
else if (reads[same].written === undefined && read.written !== undefined)
|
|
467
|
+
reads[same] = read;
|
|
468
|
+
}
|
|
469
|
+
return reads;
|
|
470
|
+
};
|
|
361
471
|
const land = async (read, spellings, folded) => {
|
|
362
472
|
if (!traversal || (read.established !== "canonical" && read.established !== "expansion") || read.target === undefined) {
|
|
363
473
|
out.push(read);
|
|
@@ -371,7 +481,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
371
481
|
}
|
|
372
482
|
out.push({ ...read, ...(walkedTree.root !== undefined ? { node: walkedTree.root } : {}) });
|
|
373
483
|
for (const m of walkedTree.members)
|
|
374
|
-
out.push({ ...(await canonicalReadOf(candidate, m.path, [m.path], "expansion", folded, m.canonical, signal)), node: m.node });
|
|
484
|
+
out.push({ ...(await canonicalReadOf(candidate, m.path, [m.path], "expansion", folded, m.canonical, signal)), node: m.node, walked: true });
|
|
375
485
|
};
|
|
376
486
|
for (const lexical of lexicalReadsOf(candidate, boundary)) {
|
|
377
487
|
if (lexical.established !== "lexical") {
|
|
@@ -393,14 +503,17 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
393
503
|
if (expanded !== undefined && expanded.paths.length > 0) {
|
|
394
504
|
matched = true;
|
|
395
505
|
for (const t of expanded.paths)
|
|
396
|
-
|
|
506
|
+
for (const read of await atSeats(t, [t, folded], "expansion", folded, expanded.canonical.get(t)))
|
|
507
|
+
await land(read, spellings, folded);
|
|
397
508
|
}
|
|
398
509
|
}
|
|
399
510
|
if (!matched)
|
|
400
|
-
|
|
511
|
+
for (const read of await atSeats(walked, spellings, "canonical", undefined, undefined))
|
|
512
|
+
await land(read, spellings, folded);
|
|
401
513
|
continue;
|
|
402
514
|
}
|
|
403
|
-
|
|
515
|
+
for (const read of await atSeats(walked, spellings, "canonical", undefined, undefined))
|
|
516
|
+
await land(read, spellings, folded);
|
|
404
517
|
}
|
|
405
518
|
}
|
|
406
519
|
return out;
|
|
@@ -421,7 +534,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
421
534
|
if (lexicalOnly)
|
|
422
535
|
return establishShellReadsLexically(plan, boundary);
|
|
423
536
|
const roots = await canonicalRoots(signal);
|
|
424
|
-
return await establishPlan(plan, { ...boundary, roots }, roots, signal);
|
|
537
|
+
return await withHomeViews(env, await establishPlan(plan, { ...boundary, roots }, roots, signal), signal);
|
|
425
538
|
},
|
|
426
539
|
reestablish: async (previous, boundary, signal) => {
|
|
427
540
|
if (lexicalOnly)
|
|
@@ -9,5 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { EstablishedPlan } from "./shell-read-establish.js";
|
|
11
11
|
import type { EstablishedRead } from "./shell-read-judge.js";
|
|
12
|
+
import { type HomeViewEnv } from "./read-deny-home.js";
|
|
12
13
|
/** Every read of either establishment whose candidate's settled target set differs between the two. */
|
|
13
14
|
export declare function movedReads(previous: EstablishedPlan, again: EstablishedPlan): EstablishedRead[];
|
|
15
|
+
/**
|
|
16
|
+
* The establishment with every read that landed carrying its HOME VIEW (read-deny-home.ts) among its spellings, nested
|
|
17
|
+
* plans included: the deny judge then places a home-anchored row wherever the target IS below the home, whatever it is
|
|
18
|
+
* spelled through. A read that did not land (lexical, unestablishable) is left as it is.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withHomeViews(env: HomeViewEnv, established: EstablishedPlan, signal?: AbortSignal): Promise<EstablishedPlan>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { homeViewsOf } from "./read-deny-home.js";
|
|
1
2
|
export function movedReads(previous, again) {
|
|
2
3
|
const before = targetsByCandidate(previous);
|
|
3
4
|
const after = targetsByCandidate(again);
|
|
@@ -37,3 +38,23 @@ function targetsByCandidate(e) {
|
|
|
37
38
|
function sameTargets(a, b) {
|
|
38
39
|
return a.size === b.size && [...a].every((t) => b.has(t));
|
|
39
40
|
}
|
|
41
|
+
export async function withHomeViews(env, established, signal) {
|
|
42
|
+
const views = homeViewsOf(env, signal);
|
|
43
|
+
const place = async (p) => {
|
|
44
|
+
const reads = new Map();
|
|
45
|
+
for (const [segment, rs] of p.reads) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const r of rs) {
|
|
48
|
+
const landed = r.target !== undefined && (r.established === "canonical" || r.established === "expansion");
|
|
49
|
+
const view = landed ? await views.of(r.target, r.node === "file" ? { leaf: "file" } : undefined) : undefined;
|
|
50
|
+
out.push(view === undefined || r.spellings.includes(view) ? r : { ...r, spellings: [...r.spellings, view] });
|
|
51
|
+
}
|
|
52
|
+
reads.set(segment, out);
|
|
53
|
+
}
|
|
54
|
+
const nested = new Map();
|
|
55
|
+
for (const [n, np] of p.nested)
|
|
56
|
+
nested.set(n, await place(np));
|
|
57
|
+
return { ...p, reads, nested };
|
|
58
|
+
};
|
|
59
|
+
return await place(established);
|
|
60
|
+
}
|
|
@@ -12,22 +12,25 @@
|
|
|
12
12
|
* · `foldShellReadFindings` folds a command's findings — every segment, every nested plan — to ONE word by
|
|
13
13
|
* the priority `identity_changed > deny_hit > outside_roots > unestablishable > declined > none`.
|
|
14
14
|
* · `SHELL_READ_DECISION` is the total table (finding × seat → disposition) that is the ONLY source of
|
|
15
|
-
* "refuse / mandated ask / ordinary ask / vouch" for a shell read. A seat is a
|
|
16
|
-
*
|
|
15
|
+
* "refuse / mandated ask / ordinary ask / vouch" for a shell read. A seat is a FACE word, not a tool name
|
|
16
|
+
* and not a permission mode: which shell face the call runs on (the full shell, the read-only shell) or
|
|
17
|
+
* which moment judges it (the re-judge after approval). The permission mode is not read here — what a
|
|
18
|
+
* mode does with the ask this table mints is decided once, downstream, where every ask is arbitrated.
|
|
17
19
|
*
|
|
18
20
|
* The floor this module is (`shell.read_boundary.friction_floor`) is stated on {@link judgeRead}.
|
|
19
21
|
*/
|
|
20
22
|
import type { BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
|
|
21
23
|
import type { ReadCandidate, ShellReadPlan, ShellReadSegment } from "./shell-read-plan.js";
|
|
24
|
+
import type { WriteSeat } from "./shell-read-write-targets.js";
|
|
22
25
|
import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
|
|
23
26
|
/** WHAT the judge found about a command — a closed set, registered in `docs/CLOSED-SETS.md`. */
|
|
24
27
|
export declare const SHELL_READ_FINDINGS: readonly ["deny_hit", "outside_roots", "unestablishable", "declined", "identity_changed", "none"];
|
|
25
28
|
export type ShellReadFinding = (typeof SHELL_READ_FINDINGS)[number];
|
|
26
|
-
/**
|
|
27
|
-
* `
|
|
28
|
-
* shell face (`handsReadOnly`); `rejudge` the post-approval re-judgment of rewritten bytes
|
|
29
|
-
* pre-spawn identity re-check. */
|
|
30
|
-
export declare const SHELL_READ_SEATS: readonly ["
|
|
29
|
+
/** WHERE a read is judged — the seats the decision table is keyed on, one per FACE: `full_shell` the
|
|
30
|
+
* writable shell (`Bash`, `Monitor`) under every permission mode and every shell-gate floor; `readonly`
|
|
31
|
+
* the read-only shell face (`handsReadOnly`); `rejudge` the post-approval re-judgment of rewritten bytes
|
|
32
|
+
* and the pre-spawn identity re-check. */
|
|
33
|
+
export declare const SHELL_READ_SEATS: readonly ["full_shell", "readonly", "rejudge"];
|
|
31
34
|
export type ShellReadSeat = (typeof SHELL_READ_SEATS)[number];
|
|
32
35
|
/** The fold priority: a command's finding is the highest-ranked finding of any candidate in it. */
|
|
33
36
|
export declare const SHELL_READ_FINDING_RANK: {
|
|
@@ -47,56 +50,51 @@ export type RankTableCoversEveryFinding = AssertAllKeysHandled<Exclude<ShellRead
|
|
|
47
50
|
export type ShellReadDisposition = "refuse" | "mandated" | "ordinary" | "vouch";
|
|
48
51
|
/**
|
|
49
52
|
* THE decision table — total over findings × seats; no cell is n/a. Row by row:
|
|
50
|
-
* · `deny_hit` — refused on every seat: a deny row is a floor
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
53
|
+
* · `deny_hit` — refused on every seat, for a spelling the station can establish: a deny row is a floor
|
|
54
|
+
* no root grant and no approval moves. A spelling the station cannot establish is not a deny hit (it is
|
|
55
|
+
* the `unestablishable` row) — the floor holds what it can place, and says so rather than guessing.
|
|
56
|
+
* · `outside_roots` — asked where a person can be asked (the full shell), refused where none can (the
|
|
57
|
+
* read-only face has no approval channel; a rewrite that leaves the roots after approval was seen by
|
|
58
|
+
* nobody).
|
|
54
59
|
* · `unestablishable` — the same shape: the floor could not say where the command reads, so it is not
|
|
55
60
|
* vouched anywhere; a person may still answer it on the full shell.
|
|
56
|
-
* · `declined` — the one
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* its allowlist is a deployment knob, so this cell is reachable and must not be n/a.
|
|
61
|
+
* · `declined` — the one deliberate fail-open of this table: the scan could not read the program bash
|
|
62
|
+
* will run, or the operands arrive on a stream. On the full shell it is an ORDINARY ask — an answerer
|
|
63
|
+
* may settle it, and an out-of-root read hidden inside such a command is then not asked again. The
|
|
64
|
+
* read-only face refuses: its allowlist is a deployment knob, so this cell is reachable and must not be
|
|
65
|
+
* n/a.
|
|
62
66
|
* · `identity_changed` — a candidate established at approval time resolves elsewhere at spawn time:
|
|
63
67
|
* refused everywhere (it is only ever produced on the `rejudge` seat).
|
|
64
|
-
* · `none` — vouched: the station has no question (the
|
|
68
|
+
* · `none` — vouched: the station has no question (the full shell's shape tier may still ask).
|
|
65
69
|
*/
|
|
66
70
|
export declare const SHELL_READ_DECISION: {
|
|
67
71
|
readonly deny_hit: {
|
|
68
|
-
readonly
|
|
69
|
-
readonly bash_classify: "refuse";
|
|
72
|
+
readonly full_shell: "refuse";
|
|
70
73
|
readonly readonly: "refuse";
|
|
71
74
|
readonly rejudge: "refuse";
|
|
72
75
|
};
|
|
73
76
|
readonly outside_roots: {
|
|
74
|
-
readonly
|
|
75
|
-
readonly bash_classify: "mandated";
|
|
77
|
+
readonly full_shell: "mandated";
|
|
76
78
|
readonly readonly: "refuse";
|
|
77
79
|
readonly rejudge: "refuse";
|
|
78
80
|
};
|
|
79
81
|
readonly unestablishable: {
|
|
80
|
-
readonly
|
|
81
|
-
readonly bash_classify: "mandated";
|
|
82
|
+
readonly full_shell: "mandated";
|
|
82
83
|
readonly readonly: "refuse";
|
|
83
84
|
readonly rejudge: "refuse";
|
|
84
85
|
};
|
|
85
86
|
readonly declined: {
|
|
86
|
-
readonly
|
|
87
|
-
readonly bash_classify: "ordinary";
|
|
87
|
+
readonly full_shell: "ordinary";
|
|
88
88
|
readonly readonly: "refuse";
|
|
89
89
|
readonly rejudge: "refuse";
|
|
90
90
|
};
|
|
91
91
|
readonly identity_changed: {
|
|
92
|
-
readonly
|
|
93
|
-
readonly bash_classify: "refuse";
|
|
92
|
+
readonly full_shell: "refuse";
|
|
94
93
|
readonly readonly: "refuse";
|
|
95
94
|
readonly rejudge: "refuse";
|
|
96
95
|
};
|
|
97
96
|
readonly none: {
|
|
98
|
-
readonly
|
|
99
|
-
readonly bash_classify: "vouch";
|
|
97
|
+
readonly full_shell: "vouch";
|
|
100
98
|
readonly readonly: "vouch";
|
|
101
99
|
readonly rejudge: "vouch";
|
|
102
100
|
};
|
|
@@ -106,7 +104,9 @@ export type DecisionTableCoversEveryFinding = AssertAllKeysHandled<Exclude<Shell
|
|
|
106
104
|
/** Compile-time fence: `never` while every seat has a cell on the `none` row (the `satisfies` above holds
|
|
107
105
|
* every other row to the same shape). */
|
|
108
106
|
export type DecisionTableCoversEverySeat = AssertAllKeysHandled<Exclude<ShellReadSeat, keyof (typeof SHELL_READ_DECISION)["none"]>>;
|
|
109
|
-
/** The disposition of one command-level finding on one seat — the table, read.
|
|
107
|
+
/** The disposition of one command-level finding on one seat — the table, read. A seat word outside
|
|
108
|
+
* {@link SHELL_READ_SEATS} (an untyped caller, or one still passing a retired word) is refused loudly: an
|
|
109
|
+
* absent disposition would read as "no verdict", which no caller can act on safely. */
|
|
110
110
|
export declare function decideShellRead(finding: ShellReadFinding, seat: ShellReadSeat): ShellReadDisposition;
|
|
111
111
|
/**
|
|
112
112
|
* The boundary a shell read is judged against: the declared roots (canonical), the working directory and
|
|
@@ -116,6 +116,27 @@ export declare function decideShellRead(finding: ShellReadFinding, seat: ShellRe
|
|
|
116
116
|
*/
|
|
117
117
|
export interface ShellReadBoundary extends BashReadonlyRootBoundary {
|
|
118
118
|
readonly alsoInside?: (canonicalTarget: string) => Promise<boolean>;
|
|
119
|
+
/** The deny table's WRITE face (`compileWriteDeny`, read-deny.ts): judges a command's write targets (candidates marked
|
|
120
|
+
* `face: "write"`) and returns the matched row's rule, or null. PRESENT-IFF there is something to judge. */
|
|
121
|
+
readonly writeDenyMatch?: (resolvedPath: string) => string | null;
|
|
122
|
+
/** The read table's rows AT or BELOW a path (`readDenySeat`'s `denyAtOrBelow`, read-deny.ts): read by the station's
|
|
123
|
+
* `writesPlaced` fact only, through {@link denyRowAtOrBelow}. */
|
|
124
|
+
readonly denyAtOrBelow?: (resolvedPath: string) => string | null;
|
|
125
|
+
/** The WRITE roots (canonical) — where a write may land without a question under a mode that accepts file commands
|
|
126
|
+
* (the task root and every additional working directory; never a read-only grant). Read for the station's
|
|
127
|
+
* `writesPlaced` fact only; the read judgment never reads it. Absent ⇒ no write is placed. */
|
|
128
|
+
readonly writeRoots?: readonly string[];
|
|
129
|
+
/** The write-protection TABLE's judge — the one a write tool's target meets (its spelling, its real target and every hop
|
|
130
|
+
* of its symlink chain): `true` ⇔ a row covers this ABSOLUTE write target; `canonical` ⇔ the path is a key the station
|
|
131
|
+
* established (judged as a key, never as a spelling). Read by the station's write facts only: a covered target is not
|
|
132
|
+
* placed, and the verdict says it was found ({@link import("./shell-read-check.js").ShellReadVerdict.writeProtected}). */
|
|
133
|
+
readonly writeProtectionJudge?: (target: string, opts?: {
|
|
134
|
+
readonly signal?: AbortSignal;
|
|
135
|
+
readonly canonical?: true;
|
|
136
|
+
}) => Promise<boolean>;
|
|
137
|
+
/** The deployment's write-target guard (`ToolPolicy.writeTargetGuard`, every policy of the leg's chain): `true` ⇔ a
|
|
138
|
+
* policy guards this ABSOLUTE write target. Read by the station's `writesPlaced` fact only: a guarded target is not placed. */
|
|
139
|
+
readonly writeTargetGuard?: (target: string, signal?: AbortSignal) => Promise<boolean>;
|
|
119
140
|
}
|
|
120
141
|
/** How far one candidate got before it was judged. `lexical` = the folded spelling is the judged target
|
|
121
142
|
* (an isolated sandbox, or a resolver that declares no authority and did not answer); `canonical` = the
|
|
@@ -136,18 +157,31 @@ export interface EstablishedRead {
|
|
|
136
157
|
/** What the environment said the target IS, where a read asked — only a traversal asks (it lists the root and
|
|
137
158
|
* every member it walks; a link's answer is its target's). Absent = nothing asked; a plain read never lists. */
|
|
138
159
|
readonly node?: "directory" | "file";
|
|
160
|
+
/** A MEMBER a traversal enumerated below its root — never a spelling of the command. The deny judge reads it
|
|
161
|
+
* ({@link import("./read-deny.js").ReadDenyMatcher.matchPath} `walked`): the segment rows still judge it, a
|
|
162
|
+
* rule-DSL row does not. Absent on the root itself, on a pattern's own matches and on every spelled operand. */
|
|
163
|
+
readonly walked?: true;
|
|
139
164
|
/** Why the candidate could not be established. */
|
|
140
165
|
readonly cause?: string;
|
|
166
|
+
/** WHERE the command's program writes this target — present only on a write target the program itself writes, at the
|
|
167
|
+
* seat this read was established at (`landing`: through the path's links; `entry`: the directory entry, a link there
|
|
168
|
+
* not followed). Absent on a read, and on a write-face target the program only names (a copy's source). `child`: the
|
|
169
|
+
* candidate's own mark ({@link import("./shell-read-write-targets.js").JudgedCandidate.child}), carried to the read. */
|
|
170
|
+
readonly written?: {
|
|
171
|
+
readonly seat: WriteSeat;
|
|
172
|
+
readonly child?: true;
|
|
173
|
+
};
|
|
141
174
|
}
|
|
142
175
|
/** What the judge found about ONE candidate. `kind` is one of the four candidate-level findings
|
|
143
176
|
* (`declined` is a segment fact, `identity_changed` a re-establishment fact — neither is a candidate's). */
|
|
144
177
|
export interface ReadFinding {
|
|
145
178
|
readonly read: EstablishedRead;
|
|
146
179
|
readonly kind: Extract<ShellReadFinding, "deny_hit" | "outside_roots" | "unestablishable" | "none">;
|
|
147
|
-
/** The deny row that matched
|
|
180
|
+
/** The deny row that matched, the view it matched on, and — for a write target — the face (`write`) that refused. */
|
|
148
181
|
readonly deny?: {
|
|
149
182
|
readonly pattern: string;
|
|
150
183
|
readonly matchedView: string;
|
|
184
|
+
readonly face?: "write";
|
|
151
185
|
};
|
|
152
186
|
readonly containment: "inside" | "outside" | "open" | "unknown";
|
|
153
187
|
}
|
|
@@ -173,6 +207,17 @@ export interface ReadFinding {
|
|
|
173
207
|
* neither is a containment candidate — both still meet the deny judge, on every spelling.
|
|
174
208
|
*/
|
|
175
209
|
export declare function judgeRead(read: EstablishedRead, boundary: ShellReadBoundary): ReadFinding;
|
|
210
|
+
/**
|
|
211
|
+
* Does a deny row lie AT or BELOW this write target, on any view the judge reads? Then a command that moves, copies or
|
|
212
|
+
* removes the target changes what the row covers, and a mode must not answer it unasked. WHICH tables answer is what the
|
|
213
|
+
* command does to the content below the target: a move or a copy carries it somewhere else (`mv .docker d` leaves
|
|
214
|
+
* `d/config.json` outside `.docker/config.json`; `mv <home> x` takes every home-anchored row's file along), so the READ
|
|
215
|
+
* table's rows answer too (`carriesContent`); a removal carries nothing anywhere — it is a write and nothing else, and
|
|
216
|
+
* only the write table answers it (the reference implementation's path check reads the read rows for a copy's or a
|
|
217
|
+
* move's sources and for sed, never for `rm` / `rmdir`). The read table answers per row (`denyAtOrBelow`); the write
|
|
218
|
+
* table's rows are rule rows, whose reach below a path is not enumerable, so any of them answers.
|
|
219
|
+
*/
|
|
220
|
+
export declare function denyRowAtOrBelow(read: EstablishedRead, boundary: ShellReadBoundary, carriesContent: boolean): boolean;
|
|
176
221
|
/**
|
|
177
222
|
* The deny floor of every home word the plan does not place: each candidate carrying a `floorSpelled` (the word
|
|
178
223
|
* spelled with the DECLARED home) gets a SHADOW candidate at that spelling, established exactly as a read is — the
|
|
@@ -197,6 +242,8 @@ export interface ShellReadFold {
|
|
|
197
242
|
readonly finding: ShellReadFinding;
|
|
198
243
|
readonly witness?: ReadFinding;
|
|
199
244
|
readonly declined?: ShellReadSegment;
|
|
245
|
+
/** Some read of the text could not be established (an `unestablishable` or `declined` finding anywhere, winning or not). */
|
|
246
|
+
readonly unestablished?: true;
|
|
200
247
|
}
|
|
201
248
|
/**
|
|
202
249
|
* Fold a judged plan to ONE finding by {@link SHELL_READ_FINDING_RANK}, over every segment and every nested
|
|
@@ -215,6 +262,9 @@ export declare function foldShellReadFindings(judged: JudgedPlan): ShellReadFold
|
|
|
215
262
|
export declare function judgeEstablishedPlan(plan: ShellReadPlan, established: ReadonlyMap<ShellReadSegment, readonly EstablishedRead[]>, boundary: ShellReadBoundary, nestedOf: (plan: ShellReadPlan) => JudgedPlan): JudgedPlan;
|
|
216
263
|
/** Every finding of a judged plan, flattened (segments and nested plans alike). */
|
|
217
264
|
export declare function allFindings(judged: JudgedPlan): ReadFinding[];
|
|
265
|
+
/** One lexical step up, family-aware. A family ROOT is its own parent — the fixpoint the climb below ends
|
|
266
|
+
* on, and the one spelling this file can recognise as "the whole filesystem" without I/O. */
|
|
267
|
+
export declare function parentDirLexically(p: string): string;
|
|
218
268
|
/**
|
|
219
269
|
* The deepest directory containing both, or `undefined` when they share none (two path families).
|
|
220
270
|
*
|
|
@@ -238,5 +288,15 @@ export declare function readRootGrantCandidateOf(judged: JudgedPlan, boundary: S
|
|
|
238
288
|
readonly dir: string;
|
|
239
289
|
readonly covers?: "exact";
|
|
240
290
|
} | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* The grant a read-root card offers for the directory `candidate` it would add. A directory that would hand out more
|
|
293
|
+
* than was read is not offered: a family root grants the whole filesystem, and one CONTAINING the declared home or a
|
|
294
|
+
* root grants the workspace. ONE fallback for both rather than no offer at all — the single path that WAS read
|
|
295
|
+
* (`single`), named VERBATIM (`covers: "exact"`); with several paths read, no offer.
|
|
296
|
+
*/
|
|
297
|
+
export declare function grantOfferFor(candidate: string, single: string | undefined, bounds: Pick<ShellReadBoundary, "roots" | "homeDir">): {
|
|
298
|
+
dir: string;
|
|
299
|
+
covers?: "exact";
|
|
300
|
+
} | undefined;
|
|
241
301
|
/** The same judged plan under another boundary — the established reads are kept, only the judgment reruns. */
|
|
242
302
|
export declare function rejudge(judged: JudgedPlan, boundary: ShellReadBoundary): JudgedPlan;
|