@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,7 +1,7 @@
|
|
|
1
1
|
import { isRemoteExecutionEnv } from "../remote-env.js";
|
|
2
2
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
3
3
|
const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative"];
|
|
4
|
-
const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain"];
|
|
4
|
+
const OPTIONAL_METHOD_KEYS = ["writeFileExclusive", "writeFileGuarded", "setFileMode", "readLink", "canonicalChain", "fileIdentity"];
|
|
5
5
|
export function malformedCapabilityDeclarations(env) {
|
|
6
6
|
const out = [];
|
|
7
7
|
for (const key of OPTIONAL_METHOD_KEYS) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The file-history REQUEST door and its bad-value doors — the pre-acquire half of the file-history phase (`prepare-file-history.ts`). */
|
|
2
|
+
import type { FileHistoryStore } from "../file-history-store.js";
|
|
3
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The file-history REQUEST door: every refusal the request (`TaskSpec`) and the deployment's own seats decide
|
|
6
|
+
* without the session, the env or the store's contents — the three bad-value doors (`RunnerDeps.onTrackFailure`,
|
|
7
|
+
* `RunnerDeps.fileHistoryStore` / `fileHistoryCapture`, `TaskSpec.fileHistory`), the retired `rewindFiles` restore
|
|
8
|
+
* sense, the restoreFiles / resumeAt / rewindFilesTo contradictions, and a restore asked of a run that holds no
|
|
9
|
+
* history (`rewind.capture_disabled` / `rewind.store_unconfigured`). SYNCHRONOUS and side-effect free; the driver
|
|
10
|
+
* calls it BEFORE the session is acquired, so a request refused here leaves the session — its leaf included —
|
|
11
|
+
* exactly as it was. What it resolves rides into the file-history phase (`prepareFileHistory`) unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveFileHistoryRequest(spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "fileHistory">, deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "fileHistoryCapture">): FileHistoryRequest;
|
|
14
|
+
/** What {@link resolveFileHistoryRequest} resolved: the validated seats and words, the run's store (after every off
|
|
15
|
+
* switch) and its capture store, and the restore target (`undefined` = no restore asked). */
|
|
16
|
+
export interface FileHistoryRequest {
|
|
17
|
+
onTrackFailure: "refuse" | "proceed-unprotected";
|
|
18
|
+
wiredSeat: FileHistoryStore | "disabled" | undefined;
|
|
19
|
+
runDeclaredOff: boolean;
|
|
20
|
+
captureOff: boolean;
|
|
21
|
+
declaredOff: boolean;
|
|
22
|
+
fileHistoryStore: FileHistoryStore | undefined;
|
|
23
|
+
captureStore: FileHistoryStore | undefined;
|
|
24
|
+
rewindTarget: string | undefined;
|
|
25
|
+
rewindBefore: boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export function resolveFileHistoryRequest(spec, deps) {
|
|
2
|
+
const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
|
|
3
|
+
if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
|
|
4
|
+
const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
|
|
5
|
+
e.code = "config.invalid_on_track_failure";
|
|
6
|
+
throw e;
|
|
7
|
+
}
|
|
8
|
+
const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
|
|
9
|
+
const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
|
|
10
|
+
const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
|
|
11
|
+
const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
|
|
12
|
+
const fileHistoryStore = declaredOff ? undefined : wiredSeat;
|
|
13
|
+
const captureStore = captureOff ? undefined : fileHistoryStore;
|
|
14
|
+
const legacyRewindFiles = spec.rewindFiles;
|
|
15
|
+
if (legacyRewindFiles === true) {
|
|
16
|
+
if (spec.resumeAt !== undefined) {
|
|
17
|
+
const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
|
|
18
|
+
e.code = "rewind.rewind_files_retired";
|
|
19
|
+
throw e;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (spec.restoreFiles === true && spec.resumeAt === undefined) {
|
|
23
|
+
const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
|
|
24
|
+
e.code = "rewind.invalid_spec";
|
|
25
|
+
throw e;
|
|
26
|
+
}
|
|
27
|
+
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
28
|
+
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
|
|
29
|
+
e.code = "rewind.conflicting_targets";
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
33
|
+
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
34
|
+
if (rewindTarget !== undefined && fileHistoryStore === undefined) {
|
|
35
|
+
if (runDeclaredOff)
|
|
36
|
+
throw captureDisabledRefusal(rewindTarget);
|
|
37
|
+
const e = new Error(wiredSeat === "disabled"
|
|
38
|
+
? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
|
|
39
|
+
: `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
|
|
40
|
+
e.code = "rewind.store_unconfigured";
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
return { onTrackFailure, wiredSeat, runDeclaredOff, captureOff, declaredOff, fileHistoryStore, captureStore, rewindTarget, rewindBefore };
|
|
44
|
+
}
|
|
45
|
+
function resolveWiredFileHistoryStore(candidate) {
|
|
46
|
+
if (candidate === undefined || candidate === "disabled")
|
|
47
|
+
return candidate;
|
|
48
|
+
const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
|
|
49
|
+
const verbOf = (v) => {
|
|
50
|
+
try {
|
|
51
|
+
return candidate[v];
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
|
|
58
|
+
const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
|
|
59
|
+
if (missing.length > 0) {
|
|
60
|
+
const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
|
|
61
|
+
const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Write "disabled" (or omit the key) to run without file history.`);
|
|
62
|
+
e.code = "config.invalid_file_history_store";
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
function resolveRunFileHistoryWord(word) {
|
|
68
|
+
if (word === undefined || word === "disabled")
|
|
69
|
+
return word === "disabled";
|
|
70
|
+
const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
|
|
71
|
+
e.code = "rewind.invalid_spec";
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
function resolveFileHistoryCaptureWord(word, wiredSeat) {
|
|
75
|
+
if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
|
|
76
|
+
return word === "off";
|
|
77
|
+
const e = new Error(word === "off"
|
|
78
|
+
? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
|
|
79
|
+
: `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
|
|
80
|
+
e.code = "config.invalid_file_history_capture";
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
function shownWord(word) {
|
|
84
|
+
return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
|
|
85
|
+
}
|
|
86
|
+
function captureDisabledRefusal(target) {
|
|
87
|
+
const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
|
|
88
|
+
e.code = "rewind.capture_disabled";
|
|
89
|
+
return e;
|
|
90
|
+
}
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
* the reminder envelope and the text helpers. Its `import type` edges up to `hooks.ts` name the gate's public
|
|
22
22
|
* contract (`ToolGateInput`, `ToolGateResult`, the observer payload), which stays declared there.
|
|
23
23
|
*/
|
|
24
|
-
import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
|
|
24
|
+
import { type ClassifierDenyCause, type DeniedBy, type GateOutcome, type ModeRefusal, type ModeRelease, type Settlement } from "../gate-outcome.js";
|
|
25
25
|
import type { AutoModeClassifierRound } from "../auto-mode.js";
|
|
26
|
-
import type
|
|
26
|
+
import { type Arbitration, type AskClassification, type AskOrigin, type AskOriginFacts } from "../ask-origin.js";
|
|
27
27
|
import { type PermissionResult, type ResolvedAsk, type ToolCallRequest } from "../tool-policy.js";
|
|
28
28
|
import type { SafeNotifier } from "../safe-notify.js";
|
|
29
29
|
import type { HookSeatOutcome, PermissionDeniedPayload, PreToolUseResult, ToolGateInput, ToolGateResult } from "../hooks.js";
|
|
@@ -45,6 +45,10 @@ export interface GateExitFacts {
|
|
|
45
45
|
* Orthogonal to `deniedBy`: one that ALLOWED is attributed too (a fallback model's allow is exactly the case
|
|
46
46
|
* an operator needs to see), which is why it rides the disposition's shared member, not the deny arm. */
|
|
47
47
|
classifier?: AutoModeClassifierRound;
|
|
48
|
+
/** The permission mode RELEASED the surviving ask (the allow exit's account of it); never beside `deniedBy`. */
|
|
49
|
+
released?: ModeRelease;
|
|
50
|
+
/** The permission mode REFUSED the surviving ask — only beside `deniedBy: "mode"`. */
|
|
51
|
+
modeRefusal?: ModeRefusal;
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
54
|
* Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
|
|
@@ -228,6 +232,14 @@ export interface GatePass {
|
|
|
228
232
|
* card on the gate result, so the refusal's `tool_end` still says WHAT was found (`readonly_out_of_root`) beside
|
|
229
233
|
* WHO refused (`ask_resolution`). Writer: the fold's read station. */
|
|
230
234
|
shellReadDetails?: unknown;
|
|
235
|
+
/** The read station PLACED every write the command makes inside the write roots (`ShellReadVerdict.writesPlaced`) — the
|
|
236
|
+
* one precondition the mode's file-command acceptance reads (the reference's path checks precede its acceptEdits arm). */
|
|
237
|
+
shellWritesPlaced?: true;
|
|
238
|
+
/** The read station did NOT place the command's writes because a `cp` / `mv` segment carries an option word
|
|
239
|
+
* (`ShellReadVerdict.unplacedOptions`) — the one non-placement reason the mode's card and the headless refusal name. */
|
|
240
|
+
shellUnplacedOptions?: {
|
|
241
|
+
readonly program: "cp" | "mv";
|
|
242
|
+
};
|
|
231
243
|
/** borrowed-mutable — the FORM of the classifier round behind a deny (`unavailable` / `parse_error`), written
|
|
232
244
|
* beside `deniedBy` by the gate's own classifier step (its attestation, read once) or by the fold / the
|
|
233
245
|
* re-check when an inherited station's attested deny arrives through the chain; undefined for a ruling
|
|
@@ -260,14 +272,19 @@ export interface GatePass {
|
|
|
260
272
|
* twin on the answered side, and narrower: the ask stays ordinary for every ANSWERER, and only the allow
|
|
261
273
|
* layer's read-only-command arm stands down. Writer: the fold. Reader: that arm, and nothing else. */
|
|
262
274
|
probeBoundaryDeclined?: true;
|
|
275
|
+
/** borrowed-mutable — the shell read station could not establish what this command reads (finding `unestablishable`
|
|
276
|
+
* or `declined`): a mode's release of the call is a release past the leg's read-deny table, announced as such.
|
|
277
|
+
* Writer: the fold's read station. Reader: the release point. */
|
|
278
|
+
shellReadUnestablished?: true;
|
|
263
279
|
/** borrowed-mutable — the shell read station minted the standing ask's directory-grant offer
|
|
264
280
|
* (`probeReadRootCandidate`). Read by the classify seat's shape probe, which withdraws the offer when its
|
|
265
281
|
* own objection stands beside the station's — the grant answers the station's finding, not the shape's.
|
|
266
282
|
* Writer: the fold's read station. */
|
|
267
283
|
shellReadOffer?: true;
|
|
268
284
|
/** borrowed-mutable — the write-protection judge covered this call and its table MATCHED: the allow
|
|
269
|
-
* layer must not clear the surviving ask, a live decider still may.
|
|
270
|
-
* on BOTH its arms
|
|
285
|
+
* layer must not clear the surviving ask, a live decider still may. Writers: the fold's fourth tighten,
|
|
286
|
+
* on BOTH its arms (a write tool's target), and the fold's read station (a shell command's written targets,
|
|
287
|
+
* `ShellReadVerdict.writeProtected`). Reader: the lanes' mandate. */
|
|
271
288
|
writeProtected?: true;
|
|
272
289
|
/** borrowed-mutable — the same judge could NOT RESOLVE the target: no automatic lane may answer the
|
|
273
290
|
* ask. Writer and reader as above; the pair is mutually exclusive (one verdict per judgment). */
|
|
@@ -276,6 +293,15 @@ export interface GatePass {
|
|
|
276
293
|
* approved path (a redaction is not cancelled because a person confirmed). Writer: the fold. Readers: the
|
|
277
294
|
* fold's probe and write-protection judges, the lanes, the allow exit. */
|
|
278
295
|
policyRewrite: unknown;
|
|
296
|
+
/** borrowed-mutable — the standing ask's CLASS SET and folded row (`classifyAsk`), written at the lanes' origin
|
|
297
|
+
* stamp and at every re-mint (the denial-limit fallback, the approved-edit round); read by the classifier
|
|
298
|
+
* station, the sandbox leg and the arbitration point. Writer: the lanes. */
|
|
299
|
+
askClassification?: AskClassification;
|
|
300
|
+
/** borrowed-mutable — the mode arbitration's verdict when it RELEASED or REFUSED the standing ask (the exit
|
|
301
|
+
* projects it onto the disposition). Writer: the lanes' arbitration point. */
|
|
302
|
+
arbitration?: Exclude<Arbitration, {
|
|
303
|
+
kind: "ask";
|
|
304
|
+
}>;
|
|
279
305
|
}
|
|
280
306
|
/**
|
|
281
307
|
* The engine's fail-closed block of phase 1 — the PreToolUse face THREW or never ANSWERED, so the engine
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
2
|
+
import { headlessApprovalRemedy } from "../ask-origin.js";
|
|
3
|
+
import { isProtectedWrite } from "../tool-registry.js";
|
|
2
4
|
import { askUnresolvableCauseOf, classifierDenyCauseOf, classifierRoundOf, decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
3
5
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
6
|
import { formatHookFeedback } from "../reminder-mint.js";
|
|
@@ -7,8 +9,14 @@ export function mintGateOutcome(facts) {
|
|
|
7
9
|
const attribution = facts.classifier !== undefined ? Object.freeze({ ...facts.classifier, ...(facts.classifier.fallback !== undefined ? { fallback: Object.freeze({ ...facts.classifier.fallback }) } : {}) }) : undefined;
|
|
8
10
|
const outcome = Object.freeze({
|
|
9
11
|
disposition: Object.freeze(facts.deniedBy !== undefined
|
|
10
|
-
? {
|
|
11
|
-
|
|
12
|
+
? {
|
|
13
|
+
kind: "denied",
|
|
14
|
+
deniedBy: facts.deniedBy,
|
|
15
|
+
...(facts.cause !== undefined ? { cause: facts.cause } : {}),
|
|
16
|
+
...(attribution !== undefined ? { classifier: attribution } : {}),
|
|
17
|
+
...(facts.modeRefusal !== undefined ? { mode: facts.modeRefusal.mode, askClass: facts.modeRefusal.askClass, classes: Object.freeze([...facts.modeRefusal.classes]) } : {}),
|
|
18
|
+
}
|
|
19
|
+
: { kind: "allowed", ...(attribution !== undefined ? { classifier: attribution } : {}), ...(facts.released !== undefined ? { released: Object.freeze({ ...facts.released, classes: Object.freeze([...facts.released.classes]) }) } : {}) }),
|
|
12
20
|
...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
|
|
13
21
|
});
|
|
14
22
|
const defects = screenGateOutcome(outcome);
|
|
@@ -16,6 +24,10 @@ export function mintGateOutcome(facts) {
|
|
|
16
24
|
throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
|
|
17
25
|
return outcome;
|
|
18
26
|
}
|
|
27
|
+
function releaseRecordOf(a) {
|
|
28
|
+
const { kind: _kind, ...record } = a;
|
|
29
|
+
return record;
|
|
30
|
+
}
|
|
19
31
|
export function directDeny(deniedBy) {
|
|
20
32
|
return mintGateOutcome({ deniedBy });
|
|
21
33
|
}
|
|
@@ -111,17 +123,24 @@ function unresolvableCauseOfSettlement(gate) {
|
|
|
111
123
|
export async function exitGate(pass) {
|
|
112
124
|
const { input, event, toolName, toolCallId, ledger, notifyPermissionDenied } = pass;
|
|
113
125
|
if (pass.decision.action === "deny") {
|
|
114
|
-
const denyReason = withParkFailureCause(decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`, pass.parkFailed);
|
|
115
126
|
if (pass.decision.updatedInput !== undefined) {
|
|
116
127
|
pass.currentInput = pass.decision.updatedInput;
|
|
117
128
|
}
|
|
118
129
|
if (pass.deniedBy === undefined)
|
|
119
130
|
throw new Error(`the tool gate refused "${toolName}" without a refusing layer — every deny site attributes itself`);
|
|
120
|
-
const
|
|
131
|
+
const ancestorArbitration = pass.arbitration === undefined && pass.deniedBy === "policy" ? input.ancestorArbitrationOf?.(toolCallId) : undefined;
|
|
132
|
+
const modeRefusal = pass.arbitration?.kind === "denied_by_mode" ? pass.arbitration : ancestorArbitration?.kind === "denied_by_mode" ? ancestorArbitration : undefined;
|
|
133
|
+
if (modeRefusal !== undefined) {
|
|
134
|
+
pass.deniedBy = "mode";
|
|
135
|
+
ledger.supersede();
|
|
136
|
+
}
|
|
137
|
+
const gate = mintGateOutcome({ deniedBy: pass.deniedBy, ...(pass.denyCause !== undefined ? { cause: pass.denyCause } : {}), ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(modeRefusal !== undefined ? { modeRefusal: { mode: modeRefusal.mode, askClass: modeRefusal.askClass, classes: modeRefusal.classes } } : {}) });
|
|
121
138
|
const unresolvable = askUnresolvableCauseOf(pass.decision, { toolCallId, toolName }) ?? unresolvableCauseOfSettlement(gate);
|
|
139
|
+
const remedy = unresolvable === "no_approver_wired" ? headlessApprovalRemedy({ mode: input.permissionMode?.(), classification: pass.askClassification, coveredWrite: isProtectedWrite(pass.req), ...(pass.shellUnplacedOptions !== undefined ? { unplacedOptions: pass.shellUnplacedOptions } : {}) }) : undefined;
|
|
140
|
+
const denyReason = withParkFailureCause(`${decisionText(pass.decision) ?? `tool "${toolName}" denied by policy`}${remedy !== undefined ? ` — ${remedy}` : ""}`, pass.parkFailed);
|
|
122
141
|
if (unresolvable !== undefined) {
|
|
123
142
|
const cause = unresolvable;
|
|
124
|
-
pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined }), "toolGate.onAskUnresolvable");
|
|
143
|
+
pass.notifier.notify(() => input.onAskUnresolvable?.({ toolName, toolCallId, cause, parkLaneExisted: pass.suspendAsk !== undefined, ...(remedy !== undefined ? { remedy } : {}) }), "toolGate.onAskUnresolvable");
|
|
125
144
|
}
|
|
126
145
|
await notifyPermissionDenied({
|
|
127
146
|
toolName,
|
|
@@ -136,7 +155,7 @@ export async function exitGate(pass) {
|
|
|
136
155
|
reason: formatHookFeedback(denyReason, input.reminderMark),
|
|
137
156
|
gate,
|
|
138
157
|
...(() => {
|
|
139
|
-
const details = pass.denyDetails ?? pass.shellReadDetails;
|
|
158
|
+
const details = pass.denyDetails ?? (modeRefusal !== undefined ? undefined : pass.shellReadDetails);
|
|
140
159
|
return details !== undefined ? { details } : {};
|
|
141
160
|
})(),
|
|
142
161
|
preToolContext: pass.preToolContext,
|
|
@@ -147,9 +166,11 @@ export async function exitGate(pass) {
|
|
|
147
166
|
if (rw !== undefined)
|
|
148
167
|
pass.currentInput = rw;
|
|
149
168
|
}
|
|
169
|
+
const release = pass.arbitration?.kind === "released" ? pass.arbitration : (() => { if (ledger.settled !== undefined)
|
|
170
|
+
return undefined; const a = pass.arbitration === undefined ? input.ancestorArbitrationOf?.(toolCallId) : undefined; return a?.kind === "released" ? a : undefined; })();
|
|
150
171
|
return {
|
|
151
172
|
updatedInput: pass.currentInput === event.input ? undefined : pass.currentInput,
|
|
152
|
-
gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}) }),
|
|
173
|
+
gate: mintGateOutcome({ ...(ledger.settled !== undefined ? { settled: ledger.settled } : {}), ...(pass.classifierRound !== undefined ? { classifier: pass.classifierRound } : {}), ...(release !== undefined ? { released: releaseRecordOf(release) } : {}) }),
|
|
153
174
|
preToolContext: pass.preToolContext,
|
|
154
175
|
};
|
|
155
176
|
}
|
|
@@ -29,8 +29,9 @@ import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
|
29
29
|
import { persistedRuleMandateOf, type RuleClearanceFacts } from "../ask-origin.js";
|
|
30
30
|
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
31
31
|
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
32
|
+
import { type ToolCallLike } from "../tool-registry.js";
|
|
32
33
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
33
|
-
import type
|
|
34
|
+
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
34
35
|
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for
|
|
35
36
|
* it alone). The PATH grammar's tools are derived from the catalog (`ruleToolGrammarOf`). */
|
|
36
37
|
export declare const PERSISTED_RULE_TOOL = "Bash";
|
|
@@ -81,10 +82,9 @@ export declare function orgRevisionEvidenceOf(resolution: OrgRuleResolution, onD
|
|
|
81
82
|
*/
|
|
82
83
|
export declare function persistedRuleHitOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | undefined;
|
|
83
84
|
/** The COMMAND arm's projection of a verdict: a call the lexer could not read against a standing deny/ask
|
|
84
|
-
* row is the lane's UNREADABLE answer
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* other verdict is {@link persistedRuleHitOf}'s hit. */
|
|
85
|
+
* row is the lane's UNREADABLE answer carrying the lexer's reason — it fails closed (a real-approval ask no
|
|
86
|
+
* automatic lane clears) — never a matched ask rule, which would let a blanket `onAsk: "allow"` clear a
|
|
87
|
+
* question nobody can answer. Every other verdict is {@link persistedRuleHitOf}'s hit. */
|
|
88
88
|
export declare function persistedRuleAnswerOf(verdict: PersistedRuleVerdict | undefined): PersistedRuleHit | PersistedRuleUnreadable | undefined;
|
|
89
89
|
/**
|
|
90
90
|
* Widened to every path-targeting tool — the persisted-rule lane's PATH arm: the call's
|
|
@@ -152,6 +152,11 @@ export declare function createPermissionRuleLanes(cfg: {
|
|
|
152
152
|
* that were dropped) and for a durable read failure — each read's lines are delivered once, whichever
|
|
153
153
|
* lane took the read. */
|
|
154
154
|
onDisclosure: (message: string) => void;
|
|
155
|
+
/** The person is told their store (or the named sources of it) was skipped for a call (the run's one notice,
|
|
156
|
+
* `ruleStoreUnreadableNotice`). */
|
|
157
|
+
onStoreUnreadable: (at: "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
158
|
+
/** The run's cut: a read it interrupts is abandoned quietly — a cancelled wait is no fact about the store. */
|
|
159
|
+
signal?: AbortSignal;
|
|
155
160
|
}): PermissionRuleLanes;
|
|
156
161
|
/** The closed reason set for the rule-offer factory's empty answer — the same three
|
|
157
162
|
* spellings the `AskRequest` and durable-row seats declare, kept at the one factory that fills both.
|
|
@@ -242,7 +247,7 @@ export declare function createRuleOffersOf(cfg: {
|
|
|
242
247
|
current: string;
|
|
243
248
|
} | undefined;
|
|
244
249
|
deniesDirectoryRead: ((directory: string) => boolean) | undefined;
|
|
245
|
-
}): (
|
|
250
|
+
}): (call: Pick<ToolCallLike, "toolName" | "face">, args: unknown, ask: RuleOffersAskFacts | undefined) => {
|
|
246
251
|
ruleOffers?: readonly RuleOffer[];
|
|
247
252
|
ruleOffersAbsence?: RuleOffersAbsence;
|
|
248
253
|
execCwd?: string;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
|
|
2
2
|
import {} from "../hooks.js";
|
|
3
|
-
import { adjudicatePersistedPathRules, adjudicatePersistedRules, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
|
+
import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
4
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
5
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
6
|
-
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
6
|
+
import { anyNamed, declaredPathTargetOf, firstNamed, protectivePathTargetOf, ruleNamesOf } from "../tool-registry.js";
|
|
7
7
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
8
|
+
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
8
9
|
import { thrownText } from "../thrown-value.js";
|
|
10
|
+
import { strongestAnswer } from "../persisted-rule-arms.js";
|
|
11
|
+
import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
12
|
+
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
|
|
9
13
|
export const PERSISTED_RULE_TOOL = "Bash";
|
|
10
14
|
const HANDOFF_BOUND = 256;
|
|
11
15
|
export function inheritedAskRuleEvidence(provider) {
|
|
@@ -59,10 +63,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
59
63
|
if (provider === undefined)
|
|
60
64
|
return { personal: undefined, org: undefined };
|
|
61
65
|
const anonymous = cfg.principal === undefined || cfg.principal === "";
|
|
62
|
-
const store =
|
|
63
|
-
?
|
|
64
|
-
provider.forLocalOwner()
|
|
65
|
-
: provider.forPrincipal(anonymous ? undefined : cfg.principal);
|
|
66
|
+
const store = ownerStoreOf(provider, cfg.principal, cfg.localOwnerDeclared);
|
|
66
67
|
const handed = new Map();
|
|
67
68
|
const read = () => store.effective({ sessionId: cfg.sessionId }).then((view) => {
|
|
68
69
|
for (const line of view.disclosures)
|
|
@@ -105,62 +106,68 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
105
106
|
}
|
|
106
107
|
const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
|
|
107
108
|
const command = req.args?.command;
|
|
108
|
-
if (req.
|
|
109
|
+
if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
|
|
109
110
|
return { status: "available", ...revisionCell };
|
|
110
|
-
const verdict = orgRuleVerdictFor(resolution.rules, { tool:
|
|
111
|
+
const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
|
|
111
112
|
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
|
|
112
113
|
},
|
|
113
114
|
}
|
|
114
115
|
: undefined;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
view = await claim(req.toolCallId);
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
cfg.onDisclosure(thrownText(err));
|
|
129
|
-
return { unreadable: true };
|
|
130
|
-
}
|
|
131
|
-
if (view.unreadable !== undefined) {
|
|
132
|
-
cfg.onDisclosure(view.unreadable);
|
|
133
|
-
return { unreadable: true };
|
|
134
|
-
}
|
|
135
|
-
const table = view.rules;
|
|
136
|
-
const liveCwd = cfg.liveCwd();
|
|
137
|
-
if (grammar === "path") {
|
|
138
|
-
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
|
|
139
|
-
}
|
|
140
|
-
const command = req.args?.command;
|
|
141
|
-
if (typeof command !== "string")
|
|
142
|
-
return undefined;
|
|
143
|
-
const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
|
|
144
|
-
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
145
|
-
if (verdict !== undefined)
|
|
146
|
-
return persistedRuleAnswerOf(verdict);
|
|
147
|
-
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
148
|
-
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
149
|
-
},
|
|
116
|
+
const personalAdjudicate = async (req) => {
|
|
117
|
+
if (anonymous && !cfg.localOwnerDeclared)
|
|
118
|
+
return undefined;
|
|
119
|
+
const laneNames = laneToolNamesOf(req);
|
|
120
|
+
if (laneNames.length === 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
let view;
|
|
123
|
+
try {
|
|
124
|
+
view = await withGateTimeout(claim(req.toolCallId), ORG_ADJUDICATION_TIMEOUT_MS, cfg.signal, "persistedRules");
|
|
150
125
|
}
|
|
151
|
-
|
|
126
|
+
catch (err) {
|
|
127
|
+
return gateWaitFailureOf(err) === "aborted" ? { unreadable: true } : skipped(thrownText(err));
|
|
128
|
+
}
|
|
129
|
+
if (view.unreadable !== undefined)
|
|
130
|
+
skipped(view.unreadable, view.unreadableSources);
|
|
131
|
+
try {
|
|
132
|
+
const answer = strongestAnswer(laneNames.map((n) => judgeAgainst(view.rules, { ...req, toolName: n }, ruleToolGrammarOf(n))));
|
|
133
|
+
return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
const why = `judging this call against the rows threw: ${thrownText(err)}`;
|
|
137
|
+
cfg.onDisclosure(`the persisted rule lane could not judge a ${req.toolName} call against the rows it read — ${why}`);
|
|
138
|
+
return { unreadable: true, reason: why };
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const judgeAgainst = (table, req, grammar) => {
|
|
142
|
+
const liveCwd = cfg.liveCwd();
|
|
143
|
+
if (grammar === "path")
|
|
144
|
+
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, liveCwd, home: cfg.home });
|
|
145
|
+
const command = req.args?.command;
|
|
146
|
+
if (typeof command !== "string")
|
|
147
|
+
return undefined;
|
|
148
|
+
const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
|
|
149
|
+
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
150
|
+
if (verdict !== undefined)
|
|
151
|
+
return persistedRuleAnswerOf(verdict);
|
|
152
|
+
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd });
|
|
153
|
+
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
|
|
154
|
+
};
|
|
155
|
+
const skipped = (why, sources) => (cfg.onDisclosure(why), cfg.onStoreUnreadable("call", sources), { unreadable: true });
|
|
156
|
+
const personal = provider.partitions.durable ? { adjudicate: personalAdjudicate } : undefined;
|
|
152
157
|
return { personal, org };
|
|
153
158
|
}
|
|
154
159
|
export function createRuleOffersOf(cfg) {
|
|
155
|
-
return (
|
|
160
|
+
return (call, args, ask) => {
|
|
161
|
+
const toolName = call.toolName;
|
|
156
162
|
if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
|
|
157
163
|
return {};
|
|
158
164
|
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
|
|
159
165
|
return {};
|
|
166
|
+
const tierSeat = firstNamed(cfg.irreversibilityTier, ruleNamesOf(call));
|
|
160
167
|
const closedDoor = ruleClearanceOf(ask, {
|
|
161
|
-
egress: cfg.egressTools
|
|
162
|
-
irreversibility:
|
|
163
|
-
shellGated: cfg.shellGatedTools.has(
|
|
168
|
+
egress: anyNamed(cfg.egressTools, ruleNamesOf(call)),
|
|
169
|
+
irreversibility: tierSeat?.value,
|
|
170
|
+
shellGated: tierSeat !== undefined && cfg.shellGatedTools.has(tierSeat.name),
|
|
164
171
|
}).word;
|
|
165
172
|
if (closedDoor !== undefined)
|
|
166
173
|
return { ruleOffersAbsence: closedDoor };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The person's persisted PATH rows as a leg's read face takes them at prepare (the gate's rule lanes read the same store
|
|
3
|
+
* per call).
|
|
4
|
+
*/
|
|
5
|
+
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
6
|
+
import type { PathRuleDenyRow } from "../../tools/fs/read-deny.js";
|
|
7
|
+
import type { RunnerDeps } from "../runner-deps.js";
|
|
8
|
+
/** The person's persisted PATH rows a leg's read face takes at prepare ({@link persistedPathRowsOf}). */
|
|
9
|
+
export interface PersistedPathRows {
|
|
10
|
+
/** Every live `Read(//dir/**)` allow row's directory (lexical, absolute): a standing yes to reads under it. */
|
|
11
|
+
readonly readDirectories: readonly string[];
|
|
12
|
+
/** Every live face-keyed path DENY row (`Read(...)`, `Edit(...)`), as a deny-table row resolved at the task's bases. */
|
|
13
|
+
readonly denyRows: readonly PathRuleDenyRow[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The person's persisted PATH rows, read ONCE at prepare from the same store query the gate's lanes read per call: a
|
|
17
|
+
* `Read(//dir/**)` allow row is a READ DIRECTORY of the run (the Bash read station, Read / Grep / Glob — one set), and a
|
|
18
|
+
* `Read(...)` / `Edit(...)` deny row joins the run's read / write deny tables (every read face, the shell's file commands
|
|
19
|
+
* and traversals). One read, two directions, so a person's allow row never reaches further than their deny rows: a
|
|
20
|
+
* directory it admits still refuses the paths a deny row names. A row the store gains mid-run applies from the next
|
|
21
|
+
* prepare; the in-flight grant (`readRootGrant`) is the mid-run door. The store not armed, or the anonymous floor ⇒ no
|
|
22
|
+
* rows. A read that failed (or did not answer in time) ⇒ no rows, and a partition the read could not read ⇒ none of ITS
|
|
23
|
+
* rows (the partition that was read contributes its own): what could not be read is skipped for this run's tables — its
|
|
24
|
+
* directories widen nothing and its deny rows judge nothing here — disclosed on the trace and announced to the person
|
|
25
|
+
* (`onUnread`); the next prepare reads again, as it would for a row the store gained mid-run. A deny row whose
|
|
26
|
+
* pattern needs a base the task cannot supply (`~/` with no declared home) is left to the per-call lane and disclosed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function persistedPathRowsOf(cfg: {
|
|
29
|
+
provider: PermissionRuleStoreProvider | undefined;
|
|
30
|
+
localOwnerDeclared: boolean;
|
|
31
|
+
principal: string | undefined;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
root: string;
|
|
34
|
+
home: string | undefined;
|
|
35
|
+
onDisclosure: (message: string) => void;
|
|
36
|
+
/** The person is told the store (or the named sources of it) was skipped ({@link ruleStoreUnreadableNotice}). */
|
|
37
|
+
onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
38
|
+
/** The run's cut: a read that has not answered by then (or by the gate's await bound) reads as unreadable. */
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}): Promise<PersistedPathRows>;
|
|
41
|
+
/**
|
|
42
|
+
* The run's ONE `permission.rule_store_unavailable` notice (audience user): the person's rules — the whole store, or the
|
|
43
|
+
* sources of it whose partition could not be read — were skipped; minted by whichever read found it first (the prepare
|
|
44
|
+
* read of the path rows, or a call's own read on the rule lane), once per run however many reads fail after it (the
|
|
45
|
+
* prepare sentence says a later call may be judged without them too). The trace keeps every read's line.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ruleStoreUnreadableNotice(onNotice: RunnerDeps["onNotice"], ids: {
|
|
48
|
+
readonly sessionId: string;
|
|
49
|
+
readonly runId: string;
|
|
50
|
+
}): (at: "prepare" | "call", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
51
|
+
/** {@link persistedPathRowsOf} for one task's prepare: only where a read face mounts (hands), disclosed on the run's trace,
|
|
52
|
+
* cut by the run's own signals. `undefined` = nothing to read, returned synchronously: a run with no person's store (or
|
|
53
|
+
* no hands) gains no suspension point on its prepare. */
|
|
54
|
+
export declare function persistedPathRowsOfTask(t: {
|
|
55
|
+
deps: Pick<RunnerDeps, "permissionRuleStore" | "localOwnerRules" | "tracer">;
|
|
56
|
+
principal: string | undefined;
|
|
57
|
+
sessionId: string;
|
|
58
|
+
root: string;
|
|
59
|
+
home: string | undefined;
|
|
60
|
+
taskId: string;
|
|
61
|
+
handsEnabled: boolean;
|
|
62
|
+
signals: readonly AbortSignal[];
|
|
63
|
+
onUnread: (at: "prepare", sources?: EffectivePermissionRules["unreadableSources"]) => void;
|
|
64
|
+
}): Promise<PersistedPathRows> | undefined;
|